]> git.lyx.org Git - lyx.git/blobdiff - src/insets/insetcommandparams.C
* src/LyXAction.C: mark goto-clear-bookmark as working without buffer
[lyx.git] / src / insets / insetcommandparams.C
index 1325605f192524d4f40fadfc2b17734809ca96f3..bd0c208ef2a7603e4dbc9bb076e96821eb204fe7 100644 (file)
  * Licence details can be found in the file COPYING.
  *
  * \author Angus Leeming
+ * \author Georg Baum
  *
- * Full author contact details are available in file CREDITS
+ * Full author contact details are available in file CREDITS.
  */
 
-#ifdef __GNUG__
-#pragma implementation
-#endif
+#include <config.h>
 
 #include "insetcommandparams.h"
-#include "lyxlex.h"
+
 #include "debug.h"
+#include "lyxlex.h"
 
-#include "support/LOstream.h"
+#include "support/lstrings.h"
 
-using std::ostream;
-using std::endl;
+#include <boost/assert.hpp>
 
 
-InsetCommandParams::InsetCommandParams()
-{}
+namespace lyx {
 
+using support::findToken;
 
-InsetCommandParams::InsetCommandParams(string const & n,
-                                       string const & c,
-                                       string const & o)
-       : cmdname(n), contents(c), options(o), preview_(false)
-{}
+using std::string;
+using std::endl;
+using std::ostream;
 
 
-string const InsetCommandParams::getAsString() const
+InsetCommandParams::InsetCommandParams(string const & name)
+       : name_(name), preview_(false)
 {
-       return cmdname + "|++|" + contents + "|++|" + options;
+       info_ = findInfo(name);
+       BOOST_ASSERT(info_);
+       params_.resize(info_->n);
 }
 
 
-void InsetCommandParams::setFromString(string const & b)
+InsetCommandParams::CommandInfo const *
+InsetCommandParams::findInfo(std::string const & name)
 {
-       string::size_type idx = b.find("|++|");
-       if (idx == string::npos) {
-               cmdname = b;
-               contents = "";
-               options = "";
-               return;
+       // No parameter may be named "preview", because that is a required
+       // flag for all commands.
+
+       // InsetBibitem
+       if (name == "bibitem") {
+               static const char * const paramnames[] = {"label", "key", ""};
+               static const bool isoptional[] = {true, false};
+               static const CommandInfo info = {2, paramnames, isoptional};
+               return &info;
+       }
+
+       // InsetBibtex
+       if (name == "bibtex") {
+               static const char * const paramnames[] =
+                               {"options", "btprint", "bibfiles", ""};
+               static const bool isoptional[] = {true, true, false};
+               static const CommandInfo info = {3, paramnames, isoptional};
+               return &info;
+       }
+
+       // InsetCitation
+       // FIXME: Use is_possible_cite_command() in
+       // src/frontends/controllers/biblio.C, see comment in src/factory.C.
+       if (name == "cite" || name == "citet" || name == "citep" || name == "citealt" ||
+           name == "citealp" || name == "citeauthor" || name == "citeyear" ||
+           name == "citeyearpar" || name == "citet*" || name == "citep*" ||
+           name == "citealt*" || name == "citealp*" ||
+           name == "citeauthor*" || name == "Citet" || name == "Citep" ||
+           name == "Citealt" || name == "Citealp" || name == "Citeauthor" ||
+           name == "Citet*" || name == "Citep*" || name == "Citealt*" ||
+           name == "Citealp*" || name == "Citeauthor*" ||
+           name == "citefield" || name == "citetitle" || name == "cite*") {
+               // standard cite does only take one argument if jurabib is
+               // not used, but jurabib extends this to two arguments, so
+               // we have to allow both here. InsetCitation takes care that
+               // LaTeX output is nevertheless correct.
+               static const char * const paramnames[] =
+                               {"after", "before", "key", ""};
+               static const bool isoptional[] = {true, true, false};
+               static const CommandInfo info = {3, paramnames, isoptional};
+               return &info;
        }
 
-       cmdname = b.substr(0, idx);
-       string tmp = b.substr(idx+4);
+       // InsetFloatlist
+       if (name == "floatlist") {
+               static const char * const paramnames[] = {"type", ""};
+               static const bool isoptional[] = {false};
+               static const CommandInfo info = {1, paramnames, isoptional};
+               return &info;
+       }
+
+       // InsetHfill
+       if (name == "hfill") {
+               static const char * const paramnames[] = {""};
+               static const CommandInfo info = {0, paramnames, 0};
+               return &info;
+       }
+
+       // InsetInclude
+       if (name == "include" || name == "input" || name == "verbatiminput" ||
+           name == "verbatiminput*") {
+               static const char * const paramnames[] = {"filename", ""};
+               static const bool isoptional[] = {false};
+               static const CommandInfo info = {1, paramnames, isoptional};
+               return &info;
+       }
+
+       // InsetIndex, InsetPrintIndex, InsetLabel
+       if (name == "index" || name == "printindex" || name == "label") {
+               static const char * const paramnames[] = {"name", ""};
+               static const bool isoptional[] = {false};
+               static const CommandInfo info = {1, paramnames, isoptional};
+               return &info;
+       }
+
+       // InsetNomencl
+       if (name == "nomenclature") {
+               static const char * const paramnames[] = {"prefix", "symbol", "description", ""};
+               static const bool isoptional[] = {true, false, false};
+               static const CommandInfo info = {3, paramnames, isoptional};
+               return &info;
+       }
+
+       // InsetPrintNomencl
+       if (name == "printnomenclature") {
+               static const char * const paramnames[] = {"labelwidth", ""};
+               static const bool isoptional[] = {true};
+               static const CommandInfo info = {1, paramnames, isoptional};
+               return &info;
+       }
 
-       idx = tmp.find("|++|");
-       if (idx == string::npos) {
-               contents = tmp;
-               options = "";
-       } else {
-               contents  = tmp.substr(0, idx);
-               options = tmp.substr(idx+4);
+       // InsetRef
+       if (name == "eqref" || name == "pageref" || name == "vpageref" ||
+           name == "vref" || name == "prettyref" || name == "ref") {
+               static const char * const paramnames[] =
+                               {"name", "reference", ""};
+               static const bool isoptional[] = {true, false};
+               static const CommandInfo info = {2, paramnames, isoptional};
+               return &info;
        }
+
+       // InsetTOC
+       if (name == "tableofcontents") {
+               static const char * const paramnames[] = {"type", ""};
+               static const bool isoptional[] = {false};
+               static const CommandInfo info = {1, paramnames, isoptional};
+               return &info;
+       }
+
+       // InsetUrl
+       if (name == "htmlurl" || name == "url") {
+               static const char * const paramnames[] =
+                               {"name", "target", ""};
+               static const bool isoptional[] = {true, false};
+               static const CommandInfo info = {2, paramnames, isoptional};
+               return &info;
+       }
+
+       return 0;
+}
+
+
+void InsetCommandParams::setCmdName(string const & name)
+{
+       name_ = name;
+       CommandInfo const * const info = findInfo(name);
+       BOOST_ASSERT(info);
+       ParamVector params(info->n);
+       // Overtake parameters with the same name
+       for (size_t i = 0; i < info_->n; ++i) {
+               int j = findToken(info->paramnames, info_->paramnames[i]);
+               if (j >= 0)
+                       params[j] = params_[i];
+       }
+       info_ = info;
+       std::swap(params, params_);
 }
 
 
 void InsetCommandParams::scanCommand(string const & cmd)
 {
-       string tcmdname, toptions, tcontents;
+       string tcmdname, toptions, tsecoptions, tcontents;
 
        if (cmd.empty()) return;
 
-       enum { WS, CMDNAME, OPTION, CONTENT } state = WS;
+       enum { WS, CMDNAME, OPTION, SECOPTION, CONTENT } state = WS;
 
        // Used to handle things like \command[foo[bar]]{foo{bar}}
        int nestdepth = 0;
 
        for (string::size_type i = 0; i < cmd.length(); ++i) {
-               char c = cmd[i];
+               char const c = cmd[i];
                if ((state == CMDNAME && c == ' ') ||
                    (state == CMDNAME && c == '[') ||
                    (state == CMDNAME && c == '{')) {
                        state = WS;
                }
                if ((state == OPTION  && c == ']') ||
+                   (state == SECOPTION  && c == ']') ||
                    (state == CONTENT && c == '}')) {
                        if (nestdepth == 0) {
                                state = WS;
@@ -90,61 +209,78 @@ void InsetCommandParams::scanCommand(string const & cmd)
                        }
                }
                if ((state == OPTION  && c == '[') ||
+                   (state == SECOPTION  && c == '[') ||
                    (state == CONTENT && c == '{')) {
                        ++nestdepth;
                }
                switch (state) {
                case CMDNAME:   tcmdname += c; break;
                case OPTION:    toptions += c; break;
+               case SECOPTION: tsecoptions += c; break;
                case CONTENT:   tcontents += c; break;
-               case WS:
+               case WS: {
+                       char const b = i? cmd[i-1]: 0;
                        if (c == '\\') {
                                state = CMDNAME;
-                       } else if (c == '[') {
+                       } else if (c == '[' && b != ']') {
                                state = OPTION;
                                nestdepth = 0; // Just to be sure
+                       } else if (c == '[' && b == ']') {
+                               state = SECOPTION;
+                               nestdepth = 0; // Just to be sure
                        } else if (c == '{') {
                                state = CONTENT;
                                nestdepth = 0; // Just to be sure
                        }
                        break;
                }
+               }
        }
 
        // Don't mess with this.
        if (!tcmdname.empty())  setCmdName(tcmdname);
        if (!toptions.empty())  setOptions(toptions);
+       if (!tsecoptions.empty())  setSecOptions(tsecoptions);
        if (!tcontents.empty()) setContents(tcontents);
 
        if (lyxerr.debugging(Debug::PARSER))
                lyxerr << "Command <" <<  cmd
-                      << "> == <" << getCommand()
+                      << "> == <" << to_utf8(getCommand())
                       << "> == <" << getCmdName()
                       << '|' << getContents()
-                      << '|' << getOptions() << '>' << endl;
+                      << '|' << getOptions()
+                      << '|' << getSecOptions() << '>' << endl;
 }
 
 
 void InsetCommandParams::read(LyXLex & lex)
 {
-       string token;
-
-       if (lex.eatLine()) {
-               token = lex.getString();
-               scanCommand(token);
-       } else {
-               lex.printError("InsetCommand: Parse error: `$$Token'");
+       if (lex.isOK()) {
+               lex.next();
+               name_ = lex.getString();
+               info_ = findInfo(name_);
+               if (!info_)
+                       lex.printError("InsetCommand: Unknown inset name `$$Token'");
        }
 
+       string token;
        while (lex.isOK()) {
                lex.next();
                token = lex.getString();
                if (token == "\\end_inset")
                        break;
+               // FIXME Why is preview_ read but not written?
                if (token == "preview") {
                        lex.next();
                        preview_ = lex.getBool();
+                       continue;
                }
+               int const i = findToken(info_->paramnames, token);
+               if (i >= 0) {
+                       lex.next(true);
+                       params_[i] = lex.getDocString();
+               } else
+                       lex.printError("Unknown parameter name `$$Token' for command " + name_);
        }
        if (token != "\\end_inset") {
                lex.printError("Missing \\end_inset at this point. "
@@ -155,27 +291,161 @@ void InsetCommandParams::read(LyXLex & lex)
 
 void InsetCommandParams::write(ostream & os) const
 {
-       os << "LatexCommand " << getCommand() << "\n";
+       os << "LatexCommand " << name_ << '\n';
+       for (size_t i = 0; i < info_->n; ++i)
+               if (!params_[i].empty())
+                       // FIXME UNICODE
+                       os << info_->paramnames[i] << ' '
+                          << LyXLex::quoteString(to_utf8(params_[i]))
+                          << '\n';
 }
 
 
-string const InsetCommandParams::getCommand() const
+docstring const InsetCommandParams::getCommand() const
 {
-       string s;
-       if (!getCmdName().empty()) s += "\\"+getCmdName();
-       if (!getOptions().empty()) s += "["+getOptions()+']';
-       s += "{"+getContents()+'}';
+       docstring s = '\\' + from_ascii(name_);
+       bool noparam = true;
+       for (size_t i = 0; i < info_->n; ++i) {
+               if (info_->optional[i]) {
+                       if (params_[i].empty()) {
+                               // We need to write this parameter even if
+                               // it is empty if nonempty optional parameters
+                               // follow before the next required parameter.
+                               for (size_t j = i + 1; j < info_->n; ++j) {
+                                       if (!info_->optional[j])
+                                               break;
+                                       if (!params_[j].empty()) {
+                                               s += "[]";
+                                               noparam = false;
+                                               break;
+                                       }
+                               }
+                       } else {
+                               s += '[' + params_[i] + ']';
+                               noparam = false;
+                       }
+               } else {
+                       s += '{' + params_[i] + '}';
+                       noparam = false;
+               }
+       }
+       if (noparam)
+               // Make sure that following stuff does not change the
+               // command name.
+               s += "{}";
        return s;
 }
 
 
+std::string const InsetCommandParams::getOptions() const
+{
+       for (size_t i = 0; i < info_->n; ++i)
+               if (info_->optional[i])
+                       return to_utf8(params_[i]);
+       lyxerr << "Programming error: get nonexisting option in "
+              << name_ << " inset." << endl;; 
+       return string();
+}
+
+
+std::string const InsetCommandParams::getSecOptions() const
+{
+       bool first = true;
+       for (size_t i = 0; i < info_->n; ++i)
+               if (info_->optional[i]) {
+                       if (first)
+                               first = false;
+                       else
+                               return to_utf8(params_[i]);
+               }
+       // Happens in InsetCitation
+       lyxerr << "Programming error: get nonexisting second option in "
+              << name_ << " inset." << endl;; 
+       return string();
+}
+
+
+std::string const InsetCommandParams::getContents() const
+{
+       for (size_t i = 0; i < info_->n; ++i)
+               if (!info_->optional[i])
+                       return to_utf8(params_[i]);
+       BOOST_ASSERT(false);
+       return string();
+}
+
+
+void InsetCommandParams::setOptions(std::string const & o)
+{
+       for (size_t i = 0; i < info_->n; ++i)
+               if (info_->optional[i]) {
+                       params_[i] = from_utf8(o);
+                       return;
+               }
+       lyxerr << "Programming error: set nonexisting option in "
+              << name_ << " inset." << endl;; 
+}
+
+
+void InsetCommandParams::setSecOptions(std::string const & s)
+{
+       bool first = true;
+       for (size_t i = 0; i < info_->n; ++i)
+               if (info_->optional[i]) {
+                       if (first)
+                               first = false;
+                       else {
+                               params_[i] = from_utf8(s);
+                               return;
+                       }
+               }
+       // Happens in InsetCitation
+       lyxerr << "Programming error: set nonexisting second option in "
+              << name_ << " inset." << endl;; 
+}
+
+
+void InsetCommandParams::setContents(std::string const & c)
+{
+       for (size_t i = 0; i < info_->n; ++i)
+               if (!info_->optional[i]) {
+                       params_[i] = from_utf8(c);
+                       return;
+               }
+       BOOST_ASSERT(false);
+}
+
+
+docstring const & InsetCommandParams::operator[](string const & name) const
+{
+       int const i = findToken(info_->paramnames, name);
+       BOOST_ASSERT(i >= 0);
+       return params_[i];
+}
+
+
+docstring & InsetCommandParams::operator[](string const & name)
+{
+       int const i = findToken(info_->paramnames, name);
+       BOOST_ASSERT(i >= 0);
+       return params_[i];
+}
+
+
+void InsetCommandParams::clear()
+{
+       for (size_t i = 0; i < info_->n; ++i)
+               params_[i].clear();
+}
+
+
 bool operator==(InsetCommandParams const & o1,
                InsetCommandParams const & o2)
 {
-       return o1.getCmdName() == o2.getCmdName()
-               && o1.getContents() == o2.getContents()
-               && o1.getOptions() == o2.getOptions()
-               && o1.preview() == o2.preview();
+       return o1.name_ == o2.name_ &&
+              o1.info_ == o2.info_ &&
+              o1.params_ == o2.params_ &&
+              o1.preview_ == o2.preview_;
 }
 
 
@@ -184,3 +454,6 @@ bool operator!=(InsetCommandParams const & o1,
 {
        return !(o1 == o2);
 }
+
+
+} // namespace lyx