2 * \file InsetCommandParams.cpp
3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Angus Leeming
8 * \author Richard Kimberly Heck
10 * Full author contact details are available in file CREDITS.
15 #include "InsetCommandParams.h"
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"
34 #include "frontends/alert.h"
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"
48 using namespace lyx::support;
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)
60 return InsetBibitem::findInfo(cmdName);
62 return InsetBibtex::findInfo(cmdName);
64 return InsetCitation::findInfo(cmdName);
66 return InsetCounter::findInfo(cmdName);
68 return InsetFloatList::findInfo(cmdName);
70 return InsetHyperlink::findInfo(cmdName);
72 return InsetInclude::findInfo(cmdName);
73 case INDEX_PRINT_CODE:
74 return InsetPrintIndex::findInfo(cmdName);
76 return InsetLabel::findInfo(cmdName);
78 return InsetLine::findInfo(cmdName);
80 return InsetNomencl::findInfo(cmdName);
81 case NOMENCL_PRINT_CODE:
82 return InsetPrintNomencl::findInfo(cmdName);
84 return InsetRef::findInfo(cmdName);
86 return InsetTOC::findInfo(cmdName);
89 // fall through in release mode
91 static const ParamInfo pi;
96 /////////////////////////////////////////////////////////////////////
98 // ParamInfo::ParamData
100 /////////////////////////////////////////////////////////////////////
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)
110 bool ParamInfo::ParamData::isOptional() const
112 return type_ == ParamInfo::LATEX_OPTIONAL;
116 bool ParamInfo::ParamData::operator==(ParamInfo::ParamData const & rhs) const
118 return name() == rhs.name() && type() == rhs.type()
119 && handling() == rhs.handling();
123 bool ParamInfo::hasParam(std::string const & name) const
125 const_iterator it = begin();
126 const_iterator last = end();
127 for (; it != last; ++it) {
128 if (it->name() == name)
135 void ParamInfo::add(std::string const & name, ParamType type,
136 ParamHandling handling, bool ignoreval,
137 docstring default_value)
139 info_.push_back(ParamData(name, type, handling, ignoreval, default_value));
143 bool ParamInfo::operator==(ParamInfo const & rhs) const
145 if (size() != rhs.size())
147 return equal(begin(), end(), rhs.begin());
151 ParamInfo::ParamData const &
152 ParamInfo::operator[](std::string const & name) const
154 const_iterator it = begin();
155 const_iterator last = end();
156 for (; it != last; ++it) {
157 if (it->name() == name)
161 // we will try to continue in release mode
162 static const ParamData pd("asdfghjkl", LYX_INTERNAL);
167 /////////////////////////////////////////////////////////////////////
169 // InsetCommandParams
171 /////////////////////////////////////////////////////////////////////
174 InsetCommandParams::InsetCommandParams(InsetCode code)
175 : insetCode_(code), preview_(false)
177 cmdName_ = getDefaultCmd(code);
178 info_ = findInfo(code, cmdName_);
182 InsetCommandParams::InsetCommandParams(InsetCode code,
183 string const & cmdName)
184 : insetCode_(code), cmdName_(cmdName), preview_(false)
186 info_ = findInfo(code, cmdName);
190 std::string InsetCommandParams::insetType() const
192 return insetName(insetCode_);
196 string InsetCommandParams::getDefaultCmd(InsetCode code)
200 return InsetBibitem::defaultCommand();
202 return InsetBibtex::defaultCommand();
204 return InsetCitation::defaultCommand();
206 return InsetCounter::defaultCommand();
207 case FLOAT_LIST_CODE:
208 return InsetFloatList::defaultCommand();
210 return InsetHyperlink::defaultCommand();
212 return InsetInclude::defaultCommand();
213 case INDEX_PRINT_CODE:
214 return InsetPrintIndex::defaultCommand();
216 return InsetLabel::defaultCommand();
218 return InsetLine::defaultCommand();
220 return InsetNomencl::defaultCommand();
221 case NOMENCL_PRINT_CODE:
222 return InsetPrintNomencl::defaultCommand();
224 return InsetRef::defaultCommand();
226 return InsetTOC::defaultCommand();
229 // fall through in release mode
235 bool InsetCommandParams::isCompatibleCommand(InsetCode code, string const & s)
239 return InsetBibitem::isCompatibleCommand(s);
241 return InsetBibtex::isCompatibleCommand(s);
243 return InsetCitation::isCompatibleCommand(s);
245 return InsetCounter::isCompatibleCommand(s);
246 case FLOAT_LIST_CODE:
247 return InsetFloatList::isCompatibleCommand(s);
249 return InsetHyperlink::isCompatibleCommand(s);
251 return InsetInclude::isCompatibleCommand(s);
252 case INDEX_PRINT_CODE:
253 return InsetPrintIndex::isCompatibleCommand(s);
255 return InsetLabel::isCompatibleCommand(s);
257 return InsetLine::isCompatibleCommand(s);
259 return InsetNomencl::isCompatibleCommand(s);
260 case NOMENCL_PRINT_CODE:
261 return InsetPrintNomencl::isCompatibleCommand(s);
263 return InsetRef::isCompatibleCommand(s);
265 return InsetTOC::isCompatibleCommand(s);
268 // fall through in release mode
274 void InsetCommandParams::setCmdName(string const & name)
276 if (!isCompatibleCommand(insetCode_, name)) {
277 LYXERR0("InsetCommand: Incompatible command name " <<
279 throw ExceptionMessage(WarningException, _("InsetCommand Error: "),
280 _("Incompatible command name."));
284 info_ = findInfo(insetCode_, cmdName_);
288 void InsetCommandParams::read(Lexer & lex)
294 void InsetCommandParams::Read(Lexer & lex, Buffer const * buffer)
296 lex.setContext("InsetCommandParams::read");
297 lex >> insetName(insetCode_).c_str();
298 lex >> "LatexCommand";
300 if (!isCompatibleCommand(insetCode_, cmdName_)) {
301 lex.printError("Incompatible command name " + cmdName_ + ".");
302 throw ExceptionMessage(WarningException, _("InsetCommandParams Error: "),
303 _("Incompatible command name."));
306 info_ = findInfo(insetCode_, cmdName_);
308 for (ParamInfo::ParamData const & param : info_)
309 if (param.ignore()) {
310 params_[param.name()] = param.defaultValue();
316 token = lex.getString();
317 if (token == "\\end_inset")
319 if (token == "preview") {
321 preview_ = lex.getBool();
324 if (hasParam(token)) {
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") {
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, ',');
338 bib = support::token(data, ',', ++i);
341 } else if (buffer && token == "options") {
342 data = from_utf8(buffer->includedFilePath(to_utf8(data), "bst"));
344 params_[token] = data;
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));
352 if (token != "\\end_inset") {
353 lex.printError("Missing \\end_inset at this point. "
355 throw ExceptionMessage(WarningException,
356 _("InsetCommandParams Error: "),
357 _("Missing \\end_inset at this point: ") + from_utf8(token));
362 void InsetCommandParams::write(ostream & os) const
368 void InsetCommandParams::Write(ostream & os, Buffer const * buffer) const
370 os << "CommandInset " << insetType() << '\n';
371 os << "LatexCommand " << cmdName_ << '\n';
373 os << "preview true\n";
374 ParamInfo::const_iterator it = info_.begin();
375 ParamInfo::const_iterator end = info_.end();
376 for (; it != end; ++it) {
379 string const & name = it->name();
380 string data = to_utf8((*this)[name]);
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") {
388 string bib = token(data, ',', i);
389 while (!bib.empty()) {
390 bib = buffer->includedFilePath(bib, "bib");
391 if (!newdata.empty())
392 newdata.append(1, ',');
394 bib = token(data, ',', ++i);
397 } else if (buffer && name == "options") {
398 data = buffer->includedFilePath(data, "bst");
401 << Lexer::quoteString(data)
408 bool InsetCommandParams::writeEmptyOptional(ParamInfo::const_iterator ci) const
410 LASSERT(ci->isOptional(), return false);
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:
419 case ParamInfo::LATEX_REQUIRED:
422 case ParamInfo::LATEX_OPTIONAL: {
423 std::string const & name = ci->name();
424 docstring const & data = (*this)[name];
436 docstring InsetCommandParams::prepareCommand(OutputParams const & runparams,
437 docstring const & command,
438 ParamInfo::ParamHandling handling) const
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
449 if (handling & ParamInfo::HANDLING_LATEXIFY
450 || handling & ParamInfo::HANDLING_INDEX_ESCAPE)
451 if ((*this)["literal"] == "true")
452 handling = ParamInfo::HANDLING_NONE;
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
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);
470 bs = from_ascii("@LyXBackslash") + i + '@';
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));
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 '&', '_', '$', '%', '#', '^', '{', '}'};
490 if (!result.empty()) {
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;
498 //(Only) \\^ needs to be terminated
499 docstring const term = (k == 5) ? from_ascii("{}") : docstring();
504 // only if not already escaped
505 if (result[previous] != '\\')
506 result.replace(pos, 1, backslash + chars_escape[k] + term);
509 // set in real backslash now
510 result = subst(result, bs, from_ascii("\\textbackslash{}"));
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
518 for (char_type c : command) {
520 if (runparams.encoding->encodable(c))
522 else if (runparams.dryrun) {
523 result += "<" + _("LyX Warning: ")
524 + _("uncodable character") + " '";
525 result += docstring(1, c);
529 } catch (EncodingException & /* e */) {
530 if (runparams.dryrun) {
531 result += "<" + _("LyX Warning: ")
532 + _("uncodable character") + " '";
533 result += docstring(1, c);
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."),
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] = { '"', '@', '|', '!' };
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;
563 result.replace(pos, 1, quote + chars_escape[k]);
567 return ltrimmed ? ltrim(result) : result;
571 docstring InsetCommandParams::getCommand(OutputParams const & runparams, bool starred, bool unhandled) const
573 docstring s = '\\' + from_ascii(cmdName_);
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
584 switch (it->type()) {
585 case ParamInfo::LYX_INTERNAL:
588 case ParamInfo::LATEX_REQUIRED: {
589 docstring const data =
590 prepareCommand(runparams, (*this)[name], handling);
591 s += '{' + data + '}';
595 case ParamInfo::LATEX_OPTIONAL: {
597 prepareCommand(runparams, (*this)[name], handling);
599 s += '[' + protectArgument(data) + ']';
601 } else if (writeEmptyOptional(it)) {
610 // Make sure that following stuff does not change the
617 docstring InsetCommandParams::getFirstNonOptParam() const
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()];
627 bool InsetCommandParams::hasParam(std::string const & name) const
629 return info_.hasParam(name);
633 docstring const & InsetCommandParams::getParamOr(std::string const & name, docstring const & defaultValue) const
636 return (*this)[name];
641 docstring const & InsetCommandParams::operator[](string const & name) const
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())
648 ParamInfo::ParamData const & param = info_[name];
650 return param.defaultValue();
655 docstring & InsetCommandParams::operator[](string const & name)
657 LATTEST(hasParam(name));
658 // this will add the name in release mode
659 ParamInfo::ParamData const & param = info_[name];
661 params_[name] = param.defaultValue();
662 return params_[name];
666 void InsetCommandParams::clear()
673 bool operator==(InsetCommandParams const & o1, InsetCommandParams const & o2)
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_;
683 bool operator!=(InsetCommandParams const & o1, InsetCommandParams const & o2)