]> git.lyx.org Git - lyx.git/blob - src/insets/InsetCommandParams.cpp
This is the first part of a cleanup of how we handle the InsetCommand hierarchy....
[lyx.git] / src / insets / InsetCommandParams.cpp
1 /**
2  * \file InsetCommandParams.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Angus Leeming
7  * \author Georg Baum
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "InsetCommandParams.h"
15
16 #include "debug.h"
17 #include "gettext.h"
18 #include "Lexer.h"
19
20 #include "support/ExceptionMessage.h"
21 #include "support/lstrings.h"
22
23 #include <boost/assert.hpp>
24
25
26 namespace lyx {
27
28 using support::findToken;
29
30 using std::string;
31 using std::endl;
32 using std::ostream;
33
34 using support::ExceptionMessage;
35 using support::WarningException;
36
37
38 //FIXME There is no reason now for this to take a string argument.
39 //It'd be much more robust if it took an Inset::Code, since then
40 //the compiler would do some checking for us.
41 InsetCommandParams::InsetCommandParams(string const & insetType)
42         : insetType_(insetType), preview_(false)
43 {
44         cmdName_ = getDefaultCmd(insetType);
45         info_ = findInfo(insetType, cmdName_);
46         BOOST_ASSERT(info_);
47         params_.resize(info_->n);
48 }
49
50
51 InsetCommandParams::InsetCommandParams(string const & insetType,
52         string const & cmdName)
53         : insetType_(insetType), cmdName_(cmdName), preview_(false)
54 {
55         info_ = findInfo(insetType, cmdName);
56         BOOST_ASSERT(info_);
57         params_.resize(info_->n);
58 }
59
60
61 //FIXME This should go into the Insets themselves...so they will tell
62 //us what parameters they want.
63 //Should this just vanish in favor of the two arg version, or is there
64 //a reason to use it in some cases? What should happen in the single
65 //arg case, then? Maybe use the default? or leave that up to the inset?
66 InsetCommandParams::CommandInfo const *
67 InsetCommandParams::findInfo(std::string const & insetType)
68 {
69         // No parameter may be named "preview", because that is a required
70         // flag for all commands.
71
72         // InsetBibitem
73         if (insetType == "bibitem") {
74                 static const char * const paramnames[] = {"label", "key", ""};
75                 static const bool isoptional[] = {true, false};
76                 static const CommandInfo info = {2, paramnames, isoptional};
77                 return &info;
78         }
79
80         // InsetBibtex
81         if (insetType == "bibtex") {
82                 static const char * const paramnames[] =
83                                 {"options", "btprint", "bibfiles", ""};
84                 static const bool isoptional[] = {true, true, false};
85                 static const CommandInfo info = {3, paramnames, isoptional};
86                 return &info;
87         }
88
89         // InsetCitation
90         if (insetType == "citation") {
91                 // standard cite does only take one argument if jurabib is
92                 // not used, but jurabib extends this to two arguments, so
93                 // we have to allow both here. InsetCitation takes care that
94                 // LaTeX output is nevertheless correct.
95                 static const char * const paramnames[] =
96                                 {"after", "before", "key", ""};
97                 static const bool isoptional[] = {true, true, false};
98                 static const CommandInfo info = {3, paramnames, isoptional};
99                 return &info;
100         }
101
102         // InsetFloatlist
103         if (insetType == "floatlist") {
104                 static const char * const paramnames[] = {"type", ""};
105                 static const bool isoptional[] = {false};
106                 static const CommandInfo info = {1, paramnames, isoptional};
107                 return &info;
108         }
109
110         // InsetHfill
111         if (insetType == "hfill") {
112                 static const char * const paramnames[] = {""};
113                 static const CommandInfo info = {0, paramnames, 0};
114                 return &info;
115         }
116
117         // InsetInclude
118         //FIXME This is really correct only for lstinputlistings, but it shouldn't
119         //cause a problem before we get things sorted out. Eventually, this calls
120         //InsetInclude::getParams(cmdName_), or something of the sort.
121         if (insetType == "include") {
122                 static const char * const paramnames[] = {"filename", "lstparams", ""};
123                 static const bool isoptional[] = {false, true};
124                 static const CommandInfo info = {2, paramnames, isoptional};
125                 return &info;
126         }
127
128         // InsetIndex, InsetPrintIndex, InsetLabel
129         if (insetType == "index" || insetType == "index_print" || insetType == "label") {
130                 static const char * const paramnames[] = {"name", ""};
131                 static const bool isoptional[] = {false};
132                 static const CommandInfo info = {1, paramnames, isoptional};
133                 return &info;
134         }
135
136         // InsetNomencl
137         if (insetType == "nomenclature") {
138                 static const char * const paramnames[] = {"prefix", "symbol", "description", ""};
139                 static const bool isoptional[] = {true, false, false};
140                 static const CommandInfo info = {3, paramnames, isoptional};
141                 return &info;
142         }
143
144         // InsetPrintNomencl
145         if (insetType == "nomencl_print") {
146                 static const char * const paramnames[] = {"labelwidth", ""};
147                 static const bool isoptional[] = {true};
148                 static const CommandInfo info = {1, paramnames, isoptional};
149                 return &info;
150         }
151
152         // InsetRef
153         if (insetType == "ref") {
154                 static const char * const paramnames[] =
155                                 {"name", "reference", ""};
156                 static const bool isoptional[] = {true, false};
157                 static const CommandInfo info = {2, paramnames, isoptional};
158                 return &info;
159         }
160
161         // InsetTOC
162         if (insetType == "toc") {
163                 static const char * const paramnames[] = {"type", ""};
164                 static const bool isoptional[] = {false};
165                 static const CommandInfo info = {1, paramnames, isoptional};
166                 return &info;
167         }
168
169         // InsetUrl
170         if (insetType == "url") {
171                 static const char * const paramnames[] =
172                                 {"name", "target", ""};
173                 static const bool isoptional[] = {true, false};
174                 static const CommandInfo info = {2, paramnames, isoptional};
175                 return &info;
176         }
177
178         return 0;
179 }
180
181
182 //FIXME Will eventually call a static method, etc.
183 InsetCommandParams::CommandInfo const *
184                 InsetCommandParams::findInfo(std::string const & insetType,
185                                              std::string const & cmdName)
186 {
187         return findInfo(insetType);
188 }
189
190
191 //FIXME Should call InsetBibitem::getDefaultCmd(), eg
192 std::string InsetCommandParams::getDefaultCmd(std::string insetType) {
193         if (insetType == "bibitem")
194                 return "bibitem";
195         if (insetType == "bibtex") 
196                 return "";
197         if (insetType == "citation")
198                 return "cite";
199         if (insetType == "floatlist")
200                 return "";
201         if (insetType == "hfill")
202                 return "hfill";
203         if (insetType == "include")
204                 return "include";
205         if (insetType == "index")
206                  return "index";
207         if (insetType == "index_print")
208                 return "print_index";
209         if (insetType == "label")
210                 return "label";
211         if (insetType == "nomenclature")
212                 return "nomenclature";
213         if (insetType == "nomencl_print")
214                 return "printnomenclature";
215         if (insetType == "ref")
216                 return "ref";
217         if (insetType == "toc")
218                 return "tableofcontents";
219         if (insetType == "url")
220                 return "url";
221         return "";      
222 }
223
224
225 void InsetCommandParams::setCmdName(string const & name)
226 {
227         //FIXME Check command compatibility
228         cmdName_ = name;
229         BOOST_ASSERT(!insetType_.empty());
230         CommandInfo const * const info = findInfo(insetType_, cmdName_);
231         BOOST_ASSERT(info);
232         ParamVector params(info->n);
233         // Overtake parameters with the same name
234         for (size_t i = 0; i < info_->n; ++i) {
235                 int j = findToken(info->paramnames, info_->paramnames[i]);
236                 if (j >= 0)
237                         params[j] = params_[i];
238         }
239         info_ = info;
240         std::swap(params, params_);
241 }
242
243
244 void InsetCommandParams::scanCommand(string const & cmd)
245 {
246         string tcmdname, toptions, tsecoptions, tcontents;
247
248         if (cmd.empty()) return;
249
250         enum { WS, CMDNAME, OPTION, SECOPTION, CONTENT } state = WS;
251
252         // Used to handle things like \command[foo[bar]]{foo{bar}}
253         int nestdepth = 0;
254
255         for (string::size_type i = 0; i < cmd.length(); ++i) {
256                 char const c = cmd[i];
257                 if ((state == CMDNAME && c == ' ') ||
258                     (state == CMDNAME && c == '[') ||
259                     (state == CMDNAME && c == '{')) {
260                         state = WS;
261                 }
262                 if ((state == OPTION  && c == ']') ||
263                     (state == SECOPTION  && c == ']') ||
264                     (state == CONTENT && c == '}')) {
265                         if (nestdepth == 0) {
266                                 state = WS;
267                         } else {
268                                 --nestdepth;
269                         }
270                 }
271                 if ((state == OPTION  && c == '[') ||
272                     (state == SECOPTION  && c == '[') ||
273                     (state == CONTENT && c == '{')) {
274                         ++nestdepth;
275                 }
276                 switch (state) {
277                 case CMDNAME:   tcmdname += c; break;
278                 case OPTION:    toptions += c; break;
279                 case SECOPTION: tsecoptions += c; break;
280                 case CONTENT:   tcontents += c; break;
281                 case WS: {
282                         char const b = i? cmd[i-1]: 0;
283                         if (c == '\\') {
284                                 state = CMDNAME;
285                         } else if (c == '[' && b != ']') {
286                                 state = OPTION;
287                                 nestdepth = 0; // Just to be sure
288                         } else if (c == '[' && b == ']') {
289                                 state = SECOPTION;
290                                 nestdepth = 0; // Just to be sure
291                         } else if (c == '{') {
292                                 state = CONTENT;
293                                 nestdepth = 0; // Just to be sure
294                         }
295                         break;
296                 }
297                 }
298         }
299
300         // Don't mess with this.
301         if (!tcmdname.empty())  setCmdName(tcmdname);
302         if (!toptions.empty())  setOptions(toptions);
303         if (!tsecoptions.empty())  setSecOptions(tsecoptions);
304         if (!tcontents.empty()) setContents(tcontents);
305
306         LYXERR(Debug::PARSER) << "Command <" <<  cmd
307                 << "> == <" << to_utf8(getCommand())
308                 << "> == <" << getCmdName()
309                 << '|' << getContents()
310                 << '|' << getOptions()
311                 << '|' << getSecOptions() << '>' << endl;
312 }
313
314
315 void InsetCommandParams::read(Lexer & lex)
316 {
317         //FIXME
318         if (lex.isOK()) {
319                 lex.next();
320                 string insetType = lex.getString();
321                 if (!insetType_.empty() && insetType != insetType_) {
322                         lex.printError("InsetCommand: Attempt to change type of parameters.");
323                         throw ExceptionMessage(WarningException, _("InsetCommand Error: "),
324                                 from_utf8("Attempt to change type of parameters."));
325                 }
326                 // OK, we survived...
327                 insetType_ = insetType;
328         }
329
330         if (lex.isOK()) {
331                 lex.next();
332                 string test = lex.getString();
333                 if (test != "LatexCommand") {
334                         lex.printError("InsetCommand: no LatexCommand line found.");
335                         throw ExceptionMessage(WarningException, _("InsetCommand error:"),
336                                 from_utf8("Can't find LatexCommand line."));
337                 }
338         }
339         lex.next();
340         cmdName_ = lex.getString();
341         //FIXME
342         //check that this command is ok with the inset...
343         //so that'll be some kind of static call, depending 
344         //upon what insetType_ is.
345         //it's possible that should go into InsetCommand.cpp,
346         //or maybe it's a standalone function.
347         info_ = findInfo(insetType_, cmdName_);
348         if (!info_) {
349                 lex.printError("InsetCommand: Unknown inset name `$$Token'");
350                 throw ExceptionMessage(WarningException,
351                         _("Unknown inset name: "), from_utf8(insetType_));
352         }
353         
354         string token;
355         while (lex.isOK()) {
356                 lex.next();
357                 token = lex.getString();
358                 if (token == "\\end_inset")
359                         break;
360                 // FIXME Why is preview_ read but not written?
361                 if (token == "preview") {
362                         lex.next();
363                         preview_ = lex.getBool();
364                         continue;
365                 }
366                 int const i = findToken(info_->paramnames, token);
367                 if (i >= 0) {
368                         lex.next(true);
369                         params_[i] = lex.getDocString();
370                 } else {
371                         lex.printError("Unknown parameter name `$$Token' for command " + cmdName_);
372                         throw ExceptionMessage(WarningException,
373                                 _("Inset Command: ") + from_ascii(cmdName_),
374                                 _("Unknown parameter name: ") + from_utf8(token));
375                 }
376         }
377         if (token != "\\end_inset") {
378                 lex.printError("Missing \\end_inset at this point. "
379                                "Read: `$$Token'");
380                 throw ExceptionMessage(WarningException,
381                         _("Missing \\end_inset at this point."),
382                         from_utf8(token));
383         }
384 }
385
386
387 void InsetCommandParams::write(ostream & os) const
388 {
389         os << "CommandInset " << insetType_ << '\n';
390         os << "LatexCommand " << cmdName_ << '\n';
391         for (size_t i = 0; i < info_->n; ++i)
392                 if (!params_[i].empty())
393                         // FIXME UNICODE
394                         os << info_->paramnames[i] << ' '
395                            << Lexer::quoteString(to_utf8(params_[i]))
396                            << '\n';
397 }
398
399
400 docstring const InsetCommandParams::getCommand() const
401 {
402         docstring s = '\\' + from_ascii(cmdName_);
403         bool noparam = true;
404         for (size_t i = 0; i < info_->n; ++i) {
405                 if (info_->optional[i]) {
406                         if (params_[i].empty()) {
407                                 // We need to write this parameter even if
408                                 // it is empty if nonempty optional parameters
409                                 // follow before the next required parameter.
410                                 for (size_t j = i + 1; j < info_->n; ++j) {
411                                         if (!info_->optional[j])
412                                                 break;
413                                         if (!params_[j].empty()) {
414                                                 s += "[]";
415                                                 noparam = false;
416                                                 break;
417                                         }
418                                 }
419                         } else {
420                                 s += '[' + params_[i] + ']';
421                                 noparam = false;
422                         }
423                 } else {
424                         s += '{' + params_[i] + '}';
425                         noparam = false;
426                 }
427         }
428         if (noparam)
429                 // Make sure that following stuff does not change the
430                 // command name.
431                 s += "{}";
432         return s;
433 }
434
435
436 std::string const InsetCommandParams::getOptions() const
437 {
438         for (size_t i = 0; i < info_->n; ++i)
439                 if (info_->optional[i])
440                         return to_utf8(params_[i]);
441         lyxerr << "Programming error: get nonexisting option in "
442                << insetType_ << " inset." << endl;
443         return string();
444 }
445
446
447 std::string const InsetCommandParams::getSecOptions() const
448 {
449         bool first = true;
450         for (size_t i = 0; i < info_->n; ++i)
451                 if (info_->optional[i]) {
452                         if (first)
453                                 first = false;
454                         else
455                                 return to_utf8(params_[i]);
456                 }
457         // Happens in InsetCitation
458         lyxerr << "Programming error: get nonexisting second option in "
459                << insetType_ << " inset." << endl;
460         return string();
461 }
462
463
464 std::string const InsetCommandParams::getContents() const
465 {
466         for (size_t i = 0; i < info_->n; ++i)
467                 if (!info_->optional[i])
468                         return to_utf8(params_[i]);
469         BOOST_ASSERT(false);
470         return string();
471 }
472
473
474 void InsetCommandParams::setOptions(std::string const & o)
475 {
476         for (size_t i = 0; i < info_->n; ++i)
477                 if (info_->optional[i]) {
478                         params_[i] = from_utf8(o);
479                         return;
480                 }
481         lyxerr << "Programming error: set nonexisting option in "
482                << insetType_ << " inset." << endl;
483 }
484
485
486 void InsetCommandParams::setSecOptions(std::string const & s)
487 {
488         bool first = true;
489         for (size_t i = 0; i < info_->n; ++i)
490                 if (info_->optional[i]) {
491                         if (first)
492                                 first = false;
493                         else {
494                                 params_[i] = from_utf8(s);
495                                 return;
496                         }
497                 }
498         // Happens in InsetCitation
499         lyxerr << "Programming error: set nonexisting second option in "
500                << insetType_ << " inset." << endl;
501 }
502
503
504 void InsetCommandParams::setContents(std::string const & c)
505 {
506         for (size_t i = 0; i < info_->n; ++i)
507                 if (!info_->optional[i]) {
508                         params_[i] = from_utf8(c);
509                         return;
510                 }
511         BOOST_ASSERT(false);
512 }
513
514
515 docstring const & InsetCommandParams::operator[](string const & name) const
516 {
517         int const i = findToken(info_->paramnames, name);
518         BOOST_ASSERT(i >= 0);
519         return params_[i];
520 }
521
522
523 docstring & InsetCommandParams::operator[](string const & name)
524 {
525         int const i = findToken(info_->paramnames, name);
526         BOOST_ASSERT(i >= 0);
527         return params_[i];
528 }
529
530
531 void InsetCommandParams::clear()
532 {
533         for (size_t i = 0; i < info_->n; ++i)
534                 params_[i].clear();
535 }
536
537
538 bool operator==(InsetCommandParams const & o1,
539                 InsetCommandParams const & o2)
540 {
541         return o1.insetType_ == o2.insetType_ &&
542                o1.cmdName_ == o2.cmdName_ &&
543                o1.info_ == o2.info_ &&
544                o1.params_ == o2.params_ &&
545                o1.preview_ == o2.preview_;
546 }
547
548
549 bool operator!=(InsetCommandParams const & o1,
550                 InsetCommandParams const & o2)
551 {
552         return !(o1 == o2);
553 }
554
555
556 } // namespace lyx