3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Lars Gullik Bjønnes
7 * \author Jean-Marc Lasgouttes
8 * \author Angus Leeming
10 * \author André Pönitz
12 * Full author contact details are available in file CREDITS.
17 #include "TextClass.h"
19 #include "LayoutFile.h"
20 #include "CiteEnginesList.h"
24 #include "FloatList.h"
28 #include "ModuleList.h"
30 #include "frontends/alert.h"
32 #include "support/lassert.h"
33 #include "support/debug.h"
34 #include "support/ExceptionMessage.h"
35 #include "support/FileName.h"
36 #include "support/filetools.h"
37 #include "support/gettext.h"
38 #include "support/lstrings.h"
39 #include "support/os.h"
40 #include "support/TempFile.h"
51 using namespace lyx::support;
55 // Keep the changes documented in the Customization manual.
57 // If you change this format, then you MUST also make sure that
58 // your changes do not invalidate the hardcoded layout file in
59 // LayoutFile.cpp. Additions will never do so, but syntax changes
60 // could. See LayoutFileList::addEmptyClass() and, especially, the
61 // definition of the layoutpost string.
62 // You should also run the development/tools/updatelayouts.py script,
63 // to update the format of all of our layout files.
65 int const LAYOUT_FORMAT = 63; //spitz: new tags CiteFramework, MaxCiteNames, extended InsetCite syntax.
68 // Layout format for the current lyx file format. Controls which format is
69 // targeted by Local Layout > Convert. In master, equal to LAYOUT_FORMAT.
70 int const LYXFILE_LAYOUT_FORMAT = LAYOUT_FORMAT;
75 class LayoutNamesEqual : public unary_function<Layout, bool> {
77 LayoutNamesEqual(docstring const & name)
80 bool operator()(Layout const & c) const
82 return c.name() == name_;
89 bool layout2layout(FileName const & filename, FileName const & tempfile,
90 int const format = LAYOUT_FORMAT)
92 FileName const script = libFileSearch("scripts", "layout2layout.py");
94 LYXERR0("Could not find layout conversion "
95 "script layout2layout.py.");
99 ostringstream command;
100 command << os::python() << ' ' << quoteName(script.toFilesystemEncoding())
102 << ' ' << quoteName(filename.toFilesystemEncoding())
103 << ' ' << quoteName(tempfile.toFilesystemEncoding());
104 string const command_str = command.str();
106 LYXERR(Debug::TCLASS, "Running `" << command_str << '\'');
108 cmd_ret const ret = runCommand(command_str);
109 if (ret.first != 0) {
110 if (format == LAYOUT_FORMAT)
111 LYXERR0("Conversion of layout with layout2layout.py has failed.");
118 string translateReadType(TextClass::ReadType rt)
121 case TextClass::BASECLASS:
123 case TextClass::MERGE:
125 case TextClass::MODULE:
126 return "module file";
127 case TextClass::CITE_ENGINE:
128 return "cite engine";
129 case TextClass::VALIDATION:
139 // This string should not be translated here,
140 // because it is a layout identifier.
141 docstring const TextClass::plain_layout_ = from_ascii(N_("Plain Layout"));
144 /////////////////////////////////////////////////////////////////////////
148 /////////////////////////////////////////////////////////////////////////
150 TextClass::TextClass()
151 : loaded_(false), tex_class_avail_(false),
152 opt_enginetype_("authoryear|numerical"), opt_fontsize_("10|11|12"),
153 opt_pagestyle_("empty|plain|headings|fancy"), pagestyle_("default"),
154 columns_(1), sides_(OneSide), secnumdepth_(3), tocdepth_(3),
155 outputType_(LATEX), outputFormat_("latex"),
156 defaultfont_(sane_font),
157 titletype_(TITLE_COMMAND_AFTER), titlename_("maketitle"),
158 min_toclevel_(0), max_toclevel_(0), maxcitenames_(2),
159 cite_full_author_list_(true)
164 bool TextClass::readStyle(Lexer & lexrc, Layout & lay) const
166 LYXERR(Debug::TCLASS, "Reading style " << to_utf8(lay.name()));
167 if (!lay.read(lexrc, *this)) {
168 LYXERR0("Error parsing style `" << to_utf8(lay.name()) << '\'');
172 lay.resfont = lay.font;
173 lay.resfont.realize(defaultfont_);
174 lay.reslabelfont = lay.labelfont;
175 lay.reslabelfont.realize(defaultfont_);
176 return true; // no errors
215 TC_ADDTOHTMLPREAMBLE,
234 LexerKeyword textClassTags[] = {
235 { "addtohtmlpreamble", TC_ADDTOHTMLPREAMBLE },
236 { "addtohtmlstyles", TC_ADDTOHTMLSTYLES },
237 { "addtopreamble", TC_ADDTOPREAMBLE },
238 { "citeengine", TC_CITEENGINE },
239 { "citeenginetype", TC_CITEENGINETYPE },
240 { "citeformat", TC_CITEFORMAT },
241 { "citeframework", TC_CITEFRAMEWORK },
242 { "classoptions", TC_CLASSOPTIONS },
243 { "columns", TC_COLUMNS },
244 { "counter", TC_COUNTER },
245 { "defaultbiblio", TC_DEFAULTBIBLIO },
246 { "defaultfont", TC_DEFAULTFONT },
247 { "defaultmodule", TC_DEFAULTMODULE },
248 { "defaultstyle", TC_DEFAULTSTYLE },
249 { "excludesmodule", TC_EXCLUDESMODULE },
250 { "float", TC_FLOAT },
251 { "format", TC_FORMAT },
252 { "fullauthorlist", TC_FULLAUTHORLIST },
253 { "htmlpreamble", TC_HTMLPREAMBLE },
254 { "htmlstyles", TC_HTMLSTYLES },
255 { "htmltocsection", TC_HTMLTOCSECTION },
256 { "ifcounter", TC_IFCOUNTER },
257 { "input", TC_INPUT },
258 { "insetlayout", TC_INSETLAYOUT },
259 { "leftmargin", TC_LEFTMARGIN },
260 { "maxcitenames", TC_MAXCITENAMES },
261 { "modifystyle", TC_MODIFYSTYLE },
262 { "nocounter", TC_NOCOUNTER },
263 { "nofloat", TC_NOFLOAT },
264 { "noinsetlayout", TC_NOINSETLAYOUT },
265 { "nostyle", TC_NOSTYLE },
266 { "outlinername", TC_OUTLINERNAME },
267 { "outputformat", TC_OUTPUTFORMAT },
268 { "outputtype", TC_OUTPUTTYPE },
269 { "packageoptions", TC_PKGOPTS },
270 { "pagestyle", TC_PAGESTYLE },
271 { "preamble", TC_PREAMBLE },
272 { "provides", TC_PROVIDES },
273 { "providesmodule", TC_PROVIDESMODULE },
274 { "providestyle", TC_PROVIDESTYLE },
275 { "requires", TC_REQUIRES },
276 { "rightmargin", TC_RIGHTMARGIN },
277 { "secnumdepth", TC_SECNUMDEPTH },
278 { "sides", TC_SIDES },
279 { "style", TC_STYLE },
280 { "titlelatexname", TC_TITLELATEXNAME },
281 { "titlelatextype", TC_TITLELATEXTYPE },
282 { "tocdepth", TC_TOCDEPTH }
288 bool TextClass::convertLayoutFormat(support::FileName const & filename, ReadType rt)
290 LYXERR(Debug::TCLASS, "Converting layout file to " << LAYOUT_FORMAT);
291 TempFile tmp("convertXXXXXX.layout");
292 FileName const tempfile = tmp.name();
293 bool success = layout2layout(filename, tempfile);
295 success = readWithoutConv(tempfile, rt) == OK;
300 std::string TextClass::convert(std::string const & str)
302 TempFile tmp1("localXXXXXX.layout");
303 FileName const fn = tmp1.name();
304 ofstream os(fn.toFilesystemEncoding().c_str());
307 TempFile tmp2("convert_localXXXXXX.layout");
308 FileName const tempfile = tmp2.name();
309 bool success = layout2layout(fn, tempfile, LYXFILE_LAYOUT_FORMAT);
312 ifstream is(tempfile.toFilesystemEncoding().c_str());
324 TextClass::ReturnValues TextClass::readWithoutConv(FileName const & filename, ReadType rt)
326 if (!filename.isReadableFile()) {
327 lyxerr << "Cannot read layout file `" << filename << "'."
332 LYXERR(Debug::TCLASS, "Reading " + translateReadType(rt) + ": " +
333 to_utf8(makeDisplayPath(filename.absFileName())));
335 // Define the plain layout used in table cells, ert, etc. Note that
336 // we do this before loading any layout file, so that classes can
337 // override features of this layout if they should choose to do so.
338 if (rt == BASECLASS && !hasLayout(plain_layout_))
339 layoutlist_.push_back(createBasicLayout(plain_layout_));
341 Lexer lexrc(textClassTags);
342 lexrc.setFile(filename);
343 ReturnValues retval = read(lexrc, rt);
345 LYXERR(Debug::TCLASS, "Finished reading " + translateReadType(rt) + ": " +
346 to_utf8(makeDisplayPath(filename.absFileName())));
352 bool TextClass::read(FileName const & filename, ReadType rt)
354 ReturnValues const retval = readWithoutConv(filename, rt);
355 if (retval != FORMAT_MISMATCH)
358 bool const worx = convertLayoutFormat(filename, rt);
360 LYXERR0 ("Unable to convert " << filename <<
361 " to format " << LAYOUT_FORMAT);
366 TextClass::ReturnValues TextClass::validate(std::string const & str)
369 return tc.read(str, VALIDATION);
373 TextClass::ReturnValues TextClass::read(std::string const & str, ReadType rt)
375 Lexer lexrc(textClassTags);
376 istringstream is(str);
378 ReturnValues retval = read(lexrc, rt);
380 if (retval != FORMAT_MISMATCH)
383 // write the layout string to a temporary file
384 TempFile tmp("TextClass_read");
385 FileName const tempfile = tmp.name();
386 ofstream os(tempfile.toFilesystemEncoding().c_str());
388 LYXERR0("Unable to create temporary file");
394 // now try to convert it to LAYOUT_FORMAT
395 if (!convertLayoutFormat(tempfile, rt)) {
396 LYXERR0("Unable to convert internal layout information to format "
405 // Reads a textclass structure from file.
406 TextClass::ReturnValues TextClass::read(Lexer & lexrc, ReadType rt)
411 // Format of files before the 'Format' tag was introduced
416 while (lexrc.isOK() && !error) {
417 int le = lexrc.lex();
420 case Lexer::LEX_FEOF:
423 case Lexer::LEX_UNDEF:
424 lexrc.printError("Unknown TextClass tag `$$Token'");
432 // used below to track whether we are in an IfStyle or IfCounter tag.
433 bool modifystyle = false;
434 bool providestyle = false;
435 bool ifcounter = false;
437 switch (static_cast<TextClassTags>(le)) {
441 format = lexrc.getInteger();
444 case TC_OUTPUTFORMAT:
446 outputFormat_ = lexrc.getString();
450 readOutputType(lexrc);
451 switch(outputType_) {
453 outputFormat_ = "latex";
456 outputFormat_ = "docbook";
459 outputFormat_ = "literate";
464 case TC_INPUT: // Include file
467 string const inc = lexrc.getString();
468 if (!path().empty() && (prefixIs(inc, "./") ||
469 prefixIs(inc, "../")))
470 tmp = fileSearch(path(), inc, "layout");
472 tmp = libFileSearch("layouts", inc,
476 lexrc.printError("Could not find input file: " + inc);
478 } else if (!read(tmp, MERGE)) {
479 lexrc.printError("Error reading input file: " + tmp.absFileName());
485 case TC_DEFAULTSTYLE:
487 docstring const name = from_utf8(subst(lexrc.getString(),
489 defaultlayout_ = name;
496 case TC_PROVIDESTYLE:
497 // if modifystyle is true, then we got here by falling through
498 // so we are not in an ProvideStyle block
504 lexrc.printError("No name given for style: `$$Token'.");
508 docstring const name = from_utf8(subst(lexrc.getString(),
511 string s = "Could not read name for style: `$$Token' "
512 + lexrc.getString() + " is probably not valid UTF-8!";
515 // Since we couldn't read the name, we just scan the rest
516 // of the style and discard it.
517 error = !readStyle(lexrc, lay);
521 bool const have_layout = hasLayout(name);
523 // If the layout already exists, then we want to add it to
524 // the existing layout, as long as we are not in an ProvideStyle
526 if (have_layout && !providestyle) {
527 Layout & lay = operator[](name);
528 error = !readStyle(lexrc, lay);
530 // If the layout does not exist, then we want to create a new
531 // one, but not if we are in a ModifyStyle block.
532 else if (!have_layout && !modifystyle) {
534 layout.setName(name);
535 error = !readStyle(lexrc, layout);
537 layoutlist_.push_back(layout);
539 if (defaultlayout_.empty()) {
540 // We do not have a default layout yet, so we choose
541 // the first layout we encounter.
542 defaultlayout_ = name;
545 // There are two ways to get here:
546 // (i) The layout exists but we are in an ProvideStyle block
547 // (ii) The layout doesn't exist, but we are in an ModifyStyle
549 // Either way, we just scan the rest and discard it
552 // signal to coverity that we do not care about the result
553 (void)readStyle(lexrc, lay);
560 docstring const style = from_utf8(subst(lexrc.getString(),
562 if (!deleteLayout(style))
563 lyxerr << "Cannot delete style `"
564 << to_utf8(style) << '\'' << endl;
568 case TC_NOINSETLAYOUT:
570 docstring const style = from_utf8(subst(lexrc.getString(),
572 if (!deleteInsetLayout(style))
573 LYXERR0("Style `" << style << "' cannot be removed\n"
574 "because it was not found!");
580 columns_ = lexrc.getInteger();
585 switch (lexrc.getInteger()) {
586 case 1: sides_ = OneSide; break;
587 case 2: sides_ = TwoSides; break;
589 lyxerr << "Impossible number of page"
590 " sides, setting to one."
600 pagestyle_ = rtrim(lexrc.getString());
604 defaultfont_ = lyxRead(lexrc);
605 if (!defaultfont_.resolved()) {
606 lexrc.printError("Warning: defaultfont should "
607 "be fully instantiated!");
608 defaultfont_.realize(sane_font);
614 secnumdepth_ = lexrc.getInteger();
619 tocdepth_ = lexrc.getInteger();
622 // First step to support options
623 case TC_CLASSOPTIONS:
624 readClassOptions(lexrc);
628 preamble_ = lexrc.getLongString(from_ascii("EndPreamble"));
631 case TC_HTMLPREAMBLE:
632 htmlpreamble_ = lexrc.getLongString(from_ascii("EndPreamble"));
636 htmlstyles_ = lexrc.getLongString(from_ascii("EndStyles"));
639 case TC_HTMLTOCSECTION:
640 html_toc_section_ = from_utf8(trim(lexrc.getString()));
643 case TC_ADDTOPREAMBLE:
644 preamble_ += lexrc.getLongString(from_ascii("EndPreamble"));
647 case TC_ADDTOHTMLPREAMBLE:
648 htmlpreamble_ += lexrc.getLongString(from_ascii("EndPreamble"));
651 case TC_ADDTOHTMLSTYLES:
652 htmlstyles_ += lexrc.getLongString(from_ascii("EndStyles"));
657 string const feature = lexrc.getString();
659 if (lexrc.getInteger())
660 provides_.insert(feature);
662 provides_.erase(feature);
668 vector<string> const req
669 = getVectorFromString(lexrc.getString());
670 requires_.insert(req.begin(), req.end());
676 string const pkg = lexrc.getString();
678 string const options = lexrc.getString();
679 package_options_[pkg] = options;
683 case TC_DEFAULTMODULE: {
685 string const module = lexrc.getString();
686 if (find(default_modules_.begin(), default_modules_.end(), module) == default_modules_.end())
687 default_modules_.push_back(module);
691 case TC_PROVIDESMODULE: {
693 string const module = lexrc.getString();
694 if (find(provided_modules_.begin(), provided_modules_.end(), module) == provided_modules_.end())
695 provided_modules_.push_back(module);
699 case TC_EXCLUDESMODULE: {
701 string const module = lexrc.getString();
702 // modules already have their own way to exclude other modules
704 LYXERR0("ExcludesModule tag cannot be used in a module!");
707 if (find(excluded_modules_.begin(), excluded_modules_.end(), module) == excluded_modules_.end())
708 excluded_modules_.push_back(module);
712 case TC_LEFTMARGIN: // left margin type
714 leftmargin_ = lexrc.getDocString();
717 case TC_RIGHTMARGIN: // right margin type
719 rightmargin_ = lexrc.getDocString();
722 case TC_INSETLAYOUT: {
724 lexrc.printError("No name given for InsetLayout: `$$Token'.");
728 docstring const name = subst(lexrc.getDocString(), '_', ' ');
730 string s = "Could not read name for InsetLayout: `$$Token' "
731 + lexrc.getString() + " is probably not valid UTF-8!";
734 // Since we couldn't read the name, we just scan the rest
735 // of the style and discard it.
736 il.read(lexrc, *this);
737 // Let's try to continue rather than abort.
739 } else if (hasInsetLayout(name)) {
740 InsetLayout & il = insetlayoutlist_[name];
741 error = !il.read(lexrc, *this);
745 error = !il.read(lexrc, *this);
747 insetlayoutlist_[name] = il;
753 error = !readFloat(lexrc);
757 error = !readCiteEngine(lexrc);
760 case TC_CITEENGINETYPE:
762 opt_enginetype_ = rtrim(lexrc.getString());
766 error = !readCiteFormat(lexrc);
769 case TC_CITEFRAMEWORK:
771 citeframework_ = rtrim(lexrc.getString());
774 case TC_MAXCITENAMES:
776 maxcitenames_ = size_t(lexrc.getInteger());
779 case TC_DEFAULTBIBLIO:
781 vector<string> const dbs =
782 getVectorFromString(rtrim(lexrc.getString()), "|");
783 vector<string>::const_iterator it = dbs.begin();
784 vector<string>::const_iterator end = dbs.end();
785 for (; it != end; ++it) {
786 if (!contains(*it, ':')) {
787 vector<string> const enginetypes =
788 getVectorFromString(opt_enginetype_, "|");
789 for (string const &s: enginetypes)
790 cite_default_biblio_style_[s] = *it;
793 string const db = split(*it, eng, ':');
794 cite_default_biblio_style_[eng] = db;
800 case TC_FULLAUTHORLIST:
802 cite_full_author_list_ &= lexrc.getBool();
807 docstring const cnt = lexrc.getDocString();
808 if (!counters_.remove(cnt))
809 LYXERR0("Unable to remove counter: " + to_utf8(cnt));
818 docstring const name = lexrc.getDocString();
820 string s = "Could not read name for counter: `$$Token' "
821 + lexrc.getString() + " is probably not valid UTF-8!";
822 lexrc.printError(s.c_str());
824 // Since we couldn't read the name, we just scan the rest
828 error = !counters_.read(lexrc, name, !ifcounter);
831 lexrc.printError("No name given for style: `$$Token'.");
836 case TC_TITLELATEXTYPE:
837 readTitleType(lexrc);
840 case TC_TITLELATEXNAME:
842 titlename_ = lexrc.getString();
847 string const nofloat = lexrc.getString();
848 floatlist_.erase(nofloat);
852 case TC_OUTLINERNAME:
853 error = !readOutlinerName(lexrc);
857 // Note that this is triggered the first time through the loop unless
858 // we hit a format tag.
859 if (format != LAYOUT_FORMAT)
860 return FORMAT_MISMATCH;
863 // at present, we abort if we encounter an error,
864 // so there is no point continuing.
871 if (defaultlayout_.empty()) {
872 LYXERR0("Error: Textclass '" << name_
873 << "' is missing a defaultstyle.");
877 // Try to erase "stdinsets" from the provides_ set.
879 // Provides stdinsets 1
880 // declaration simply tells us that the standard insets have been
881 // defined. (It's found in stdinsets.inc but could also be used in
882 // user-defined files.) There isn't really any such package. So we
883 // might as well go ahead and erase it.
884 // If we do not succeed, then it was not there, which means that
885 // the textclass did not provide the definitions of the standard
886 // insets. So we need to try to load them.
887 int erased = provides_.erase("stdinsets");
889 FileName tmp = libFileSearch("layouts", "stdinsets.inc");
892 frontend::Alert::warning(_("Missing File"),
893 _("Could not find stdinsets.inc! This may lead to data loss!"));
895 } else if (!read(tmp, MERGE)) {
896 frontend::Alert::warning(_("Corrupt File"),
897 _("Could not read stdinsets.inc! This may lead to data loss!"));
902 min_toclevel_ = Layout::NOT_IN_TOC;
903 max_toclevel_ = Layout::NOT_IN_TOC;
904 const_iterator lit = begin();
905 const_iterator len = end();
906 for (; lit != len; ++lit) {
907 int const toclevel = lit->toclevel;
908 if (toclevel != Layout::NOT_IN_TOC) {
909 if (min_toclevel_ == Layout::NOT_IN_TOC)
910 min_toclevel_ = toclevel;
912 min_toclevel_ = min(min_toclevel_, toclevel);
913 max_toclevel_ = max(max_toclevel_, toclevel);
916 LYXERR(Debug::TCLASS, "Minimum TocLevel is " << min_toclevel_
917 << ", maximum is " << max_toclevel_);
919 return (error ? ERROR : OK);
923 void TextClass::readTitleType(Lexer & lexrc)
925 LexerKeyword titleTypeTags[] = {
926 { "commandafter", TITLE_COMMAND_AFTER },
927 { "environment", TITLE_ENVIRONMENT }
930 PushPopHelper pph(lexrc, titleTypeTags);
932 int le = lexrc.lex();
934 case Lexer::LEX_UNDEF:
935 lexrc.printError("Unknown output type `$$Token'");
937 case TITLE_COMMAND_AFTER:
938 case TITLE_ENVIRONMENT:
939 titletype_ = static_cast<TitleLatexType>(le);
942 LYXERR0("Unhandled value " << le << " in TextClass::readTitleType.");
948 void TextClass::readOutputType(Lexer & lexrc)
950 LexerKeyword outputTypeTags[] = {
951 { "docbook", DOCBOOK },
953 { "literate", LITERATE }
956 PushPopHelper pph(lexrc, outputTypeTags);
958 int le = lexrc.lex();
960 case Lexer::LEX_UNDEF:
961 lexrc.printError("Unknown output type `$$Token'");
966 outputType_ = static_cast<OutputType>(le);
969 LYXERR0("Unhandled value " << le);
975 void TextClass::readClassOptions(Lexer & lexrc)
985 LexerKeyword classOptionsTags[] = {
987 {"fontsize", CO_FONTSIZE },
988 {"header", CO_HEADER },
989 {"other", CO_OTHER },
990 {"pagestyle", CO_PAGESTYLE }
993 lexrc.pushTable(classOptionsTags);
995 while (!getout && lexrc.isOK()) {
996 int le = lexrc.lex();
998 case Lexer::LEX_UNDEF:
999 lexrc.printError("Unknown ClassOption tag `$$Token'");
1007 opt_fontsize_ = rtrim(lexrc.getString());
1011 opt_pagestyle_ = rtrim(lexrc.getString());
1015 if (options_.empty())
1016 options_ = lexrc.getString();
1018 options_ += ',' + lexrc.getString();
1022 class_header_ = subst(lexrc.getString(), """, "\"");
1033 bool TextClass::readCiteEngine(Lexer & lexrc)
1035 int const type = readCiteEngineType(lexrc);
1036 if (type & ENGINE_TYPE_AUTHORYEAR)
1037 cite_styles_[ENGINE_TYPE_AUTHORYEAR].clear();
1038 if (type & ENGINE_TYPE_NUMERICAL)
1039 cite_styles_[ENGINE_TYPE_NUMERICAL].clear();
1040 if (type & ENGINE_TYPE_DEFAULT)
1041 cite_styles_[ENGINE_TYPE_DEFAULT].clear();
1043 bool getout = false;
1044 while (!getout && lexrc.isOK()) {
1046 def = lexrc.getString();
1047 def = subst(def, " ", "");
1048 def = subst(def, "\t", "");
1049 if (compare_ascii_no_case(def, "end") == 0) {
1054 char ichar = def[0];
1057 if (isUpperCase(ichar)) {
1058 cs.forceUpperCase = true;
1059 def[0] = lowercase(ichar);
1062 /** For portability reasons (between different
1063 * cite engines such as natbib and biblatex),
1064 * we distinguish between:
1065 * 1. The LyX name as output in the LyX file
1066 * 2. Possible aliases that might fall back to
1067 * the given LyX name in the current engine
1068 * 3. The actual LaTeX command that is output
1069 * (2) and (3) are optional.
1070 * Also, the GUI string for the starred version can
1073 * LyXName|alias,nextalias*<!stardesc!stardesctooltip>[][]=latexcmd
1082 ScanMode mode = LyXName;
1083 ScanMode oldmode = LyXName;
1088 size_t const n = def.size();
1089 for (size_t i = 0; i != n; ++i) {
1093 else if (ichar == '=')
1095 else if (ichar == '<') {
1098 } else if (ichar == '>')
1100 else if (mode == LaTeXCmd)
1102 else if (mode == StarDesc)
1104 else if (ichar == '$')
1105 cs.hasQualifiedList = true;
1106 else if (ichar == '*')
1107 cs.hasStarredVersion = true;
1108 else if (ichar == '[' && cs.textAfter)
1109 cs.textBefore = true;
1110 else if (ichar == '[')
1111 cs.textAfter = true;
1112 else if (ichar != ']') {
1120 cs.cmd = latex_cmd.empty() ? lyx_cmd : latex_cmd;
1121 if (!alias.empty()) {
1122 vector<string> const aliases = getVectorFromString(alias);
1123 for (string const &s: aliases)
1124 cite_command_aliases_[s] = lyx_cmd;
1126 vector<string> const stardescs = getVectorFromString(stardesc, "!");
1127 int size = stardesc.size();
1129 cs.stardesc = stardescs[0];
1131 cs.startooltip = stardescs[1];
1132 if (type & ENGINE_TYPE_AUTHORYEAR)
1133 cite_styles_[ENGINE_TYPE_AUTHORYEAR].push_back(cs);
1134 if (type & ENGINE_TYPE_NUMERICAL)
1135 cite_styles_[ENGINE_TYPE_NUMERICAL].push_back(cs);
1136 if (type & ENGINE_TYPE_DEFAULT)
1137 cite_styles_[ENGINE_TYPE_DEFAULT].push_back(cs);
1143 int TextClass::readCiteEngineType(Lexer & lexrc) const
1145 LATTEST(ENGINE_TYPE_DEFAULT ==
1146 (ENGINE_TYPE_AUTHORYEAR | ENGINE_TYPE_NUMERICAL));
1147 if (!lexrc.next()) {
1148 lexrc.printError("No cite engine type given for token: `$$Token'.");
1149 return ENGINE_TYPE_DEFAULT;
1151 string const type = rtrim(lexrc.getString());
1152 if (compare_ascii_no_case(type, "authoryear") == 0)
1153 return ENGINE_TYPE_AUTHORYEAR;
1154 else if (compare_ascii_no_case(type, "numerical") == 0)
1155 return ENGINE_TYPE_NUMERICAL;
1156 else if (compare_ascii_no_case(type, "default") != 0) {
1157 string const s = "Unknown cite engine type `" + type
1158 + "' given for token: `$$Token',";
1159 lexrc.printError(s);
1161 return ENGINE_TYPE_DEFAULT;
1165 bool TextClass::readCiteFormat(Lexer & lexrc)
1167 int const type = readCiteEngineType(lexrc);
1170 while (lexrc.isOK()) {
1172 etype = lexrc.getString();
1173 if (compare_ascii_no_case(etype, "end") == 0)
1178 definition = lexrc.getString();
1179 char initchar = etype[0];
1180 if (initchar == '#')
1182 if (initchar == '!' || initchar == '_') {
1183 if (type & ENGINE_TYPE_AUTHORYEAR)
1184 cite_macros_[ENGINE_TYPE_AUTHORYEAR][etype] = definition;
1185 if (type & ENGINE_TYPE_NUMERICAL)
1186 cite_macros_[ENGINE_TYPE_NUMERICAL][etype] = definition;
1187 if (type & ENGINE_TYPE_DEFAULT)
1188 cite_macros_[ENGINE_TYPE_DEFAULT][etype] = definition;
1190 if (type & ENGINE_TYPE_AUTHORYEAR)
1191 cite_formats_[ENGINE_TYPE_AUTHORYEAR][etype] = definition;
1192 if (type & ENGINE_TYPE_NUMERICAL)
1193 cite_formats_[ENGINE_TYPE_NUMERICAL][etype] = definition;
1194 if (type & ENGINE_TYPE_DEFAULT)
1195 cite_formats_[ENGINE_TYPE_DEFAULT][etype] = definition;
1202 bool TextClass::readFloat(Lexer & lexrc)
1219 FT_ALLOWED_PLACEMENT,
1225 LexerKeyword floatTags[] = {
1226 { "allowedplacement", FT_ALLOWED_PLACEMENT },
1227 { "allowssideways", FT_ALLOWS_SIDEWAYS },
1228 { "allowswide", FT_ALLOWS_WIDE },
1230 { "extension", FT_EXT },
1231 { "guiname", FT_NAME },
1232 { "htmlattr", FT_HTMLATTR },
1233 { "htmlstyle", FT_HTMLSTYLE },
1234 { "htmltag", FT_HTMLTAG },
1235 { "ispredefined", FT_PREDEFINED },
1236 { "listcommand", FT_LISTCOMMAND },
1237 { "listname", FT_LISTNAME },
1238 { "numberwithin", FT_WITHIN },
1239 { "placement", FT_PLACEMENT },
1240 { "refprefix", FT_REFPREFIX },
1241 { "style", FT_STYLE },
1242 { "type", FT_TYPE },
1243 { "usesfloatpkg", FT_USESFLOAT }
1246 lexrc.pushTable(floatTags);
1250 docstring htmlstyle;
1256 string allowed_placement = "!htbpH";
1261 bool usesfloat = true;
1262 bool ispredefined = false;
1263 bool allowswide = true;
1264 bool allowssideways = true;
1266 bool getout = false;
1267 while (!getout && lexrc.isOK()) {
1268 int le = lexrc.lex();
1270 case Lexer::LEX_UNDEF:
1271 lexrc.printError("Unknown float tag `$$Token'");
1279 type = lexrc.getString();
1280 if (floatlist_.typeExist(type)) {
1281 Floating const & fl = floatlist_.getType(type);
1282 placement = fl.placement();
1284 within = fl.within();
1287 listname = fl.listName();
1288 usesfloat = fl.usesFloatPkg();
1289 ispredefined = fl.isPredefined();
1290 listcommand = fl.listCommand();
1291 refprefix = fl.refPrefix();
1296 name = lexrc.getString();
1300 placement = lexrc.getString();
1302 case FT_ALLOWED_PLACEMENT:
1304 allowed_placement = lexrc.getString();
1308 ext = lexrc.getString();
1312 within = lexrc.getString();
1313 if (within == "none")
1318 style = lexrc.getString();
1320 case FT_LISTCOMMAND:
1322 listcommand = lexrc.getString();
1326 refprefix = lexrc.getString();
1330 listname = lexrc.getString();
1334 usesfloat = lexrc.getBool();
1338 ispredefined = lexrc.getBool();
1340 case FT_ALLOWS_SIDEWAYS:
1342 allowssideways = lexrc.getBool();
1344 case FT_ALLOWS_WIDE:
1346 allowswide = lexrc.getBool();
1350 htmlattr = lexrc.getString();
1354 htmlstyle = lexrc.getLongString(from_ascii("EndHTMLStyle"));
1358 htmltag = lexrc.getString();
1368 // Here we have a full float if getout == true
1370 if (!usesfloat && listcommand.empty()) {
1371 // if this float uses the same auxfile as an existing one,
1372 // there is no need for it to provide a list command.
1373 FloatList::const_iterator it = floatlist_.begin();
1374 FloatList::const_iterator en = floatlist_.end();
1375 bool found_ext = false;
1376 for (; it != en; ++it) {
1377 if (it->second.ext() == ext) {
1383 LYXERR0("The layout does not provide a list command " <<
1384 "for the float `" << type << "'. LyX will " <<
1385 "not be able to produce a float list.");
1387 Floating fl(type, placement, ext, within, style, name,
1388 listname, listcommand, refprefix, allowed_placement,
1389 htmltag, htmlattr, htmlstyle, usesfloat, ispredefined,
1390 allowswide, allowssideways);
1391 floatlist_.newFloat(fl);
1392 // each float has its own counter
1393 counters_.newCounter(from_ascii(type), from_ascii(within),
1394 docstring(), docstring());
1395 // also define sub-float counters
1396 docstring const subtype = "sub-" + from_ascii(type);
1397 counters_.newCounter(subtype, from_ascii(type),
1398 "\\alph{" + subtype + "}", docstring());
1404 bool TextClass::readOutlinerName(Lexer & lexrc)
1409 type = lexrc.getString();
1411 lexrc.printError("No type given for OutlinerName: `$$Token'.");
1415 name = lexrc.getDocString();
1417 lexrc.printError("No name given for OutlinerName: `$$Token'.");
1420 outliner_names_[type] = name;
1425 string const & TextClass::prerequisites(string const & sep) const
1427 if (contains(prerequisites_, ',')) {
1428 vector<string> const pres = getVectorFromString(prerequisites_);
1429 prerequisites_ = getStringFromVector(pres, sep);
1431 return prerequisites_;
1435 bool TextClass::hasLayout(docstring const & n) const
1437 docstring const name = n.empty() ? defaultLayoutName() : n;
1439 return find_if(layoutlist_.begin(), layoutlist_.end(),
1440 LayoutNamesEqual(name))
1441 != layoutlist_.end();
1445 bool TextClass::hasInsetLayout(docstring const & n) const
1449 InsetLayouts::const_iterator it = insetlayoutlist_.find(n);
1450 return it != insetlayoutlist_.end();
1454 Layout const & TextClass::operator[](docstring const & name) const
1456 LATTEST(!name.empty());
1459 find_if(begin(), end(), LayoutNamesEqual(name));
1462 LYXERR0("We failed to find the layout '" << name
1463 << "' in the layout list. You MUST investigate!");
1464 for (const_iterator cit = begin(); cit != end(); ++cit)
1465 lyxerr << " " << to_utf8(cit->name()) << endl;
1467 // We require the name to exist
1468 static const Layout dummy;
1469 LASSERT(false, return dummy);
1476 Layout & TextClass::operator[](docstring const & name)
1478 LATTEST(!name.empty());
1479 // Safe to continue, given what we do below.
1481 iterator it = find_if(begin(), end(), LayoutNamesEqual(name));
1484 LYXERR0("We failed to find the layout '" << to_utf8(name)
1485 << "' in the layout list. You MUST investigate!");
1486 for (const_iterator cit = begin(); cit != end(); ++cit)
1487 LYXERR0(" " << to_utf8(cit->name()));
1489 // we require the name to exist
1491 // we are here only in release mode
1492 layoutlist_.push_back(createBasicLayout(name, true));
1493 it = find_if(begin(), end(), LayoutNamesEqual(name));
1500 bool TextClass::deleteLayout(docstring const & name)
1502 if (name == defaultLayoutName() || name == plainLayoutName())
1505 LayoutList::iterator it =
1506 remove_if(layoutlist_.begin(), layoutlist_.end(),
1507 LayoutNamesEqual(name));
1509 LayoutList::iterator end = layoutlist_.end();
1510 bool const ret = (it != end);
1511 layoutlist_.erase(it, end);
1516 bool TextClass::deleteInsetLayout(docstring const & name)
1518 return insetlayoutlist_.erase(name);
1522 // Load textclass info if not loaded yet
1523 bool TextClass::load(string const & path) const
1528 // Read style-file, provided path is searched before the system ones
1529 // If path is a file, it is loaded directly.
1530 FileName layout_file(path);
1531 if (!path.empty() && !layout_file.isReadableFile())
1532 layout_file = FileName(addName(path, name_ + ".layout"));
1533 if (layout_file.empty() || !layout_file.exists())
1534 layout_file = libFileSearch("layouts", name_, "layout");
1535 loaded_ = const_cast<TextClass*>(this)->read(layout_file);
1538 lyxerr << "Error reading `"
1539 << to_utf8(makeDisplayPath(layout_file.absFileName()))
1540 << "'\n(Check `" << name_
1541 << "')\nCheck your installation and "
1542 "try Options/Reconfigure..."
1550 bool DocumentClass::addLayoutIfNeeded(docstring const & n) const
1555 layoutlist_.push_back(createBasicLayout(n, true));
1560 string DocumentClass::forcedLayouts() const
1564 const_iterator const e = end();
1565 for (const_iterator i = begin(); i != e; ++i) {
1566 if (i->forcelocal > 0) {
1568 os << "Format " << LAYOUT_FORMAT << '\n';
1578 InsetLayout const & DocumentClass::insetLayout(docstring const & name) const
1580 // FIXME The fix for the InsetLayout part of 4812 would be here:
1581 // Add the InsetLayout to the document class if it is not found.
1583 InsetLayouts::const_iterator cen = insetlayoutlist_.end();
1584 while (!n.empty()) {
1585 InsetLayouts::const_iterator cit = insetlayoutlist_.lower_bound(n);
1586 if (cit != cen && cit->first == n) {
1587 if (cit->second.obsoleted_by().empty())
1589 n = cit->second.obsoleted_by();
1590 return insetLayout(n);
1592 // If we have a generic prefix (e.g., "Note:"),
1593 // try if this one alone is found.
1594 size_t i = n.find(':');
1595 if (i == string::npos)
1599 // Layout "name" not found.
1600 return plainInsetLayout();
1604 InsetLayout const & DocumentClass::plainInsetLayout() {
1605 static const InsetLayout plain_insetlayout_;
1606 return plain_insetlayout_;
1610 docstring const & TextClass::defaultLayoutName() const
1612 return defaultlayout_;
1616 Layout const & TextClass::defaultLayout() const
1618 return operator[](defaultLayoutName());
1622 bool TextClass::isDefaultLayout(Layout const & layout) const
1624 return layout.name() == defaultLayoutName();
1628 bool TextClass::isPlainLayout(Layout const & layout) const
1630 return layout.name() == plainLayoutName();
1634 Layout TextClass::createBasicLayout(docstring const & name, bool unknown) const
1636 static Layout * defaultLayout = NULL;
1638 if (defaultLayout) {
1639 defaultLayout->setUnknown(unknown);
1640 defaultLayout->setName(name);
1641 return *defaultLayout;
1644 static char const * s = "Margin Static\n"
1645 "LatexType Paragraph\n"
1648 "AlignPossible Left, Right, Center\n"
1649 "LabelType No_Label\n"
1651 istringstream ss(s);
1652 Lexer lex(textClassTags);
1654 defaultLayout = new Layout;
1655 defaultLayout->setUnknown(unknown);
1656 defaultLayout->setName(name);
1657 if (!readStyle(lex, *defaultLayout)) {
1658 // The only way this happens is because the hardcoded layout above
1662 return *defaultLayout;
1666 DocumentClassPtr getDocumentClass(
1667 LayoutFile const & baseClass, LayoutModuleList const & modlist,
1668 LayoutModuleList const & celist,
1671 DocumentClassPtr doc_class =
1672 DocumentClassPtr(new DocumentClass(baseClass));
1673 LayoutModuleList::const_iterator it = modlist.begin();
1674 LayoutModuleList::const_iterator en = modlist.end();
1675 for (; it != en; ++it) {
1676 string const modName = *it;
1677 LyXModule * lm = theModuleList[modName];
1679 docstring const msg =
1680 bformat(_("The module %1$s has been requested by\n"
1681 "this document but has not been found in the list of\n"
1682 "available modules. If you recently installed it, you\n"
1683 "probably need to reconfigure LyX.\n"), from_utf8(modName));
1685 frontend::Alert::warning(_("Module not available"), msg);
1688 if (!lm->isAvailable() && !clone) {
1689 docstring const prereqs = from_utf8(getStringFromVector(lm->prerequisites(), "\n\t"));
1690 docstring const msg =
1691 bformat(_("The module %1$s requires a package that is not\n"
1692 "available in your LaTeX installation, or a converter that\n"
1693 "you have not installed. LaTeX output may not be possible.\n"
1694 "Missing prerequisites:\n"
1696 "See section 3.1.2.3 (Modules) of the User's Guide for more information."),
1697 from_utf8(modName), prereqs);
1698 frontend::Alert::warning(_("Package not available"), msg, true);
1700 FileName layout_file = libFileSearch("layouts", lm->getFilename());
1701 if (!doc_class->read(layout_file, TextClass::MODULE)) {
1702 docstring const msg =
1703 bformat(_("Error reading module %1$s\n"), from_utf8(modName));
1704 frontend::Alert::warning(_("Read Error"), msg);
1708 LayoutModuleList::const_iterator cit = celist.begin();
1709 LayoutModuleList::const_iterator cen = celist.end();
1710 for (; cit != cen; ++cit) {
1711 string const ceName = *cit;
1712 LyXCiteEngine * ce = theCiteEnginesList[ceName];
1714 docstring const msg =
1715 bformat(_("The cite engine %1$s has been requested by\n"
1716 "this document but has not been found in the list of\n"
1717 "available engines. If you recently installed it, you\n"
1718 "probably need to reconfigure LyX.\n"), from_utf8(ceName));
1720 frontend::Alert::warning(_("Cite Engine not available"), msg);
1723 if (!ce->isAvailable() && !clone) {
1724 docstring const prereqs = from_utf8(getStringFromVector(ce->prerequisites(), "\n\t"));
1725 docstring const msg =
1726 bformat(_("The cite engine %1$s requires a package that is not\n"
1727 "available in your LaTeX installation, or a converter that\n"
1728 "you have not installed. LaTeX output may not be possible.\n"
1729 "Missing prerequisites:\n"
1731 "See section 3.1.2.3 (Modules) of the User's Guide for more information."),
1732 from_utf8(ceName), prereqs);
1733 frontend::Alert::warning(_("Package not available"), msg, true);
1735 FileName layout_file = libFileSearch("citeengines", ce->getFilename());
1736 if (!doc_class->read(layout_file, TextClass::CITE_ENGINE)) {
1737 docstring const msg =
1738 bformat(_("Error reading cite engine %1$s\n"), from_utf8(ceName));
1739 frontend::Alert::warning(_("Read Error"), msg);
1747 /////////////////////////////////////////////////////////////////////////
1751 /////////////////////////////////////////////////////////////////////////
1753 DocumentClass::DocumentClass(LayoutFile const & tc)
1758 bool DocumentClass::hasLaTeXLayout(std::string const & lay) const
1760 LayoutList::const_iterator it = layoutlist_.begin();
1761 LayoutList::const_iterator end = layoutlist_.end();
1762 for (; it != end; ++it)
1763 if (it->latexname() == lay)
1769 bool DocumentClass::provides(string const & p) const
1771 return provides_.find(p) != provides_.end();
1775 bool DocumentClass::hasTocLevels() const
1777 return min_toclevel_ != Layout::NOT_IN_TOC;
1781 Layout const & DocumentClass::getTOCLayout() const
1783 // we're going to look for the layout with the minimum toclevel
1784 TextClass::LayoutList::const_iterator lit = begin();
1785 TextClass::LayoutList::const_iterator const len = end();
1786 int minlevel = 1000;
1787 Layout const * lay = NULL;
1788 for (; lit != len; ++lit) {
1789 int const level = lit->toclevel;
1790 // we don't want Part or unnumbered sections
1791 if (level == Layout::NOT_IN_TOC || level < 0
1792 || level >= minlevel || lit->counter.empty())
1799 // hmm. that is very odd, so we'll do our best.
1800 return operator[](defaultLayoutName());
1804 Layout const & DocumentClass::htmlTOCLayout() const
1806 if (html_toc_section_.empty())
1807 html_toc_section_ = getTOCLayout().name();
1808 return operator[](html_toc_section_);
1812 string const DocumentClass::getCiteFormat(CiteEngineType const & type,
1813 string const & entry, bool const punct, string const & fallback) const
1815 string default_format = "{%fullnames:author%[[%fullnames:author%, ]][[{%fullnames:editor%[[%fullnames:editor%, ed., ]]}]]}\"%title%\"{%journal%[[, {!<i>!}%journal%{!</i>!}]][[{%publisher%[[, %publisher%]][[{%institution%[[, %institution%]]}]]}]]}{%year%[[ (%year%)]]}{%pages%[[, %pages%]]}";
1817 default_format += ".";
1819 map<CiteEngineType, map<string, string> >::const_iterator itype = cite_formats_.find(type);
1820 if (itype == cite_formats_.end())
1821 return default_format;
1822 map<string, string>::const_iterator it = itype->second.find(entry);
1823 if (it == itype->second.end() && !fallback.empty())
1824 it = itype->second.find(fallback);
1825 if (it == itype->second.end())
1826 return default_format;
1828 return it->second + ".";
1833 string const & DocumentClass::getCiteMacro(CiteEngineType const & type,
1834 string const & macro) const
1836 static string empty;
1837 map<CiteEngineType, map<string, string> >::const_iterator itype = cite_macros_.find(type);
1838 if (itype == cite_macros_.end())
1840 map<string, string>::const_iterator it = itype->second.find(macro);
1841 if (it == itype->second.end())
1847 vector<string> const DocumentClass::citeCommands(
1848 CiteEngineType const & type) const
1850 vector<CitationStyle> const styles = citeStyles(type);
1851 vector<CitationStyle>::const_iterator it = styles.begin();
1852 vector<CitationStyle>::const_iterator end = styles.end();
1853 vector<string> cmds;
1854 for (; it != end; ++it) {
1855 CitationStyle const cite = *it;
1856 cmds.push_back(cite.name);
1862 vector<CitationStyle> const & DocumentClass::citeStyles(
1863 CiteEngineType const & type) const
1865 static vector<CitationStyle> empty;
1866 map<CiteEngineType, vector<CitationStyle> >::const_iterator it = cite_styles_.find(type);
1867 if (it == cite_styles_.end())
1873 /////////////////////////////////////////////////////////////////////////
1877 /////////////////////////////////////////////////////////////////////////
1879 ostream & operator<<(ostream & os, PageSides p)