/********************************************************************** * * display.cpp -- Context sensitive macro language * Copyright (C) 1999 The New Zealand Digital Library Project * * A component of the Greenstone digital library software * from the New Zealand Digital Library Project at the * University of Waikato, New Zealand. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * *********************************************************************/ #include "display.h" #include "gsdlunicode.h" #include "unitool.h" #include // include to get NULL #include text_t displayclass::defaultpackage = "Global"; ///////////////////////////////////// // misc classes ///////////////////////////////////// // precedencetype defines a 'precedence value' for each parameter typedef map precedencetype; inline bool operator==(const mvalue &x, const mvalue &y) { return (x.filename==y.filename && x.value==y.value); } inline bool operator<(const mvalue &x, const mvalue &y) { return (x.filename defparammap; typedef map defmacromap; typedef map defpackagemap; // all methods in parammacros_t assume the parameters // and packages in a standard form and not empty class parammacros_t { protected: defpackagemap macros; public: typedef defpackagemap::const_iterator const_package_iterator; typedef defpackagemap::size_type package_size_type; // setmacro returns 0 if there was no error, // -1 if it redefined a macro // -2 if it hid a Global macro // -3 if it redefined a macro and hid a Global macro // -4 if either a package, macroname, or params were not supplied int setmacro(const text_t &package, const text_t ¯oname, const text_t ¶ms, const text_t &filename, const text_t ¯ovalue); void delmacro(const text_t &package, const text_t ¯oname, const text_t ¶ms); void clear () {macros.erase(macros.begin(), macros.end());} // top level stuff const_package_iterator package_begin () const {return macros.begin();} const_package_iterator package_end () const {return macros.end();} bool package_empty () const {return macros.empty();} package_size_type package_size() const {return macros.size();} // methods to find things defmacromap *package_find (const text_t &packagename); defparammap *macro_find (const text_t &packagename, const text_t ¯oname); mvalue *parameter_find (const text_t &packagename, const text_t ¯oname, const text_t ¶metername); }; // setmacro returns 0 if there was no error, // -1 if it redefined a macro // -2 if it hid a Global macro // -3 if it redefined a macro and hid a Global macro // -4 if either a package, macroname, or params were not supplied int parammacros_t::setmacro(const text_t &package, const text_t ¯oname, const text_t ¶ms, const text_t &filename, const text_t ¯ovalue) { paramhashtype paramhash; defmacromap *macromapptr; defparammap *parammapptr; mvalue *mvalueptr; int warning = 0; // check to see if we would be hiding a Global macro with the // same name. Note: it doesn't matter what parameters are used // a Global macro will still be hid by a non-Global one with // the same name. if ((package != displayclass::defaultpackage) && (macro_find (displayclass::defaultpackage, macroname) != NULL)) { warning -= 2; // found the macroname } // define this macro macromapptr = &(macros[package]); // -- package parammapptr = &((*macromapptr)[macroname]); // -- macro name // -- parameters if ((*parammapptr).find(params) != (*parammapptr).end()) { warning -= 1; // found the parameters } mvalueptr = &((*parammapptr)[params]); // -- value (*mvalueptr).filename = filename; (*mvalueptr).value = macrovalue; return warning; } void parammacros_t::delmacro(const text_t &package, const text_t ¯oname, const text_t ¶ms) { // make sure everything was supplied if (package.empty() || macroname.empty() || params.empty()) return; // find the package and macroname defparammap *parammapptr = macro_find(package, macroname); if (parammapptr == NULL) return; // find the parameters defparammap::iterator parammapit = parammapptr->find(params); if (parammapit == parammapptr->end()) return; // finally delete this element parammapptr->erase(parammapit); } defmacromap *parammacros_t::package_find (const text_t &packagename) { if (packagename.empty()) return NULL; defpackagemap::iterator it = macros.find(packagename); if (it == macros.end()) return NULL; return &((*it).second); } defparammap *parammacros_t::macro_find (const text_t &packagename, const text_t ¯oname) { defmacromap *macromapptr = package_find(packagename); if (macromapptr == NULL || macroname.empty()) return NULL; defmacromap::iterator it = (*macromapptr).find(macroname); if (it == (*macromapptr).end()) return NULL; return &((*it).second); } mvalue *parammacros_t::parameter_find (const text_t &packagename, const text_t ¯oname, const text_t ¶metername) { defparammap *parammapptr = macro_find(packagename, macroname); if (parammapptr == NULL || parametername.empty()) return NULL; defparammap::iterator it = (*parammapptr).find(parametername); if (it == (*parammapptr).end()) return NULL; return &((*it).second); } ///////////////////////////////////// // stuff for currentmacros_t ///////////////////////////////////// typedef map curmacromap; typedef map curpackagemap; // all methods in currentmacros_t assume the parameters // and packages in a standard form and not empty class currentmacros_t { protected: curpackagemap macros; public: typedef curpackagemap::const_iterator const_package_iterator; typedef curpackagemap::size_type package_size_type; // setmacro returns 0 if there was no error, // -1 if it redefined a macro // -4 if either a package, or macroname were not supplied int setmacro(const text_t &package, const text_t ¯oname, const text_t &filename, const text_t ¯ovalue); void delmacro(const text_t &package, const text_t ¯oname); void clear () {macros.erase(macros.begin(), macros.end());} // top level stuff const_package_iterator package_begin () const {return macros.begin();} const_package_iterator package_end () const {return macros.end();} bool package_empty () const {return macros.empty();} package_size_type package_size() const {return macros.size();} // methods to find things curmacromap *package_find (const text_t &packagename); mvalue *macro_find (const text_t &packagename, const text_t ¯oname); }; // setmacro returns 0 if there was no error, // -1 if it redefined a macro // -4 if either a package, or macroname were not supplied int currentmacros_t::setmacro(const text_t &package, const text_t ¯oname, const text_t &filename, const text_t ¯ovalue) { int warning = 0; // make sure everything was supplied if (package.empty() || macroname.empty()) return -4; // define this macro curmacromap *macromapptr = &(macros[package]); if ((*macromapptr).find(macroname) != (*macromapptr).end()) { warning -= 1; // found the macroname } mvalue *mvalueptr = &((*macromapptr)[macroname]); // -- value (*mvalueptr).filename = filename; (*mvalueptr).value = macrovalue; return warning; } void currentmacros_t::delmacro(const text_t &package, const text_t ¯oname) { // make sure everything was supplied if (package.empty() || macroname.empty()) return; // find the package curmacromap *macromapptr = package_find(package); if (macromapptr == NULL) return; // find the macroname curmacromap::iterator macromapit = macromapptr->find(macroname); if (macromapit == macromapptr->end()) return; // finally delete this element macromapptr->erase(macromapit); } curmacromap *currentmacros_t::package_find (const text_t &packagename) { if (packagename.empty()) return NULL; curpackagemap::iterator it = macros.find(packagename); if (it == macros.end()) return NULL; return &((*it).second); } mvalue *currentmacros_t::macro_find (const text_t &packagename, const text_t ¯oname) { curmacromap *macromapptr = package_find(packagename); if (macromapptr == NULL || macroname.empty()) return NULL; curmacromap::iterator it = (*macromapptr).find(macroname); if (it == (*macromapptr).end()) return NULL; return &((*it).second); } ///////////////////////////////////// // support functions ///////////////////////////////////// // this calculation of precedence will make one occurance of a high // precedence item override many occurances of lower precedence items void calcprecedence (const text_t &precedstr, precedencetype &precedhash) { text_t param; double multiple = 5.0; double nextprec = multiple; text_t::const_iterator here, end; // set precedhash to an empty hash precedhash.erase(precedhash.begin(), precedhash.end()); // iterate through paramstring ignoring space and extracting // out key value pairs. here = precedstr.begin(); end = precedstr.end(); while (here != end) { // get the next parameter param.clear(); // set param to an empty string while (here != end) { if (*here == ',') { // found the end of the parameter ++here; break; } else if (*here == ' ') { // do nothing (ignore spaces) } else { // character in parameter param.push_back (*here); // copy this character } ++here; } // if a parameter was found insert its value if (!param.empty()) { precedhash[param] = nextprec; nextprec *= multiple; } } } // decides how specific any given parameter is to the page parameters. Returns // negative if any options are different or else the sum of the precedence for // the options which match. double getspecificness(const paramhashtype &pageparams, const paramhashtype &theseparams, const precedencetype &precedhash) { double specificness = 0.0; paramhashtype::const_iterator here, pagevalueit; precedencetype::const_iterator precedit; text_t thesekey, thesevalue, constignore("ignore"); here = theseparams.begin(); while (here != theseparams.end()) { thesekey = (*here).first; thesevalue = (*here).second; if (thesekey == constignore) { // do nothing, this is a placeholder } else { pagevalueit = pageparams.find(thesekey); if ((pagevalueit != pageparams.end()) && ((*pagevalueit).second == thesevalue)) { // this parameter fits, add its precedence value precedit = precedhash.find(thesekey); if (precedit == precedhash.end()) specificness += 1.0; // no precedence defined else specificness += (*precedit).second; } else { return -1.0; // did not match } } ++here; } return specificness; } void splitparams (const text_t ¶mstring, paramhashtype ¶mhash) { text_t key; text_t value; text_t::const_iterator here, end; // set paramhash to an empty hash paramhash.erase(paramhash.begin(), paramhash.end()); // iterate through paramstring ignoring space and extracting // out key value pairs. here = paramstring.begin(); end = paramstring.end(); while (here != end) { // reset key and value key.clear(); value.clear(); // get key while (here != end) { if (*here == ',') { // have a key with no value break; } else if (*here == '=') { // found the end of the key ++here; break; } else if (*here == ' ') { // do nothing (ignore spaces) } else { // character in key key.push_back (*here); // copy this character } ++here; } // get value while (here != end) { if (*here == '=') { // multiple values for one key, ignore previous // values value.clear(); } else if (*here == ',') { // found the end of the value ++here; break; } else if (*here == ' ') { // do nothing (ignore spaces) } else { // character in value value.push_back (*here); // copy this character } ++here; } // if a key was found insert the key/value pair in // the map if (!key.empty()) { paramhash[key] = value; } } } void joinparams (const paramhashtype ¶mhash, text_t ¶mstring) { //set paramstring to an empty string paramstring.clear(); //iterate through the paramhash paramhashtype::const_iterator thepair; int firstparam = 1; for (thepair=paramhash.begin();thepair!=paramhash.end(); ++thepair) { if (!firstparam) paramstring.push_back(','); firstparam = 0; // insert pairs of "key=value" text_t thevalue; paramstring.append((*thepair).first); paramstring.push_back('='); paramstring.append((*thepair).second); } } ///////////////////////////////////// // parsing support functions ///////////////////////////////////// inline int my_isalpha (unsigned short c) { return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')); } inline int my_isalphanum (unsigned short c) { return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >='0' && c <= '9')); } inline unsigned short my_ttnextchar (text_t::const_iterator &here, text_t::const_iterator &end) { if (here != end) { ++here; if (here != end) return (*here); } return '\0'; } ///////////////////////////////////// // methods for display ///////////////////////////////////// // public methods for display displayclass::displayclass () { defaultmacros = new parammacros_t; collectionmacros = new parammacros_t; defaultfiles = new fileinfomap; orderparamlist = new paramspeclist; currentmacros = new currentmacros_t; outc = NULL; logout = NULL; /* this is a bit of a hack, but if the displayclass has never seen this parameter before when it does openpage() then macros with this set will always be ignored. And we set collection-specific macros after doing openpage() - jrm */ allparams.insert("l=en"); } displayclass::~displayclass () { delete defaultmacros; delete collectionmacros; delete defaultfiles; delete orderparamlist; delete currentmacros; } // setdefaultmacro adds an entry to the list of default macros // returns 0 if there was no error, // -1 if it redefined a macro // -2 if it hid a Global macro // -3 if it redefined a macro and hid a Global macro // -4 if no macroname was supplied int displayclass::setdefaultmacro (const text_t &package, const text_t ¯oname, const text_t &mparams, const text_t ¯ovalue) { return setdefaultmacro (package, macroname, mparams, "memory", macrovalue); } // as we are using one character lookahead the // value of line might be off by one. // the input file must be in the utf-8 or unicode format // initially for each file isunicode should be set to 0 and // bigendian should be set to 1 // 0 will be returned when the end of the file has been found unsigned short my_uni_get (/*unistream*/ifstream &fin, int &line, int &isunicode, int &bigendian) { unsigned short c = 0; if (isunicode) { // unicode text // get the next two characters unsigned char c1 = 0, c2 = 0; if (!fin.eof()) c1=fin.get(); if (!fin.eof()) c2=fin.get(); else c1 = 0; // if they indicate the order get the next character // otherwise just get these characters if (c1 == 0xff && c2 == 0xfe) { bigendian = 0; c = my_uni_get (fin, line, isunicode, bigendian); } else if (c1 == 0xfe && c2 == 0xff) { bigendian = 1; c = my_uni_get (fin, line, isunicode, bigendian); } else c = (bigendian) ? (c1*256+c2) : (c2*256+c1); } else { // utf-8 text // how many characters we get depends on what we find unsigned char c1 = 0, c2 = 0, c3 = 0; while (!fin.eof()) { c1=fin.get(); if (c1 == 0xfe || c1 == 0xff) { // switch to unicode isunicode = 1; if (!fin.eof()) c2=fin.get(); if (c1 == 0xff && c2 == 0xfe) bigendian = 0; else bigendian = 1; c = my_uni_get (fin, line, isunicode, bigendian); break; } else if (c1 <= 0x7f) { // one byte character c = c1; break; } else if (c1 >= 0xc0 && c1 <= 0xdf) { // two byte character if (!fin.eof()) c2=fin.get(); c = ((c1 & 0x1f) << 6) + (c2 & 0x3f); break; } else if (c1 >= 0xe0 && c1 <= 0xef) { // three byte character if (!fin.eof()) c2=fin.get(); if (!fin.eof()) c3=fin.get(); c = ((c1 & 0xf) << 12) + ((c2 & 0x3f) << 6) + (c3 & 0x3f); break; } // if we get here there was an error in the file, we should // be able to recover from it however, maybe the file is in // another encoding } } if (c == '\n') ++line; return c; } // loads a macro file into the specified macrotable datastructure // returns 0 if didn't need to load the file (it was already loaded) // 1 if was (re)loaded // -1 an error occurred while trying to load the file int displayclass::loadparammacros (parammacros_t* macrotable, const text_t &thisfilename) { // convert the filename to a C string char *filenamestr = thisfilename.getcstr(); outconvertclass text_t2ascii; // see if we need to open this file -- this isn't implemented yet // open the file // unistream fin (filenamestr); ifstream fin (filenamestr); if (fin.fail()) return -1; // read failed text_t package = defaultpackage; int line = 1; int isunicode = 0, bigendian = 1; // pre-fetch the next character unsigned short c = my_uni_get(fin, line, isunicode, bigendian); text_t macropackage, macroname, macroparameters, macrovalue; int err; // for keeping track of whether an error occurred somewhere while (!fin.eof()) { // expect: white space, comment, "package", or macroname if (is_unicode_space(c)) { // found some white-space c = my_uni_get(fin, line, isunicode, bigendian); } else if (c == '#') { // found the start of a comment // skip all characters up to the end of the line c = my_uni_get(fin, line, isunicode, bigendian); // skip the '#' while (!fin.eof ()) { if (c == '\n') break; c = my_uni_get(fin, line, isunicode, bigendian); } } else if (c == 'p') { // found the start of 'package' (hopefully) // get everything up to the next space text_t tmp; while (!fin.eof() && my_isalpha(c)) { tmp.push_back(c); c = my_uni_get(fin, line, isunicode, bigendian); } // see if we have a package name if (tmp == "package") { // skip all white space while (!fin.eof() && is_unicode_space(c)) c = my_uni_get(fin, line, isunicode, bigendian); // get the package name tmp.clear(); // init tmp while (!fin.eof() && my_isalphanum(c)) { tmp.push_back(c); c = my_uni_get(fin, line, isunicode, bigendian); } package = tmp; if (package.empty()) package = defaultpackage; } else { // error if (logout != NULL) { (*logout) << text_t2ascii << text_t("Expected 'package' on line ") + line + text_t(" of ") + thisfilename + "\n"; } } } else if (c == '_') { // found the start of a macro (hopefully) c = my_uni_get(fin, line, isunicode, bigendian); // skip the _ // init variables err = 0; macropackage = package; macroname.clear(); // init macroname macroparameters.clear(); // init macroname macrovalue.clear(); // init macroname // get the macro name while ((!fin.eof()) && (!is_unicode_space(c)) && (c != '\\') && (c != '_') &&(c != ':') && (macroname.size() < 80)) { macroname.push_back(c); c = my_uni_get(fin, line, isunicode, bigendian); } if (c == ':') { // we actually had the macro package c = my_uni_get(fin, line, isunicode, bigendian); // skip : macropackage = macroname; macroname.clear (); // get the macro name (honest!) while ((!fin.eof()) && (!is_unicode_space(c)) && (c != '\\') && (c != '_') &&(c != ':') && (macroname.size() < 80)) { macroname.push_back(c); c = my_uni_get(fin, line, isunicode, bigendian); } } if (!err && c == '_') { c = my_uni_get(fin, line, isunicode, bigendian); // skip the _ // skip all white space while (!fin.eof() && is_unicode_space(c)) c = my_uni_get(fin, line, isunicode, bigendian); } else if (!err) err = 1; // get the macro parameters (optional) if (!err && c == '[') { c = my_uni_get(fin, line, isunicode, bigendian); // skip the [ while ((!fin.eof()) && (c != '\n') && (c != '\\') && (c != ']')) { macroparameters.push_back(c); c = my_uni_get(fin, line, isunicode, bigendian); } if (c == ']') { c = my_uni_get(fin, line, isunicode, bigendian); // skip the ] // skip all white space while (!fin.eof() && is_unicode_space(c)) c = my_uni_get(fin, line, isunicode, bigendian); } else if (!err) err = 2; } // get the macro value if (!err && c == '{') { c = my_uni_get(fin, line, isunicode, bigendian); // skip the { while ((!fin.eof()) && (c != '}')) { if (c == '\\') { macrovalue.push_back(c); // keep the '\' c = my_uni_get(fin, line, isunicode, bigendian); // store the *next* value regardless if (!fin.eof()) macrovalue.push_back(c); c = my_uni_get(fin, line, isunicode, bigendian); } // DL CONSULTING FIX: Fix bug where the end of the macro definition would be missed if a backslash // was the second-to-last character of the macro. [Michael Dewsnip, DL Consulting Ltd.] else { macrovalue.push_back(c); c = my_uni_get(fin, line, isunicode, bigendian); } } if (c == '}') { c = my_uni_get(fin, line, isunicode, bigendian); // skip the } // define the macro err = setparammacro (macrotable, macropackage, macroname, macroparameters, thisfilename, macrovalue); if ((err == -1 || err == -3) && logout != NULL) { (*logout) << text_t2ascii << ("Warning: redefinition of _" + package + ":" + macroname + "_[" + macroparameters + "] on line " + line + " of " + thisfilename + "\n"); } else if (err == -2 && logout != NULL) { // I don't think we really want this warning since it's a very // common occurance - Stefan. // (*logout) << text_t2ascii << "Warning: _" << // package << ":" << macroname << "_[" << macroparameters << // "] on line "; // (*logout) << line; // (*logout) << text_t2ascii << " of " << // thisfilename << " hides a Global macro with the same name\n"; } else if (err == -4 && logout != NULL) { (*logout) << text_t2ascii << text_t("Error: macro name expected on line ") + line + text_t(" of ") + thisfilename + "\n"; } err = 0; // for the test below } else if (!err) err = 3; } else if (!err) err = 4; if (err) { // found an error, skip to the end of the line if (logout != NULL) { text_t message = "Error: "; if (err == 1) message += "'_'"; else if (err == 2) message += "']'"; else if (err == 3) message += "'}'"; else if (err == 4) message += "'{'"; message += " expected on line "; message += line; message += " of " + thisfilename + "\n"; (*logout) << text_t2ascii << message; // (*logout) << text_t2ascii << "Error: "; // if (err == 1) (*logout) << text_t2ascii << "'_'"; // else if (err == 2) (*logout) << text_t2ascii << "']'"; // else if (err == 3) (*logout) << text_t2ascii << "'}'"; // else if (err == 4) (*logout) << text_t2ascii << "'{'"; // (*logout) << text_t2ascii << " expected on line "; // (*logout) << line ; // (*logout) << text_t2ascii << " of " << thisfilename << "\n"; } while (!fin.eof ()) { if (c == '\n') break; c = my_uni_get(fin, line, isunicode, bigendian); } } } else { // found an error, skip to the end of the line if (logout != NULL) { (*logout) << text_t2ascii << text_t("Error: Unexpected input on line ") + line + text_t(" of ") + thisfilename + "\n"; } while (!fin.eof ()) { if (c == '\n') break; c = my_uni_get(fin, line, isunicode, bigendian); } } } fin.close (); // free up memory delete []filenamestr; return 0; } // loads a default macro file (if it isn't already loaded) // returns 0 if didn't need to load the file (it was already loaded) // 1 if was (re)loaded // -1 an error occurred while trying to load the file int displayclass::loaddefaultmacros (const text_t &thisfilename) { return loadparammacros(defaultmacros,thisfilename); } // loads a collection specific macro file // returns 0 if didn't need to load the file (it was already loaded) // 1 if was (re)loaded // -1 an error occurred while trying to load the file int displayclass::loadcollectionmacros (const text_t &thisfilename) { return loadparammacros(collectionmacros,thisfilename); } void displayclass::unloaddefaultmacros () { defaultmacros->clear(); } void displayclass::unloadcollectionmacros () { collectionmacros->clear(); } // prepares to create a page. void displayclass::openpage (const text_t &thispageparams, const text_t &thisprecedence) { // init variables currentmacros->clear(); // reload any default macro files which have changed. if (checkdefaultmacrofiles() < 0) { // print error message } setpageparams (thispageparams, thisprecedence); } // changes the parameters for the current page. void displayclass::setpageparams (text_t thispageparams, text_t thisprecedence) { paramhashtype thissplitparams, splittheseparams; precedencetype precedhash; text_tset::iterator text_tsetit; paramspec tmpps; precedence = thisprecedence; calcprecedence(thisprecedence, precedhash); splitparams(thispageparams, thissplitparams); joinparams(thissplitparams, thispageparams); params = thispageparams; // get a list of parameters with their specificness orderparamlist->erase(orderparamlist->begin(), orderparamlist->end()); for (text_tsetit=allparams.begin(); text_tsetit!=allparams.end(); ++text_tsetit) { tmpps.param = (*text_tsetit); splitparams(tmpps.param, splittheseparams); tmpps.spec = getspecificness(thissplitparams, splittheseparams, precedhash); if (tmpps.spec >= 0) { orderparamlist->push_back (tmpps); } } // sort the list sort(orderparamlist->begin(), orderparamlist->end()); #if 0 // debugging outconvertclass text_t2ascii; paramspeclist::iterator pshere = orderparamlist->begin(); paramspeclist::iterator psend = orderparamlist->end(); while (pshere != psend) { cerr << text_t2ascii << "param=" << (*pshere).param; cerr << " spec=" << (int)((*pshere).spec) << "\n"; ++pshere; } #endif } // overrides (or sets) a macro for the current page. // returns 0 if there was no error, // -1 if it redefined a macro // -4 if no macroname was supplied int displayclass::setmacro (const text_t ¯oname, const text_t &package, const text_t ¯ovalue) { // make sure a macroname was supplied if (macroname.empty()) return -4; // make package "Global" if it doesn't point to anything yet if (package.empty()) return currentmacros->setmacro(defaultpackage, macroname, "memory", macrovalue); // set the macro return currentmacros->setmacro(package, macroname, "memory", macrovalue); } int displayclass::setmacroif (const text_t ¯o_to_set_and_test, const text_t ¯o_to_set_if_macro_not_set, const text_t ¯oname, const text_t &package) { if (macroname.empty()) return -4; if (macro_to_set_and_test.empty()) return -40; text_t temp; expandstring(defaultpackage, macro_to_set_and_test, temp); if (package.empty()) { if (temp == macro_to_set_and_test) return currentmacros->setmacro(defaultpackage, macroname, "memory", macro_to_set_if_macro_not_set); return currentmacros->setmacro(defaultpackage, macroname, "memory", macro_to_set_and_test); } else { if (temp == macro_to_set_and_test) return currentmacros->setmacro(package, macroname, "memory", macro_to_set_if_macro_not_set); return currentmacros->setmacro(package, macroname, "memory", macro_to_set_and_test); } } void displayclass::expandstring (const text_t &package, const text_t &inputtext, text_t &outputtext, int recursiondepth) { text_t macroname, macropackage, macroargs; text_t::const_iterator tthere = inputtext.begin(); text_t::const_iterator ttend = inputtext.end(); unsigned short c = '\0'; int openbrackets=0; if (package.empty()) expandstring(defaultpackage, inputtext, outputtext, recursiondepth); outputtext.clear(); // use one-character lookahead if (tthere != ttend) c = (*tthere); while (tthere != ttend) { if (c == '\\') { // found an escape character c = my_ttnextchar (tthere, ttend); // skip the escape character if (tthere != ttend) { if (c == 'n') outputtext.push_back('\n'); else if (c == 't') outputtext.push_back('\t'); else outputtext.push_back(c); } c = my_ttnextchar (tthere, ttend); } else if (c == '_') { // might have found the start of a macro // embedded macros (macros within the names // of other macros) are no longer supported -- Rodger. // save the current state (in case we need to back out) text_t::const_iterator savedtthere = tthere; unsigned short savedc = c; macroname.clear(); macropackage = package; macroargs.clear(); c = my_ttnextchar (tthere, ttend); // skip the '_' // get the macroname while (tthere != ttend && (!is_unicode_space(c)) && (c != '\\') && (c != '_') &&(c != ':') && (macroname.size() < 80)) { macroname.push_back((unsigned char)c); c = my_ttnextchar (tthere, ttend); } if (c == ':') { // we actually had the macro package c = my_ttnextchar (tthere, ttend); macropackage = macroname; macroname.clear (); // get the macro name (honest!) while ((tthere != ttend) && (!is_unicode_space(c)) && (c != '\\') && (c != '_') &&(c != ':') && (macroname.size() < 80)) { macroname.push_back((unsigned char)c); c = my_ttnextchar (tthere, ttend); } } if ((tthere != ttend) && (c == '_') && (macroname.size() > 0)) { // found a macro!!!! (maybe) c = my_ttnextchar (tthere, ttend); // skip the '_' // get the parameters (if there are any) if ((tthere != ttend) && (c == '(')) { c = my_ttnextchar (tthere, ttend); // skip '(' ++openbrackets; // have to be careful of quotes unsigned short quote = '\0'; while (tthere != ttend) { if (c == '\\') { // have an escape character, save escape // character and next character as well macroargs.push_back (c); c = my_ttnextchar (tthere, ttend); } else if (quote == '\0') { // not in a quote at the moment if (c == '(') { ++openbrackets; } else if (c == ')') { --openbrackets; if (openbrackets==0) { // found end of the arguments c = my_ttnextchar (tthere, ttend); // skip ')' break; } else { // nested brackets } } else if (c == '\'' || c == '\"') { // found a quote quote = c; } } else { // we are in a quote if (c == quote) quote = '\0'; } // save this character if (tthere != ttend) macroargs.push_back (c); c = my_ttnextchar (tthere, ttend); } // while (tthere != ttend) } // // we now have the macropackage, macroname, and macroargs // // try to expand out this macro text_t expandedmacro; if (macro (macroname, macropackage.empty()?defaultpackage:macropackage, macroargs, expandedmacro, recursiondepth)) { // append the expanded macro outputtext += expandedmacro; } else { // wasn't a macro tthere = savedtthere; c = savedc; outputtext.push_back(c); // the '_' c = my_ttnextchar (tthere, ttend); } } else { // wasn't a macro tthere = savedtthere; c = savedc; outputtext.push_back(c); // the '_' c = my_ttnextchar (tthere, ttend); } } else { // nothing of interest outputtext.push_back(c); // the '_' c = my_ttnextchar (tthere, ttend); } } // end of while (tthere != ttend) } ostream *displayclass::setlogout (ostream *thelogout) { ostream *oldlogout = logout; logout = thelogout; return oldlogout; } // protected methods for display // reloads any default macro files which have changed // returns 0 no errors occurred // -1 an error occurred while trying to load one of the files int displayclass::checkdefaultmacrofiles () { // this isn't implemented yet return 0; } // isdefaultmacro sees if there is an entry in the list of // default macros with the given package and macro name // returns 0 if no macros in the package or in the global package // were found // 1 if no macros in the package were found but a macro // in the global package was found // 2 if a macro in the given package was found int displayclass::isdefaultmacro (const text_t &package, const text_t ¯oname) { // make sure a macroname was supplied if (macroname.empty()) return 0; if (!package.empty()) { // check the given package if (defaultmacros->macro_find(package, macroname) != NULL) return 2; } // check the Global package if (defaultmacros->macro_find(defaultpackage, macroname) != NULL) return 1; // not found return 0; } // havemacro sees if there is an entry in the list of macros or // default macros with the given package and macro name // returns 0 if no macros in the package or in the global package // were found // 1 if no macros in the package were found but a macro // in the global package was found // 2 if a macro in the given package was found // 4 if no macros in the package were found but a macro // in the global package was found in default macros // 8 if a macro in the given package was found in default int displayclass::havemacro(const text_t &package, const text_t ¯oname) { // make sure a macroname was supplied if (macroname.empty()) return 0; // make package "Global" if it doesn't point to anything yet if (package.empty()) { // check the Global package in current macros list if (currentmacros->macro_find(defaultpackage, macroname) != NULL) return 1; // check the Global package in default macros list if (defaultmacros->macro_find(defaultpackage, macroname) != NULL) return 4; } else { // check the given package in current macros list if (currentmacros->macro_find(package, macroname) != NULL) return 2; // check the Global package in current macros list if (currentmacros->macro_find(defaultpackage, macroname) != NULL) return 1; // check the given package in default macros list if (defaultmacros->macro_find(package, macroname) != NULL) return 8; // check the Global package in default macros list if (defaultmacros->macro_find(defaultpackage, macroname) != NULL) return 4; } // nothing was found return 0; } // setparammacro adds an entry to the list of default macros // returns 0 if there was no error, // -1 if it redefined a macro // -2 if it hid a Global macro // -3 if it redefined a macro and hid a Global macro // -4 if no macroname was supplied int displayclass::setparammacro (parammacros_t* macrotable, const text_t &package, const text_t ¯oname, text_t macroparams, const text_t &filename, const text_t ¯ovalue) { // make sure a macroname was supplied if (macroname.empty()) return -4; // put the parameters in a standard form paramhashtype paramhash; if (macroparams.empty()) macroparams = "ignore=yes"; splitparams (macroparams, paramhash); joinparams (paramhash, macroparams); // remember these parameters allparams.insert (macroparams); // remember this filename (this part isn't finished yet -- Rodger). // set the macro if (package.empty()) { return macrotable->setmacro(defaultpackage, macroname, macroparams, filename, macrovalue); } else { return macrotable->setmacro(package, macroname, macroparams, filename, macrovalue); } } // setdefaultmacro adds an entry to the list of default macros // returns 0 if there was no error, // -1 if it redefined a macro // -2 if it hid a Global macro // -3 if it redefined a macro and hid a Global macro // -4 if no macroname was supplied int displayclass::setdefaultmacro (const text_t &package, const text_t ¯oname, text_t macroparams, const text_t &filename, const text_t ¯ovalue) { return setparammacro(defaultmacros,package,macroname, macroparams,filename,macrovalue); } // public function int displayclass::setcollectionmacro(const text_t &package, const text_t ¯oname, text_t mparams, const text_t ¯ovalue) { return setcollectionmacro(package, macroname, mparams, "memory", macrovalue); } // setcollectionmacro adds an entry to the list of collection specific macros // returns 0 if there was no error, // -1 if it redefined a macro // -2 if it hid a Global macro // -3 if it redefined a macro and hid a Global macro // -4 if no macroname was supplied int displayclass::setcollectionmacro (const text_t &package, const text_t ¯oname, text_t mparams, const text_t &filename, const text_t ¯ovalue) { return setparammacro(collectionmacros,package,macroname,mparams, filename,macrovalue); } // evaluates a boolean expression // returns false if expr equals "" or "0". // otherwise returns true *unless* expr is // format "XXXX" eq/ne "dddd" in which case // the two quoted strings are compared bool displayclass::boolexpr (const text_t &package, const text_t &expr, int recursiondepth) { if (expr.empty()) return false; text_t expexpr; if (package.empty()) { expandstring (defaultpackage, expr, expexpr, recursiondepth); } else { expandstring (package, expr, expexpr, recursiondepth); } if (expexpr.empty() || expexpr == "0") return false; // allow \" as start of exp as well as ", cos this is what the GLI writes out if (expr[0] != '\"' && !(expr[0] == '\\' && expr[1] == '"')) { return true; } // don't use expexpr while separating quoted parts of // expression just in case expanded out macros contain // quotes text_t::const_iterator here = expr.begin(); text_t::const_iterator end = expr.end(); int quotecount = 0; text_t string1, expstring1; text_t string2, expstring2; text_t op; text_t combineop; bool result = false; // an empty string is false bool result2 = false; while (here != end) { // get a comparison quotecount = 0; string1.clear(); string2.clear(); op.clear(); while (here != end) { if (*here == '\\' && *(here+1) =='"') { ++quotecount; // found an escaped quote ++here; } else if (*here == '"') ++quotecount; else if (quotecount == 1) string1.push_back(*here); else if ((quotecount == 2) && !is_unicode_space (*here)) op.push_back(*here); else if (quotecount == 3) string2.push_back(*here); else if (quotecount >= 4) break; ++here; } expandstring (package, string1, expstring1, recursiondepth); expandstring (package, string2, expstring2, recursiondepth); // get next result result2 = true; // any badly formatted string will return true if (op == "eq") result2 = (expstring1 == expstring2); else if (op == "ne") result2 = (expstring1 != expstring2); else if (op == "gt") result2 = (expstring1 > expstring2); else if (op == "ge") result2 = (expstring1 >= expstring2); else if (op == "lt") result2 = (expstring1 < expstring2); else if (op == "le") result2 = (expstring1 <= expstring2); else if (op == "==") result2 = (expstring1.getint() == expstring2.getint()); else if (op == "!=") result2 = (expstring1.getint() != expstring2.getint()); else if (op == ">") result2 = (expstring1.getint() > expstring2.getint()); else if (op == ">=") result2 = (expstring1.getint() >= expstring2.getint()); else if (op == "<") result2 = (expstring1.getint() < expstring2.getint()); else if (op == "<=") result2 = (expstring1.getint() <= expstring2.getint()); else if (op == "sw") result2 = (starts_with(expstring1,expstring2)); else if (op == "ew") result2 = (ends_with(expstring1,expstring2)); // combine the results if (combineop == "&&") result = (result && result2); else if (combineop == "||") result = (result || result2); else result = result2; // get next combination operator combineop.clear(); while (here != end && *here != '"') { if (!is_unicode_space(*here)) combineop.push_back(*here); ++here; } } return result; } mvalue* displayclass::macro_find (parammacros_t* macrotable, const text_t &packagename, const text_t ¯oname) { mvalue *macrovalue = NULL; // next look in the named parammacros for the named package if (macrotable->macro_find (packagename, macroname) != NULL) { paramspeclist::iterator paramhere, paramend; paramhere = orderparamlist->begin(); paramend = orderparamlist->end(); while ((macrovalue == NULL) && (paramhere != paramend)) { macrovalue = macrotable->parameter_find(packagename, macroname, (*paramhere).param); ++paramhere; } } return macrovalue; } // (recursively) expand out a macro // returns true if the macro was found // false if the macro was not found // macropackage should never be empty bool displayclass::macro (const text_t ¯oname, const text_t ¯opackage, const text_t ¯oparam, text_t &outputtext, int recursiondepth) { assert(!macropackage.empty()); //??? //outconvertclass text_t2ascii; text_tlist splitmacroparam; text_t::const_iterator hereit, endit; text_t aparam; unsigned short c = '\0', quote = '\0'; // cerr << "r: " << recursiondepth << "\n"; // check for deep recursion if (recursiondepth >= MAXRECURSIONDEPTH) { if (logout != NULL) (*logout) << "Warning: deep recursion, limiting macro expansion\n"; return false; } // check the macropackage //if (macropackage.empty()) macropackage = "Global"; // get the parameters (but don't expand them) if (macroparam.size() > 0) { // set up for loop hereit = macroparam.begin(); endit = macroparam.end(); if (hereit != endit) c = (*hereit); int openbrackets=0; // keep track of bracket level eg _If_(1, _macro_(arg)) // this allows commas inside brackets (as arguments) while (hereit != endit) { // get the next parameter aparam.clear(); quote = '\0'; // not-quoted unless proven quoted // ignore initial whitespace while ((hereit!=endit)&&is_unicode_space(c)) c=my_ttnextchar(hereit,endit); // look for the end of the parameter while (hereit != endit) { if (c == '\\') { // found escape character, also get next character aparam.push_back(c); c = my_ttnextchar (hereit, endit); if (hereit != endit) aparam.push_back(c); } else if (quote=='\0' && (c=='\'' /*|| c=='"'*/)) { // found a quoted section quote = c; if (openbrackets>0) aparam.push_back(c); } else if (quote!='\0' && c==quote) { // found the end of a quote quote = '\0'; if (openbrackets<0) aparam.push_back(c); } else if (quote=='\0' && (c==',' || c==')') && openbrackets==0) { // found the end of a parameter c = my_ttnextchar (hereit, endit); break; } else if (c=='(') { aparam.push_back(c); ++openbrackets; } else if (c==')') { --openbrackets; aparam.push_back(c); } else { // ordinary character aparam.push_back(c); } c = my_ttnextchar (hereit, endit); } // add this parameter to the list splitmacroparam.push_back(aparam); } } if (macroname == "If") { // get the condition, then clause and else clause text_tlist::iterator paramcond = splitmacroparam.begin(); text_tlist::iterator paramend = splitmacroparam.end(); text_tlist::iterator paramthen = paramend; text_tlist::iterator paramelse = paramend; if (paramcond != paramend) { paramthen = paramcond; ++paramthen; } if (paramthen != paramend) { paramelse = paramthen; ++paramelse; } // will always output something outputtext.clear(); // expand out the first parameter if (paramcond != paramend) { /// text_t tmpoutput; /// expandstring (macropackage, *paramcond, tmpoutput, recursiondepth+1); /// lc (tmpoutput); // test the expanded string if (boolexpr (macropackage, *paramcond, recursiondepth+1)) { //(tmpoutput.size()) && (tmpoutput != "false") && (tmpoutput != "0")) { // true if (paramthen != paramend) expandstring (macropackage, *paramthen, outputtext, recursiondepth+1); } else { // false if (paramelse != paramend) expandstring (macropackage, *paramelse, outputtext, recursiondepth+1); } } return true; } // try and find this macro // this list might be replaced by something a little more // sophisticated in the future. text_tlist packagelist; packagelist.push_back (macropackage); packagelist.push_back ("Global"); text_tlist::iterator packagehere = packagelist.begin(); text_tlist::iterator packageend = packagelist.end(); mvalue *macrovalue = NULL; while (packagehere != packageend) { // first look in the currentmacros macrovalue = currentmacros->macro_find(*packagehere, macroname); if (macrovalue != NULL) break; macrovalue = currentmacros->macro_find("Style", macroname); if (macrovalue != NULL) break; // look in the collection specific macros macrovalue = macro_find(collectionmacros,*packagehere,macroname); if (macrovalue != NULL) break; // and again in the collection specific package "Style" macrovalue = macro_find(collectionmacros,(const char*)"Style",macroname); if (macrovalue != NULL) break; // look in the default macros macrovalue = macro_find(defaultmacros,*packagehere,macroname); if (macrovalue != NULL) break; // and again in the package "Style" macrovalue = macro_find(defaultmacros,(const char*)"Style",macroname); if (macrovalue != NULL) break; ++packagehere; } if (macrovalue != NULL) { // we found a macro // replace all the option macros (_1_, _2_, ...) in the value of this macro // and decide if we need to expand the value of this macro bool needsexpansion = false; text_t tempmacrovalue; tempmacrovalue.clear(); hereit = macrovalue->value.begin(); endit = macrovalue->value.end(); if (hereit != endit) c = (*hereit); while (hereit != endit) { if (c == '\\') { // found an escape character needsexpansion = true; tempmacrovalue.push_back(c); c = my_ttnextchar (hereit, endit); if (hereit != endit) tempmacrovalue.push_back(c); c = my_ttnextchar (hereit, endit); } else if (c == '_') { text_t::const_iterator savedhereit = hereit; // attempt to get a number int argnum = 0; unsigned short digc = my_ttnextchar (hereit, endit); while (digc >= '0' && digc <= '9') { argnum = argnum*10 + digc - '0'; digc = my_ttnextchar (hereit, endit); } if (digc == '_' && argnum > 0) { // found an option macro, append the appropriate text text_tlist::iterator ttlisthere = splitmacroparam.begin(); text_tlist::iterator ttlistend = splitmacroparam.end(); while ((argnum > 1) && (ttlisthere != ttlistend)) { --argnum; ++ttlisthere; } if (ttlisthere != ttlistend) { tempmacrovalue.append(*ttlisthere); if (findchar((*ttlisthere).begin(), (*ttlisthere).end(), '_') != (*ttlisthere).end()) needsexpansion = true; } c = my_ttnextchar (hereit, endit); } else { // wasn't a option macro needsexpansion = true; tempmacrovalue.push_back(c); hereit = savedhereit; c = my_ttnextchar (hereit, endit); } } else { tempmacrovalue.push_back(c); c = my_ttnextchar (hereit, endit); } } // recursively replace this macro (if we need to) if (needsexpansion) { expandstring (*packagehere, tempmacrovalue, outputtext, recursiondepth+1); } else { outputtext += tempmacrovalue; } return true; } // GRB: deal with remaining code properly as it faults return false; outconvertclass text_t2ascii; if (logout != NULL) { (*logout) << text_t2ascii << "Warning: _" << macropackage << ":" << macroname << "_ not found\n"; } return false; // didn't find the macro } void displayclass::printdefaultmacros () { defpackagemap::const_iterator packagemapit; defmacromap::const_iterator macromapit; defparammap::const_iterator parammapit; const mvalue *mvalueptr; outconvertclass text_t2ascii; text_t packagename, macroname, macroparam; for (packagemapit = defaultmacros->package_begin(); packagemapit != defaultmacros->package_end(); ++packagemapit) { packagename = (*packagemapit).first; // cerr << "***package : \"" << packagename << "\"\n"; for (macromapit = (*packagemapit).second.begin(); macromapit != (*packagemapit).second.end(); ++macromapit) { macroname = (*macromapit).first; // cerr << "***macroname : \"" << macroname << "\"\n"; for (parammapit = (*macromapit).second.begin(); parammapit != (*macromapit).second.end(); ++parammapit) { macroparam = (*parammapit).first; mvalueptr = &((*parammapit).second); cerr << text_t2ascii << "package : \"" << packagename << "\"\n"; cerr << text_t2ascii << "macroname : \"" << macroname << "\"\n"; cerr << text_t2ascii << "parameters: [" << macroparam << "]\n"; cerr << text_t2ascii << "filename : \"" << mvalueptr->filename << "\"\n"; cerr << text_t2ascii << "value : {" << mvalueptr->value << "}\n\n"; } } } } void displayclass::printallparams () { text_tset::iterator text_tsetit; outconvertclass text_t2ascii; cerr << text_t2ascii << "** allparams\n"; for (text_tsetit=allparams.begin(); text_tsetit!=allparams.end(); ++text_tsetit) { cerr << text_t2ascii << (*text_tsetit) << "\n"; } cerr << text_t2ascii << "\n"; } ///////////////////////////////////// // stuff to do tricky output ///////////////////////////////////// displayclass &operator<< (outconvertclass &theoutc, displayclass &display) { display.setconvertclass (&theoutc); return display; } displayclass &operator<< (displayclass &display, const text_t &t) { outconvertclass *theoutc = display.getconvertclass(); if (theoutc == NULL) return display; text_t output; display.expandstring (t, output); (*theoutc) << output; return display; }