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