]> git.lyx.org Git - lyx.git/blob - src/insets/InsetCommandParams.cpp
523a4c81dc2d8b9417485072ad5fb1ba720b8f08
[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  * \author Richard Kimberly Heck
9  *
10  * Full author contact details are available in file CREDITS.
11  */
12
13 #include <config.h>
14
15 #include "InsetCommandParams.h"
16
17 #include "InsetBibitem.h"
18 #include "InsetBibtex.h"
19 #include "InsetCitation.h"
20 #include "InsetCounter.h"
21 #include "InsetFloatList.h"
22 #include "InsetHyperlink.h"
23 #include "InsetInclude.h"
24 #include "InsetIndex.h"
25 #include "InsetLabel.h"
26 #include "InsetLine.h"
27 #include "InsetNomencl.h"
28 #include "InsetRef.h"
29 #include "InsetTOC.h"
30
31 #include "Buffer.h"
32 #include "Encoding.h"
33
34 #include "frontends/alert.h"
35
36 #include "support/debug.h"
37 #include "support/docstream.h"
38 #include "support/ExceptionMessage.h"
39 #include "support/gettext.h"
40 #include "support/lassert.h"
41 #include "support/Lexer.h"
42 #include "support/lstrings.h"
43
44 #include <algorithm>
45 #include <functional>
46
47 using namespace std;
48 using namespace lyx::support;
49
50
51 namespace lyx {
52
53 /// Get information for \p code and command \p cmdName.
54 /// Don't call this without first making sure the command name is
55 /// acceptable to the inset.
56 static ParamInfo const & findInfo(InsetCode code, string const & cmdName)
57 {
58         switch (code) {
59         case BIBITEM_CODE:
60                 return InsetBibitem::findInfo(cmdName);
61         case BIBTEX_CODE:
62                 return InsetBibtex::findInfo(cmdName);
63         case CITE_CODE:
64                 return InsetCitation::findInfo(cmdName);
65         case COUNTER_CODE:
66                 return InsetCounter::findInfo(cmdName);
67         case FLOAT_LIST_CODE:
68                 return InsetFloatList::findInfo(cmdName);
69         case HYPERLINK_CODE:
70                 return InsetHyperlink::findInfo(cmdName);
71         case INCLUDE_CODE:
72                 return InsetInclude::findInfo(cmdName);
73         case INDEX_PRINT_CODE:
74                 return InsetPrintIndex::findInfo(cmdName);
75         case LABEL_CODE:
76                 return InsetLabel::findInfo(cmdName);
77         case LINE_CODE:
78                 return InsetLine::findInfo(cmdName);
79         case NOMENCL_CODE:
80                 return InsetNomencl::findInfo(cmdName);
81         case NOMENCL_PRINT_CODE:
82                 return InsetPrintNomencl::findInfo(cmdName);
83         case REF_CODE:
84                 return InsetRef::findInfo(cmdName);
85         case TOC_CODE:
86                 return InsetTOC::findInfo(cmdName);
87         default:
88                 LATTEST(false);
89                 // fall through in release mode
90         }
91         static const ParamInfo pi;
92         return pi;
93 }
94
95
96 /////////////////////////////////////////////////////////////////////
97 //
98 // ParamInfo::ParamData
99 //
100 /////////////////////////////////////////////////////////////////////
101
102 ParamInfo::ParamData::ParamData(std::string const & s, ParamType t,
103                                 ParamHandling h, bool ignore,
104                                 docstring default_value)
105         : name_(s), type_(t), handling_(h), ignore_(ignore),
106           default_value_(default_value)
107 {}
108
109
110 bool ParamInfo::ParamData::isOptional() const
111 {
112         return type_ == ParamInfo::LATEX_OPTIONAL;
113 }
114
115
116 bool ParamInfo::ParamData::operator==(ParamInfo::ParamData const & rhs) const
117 {
118         return name() == rhs.name() && type() == rhs.type()
119                 && handling() == rhs.handling();
120 }
121
122
123 bool ParamInfo::hasParam(std::string const & name) const
124 {
125         const_iterator it = begin();
126         const_iterator last = end();
127         for (; it != last; ++it) {
128                 if (it->name() == name)
129                         return true;
130         }
131         return false;
132 }
133
134
135 void ParamInfo::add(std::string const & name, ParamType type,
136                     ParamHandling handling, bool ignoreval,
137                     docstring default_value)
138 {
139         info_.push_back(ParamData(name, type, handling, ignoreval, default_value));
140 }
141
142
143 bool ParamInfo::operator==(ParamInfo const & rhs) const
144 {
145         if (size() != rhs.size())
146                 return false;
147         return equal(begin(), end(), rhs.begin());
148 }
149
150
151 ParamInfo::ParamData const &
152         ParamInfo::operator[](std::string const & name) const
153 {
154         const_iterator it = begin();
155         const_iterator last = end();
156         for (; it != last; ++it) {
157                 if (it->name() == name)
158                         return *it;
159         }
160         LATTEST(false);
161         // we will try to continue in release mode
162         static const ParamData pd("asdfghjkl", LYX_INTERNAL);
163         return pd;
164 }
165
166
167 /////////////////////////////////////////////////////////////////////
168 //
169 // InsetCommandParams
170 //
171 /////////////////////////////////////////////////////////////////////
172
173
174 InsetCommandParams::InsetCommandParams(InsetCode code)
175         : insetCode_(code), preview_(false)
176 {
177         cmdName_ = getDefaultCmd(code);
178         info_ = findInfo(code, cmdName_);
179 }
180
181
182 InsetCommandParams::InsetCommandParams(InsetCode code,
183         string const & cmdName)
184         : insetCode_(code), cmdName_(cmdName), preview_(false)
185 {
186         info_ = findInfo(code, cmdName);
187 }
188
189
190 std::string InsetCommandParams::insetType() const
191 {
192         return insetName(insetCode_);
193 }
194
195
196 string InsetCommandParams::getDefaultCmd(InsetCode code)
197 {
198         switch (code) {
199                 case BIBITEM_CODE:
200                         return InsetBibitem::defaultCommand();
201                 case BIBTEX_CODE:
202                         return InsetBibtex::defaultCommand();
203                 case CITE_CODE:
204                         return InsetCitation::defaultCommand();
205                 case COUNTER_CODE:
206                         return InsetCounter::defaultCommand();
207                 case FLOAT_LIST_CODE:
208                         return InsetFloatList::defaultCommand();
209                 case HYPERLINK_CODE:
210                         return InsetHyperlink::defaultCommand();
211                 case INCLUDE_CODE:
212                         return InsetInclude::defaultCommand();
213                 case INDEX_PRINT_CODE:
214                         return InsetPrintIndex::defaultCommand();
215                 case LABEL_CODE:
216                         return InsetLabel::defaultCommand();
217                 case LINE_CODE:
218                         return InsetLine::defaultCommand();
219                 case NOMENCL_CODE:
220                         return InsetNomencl::defaultCommand();
221                 case NOMENCL_PRINT_CODE:
222                         return InsetPrintNomencl::defaultCommand();
223                 case REF_CODE:
224                         return InsetRef::defaultCommand();
225                 case TOC_CODE:
226                         return InsetTOC::defaultCommand();
227                 default:
228                         LATTEST(false);
229                         // fall through in release mode
230         }
231         return string();
232 }
233
234
235 bool InsetCommandParams::isCompatibleCommand(InsetCode code, string const & s)
236 {
237         switch (code) {
238                 case BIBITEM_CODE:
239                         return InsetBibitem::isCompatibleCommand(s);
240                 case BIBTEX_CODE:
241                         return InsetBibtex::isCompatibleCommand(s);
242                 case CITE_CODE:
243                         return InsetCitation::isCompatibleCommand(s);
244                 case COUNTER_CODE:
245                         return InsetCounter::isCompatibleCommand(s);
246                 case FLOAT_LIST_CODE:
247                         return InsetFloatList::isCompatibleCommand(s);
248                 case HYPERLINK_CODE:
249                         return InsetHyperlink::isCompatibleCommand(s);
250                 case INCLUDE_CODE:
251                         return InsetInclude::isCompatibleCommand(s);
252                 case INDEX_PRINT_CODE:
253                         return InsetPrintIndex::isCompatibleCommand(s);
254                 case LABEL_CODE:
255                         return InsetLabel::isCompatibleCommand(s);
256                 case LINE_CODE:
257                         return InsetLine::isCompatibleCommand(s);
258                 case NOMENCL_CODE:
259                         return InsetNomencl::isCompatibleCommand(s);
260                 case NOMENCL_PRINT_CODE:
261                         return InsetPrintNomencl::isCompatibleCommand(s);
262                 case REF_CODE:
263                         return InsetRef::isCompatibleCommand(s);
264                 case TOC_CODE:
265                         return InsetTOC::isCompatibleCommand(s);
266         default:
267                 LATTEST(false);
268                 // fall through in release mode
269         }
270         return false;
271 }
272
273
274 void InsetCommandParams::setCmdName(string const & name)
275 {
276         if (!isCompatibleCommand(insetCode_, name)) {
277                 LYXERR0("InsetCommand: Incompatible command name " <<
278                                 name << ".");
279                 throw ExceptionMessage(WarningException, _("InsetCommand Error: "),
280                                        _("Incompatible command name."));
281         }
282
283         cmdName_ = name;
284         info_ = findInfo(insetCode_, cmdName_);
285 }
286
287
288 void InsetCommandParams::read(Lexer & lex)
289 {
290         Read(lex, nullptr);
291 }
292
293
294 void InsetCommandParams::Read(Lexer & lex, Buffer const * buffer)
295 {
296         lex.setContext("InsetCommandParams::read");
297         lex >> insetName(insetCode_).c_str();
298         lex >> "LatexCommand";
299         lex >> cmdName_;
300         if (!isCompatibleCommand(insetCode_, cmdName_)) {
301                 lex.printError("Incompatible command name " + cmdName_ + ".");
302                 throw ExceptionMessage(WarningException, _("InsetCommandParams Error: "),
303                                        _("Incompatible command name."));
304         }
305
306         info_ = findInfo(insetCode_, cmdName_);
307
308         for (ParamInfo::ParamData const & param : info_)
309                 if (param.ignore()) {
310                         params_[param.name()] = param.defaultValue();
311                 }
312
313         string token;
314         while (lex.isOK()) {
315                 lex.next();
316                 token = lex.getString();
317                 if (token == "\\end_inset")
318                         break;
319                 if (token == "preview") {
320                         lex.next();
321                         preview_ = lex.getBool();
322                         continue;
323                 }
324                 if (hasParam(token)) {
325                         lex.next(true);
326                         docstring data = lex.getDocString();
327                         if (buffer && token == "filename") {
328                                 data = from_utf8(buffer->includedFilePath(to_utf8(data)));
329                         } else if (buffer && token == "bibfiles") {
330                                 int i = 0;
331                                 docstring newdata;
332                                 docstring bib = support::token(data, ',', i);
333                                 while (!bib.empty()) {
334                                         bib = from_utf8(buffer->includedFilePath(to_utf8(bib), "bib"));
335                                         if (!newdata.empty())
336                                                 newdata.append(1, ',');
337                                         newdata.append(bib);
338                                         bib = support::token(data, ',', ++i);
339                                 }
340                                 data = newdata;
341                         } else if (buffer && token == "options") {
342                                 data = from_utf8(buffer->includedFilePath(to_utf8(data), "bst"));
343                         }
344                         params_[token] = data;
345                 } else {
346                         lex.printError("Unknown parameter name `$$Token' for command " + cmdName_);
347                         throw ExceptionMessage(WarningException,
348                                 _("InsetCommandParams: ") + from_ascii(cmdName_),
349                                 _("Unknown parameter name: ") + from_utf8(token));
350                 }
351         }
352         if (token != "\\end_inset") {
353                 lex.printError("Missing \\end_inset at this point. "
354                                "Read: `$$Token'");
355                 throw ExceptionMessage(WarningException,
356                         _("InsetCommandParams Error: "),
357                         _("Missing \\end_inset at this point: ") + from_utf8(token));
358         }
359 }
360
361
362 void InsetCommandParams::write(ostream & os) const
363 {
364         Write(os, nullptr);
365 }
366
367
368 void InsetCommandParams::Write(ostream & os, Buffer const * buffer) const
369 {
370         os << "CommandInset " << insetType() << '\n';
371         os << "LatexCommand " << cmdName_ << '\n';
372         if (preview_)
373                 os << "preview true\n";
374         ParamInfo::const_iterator it  = info_.begin();
375         ParamInfo::const_iterator end = info_.end();
376         for (; it != end; ++it) {
377                 if (it->ignore())
378                         continue;
379                 string const & name = it->name();
380                 string data = to_utf8((*this)[name]);
381                 if (!data.empty()) {
382                         // Adjust path of files if document was moved
383                         if (buffer && name == "filename") {
384                                 data = buffer->includedFilePath(data);
385                         } else if (buffer && name == "bibfiles") {
386                                 int i = 0;
387                                 string newdata;
388                                 string bib = token(data, ',', i);
389                                 while (!bib.empty()) {
390                                         bib = buffer->includedFilePath(bib, "bib");
391                                         if (!newdata.empty())
392                                                 newdata.append(1, ',');
393                                         newdata.append(bib);
394                                         bib = token(data, ',', ++i);
395                                 }
396                                 data = newdata;
397                         } else if (buffer && name == "options") {
398                                 data = buffer->includedFilePath(data, "bst");
399                         }
400                         os << name << ' '
401                            << Lexer::quoteString(data)
402                            << '\n';
403                 }
404         }
405 }
406
407
408 bool InsetCommandParams::writeEmptyOptional(ParamInfo::const_iterator ci) const
409 {
410         LASSERT(ci->isOptional(), return false);
411
412         ++ci; // we want to start with the next one
413         ParamInfo::const_iterator end = info_.end();
414         for (; ci != end; ++ci) {
415                 switch (ci->type()) {
416                 case ParamInfo::LYX_INTERNAL:
417                         break;
418
419                 case ParamInfo::LATEX_REQUIRED:
420                         return false;
421
422                 case ParamInfo::LATEX_OPTIONAL: {
423                         std::string const & name = ci->name();
424                         docstring const & data = (*this)[name];
425                         if (!data.empty())
426                                 return true;
427                         break;
428                 }
429
430                 } //end switch
431         }
432         return false;
433 }
434
435
436 docstring InsetCommandParams::prepareCommand(OutputParams const & runparams,
437                                              docstring const & command,
438                                              ParamInfo::ParamHandling handling) const
439 {
440         docstring result;
441         bool ltrimmed = false;
442         // Trimming can be done on top of any of the other handlings
443         // We check this here since handling might be changed below.
444         if (handling & ParamInfo::HANDLING_LTRIM) {
445                 // this is used if no other handling is done
446                 result = command;
447                 ltrimmed = true;
448         }
449         if (handling & ParamInfo::HANDLING_LATEXIFY
450             || handling & ParamInfo::HANDLING_INDEX_ESCAPE)
451                 if ((*this)["literal"] == "true")
452                         handling = ParamInfo::HANDLING_NONE;
453
454         // LATEXIFY, ESCAPE and NONE are mutually exclusive
455         if (handling & ParamInfo::HANDLING_LATEXIFY) {
456                 // First handle backslash
457                 // we cannot replace yet with \textbackslash{}
458                 // as the braces would be erroneously escaped
459                 // in the following routines ("\textbackslash\{\}").
460                 // So create a unique placeholder which is replaced
461                 // in the end.
462                 docstring bs = from_ascii("@LyXBackslash@");
463                 // We are super-careful and assure the placeholder
464                 // does not exist in the string
465                 for (int i = 0; ; ++i) {
466                         if (!contains(command, bs)) {
467                                 result = subst(command, from_ascii("\\"), bs);
468                                 break;
469                         }
470                         bs = from_ascii("@LyXBackslash") + i + '@';
471                 }
472                 // Then get LaTeX macros
473                 pair<docstring, docstring> command_latexed =
474                         runparams.encoding->latexString(result, runparams.dryrun);
475                 result = command_latexed.first;
476                 if (!command_latexed.second.empty()) {
477                         // Issue a warning about omitted characters
478                         // FIXME: should be passed to the error dialog
479                         frontend::Alert::warning(_("Uncodable characters"),
480                                 bformat(_("The following characters that are used in the inset %1$s are not\n"
481                                           "representable in the current encoding and therefore have been omitted:\n%2$s."),
482                                         from_utf8(insetType()), command_latexed.second));
483                 }
484                 // Now escape special commands
485                 static docstring const backslash = from_ascii("\\");
486                 int const nchars_escape = 8;
487                 static char_type const chars_escape[nchars_escape] = {
488                         '&', '_', '$', '%', '#', '^', '{', '}'};
489
490                 if (!result.empty()) {
491                         int previous;
492                         // The characters in chars_name[] need to be changed to a command when
493                         // they are LaTeXified.
494                         for (int k = 0; k < nchars_escape; k++)
495                                 for (size_t i = 0, pos;
496                                         (pos = result.find(chars_escape[k], i)) != string::npos;
497                                         i = pos + 2) {
498                                                 //(Only) \\^ needs to be terminated
499                                                 docstring const term = (k == 5) ? from_ascii("{}") : docstring();
500                                                 if (pos == 0)
501                                                         previous = 0;
502                                                 else
503                                                         previous = pos - 1;
504                                                 // only if not already escaped
505                                                 if (result[previous] != '\\')
506                                                         result.replace(pos, 1, backslash + chars_escape[k] + term);
507                                 }
508                 }
509                 // set in real backslash now
510                 result = subst(result, bs, from_ascii("\\textbackslash{}"));
511         }
512         else if (handling & ParamInfo::HANDLING_ESCAPE)
513                 result = escape(command);
514         else if (handling & ParamInfo::HANDLING_NONE) {
515                 // we can only output characters covered by the current
516                 // encoding!
517                 docstring uncodable;
518                 for (char_type c : command) {
519                         try {
520                                 if (runparams.encoding->encodable(c))
521                                         result += c;
522                                 else if (runparams.dryrun) {
523                                         result += "<" + _("LyX Warning: ")
524                                            + _("uncodable character") + " '";
525                                         result += docstring(1, c);
526                                         result += "'>";
527                                 } else
528                                         uncodable += c;
529                         } catch (EncodingException & /* e */) {
530                                 if (runparams.dryrun) {
531                                         result += "<" + _("LyX Warning: ")
532                                            + _("uncodable character") + " '";
533                                         result += docstring(1, c);
534                                         result += "'>";
535                                 } else
536                                         uncodable += c;
537                         }
538                 }
539                 if (!uncodable.empty() && !runparams.silent) {
540                         // issue a warning about omitted characters
541                         // FIXME: should be passed to the error dialog
542                         frontend::Alert::warning(_("Uncodable characters in inset"),
543                                 bformat(_("The following characters in one of the insets are\n"
544                                           "not representable in the current encoding and have been omitted: %1$s.\n"
545                                           "Unchecking 'Literal' in the respective inset dialog might help."),
546                                 uncodable));
547                 }
548         }
549         // INDEX_ESCAPE is independent of the others
550         if (handling & ParamInfo::HANDLING_INDEX_ESCAPE) {
551                 // Now escape special commands
552                 static docstring const quote = from_ascii("\"");
553                 int const nchars_escape = 4;
554                 static char_type const chars_escape[nchars_escape] = { '"', '@', '|', '!' };
555
556                 if (!result.empty()) {
557                         // The characters in chars_name[] need to be changed to a command when
558                         // they are LaTeXified.
559                         for (int k = 0; k < nchars_escape; k++)
560                                 for (size_t i = 0, pos;
561                                         (pos = result.find(chars_escape[k], i)) != string::npos;
562                                         i = pos + 2)
563                                                 result.replace(pos, 1, quote + chars_escape[k]);
564                 }
565         }
566
567         return ltrimmed ? ltrim(result) : result;
568 }
569
570
571 docstring InsetCommandParams::getCommand(OutputParams const & runparams, bool starred, bool unhandled) const
572 {
573         docstring s = '\\' + from_ascii(cmdName_);
574         if (starred)
575                 s += from_utf8("*");
576         bool noparam = true;
577         ParamInfo::const_iterator it  = info_.begin();
578         ParamInfo::const_iterator end = info_.end();
579         for (; it != end; ++it) {
580                 std::string const & name = it->name();
581                 ParamInfo::ParamHandling handling = unhandled ?
582                                         ParamInfo::HANDLING_NONE
583                                       : it->handling();
584                 switch (it->type()) {
585                 case ParamInfo::LYX_INTERNAL:
586                         break;
587
588                 case ParamInfo::LATEX_REQUIRED: {
589                         docstring const data =
590                                 prepareCommand(runparams, (*this)[name], handling);
591                         s += '{' + data + '}';
592                         noparam = false;
593                         break;
594                 }
595                 case ParamInfo::LATEX_OPTIONAL: {
596                         docstring data =
597                                 prepareCommand(runparams, (*this)[name], handling);
598                         if (!data.empty()) {
599                                 s += '[' + protectArgument(data) + ']';
600                                 noparam = false;
601                         } else if (writeEmptyOptional(it)) {
602                                         s += "[]";
603                                         noparam = false;
604                         }
605                         break;
606                 }
607                 } //end switch
608         }
609         if (noparam)
610                 // Make sure that following stuff does not change the
611                 // command name.
612                 s += "{}";
613         return s;
614 }
615
616
617 docstring InsetCommandParams::getFirstNonOptParam() const
618 {
619         ParamInfo::const_iterator it =
620                 find_if(info_.begin(), info_.end(),
621                         [](ParamInfo::ParamData const & d) { return !d.isOptional(); });
622         LASSERT(it != info_.end(), return docstring());
623         return (*this)[it->name()];
624 }
625
626
627 bool InsetCommandParams::hasParam(std::string const & name) const
628 {
629         return info_.hasParam(name);
630 }
631
632
633 docstring const & InsetCommandParams::getParamOr(std::string const & name, docstring const & defaultValue) const
634 {
635         if (hasParam(name))
636                 return (*this)[name];
637         return defaultValue;
638 }
639
640
641 docstring const & InsetCommandParams::operator[](string const & name) const
642 {
643         static const docstring dummy;
644         LASSERT(hasParam(name), return dummy);
645         ParamMap::const_iterator data = params_.find(name);
646         if (data == params_.end() || data->second.empty())
647                 return dummy;
648         ParamInfo::ParamData const & param = info_[name];
649         if (param.ignore())
650                 return param.defaultValue();
651         return data->second;
652 }
653
654
655 docstring & InsetCommandParams::operator[](string const & name)
656 {
657         LATTEST(hasParam(name));
658         // this will add the name in release mode
659         ParamInfo::ParamData const & param = info_[name];
660         if (param.ignore())
661                 params_[name] = param.defaultValue();
662         return params_[name];
663 }
664
665
666 void InsetCommandParams::clear()
667 {
668         params_.clear();
669         preview(false);
670 }
671
672
673 bool operator==(InsetCommandParams const & o1, InsetCommandParams const & o2)
674 {
675         return o1.insetCode_ == o2.insetCode_
676                 && o1.cmdName_ == o2.cmdName_
677                 && o1.info_ == o2.info_
678                 && o1.params_ == o2.params_
679                 && o1.preview_ == o2.preview_;
680 }
681
682
683 bool operator!=(InsetCommandParams const & o1, InsetCommandParams const & o2)
684 {
685         return !(o1 == o2);
686 }
687
688
689 } // namespace lyx