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"
21 #include "support/debug.h"
22 #include "support/gettext.h"
24 #include "FloatList.h"
29 #include "frontends/alert.h"
31 #include "support/lstrings.h"
32 #include "support/filetools.h"
33 #include "support/os.h"
38 using namespace lyx::support;
44 class LayoutNamesEqual : public unary_function<LayoutPtr, bool> {
46 LayoutNamesEqual(docstring const & name)
49 bool operator()(LayoutPtr const & c) const
51 return c->name() == name_;
61 bool layout2layout(FileName const & filename, FileName const & tempfile)
63 FileName const script = libFileSearch("scripts", "layout2layout.py");
65 lyxerr << "Could not find layout conversion "
66 "script layout2layout.py." << endl;
70 ostringstream command;
71 command << os::python() << ' ' << quoteName(script.toFilesystemEncoding())
72 << ' ' << quoteName(filename.toFilesystemEncoding())
73 << ' ' << quoteName(tempfile.toFilesystemEncoding());
74 string const command_str = command.str();
76 LYXERR(Debug::TCLASS, "Running `" << command_str << '\'');
79 runCommand(command_str);
81 lyxerr << "Could not run layout conversion "
82 "script layout2layout.py." << endl;
91 TextClass::TextClass(string const & fn, string const & cln,
92 string const & desc, bool texClassAvail )
93 : name_(fn), latexname_(cln), description_(desc),
94 floatlist_(new FloatList), counters_(new Counters),
95 texClassAvail_(texClassAvail)
103 pagestyle_ = "default";
104 defaultfont_ = sane_font;
105 opt_fontsize_ = "10|11|12";
106 opt_pagestyle_ = "empty|plain|headings|fancy";
107 titletype_ = TITLE_COMMAND_AFTER;
108 titlename_ = "maketitle";
113 bool TextClass::isTeXClassAvailable() const
115 return texClassAvail_;
119 bool TextClass::readStyle(Lexer & lexrc, Layout & lay)
121 LYXERR(Debug::TCLASS, "Reading style " << to_utf8(lay.name()));
122 if (!lay.read(lexrc, *this)) {
124 lay.resfont = lay.font;
125 lay.resfont.realize(defaultfont());
126 lay.reslabelfont = lay.labelfont;
127 lay.reslabelfont.realize(defaultfont());
128 return false; // no errors
130 lyxerr << "Error parsing style `" << to_utf8(lay.name()) << '\'' << endl;
163 // Reads a textclass structure from file.
164 bool TextClass::read(FileName const & filename, ReadType rt)
166 if (!filename.isReadableFile()) {
167 lyxerr << "Cannot read layout file `" << filename << "'."
172 keyword_item textClassTags[] = {
173 { "classoptions", TC_CLASSOPTIONS },
174 { "columns", TC_COLUMNS },
175 { "counter", TC_COUNTER },
176 { "defaultfont", TC_DEFAULTFONT },
177 { "defaultstyle", TC_DEFAULTSTYLE },
178 { "environment", TC_ENVIRONMENT },
179 { "float", TC_FLOAT },
180 { "format", TC_FORMAT },
181 { "input", TC_INPUT },
182 { "insetlayout", TC_INSETLAYOUT },
183 { "leftmargin", TC_LEFTMARGIN },
184 { "nofloat", TC_NOFLOAT },
185 { "nostyle", TC_NOSTYLE },
186 { "outputtype", TC_OUTPUTTYPE },
187 { "pagestyle", TC_PAGESTYLE },
188 { "preamble", TC_PREAMBLE },
189 { "provides", TC_PROVIDES },
190 { "rightmargin", TC_RIGHTMARGIN },
191 { "secnumdepth", TC_SECNUMDEPTH },
192 { "sides", TC_SIDES },
193 { "style", TC_STYLE },
194 { "titlelatexname", TC_TITLELATEXNAME },
195 { "titlelatextype", TC_TITLELATEXTYPE },
196 { "tocdepth", TC_TOCDEPTH }
201 LYXERR(Debug::TCLASS, "Reading textclass ");
204 LYXERR(Debug::TCLASS, "Reading input file ");
207 LYXERR(Debug::TCLASS, "Reading module file ");
212 LYXERR(Debug::TCLASS, to_utf8(makeDisplayPath(filename.absFilename())));
214 Lexer lexrc(textClassTags,
215 sizeof(textClassTags) / sizeof(textClassTags[0]));
217 lexrc.setFile(filename);
218 bool error = !lexrc.isOK();
220 // Format of files before the 'Format' tag was introduced
224 while (lexrc.isOK() && !error) {
225 int le = lexrc.lex();
228 case Lexer::LEX_FEOF:
231 case Lexer::LEX_UNDEF:
232 lexrc.printError("Unknown TextClass tag `$$Token'");
240 switch (static_cast<TextClassTags>(le)) {
244 format = lexrc.getInteger();
247 case TC_OUTPUTTYPE: // output type definition
248 readOutputType(lexrc);
251 case TC_INPUT: // Include file
253 string const inc = lexrc.getString();
254 FileName tmp = libFileSearch("layouts", inc,
258 lexrc.printError("Could not find input"
261 } else if (read(tmp, MERGE)) {
262 lexrc.printError("Error reading input"
263 "file: " + tmp.absFilename());
269 case TC_DEFAULTSTYLE:
271 docstring const name = from_utf8(subst(lexrc.getString(),
273 defaultlayout_ = name;
280 docstring const name = from_utf8(subst(lexrc.getString(),
283 string s = "Could not read name for style: `$$Token' "
284 + lexrc.getString() + " is probably not valid UTF-8!";
285 lexrc.printError(s.c_str());
287 error = readStyle(lexrc, lay);
288 } else if (hasLayout(name)) {
289 Layout * lay = operator[](name).get();
290 error = readStyle(lexrc, *lay);
294 if (le == TC_ENVIRONMENT)
295 lay.is_environment = true;
296 error = readStyle(lexrc, lay);
298 layoutlist_.push_back(
299 boost::shared_ptr<Layout>(new Layout(lay))
302 if (defaultlayout_.empty()) {
303 // We do not have a default
304 // layout yet, so we choose
305 // the first layout we
307 defaultlayout_ = name;
312 lexrc.printError("No name given for style: `$$Token'.");
319 docstring const style = from_utf8(subst(lexrc.getString(),
321 if (!deleteLayout(style))
322 lyxerr << "Cannot delete style `"
323 << to_utf8(style) << '\'' << endl;
324 // lexrc.printError("Cannot delete style"
331 columns_ = lexrc.getInteger();
336 switch (lexrc.getInteger()) {
337 case 1: sides_ = OneSide; break;
338 case 2: sides_ = TwoSides; break;
340 lyxerr << "Impossible number of page"
341 " sides, setting to one."
351 pagestyle_ = rtrim(lexrc.getString());
355 defaultfont_ = lyxRead(lexrc);
356 if (!defaultfont_.resolved()) {
357 lexrc.printError("Warning: defaultfont should "
358 "be fully instantiated!");
359 defaultfont_.realize(sane_font);
365 secnumdepth_ = lexrc.getInteger();
370 tocdepth_ = lexrc.getInteger();
373 // First step to support options
374 case TC_CLASSOPTIONS:
375 readClassOptions(lexrc);
379 preamble_ = from_utf8(lexrc.getLongString("EndPreamble"));
384 string const feature = lexrc.getString();
386 if (lexrc.getInteger())
387 provides_.insert(feature);
389 provides_.erase(feature);
393 case TC_LEFTMARGIN: // left margin type
395 leftmargin_ = lexrc.getDocString();
398 case TC_RIGHTMARGIN: // right margin type
400 rightmargin_ = lexrc.getDocString();
404 docstring const name = subst(lexrc.getDocString(), '_', ' ');
405 readInsetLayout(lexrc, name);
414 case TC_TITLELATEXTYPE:
415 readTitleType(lexrc);
417 case TC_TITLELATEXNAME:
419 titlename_ = lexrc.getString();
423 string const nofloat = lexrc.getString();
424 floatlist_->erase(nofloat);
428 if (format != FORMAT)
432 if (format != FORMAT) {
433 LYXERR(Debug::TCLASS, "Converting layout file from format "
434 << format << " to " << FORMAT);
435 FileName const tempfile = FileName::tempName();
436 error = !layout2layout(filename, tempfile);
438 error = read(tempfile, rt);
439 tempfile.removeFile();
444 LYXERR(Debug::TCLASS, "Finished reading module file "
445 << to_utf8(makeDisplayPath(filename.absFilename())));
446 else if (rt == MERGE)
447 LYXERR(Debug::TCLASS, "Finished reading input file "
448 << to_utf8(makeDisplayPath(filename.absFilename())));
449 else { // we are at top level here.
450 LYXERR(Debug::TCLASS, "Finished reading textclass "
451 << to_utf8(makeDisplayPath(filename.absFilename())));
452 if (defaultlayout_.empty()) {
453 lyxerr << "Error: Textclass '" << name_
454 << "' is missing a defaultstyle." << endl;
458 min_toclevel_ = Layout::NOT_IN_TOC;
459 max_toclevel_ = Layout::NOT_IN_TOC;
460 const_iterator cit = begin();
461 const_iterator the_end = end();
462 for ( ; cit != the_end ; ++cit) {
463 int const toclevel = (*cit)->toclevel;
464 if (toclevel != Layout::NOT_IN_TOC) {
465 if (min_toclevel_ == Layout::NOT_IN_TOC)
466 min_toclevel_ = toclevel;
468 min_toclevel_ = min(min_toclevel_,
470 max_toclevel_ = max(max_toclevel_,
474 LYXERR(Debug::TCLASS, "Minimum TocLevel is " << min_toclevel_
475 << ", maximum is " << max_toclevel_);
483 void TextClass::readTitleType(Lexer & lexrc)
485 keyword_item titleTypeTags[] = {
486 { "commandafter", TITLE_COMMAND_AFTER },
487 { "environment", TITLE_ENVIRONMENT }
490 PushPopHelper pph(lexrc, titleTypeTags, TITLE_ENVIRONMENT);
492 int le = lexrc.lex();
494 case Lexer::LEX_UNDEF:
495 lexrc.printError("Unknown output type `$$Token'");
497 case TITLE_COMMAND_AFTER:
498 case TITLE_ENVIRONMENT:
499 titletype_ = static_cast<TitleLatexType>(le);
502 lyxerr << "Unhandled value " << le
503 << " in TextClass::readTitleType." << endl;
510 void TextClass::readOutputType(Lexer & lexrc)
512 keyword_item outputTypeTags[] = {
513 { "docbook", DOCBOOK },
515 { "literate", LITERATE }
518 PushPopHelper pph(lexrc, outputTypeTags, LITERATE);
520 int le = lexrc.lex();
522 case Lexer::LEX_UNDEF:
523 lexrc.printError("Unknown output type `$$Token'");
528 outputType_ = static_cast<OutputType>(le);
531 lyxerr << "Unhandled value " << le
532 << " in TextClass::readOutputType." << endl;
539 enum ClassOptionsTags {
548 void TextClass::readClassOptions(Lexer & lexrc)
550 keyword_item classOptionsTags[] = {
552 {"fontsize", CO_FONTSIZE },
553 {"header", CO_HEADER },
554 {"other", CO_OTHER },
555 {"pagestyle", CO_PAGESTYLE }
558 lexrc.pushTable(classOptionsTags, CO_END);
560 while (!getout && lexrc.isOK()) {
561 int le = lexrc.lex();
563 case Lexer::LEX_UNDEF:
564 lexrc.printError("Unknown ClassOption tag `$$Token'");
568 switch (static_cast<ClassOptionsTags>(le)) {
571 opt_fontsize_ = rtrim(lexrc.getString());
575 opt_pagestyle_ = rtrim(lexrc.getString());
579 options_ = lexrc.getString();
583 class_header_ = subst(lexrc.getString(), """, "\"");
594 enum InsetLayoutTags {
615 void TextClass::readInsetLayout(Lexer & lexrc, docstring const & name)
617 keyword_item elementTags[] = {
618 { "bgcolor", IL_BGCOLOR },
619 { "decoration", IL_DECORATION },
622 { "forceltr", IL_FORCELTR },
623 { "freespacing", IL_FREESPACING },
624 { "keepempty", IL_KEEPEMPTY },
625 { "labelfont", IL_LABELFONT },
626 { "labelstring", IL_LABELSTRING },
627 { "latexname", IL_LATEXNAME },
628 { "latexparam", IL_LATEXPARAM },
629 { "latextype", IL_LATEXTYPE },
630 { "lyxtype", IL_LYXTYPE },
631 { "multipar", IL_MULTIPAR },
632 { "needprotect", IL_NEEDPROTECT },
633 { "passthru", IL_PASSTHRU },
634 { "preamble", IL_PREAMBLE }
637 lexrc.pushTable(elementTags, IL_END);
640 docstring labelstring;
645 FontInfo font = inherit_font;
646 FontInfo labelfont = inherit_font;
647 ColorCode bgcolor(Color_background);
649 bool multipar = false;
650 bool passthru = false;
651 bool needprotect = false;
652 bool keepempty = false;
653 bool freespacing = false;
654 bool forceltr = false;
657 while (!getout && lexrc.isOK()) {
658 int le = lexrc.lex();
660 case Lexer::LEX_UNDEF:
661 lexrc.printError("Unknown ClassOption tag `$$Token'");
665 switch (static_cast<InsetLayoutTags>(le)) {
668 lyxtype = lexrc.getString();
672 latextype = lexrc.getString();
676 labelstring = lexrc.getDocString();
680 decoration = lexrc.getString();
684 latexname = lexrc.getString();
688 latexparam = subst(lexrc.getString(), """, "\"");
691 labelfont = lyxRead(lexrc, inherit_font);
695 forceltr = lexrc.getBool();
699 multipar = lexrc.getBool();
703 passthru = lexrc.getBool();
707 keepempty = lexrc.getBool();
711 freespacing = lexrc.getBool();
715 needprotect = lexrc.getBool();
718 font = lyxRead(lexrc, inherit_font);
719 // So: define font before labelfont
724 string const token = lexrc.getString();
725 bgcolor = lcolor.getFromLyXName(token);
729 preamble = lexrc.getLongString("EndPreamble");
737 // Here add element to list if getout == true
740 il.name = to_ascii(name);
741 il.lyxtype = lyxtype;
742 il.labelstring = labelstring;
743 il.decoration = decoration;
744 il.latextype = latextype;
745 il.latexname = latexname;
746 il.latexparam = latexparam;
747 il.multipar = multipar;
748 il.passthru = passthru;
749 il.needprotect = needprotect;
750 il.freespacing = freespacing;
751 il.forceltr = forceltr;
752 il.keepempty = keepempty;
754 // The label font is generally used as-is without
755 // any realization against a given context.
756 labelfont.realize(sane_font);
757 il.labelfont = labelfont;
758 il.bgcolor = bgcolor;
759 il.preamble = preamble;
760 insetlayoutlist_[name] = il;
781 void TextClass::readFloat(Lexer & lexrc)
783 keyword_item floatTags[] = {
785 { "extension", FT_EXT },
786 { "guiname", FT_NAME },
787 { "latexbuiltin", FT_BUILTIN },
788 { "listname", FT_LISTNAME },
789 { "numberwithin", FT_WITHIN },
790 { "placement", FT_PLACEMENT },
791 { "style", FT_STYLE },
795 lexrc.pushTable(floatTags, FT_END);
804 bool builtin = false;
807 while (!getout && lexrc.isOK()) {
808 int le = lexrc.lex();
810 case Lexer::LEX_UNDEF:
811 lexrc.printError("Unknown ClassOption tag `$$Token'");
815 switch (static_cast<FloatTags>(le)) {
818 type = lexrc.getString();
819 if (floatlist_->typeExist(type)) {
820 Floating const & fl = floatlist_->getType(type);
821 placement = fl.placement();
823 within = fl.within();
826 listName = fl.listName();
827 builtin = fl.builtin();
832 name = lexrc.getString();
836 placement = lexrc.getString();
840 ext = lexrc.getString();
844 within = lexrc.getString();
845 if (within == "none")
850 style = lexrc.getString();
854 listName = lexrc.getString();
858 builtin = lexrc.getBool();
866 // Here if have a full float if getout == true
868 Floating fl(type, placement, ext, within,
869 style, name, listName, builtin);
870 floatlist_->newFloat(fl);
871 // each float has its own counter
872 counters_->newCounter(from_ascii(type), from_ascii(within),
873 docstring(), docstring());
884 CT_LABELSTRING_APPENDIX,
889 void TextClass::readCounter(Lexer & lexrc)
891 keyword_item counterTags[] = {
893 { "labelstring", CT_LABELSTRING },
894 { "labelstringappendix", CT_LABELSTRING_APPENDIX },
896 { "within", CT_WITHIN }
899 lexrc.pushTable(counterTags, CT_END);
903 docstring labelstring;
904 docstring labelstring_appendix;
907 while (!getout && lexrc.isOK()) {
908 int le = lexrc.lex();
910 case Lexer::LEX_UNDEF:
911 lexrc.printError("Unknown ClassOption tag `$$Token'");
915 switch (static_cast<CounterTags>(le)) {
918 name = lexrc.getDocString();
919 if (counters_->hasCounter(name))
920 LYXERR(Debug::TCLASS, "Reading existing counter " << to_utf8(name));
922 LYXERR(Debug::TCLASS, "Reading new counter " << to_utf8(name));
926 within = lexrc.getDocString();
927 if (within == "none")
932 labelstring = lexrc.getDocString();
933 labelstring_appendix = labelstring;
935 case CT_LABELSTRING_APPENDIX:
937 labelstring_appendix = lexrc.getDocString();
945 // Here if have a full counter if getout == true
947 counters_->newCounter(name, within,
948 labelstring, labelstring_appendix);
954 FontInfo const & TextClass::defaultfont() const
960 docstring const & TextClass::leftmargin() const
966 docstring const & TextClass::rightmargin() const
972 bool TextClass::hasLayout(docstring const & n) const
974 docstring const name = n.empty() ? defaultLayoutName() : n;
976 return find_if(layoutlist_.begin(), layoutlist_.end(),
977 LayoutNamesEqual(name))
978 != layoutlist_.end();
983 LayoutPtr const & TextClass::operator[](docstring const & name) const
985 BOOST_ASSERT(!name.empty());
987 LayoutList::const_iterator cit =
988 find_if(layoutlist_.begin(),
990 LayoutNamesEqual(name));
992 if (cit == layoutlist_.end()) {
993 lyxerr << "We failed to find the layout '" << to_utf8(name)
994 << "' in the layout list. You MUST investigate!"
996 for (LayoutList::const_iterator it = layoutlist_.begin();
997 it != layoutlist_.end(); ++it)
998 lyxerr << " " << to_utf8(it->get()->name()) << endl;
1000 // we require the name to exist
1001 BOOST_ASSERT(false);
1008 bool TextClass::deleteLayout(docstring const & name)
1010 if (name == defaultLayoutName())
1013 LayoutList::iterator it =
1014 remove_if(layoutlist_.begin(), layoutlist_.end(),
1015 LayoutNamesEqual(name));
1017 LayoutList::iterator end = layoutlist_.end();
1018 bool const ret = (it != end);
1019 layoutlist_.erase(it, end);
1024 // Load textclass info if not loaded yet
1025 bool TextClass::load(string const & path) const
1030 // Read style-file, provided path is searched before the system ones
1031 FileName layout_file;
1033 layout_file = FileName(addName(path, name_ + ".layout"));
1034 if (layout_file.empty() || !layout_file.exists())
1035 layout_file = libFileSearch("layouts", name_, "layout");
1036 loaded_ = const_cast<TextClass*>(this)->read(layout_file) == 0;
1039 lyxerr << "Error reading `"
1040 << to_utf8(makeDisplayPath(layout_file.absFilename()))
1041 << "'\n(Check `" << name_
1042 << "')\nCheck your installation and "
1043 "try Options/Reconfigure..." << endl;
1050 FloatList & TextClass::floats()
1052 return *floatlist_.get();
1056 FloatList const & TextClass::floats() const
1058 return *floatlist_.get();
1062 Counters & TextClass::counters() const
1064 return *counters_.get();
1068 // Return the layout object of an inset given by name. If the name
1069 // is not found as such, the part after the ':' is stripped off, and
1070 // searched again. In this way, an error fallback can be provided:
1071 // An erroneous 'CharStyle:badname' (e.g., after a documentclass switch)
1072 // will invoke the layout object defined by name = 'CharStyle'.
1073 // If that doesn't work either, an empty object returns (shouldn't
1074 // happen). -- Idea JMarc, comment MV
1075 InsetLayout const & TextClass::insetlayout(docstring const & name) const
1078 while (!n.empty()) {
1079 if (insetlayoutlist_.count(n) > 0)
1080 return insetlayoutlist_[n];
1081 docstring::size_type i = n.find(':');
1082 if (i == string::npos)
1086 static InsetLayout empty;
1087 empty.labelstring = from_utf8("UNDEFINED");
1088 empty.labelfont = sane_font;
1089 empty.labelfont.setColor(Color_error);
1090 empty.bgcolor = Color_error;
1095 docstring const & TextClass::defaultLayoutName() const
1097 // This really should come from the actual layout... (Lgb)
1098 return defaultlayout_;
1102 LayoutPtr const & TextClass::defaultLayout() const
1104 return operator[](defaultLayoutName());
1108 string const & TextClass::name() const
1114 string const & TextClass::latexname() const
1116 const_cast<TextClass*>(this)->load();
1121 string const & TextClass::description() const
1123 return description_;
1127 string const & TextClass::opt_fontsize() const
1129 return opt_fontsize_;
1133 string const & TextClass::opt_pagestyle() const
1135 return opt_pagestyle_;
1139 string const & TextClass::options() const
1145 string const & TextClass::class_header() const
1147 return class_header_;
1151 string const & TextClass::pagestyle() const
1157 docstring const & TextClass::preamble() const
1163 PageSides TextClass::sides() const
1169 int TextClass::secnumdepth() const
1171 return secnumdepth_;
1175 int TextClass::tocdepth() const
1181 OutputType TextClass::outputType() const
1187 bool TextClass::provides(string const & p) const
1189 return provides_.find(p) != provides_.end();
1193 unsigned int TextClass::columns() const
1199 TitleLatexType TextClass::titletype() const
1205 string const & TextClass::titlename() const
1211 int TextClass::size() const
1213 return layoutlist_.size();
1217 int TextClass::min_toclevel() const
1219 return min_toclevel_;
1223 int TextClass::max_toclevel() const
1225 return max_toclevel_;
1229 bool TextClass::hasTocLevels() const
1231 return min_toclevel_ != Layout::NOT_IN_TOC;
1235 ostream & operator<<(ostream & os, PageSides p)