]> git.lyx.org Git - lyx.git/blob - src/insets/InsetCommandParams.cpp
3fd14f7a11c46e15e383ebf4bf3505b1c33cabfd
[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 InsetCode, 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         // InsetHyperlink
118         if (insetType == "href") {
119                 static const char * const paramnames[] =
120                                 {"name", "target", ""};
121                 static const bool isoptional[] = {true, false};
122                 static const CommandInfo info = {2, paramnames, isoptional};
123                 return &info;
124         }
125
126         // InsetInclude
127         //FIXME This is really correct only for lstinputlistings, but it shouldn't
128         //cause a problem before we get things sorted out. Eventually, this calls
129         //InsetInclude::getParams(cmdName_), or something of the sort.
130         if (insetType == "include") {
131                 static const char * const paramnames[] = {"filename", "lstparams", ""};
132                 static const bool isoptional[] = {false, true};
133                 static const CommandInfo info = {2, paramnames, isoptional};
134                 return &info;
135         }
136
137         // InsetIndex, InsetPrintIndex, InsetLabel
138         if (insetType == "index" || insetType == "index_print" || insetType == "label") {
139                 static const char * const paramnames[] = {"name", ""};
140                 static const bool isoptional[] = {false};
141                 static const CommandInfo info = {1, paramnames, isoptional};
142                 return &info;
143         }
144
145         // InsetNomencl
146         if (insetType == "nomenclature") {
147                 static const char * const paramnames[] = {"prefix", "symbol", "description", ""};
148                 static const bool isoptional[] = {true, false, false};
149                 static const CommandInfo info = {3, paramnames, isoptional};
150                 return &info;
151         }
152
153         // InsetPrintNomencl
154         if (insetType == "nomencl_print") {
155                 static const char * const paramnames[] = {"labelwidth", ""};
156                 static const bool isoptional[] = {true};
157                 static const CommandInfo info = {1, paramnames, isoptional};
158                 return &info;
159         }
160
161         // InsetRef
162         if (insetType == "ref") {
163                 static const char * const paramnames[] =
164                                 {"name", "reference", ""};
165                 static const bool isoptional[] = {true, false};
166                 static const CommandInfo info = {2, paramnames, isoptional};
167                 return &info;
168         }
169
170         // InsetTOC
171         if (insetType == "toc") {
172                 static const char * const paramnames[] = {"type", ""};
173                 static const bool isoptional[] = {false};
174                 static const CommandInfo info = {1, 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 "bibtex"; //this is an unused dummy
197         if (insetType == "citation")
198                 return "cite";
199         if (insetType == "floatlist")
200                 return "listoftables";
201         if (insetType == "hfill")
202                 return "hfill";
203         if (insetType == "href")
204                 return "href";
205         if (insetType == "include")
206                 return "include";
207         if (insetType == "index")
208                  return "index";
209         if (insetType == "index_print")
210                 return "print_index";
211         if (insetType == "label")
212                 return "label";
213         if (insetType == "nomenclature")
214                 return "nomenclature";
215         if (insetType == "nomencl_print")
216                 return "printnomenclature";
217         if (insetType == "ref")
218                 return "ref";
219         if (insetType == "toc")
220                 return "tableofcontents";
221         BOOST_ASSERT(false);
222         return "";
223 }
224
225
226 void InsetCommandParams::setCmdName(string const & name)
227 {
228         //FIXME Check command compatibility
229         cmdName_ = name;
230         BOOST_ASSERT(!insetType_.empty());
231         CommandInfo const * const info = findInfo(insetType_, cmdName_);
232         BOOST_ASSERT(info);
233         ParamVector params(info->n);
234         // Overtake parameters with the same name
235         for (size_t i = 0; i < info_->n; ++i) {
236                 int j = findToken(info->paramnames, info_->paramnames[i]);
237                 if (j >= 0)
238                         params[j] = params_[i];
239         }
240         info_ = info;
241         std::swap(params, params_);
242 }
243
244
245 void InsetCommandParams::scanCommand(string const & cmd)
246 {
247         string tcmdname, toptions, tsecoptions, tcontents;
248
249         if (cmd.empty()) return;
250
251         enum { WS, CMDNAME, OPTION, SECOPTION, CONTENT } state = WS;
252
253         // Used to handle things like \command[foo[bar]]{foo{bar}}
254         int nestdepth = 0;
255
256         for (string::size_type i = 0; i < cmd.length(); ++i) {
257                 char const c = cmd[i];
258                 if ((state == CMDNAME && c == ' ') ||
259                     (state == CMDNAME && c == '[') ||
260                     (state == CMDNAME && c == '{')) {
261                         state = WS;
262                 }
263                 if ((state == OPTION  && c == ']') ||
264                     (state == SECOPTION  && c == ']') ||
265                     (state == CONTENT && c == '}')) {
266                         if (nestdepth == 0) {
267                                 state = WS;
268                         } else {
269                                 --nestdepth;
270                         }
271                 }
272                 if ((state == OPTION  && c == '[') ||
273                     (state == SECOPTION  && c == '[') ||
274                     (state == CONTENT && c == '{')) {
275                         ++nestdepth;
276                 }
277                 switch (state) {
278                 case CMDNAME:   tcmdname += c; break;
279                 case OPTION:    toptions += c; break;
280                 case SECOPTION: tsecoptions += c; break;
281                 case CONTENT:   tcontents += c; break;
282                 case WS: {
283                         char const b = i? cmd[i-1]: 0;
284                         if (c == '\\') {
285                                 state = CMDNAME;
286                         } else if (c == '[' && b != ']') {
287                                 state = OPTION;
288                                 nestdepth = 0; // Just to be sure
289                         } else if (c == '[' && b == ']') {
290                                 state = SECOPTION;
291                                 nestdepth = 0; // Just to be sure
292                         } else if (c == '{') {
293                                 state = CONTENT;
294                                 nestdepth = 0; // Just to be sure
295                         }
296                         break;
297                 }
298                 }
299         }
300
301         // Don't mess with this.
302         if (!tcmdname.empty())  setCmdName(tcmdname);
303         if (!toptions.empty())  setOptions(toptions);
304         if (!tsecoptions.empty())  setSecOptions(tsecoptions);
305         if (!tcontents.empty()) setContents(tcontents);
306
307         LYXERR(Debug::PARSER) << "Command <" <<  cmd
308                 << "> == <" << to_utf8(getCommand())
309                 << "> == <" << getCmdName()
310                 << '|' << getContents()
311                 << '|' << getOptions()
312                 << '|' << getSecOptions() << '>' << endl;
313 }
314
315
316 void InsetCommandParams::read(Lexer & lex)
317 {
318         //FIXME
319         if (lex.isOK()) {
320                 lex.next();
321                 string insetType = lex.getString();
322                 if (!insetType_.empty() && insetType != insetType_) {
323                         lex.printError("InsetCommand: Attempt to change type of parameters.");
324                         throw ExceptionMessage(WarningException, _("InsetCommand Error: "),
325                                 from_utf8("Attempt to change type of parameters."));
326                 }
327                 // OK, we survived...
328                 insetType_ = insetType;
329         }
330
331         if (lex.isOK()) {
332                 lex.next();
333                 string test = lex.getString();
334                 if (test != "LatexCommand") {
335                         lex.printError("InsetCommand: no LatexCommand line found.");
336                         throw ExceptionMessage(WarningException, _("InsetCommand error:"),
337                                 from_utf8("Can't find LatexCommand line."));
338                 }
339         }
340         lex.next();
341         cmdName_ = lex.getString();
342         //FIXME
343         //check that this command is ok with the inset...
344         //so that'll be some kind of static call, depending 
345         //upon what insetType_ is.
346         //it's possible that should go into InsetCommand.cpp,
347         //or maybe it's a standalone function.
348         info_ = findInfo(insetType_, cmdName_);
349         if (!info_) {
350                 lex.printError("InsetCommand: Unknown inset name `$$Token'");
351                 throw ExceptionMessage(WarningException,
352                         _("Unknown inset name: "), from_utf8(insetType_));
353         }
354         
355         string token;
356         while (lex.isOK()) {
357                 lex.next();
358                 token = lex.getString();
359                 if (token == "\\end_inset")
360                         break;
361                 // FIXME Why is preview_ read but not written?
362                 if (token == "preview") {
363                         lex.next();
364                         preview_ = lex.getBool();
365                         continue;
366                 }
367                 int const i = findToken(info_->paramnames, token);
368                 if (i >= 0) {
369                         lex.next(true);
370                         params_[i] = lex.getDocString();
371                 } else {
372                         lex.printError("Unknown parameter name `$$Token' for command " + cmdName_);
373                         throw ExceptionMessage(WarningException,
374                                 _("Inset Command: ") + from_ascii(cmdName_),
375                                 _("Unknown parameter name: ") + from_utf8(token));
376                 }
377         }
378         if (token != "\\end_inset") {
379                 lex.printError("Missing \\end_inset at this point. "
380                                "Read: `$$Token'");
381                 throw ExceptionMessage(WarningException,
382                         _("Missing \\end_inset at this point."),
383                         from_utf8(token));
384         }
385 }
386
387
388 void InsetCommandParams::write(ostream & os) const
389 {
390         os << "CommandInset " << insetType_ << '\n';
391         os << "LatexCommand " << cmdName_ << '\n';
392         for (size_t i = 0; i < info_->n; ++i)
393                 if (!params_[i].empty())
394                         // FIXME UNICODE
395                         os << info_->paramnames[i] << ' '
396                            << Lexer::quoteString(to_utf8(params_[i]))
397                            << '\n';
398 }
399
400
401 docstring const InsetCommandParams::getCommand() const
402 {
403         docstring s = '\\' + from_ascii(cmdName_);
404         bool noparam = true;
405         for (size_t i = 0; i < info_->n; ++i) {
406                 if (info_->optional[i]) {
407                         if (params_[i].empty()) {
408                                 // We need to write this parameter even if
409                                 // it is empty if nonempty optional parameters
410                                 // follow before the next required parameter.
411                                 for (size_t j = i + 1; j < info_->n; ++j) {
412                                         if (!info_->optional[j])
413                                                 break;
414                                         if (!params_[j].empty()) {
415                                                 s += "[]";
416                                                 noparam = false;
417                                                 break;
418                                         }
419                                 }
420                         } else {
421                                 s += '[' + params_[i] + ']';
422                                 noparam = false;
423                         }
424                 } else {
425                         s += '{' + params_[i] + '}';
426                         noparam = false;
427                 }
428         }
429         if (noparam)
430                 // Make sure that following stuff does not change the
431                 // command name.
432                 s += "{}";
433         return s;
434 }
435
436
437 std::string const InsetCommandParams::getOptions() const
438 {
439         for (size_t i = 0; i < info_->n; ++i)
440                 if (info_->optional[i])
441                         return to_utf8(params_[i]);
442         lyxerr << "Programming error: get nonexisting option in "
443                << insetType_ << " inset." << endl;
444         return string();
445 }
446
447
448 std::string const InsetCommandParams::getSecOptions() const
449 {
450         bool first = true;
451         for (size_t i = 0; i < info_->n; ++i)
452                 if (info_->optional[i]) {
453                         if (first)
454                                 first = false;
455                         else
456                                 return to_utf8(params_[i]);
457                 }
458         // Happens in InsetCitation
459         lyxerr << "Programming error: get nonexisting second option in "
460                << insetType_ << " inset." << endl;
461         return string();
462 }
463
464
465 std::string const InsetCommandParams::getContents() const
466 {
467         for (size_t i = 0; i < info_->n; ++i)
468                 if (!info_->optional[i])
469                         return to_utf8(params_[i]);
470         BOOST_ASSERT(false);
471         return string();
472 }
473
474
475 void InsetCommandParams::setOptions(std::string const & o)
476 {
477         for (size_t i = 0; i < info_->n; ++i)
478                 if (info_->optional[i]) {
479                         params_[i] = from_utf8(o);
480                         return;
481                 }
482         lyxerr << "Programming error: set nonexisting option in "
483                << insetType_ << " inset." << endl;
484 }
485
486
487 void InsetCommandParams::setSecOptions(std::string const & s)
488 {
489         bool first = true;
490         for (size_t i = 0; i < info_->n; ++i)
491                 if (info_->optional[i]) {
492                         if (first)
493                                 first = false;
494                         else {
495                                 params_[i] = from_utf8(s);
496                                 return;
497                         }
498                 }
499         // Happens in InsetCitation
500         lyxerr << "Programming error: set nonexisting second option in "
501                << insetType_ << " inset." << endl;
502 }
503
504
505 void InsetCommandParams::setContents(std::string const & c)
506 {
507         for (size_t i = 0; i < info_->n; ++i)
508                 if (!info_->optional[i]) {
509                         params_[i] = from_utf8(c);
510                         return;
511                 }
512         BOOST_ASSERT(false);
513 }
514
515
516 docstring const & InsetCommandParams::operator[](string const & name) const
517 {
518         int const i = findToken(info_->paramnames, name);
519         BOOST_ASSERT(i >= 0);
520         return params_[i];
521 }
522
523
524 docstring & InsetCommandParams::operator[](string const & name)
525 {
526         int const i = findToken(info_->paramnames, name);
527         BOOST_ASSERT(i >= 0);
528         return params_[i];
529 }
530
531
532 void InsetCommandParams::clear()
533 {
534         for (size_t i = 0; i < info_->n; ++i)
535                 params_[i].clear();
536 }
537
538
539 bool operator==(InsetCommandParams const & o1,
540                 InsetCommandParams const & o2)
541 {
542         return o1.insetType_ == o2.insetType_ &&
543                o1.cmdName_ == o2.cmdName_ &&
544                o1.info_ == o2.info_ &&
545                o1.params_ == o2.params_ &&
546                o1.preview_ == o2.preview_;
547 }
548
549
550 bool operator!=(InsetCommandParams const & o1,
551                 InsetCommandParams const & o2)
552 {
553         return !(o1 == o2);
554 }
555
556
557 } // namespace lyx