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"
23 #include "FloatList.h"
28 #include "frontends/alert.h"
30 #include "support/debug.h"
31 #include "support/ExceptionMessage.h"
32 #include "support/FileName.h"
33 #include "support/filetools.h"
34 #include "support/gettext.h"
35 #include "support/lstrings.h"
36 #include "support/os.h"
41 #include "boost/assert.hpp"
44 using namespace lyx::support;
50 class LayoutNamesEqual : public unary_function<Layout, bool> {
52 LayoutNamesEqual(docstring const & name)
55 bool operator()(Layout const & c) const
57 return c.name() == name_;
67 bool layout2layout(FileName const & filename, FileName const & tempfile)
69 FileName const script = libFileSearch("scripts", "layout2layout.py");
71 lyxerr << "Could not find layout conversion "
72 "script layout2layout.py." << endl;
76 ostringstream command;
77 command << os::python() << ' ' << quoteName(script.toFilesystemEncoding())
78 << ' ' << quoteName(filename.toFilesystemEncoding())
79 << ' ' << quoteName(tempfile.toFilesystemEncoding());
80 string const command_str = command.str();
82 LYXERR(Debug::TCLASS, "Running `" << command_str << '\'');
85 runCommand(command_str);
87 lyxerr << "Could not run layout conversion "
88 "script layout2layout.py." << endl;
95 std::string translateRT(TextClass::ReadType rt)
98 case TextClass::BASECLASS:
100 case TextClass::MERGE:
102 case TextClass::MODULE:
103 return "module file";
112 docstring const TextClass::emptylayout_ = from_ascii("PlainLayout");
115 InsetLayout DocumentClass::empty_insetlayout_;
118 TextClass::TextClass()
125 pagestyle_ = "default";
126 defaultfont_ = sane_font;
127 opt_fontsize_ = "10|11|12";
128 opt_pagestyle_ = "empty|plain|headings|fancy";
129 titletype_ = TITLE_COMMAND_AFTER;
130 titlename_ = "maketitle";
132 // a hack to make this available for translation
133 // i'm sure there must be a better way (rgh)
138 bool TextClass::readStyle(Lexer & lexrc, Layout & lay)
140 LYXERR(Debug::TCLASS, "Reading style " << to_utf8(lay.name()));
141 if (!lay.read(lexrc, *this)) {
142 lyxerr << "Error parsing style `" << to_utf8(lay.name()) << '\'' << endl;
146 lay.resfont = lay.font;
147 lay.resfont.realize(defaultfont_);
148 lay.reslabelfont = lay.labelfont;
149 lay.reslabelfont.realize(defaultfont_);
150 return true; // no errors
183 // Reads a textclass structure from file.
184 bool TextClass::read(FileName const & filename, ReadType rt)
186 if (!filename.isReadableFile()) {
187 lyxerr << "Cannot read layout file `" << filename << "'."
192 keyword_item textClassTags[] = {
193 { "classoptions", TC_CLASSOPTIONS },
194 { "columns", TC_COLUMNS },
195 { "counter", TC_COUNTER },
196 { "defaultfont", TC_DEFAULTFONT },
197 { "defaultstyle", TC_DEFAULTSTYLE },
198 { "environment", TC_ENVIRONMENT },
199 { "float", TC_FLOAT },
200 { "format", TC_FORMAT },
201 { "input", TC_INPUT },
202 { "insetlayout", TC_INSETLAYOUT },
203 { "leftmargin", TC_LEFTMARGIN },
204 { "nofloat", TC_NOFLOAT },
205 { "nostyle", TC_NOSTYLE },
206 { "outputtype", TC_OUTPUTTYPE },
207 { "pagestyle", TC_PAGESTYLE },
208 { "preamble", TC_PREAMBLE },
209 { "provides", TC_PROVIDES },
210 { "requires", TC_REQUIRES },
211 { "rightmargin", TC_RIGHTMARGIN },
212 { "secnumdepth", TC_SECNUMDEPTH },
213 { "sides", TC_SIDES },
214 { "style", TC_STYLE },
215 { "titlelatexname", TC_TITLELATEXNAME },
216 { "titlelatextype", TC_TITLELATEXTYPE },
217 { "tocdepth", TC_TOCDEPTH }
220 LYXERR(Debug::TCLASS, "Reading " + translateRT(rt) + ": " +
221 to_utf8(makeDisplayPath(filename.absFilename())));
223 // Define the `empty' layout used in table cells, ert, etc. Note that
224 // we do this before loading any layout file, so that classes can
225 // override features of this layout if they should choose to do so.
226 if (rt == BASECLASS) {
227 static char const * s = "Margin Static\n"
228 "LatexType Paragraph\n"
231 "AlignPossible Left, Right, Center\n"
232 "LabelType No_Label\n"
235 Lexer lex(textClassTags, sizeof(textClassTags) / sizeof(textClassTags[0]));
238 lay.setName(emptylayout_);
239 if (!readStyle(lex, lay)) {
240 // The only way this happens is because the hardcoded layout above
244 layoutlist_.push_back(lay);
247 Lexer lexrc(textClassTags,
248 sizeof(textClassTags) / sizeof(textClassTags[0]));
250 lexrc.setFile(filename);
251 bool error = !lexrc.isOK();
253 // Format of files before the 'Format' tag was introduced
257 while (lexrc.isOK() && !error) {
258 int le = lexrc.lex();
261 case Lexer::LEX_FEOF:
264 case Lexer::LEX_UNDEF:
265 lexrc.printError("Unknown TextClass tag `$$Token'");
273 switch (static_cast<TextClassTags>(le)) {
277 format = lexrc.getInteger();
280 case TC_OUTPUTTYPE: // output type definition
281 readOutputType(lexrc);
284 case TC_INPUT: // Include file
286 string const inc = lexrc.getString();
287 FileName tmp = libFileSearch("layouts", inc,
291 lexrc.printError("Could not find input file: " + inc);
293 } else if (!read(tmp, MERGE)) {
294 lexrc.printError("Error reading input"
295 "file: " + tmp.absFilename());
301 case TC_DEFAULTSTYLE:
303 docstring const name = from_utf8(subst(lexrc.getString(),
305 defaultlayout_ = name;
312 docstring const name = from_utf8(subst(lexrc.getString(),
315 string s = "Could not read name for style: `$$Token' "
316 + lexrc.getString() + " is probably not valid UTF-8!";
317 lexrc.printError(s.c_str());
319 //FIXME If we're just dropping this layout, do we really
320 //care whether there's an error?? Or should we just set
321 //error to true, since we couldn't even read the name?
322 error = !readStyle(lexrc, lay);
323 } else if (hasLayout(name)) {
324 Layout & lay = operator[](name);
325 error = !readStyle(lexrc, lay);
329 if (le == TC_ENVIRONMENT)
330 lay.is_environment = true;
331 error = !readStyle(lexrc, lay);
333 layoutlist_.push_back(lay);
335 if (defaultlayout_.empty()) {
336 // We do not have a default layout yet, so we choose
337 // the first layout we encounter.
338 defaultlayout_ = name;
343 //FIXME Should we also eat the style here? viz:
345 //readStyle(lexrc, lay);
347 lexrc.printError("No name given for style: `$$Token'.");
354 docstring const style = from_utf8(subst(lexrc.getString(),
356 if (!deleteLayout(style))
357 lyxerr << "Cannot delete style `"
358 << to_utf8(style) << '\'' << endl;
364 columns_ = lexrc.getInteger();
369 switch (lexrc.getInteger()) {
370 case 1: sides_ = OneSide; break;
371 case 2: sides_ = TwoSides; break;
373 lyxerr << "Impossible number of page"
374 " sides, setting to one."
384 pagestyle_ = rtrim(lexrc.getString());
388 defaultfont_ = lyxRead(lexrc);
389 if (!defaultfont_.resolved()) {
390 lexrc.printError("Warning: defaultfont should "
391 "be fully instantiated!");
392 defaultfont_.realize(sane_font);
398 secnumdepth_ = lexrc.getInteger();
403 tocdepth_ = lexrc.getInteger();
406 // First step to support options
407 case TC_CLASSOPTIONS:
408 readClassOptions(lexrc);
412 preamble_ = from_utf8(lexrc.getLongString("EndPreamble"));
417 string const feature = lexrc.getString();
419 if (lexrc.getInteger())
420 provides_.insert(feature);
422 provides_.erase(feature);
428 vector<string> const req
429 = getVectorFromString(lexrc.getString());
430 requires_.insert(req.begin(), req.end());
434 case TC_LEFTMARGIN: // left margin type
436 leftmargin_ = lexrc.getDocString();
439 case TC_RIGHTMARGIN: // right margin type
441 rightmargin_ = lexrc.getDocString();
447 if (il.read(lexrc)) {
448 insetlayoutlist_[il.name()] = il;
450 // else there was an error, so forget it
462 case TC_TITLELATEXTYPE:
463 readTitleType(lexrc);
466 case TC_TITLELATEXNAME:
468 titlename_ = lexrc.getString();
473 string const nofloat = lexrc.getString();
474 floatlist_.erase(nofloat);
479 //Note that this is triggered the first time through the loop unless
480 //we hit a format tag.
481 if (format != FORMAT)
485 if (format != FORMAT) {
486 LYXERR(Debug::TCLASS, "Converting layout file from format "
487 << format << " to " << FORMAT);
488 FileName const tempfile = FileName::tempName();
489 bool success = layout2layout(filename, tempfile);
492 tempfile.removeFile();
496 LYXERR(Debug::TCLASS, "Finished reading " + translateRT(rt) + ": " +
497 to_utf8(makeDisplayPath(filename.absFilename())));
502 if (defaultlayout_.empty()) {
503 lyxerr << "Error: Textclass '" << name_
504 << "' is missing a defaultstyle." << endl;
508 //Try to erase "stdinsets" from the provides_ set.
510 // Provides stdinsets 1
511 //declaration simply tells us that the standard insets have been
512 //defined. (It's found in stdinsets.inc but could also be used in
513 //user-defined files.) There isn't really any such package. So we
514 //might as well go ahead and erase it.
515 //If we do not succeed, then it was not there, which means that
516 //the textclass did not provide the definitions of the standard
517 //insets. So we need to try to load them.
518 int erased = provides_.erase("stdinsets");
520 FileName tmp = libFileSearch("layouts", "stdinsets.inc");
523 throw ExceptionMessage(WarningException, _("Missing File"),
524 _("Could not find stdinsets.inc! This may lead to data loss!"));
526 } else if (!read(tmp, MERGE)) {
527 throw ExceptionMessage(WarningException, _("Corrupt File"),
528 _("Could not read stdinsets.inc! This may lead to data loss!"));
533 min_toclevel_ = Layout::NOT_IN_TOC;
534 max_toclevel_ = Layout::NOT_IN_TOC;
535 const_iterator lit = begin();
536 const_iterator len = end();
537 for (; lit != len; ++lit) {
538 int const toclevel = lit->toclevel;
539 if (toclevel != Layout::NOT_IN_TOC) {
540 if (min_toclevel_ == Layout::NOT_IN_TOC)
541 min_toclevel_ = toclevel;
543 min_toclevel_ = min(min_toclevel_, toclevel);
544 max_toclevel_ = max(max_toclevel_, toclevel);
547 LYXERR(Debug::TCLASS, "Minimum TocLevel is " << min_toclevel_
548 << ", maximum is " << max_toclevel_);
554 void TextClass::readTitleType(Lexer & lexrc)
556 keyword_item titleTypeTags[] = {
557 { "commandafter", TITLE_COMMAND_AFTER },
558 { "environment", TITLE_ENVIRONMENT }
561 PushPopHelper pph(lexrc, titleTypeTags, TITLE_ENVIRONMENT);
563 int le = lexrc.lex();
565 case Lexer::LEX_UNDEF:
566 lexrc.printError("Unknown output type `$$Token'");
568 case TITLE_COMMAND_AFTER:
569 case TITLE_ENVIRONMENT:
570 titletype_ = static_cast<TitleLatexType>(le);
573 lyxerr << "Unhandled value " << le
574 << " in TextClass::readTitleType." << endl;
581 void TextClass::readOutputType(Lexer & lexrc)
583 keyword_item outputTypeTags[] = {
584 { "docbook", DOCBOOK },
586 { "literate", LITERATE }
589 PushPopHelper pph(lexrc, outputTypeTags, LITERATE);
591 int le = lexrc.lex();
593 case Lexer::LEX_UNDEF:
594 lexrc.printError("Unknown output type `$$Token'");
599 outputType_ = static_cast<OutputType>(le);
602 lyxerr << "Unhandled value " << le
603 << " in TextClass::readOutputType." << endl;
610 enum ClassOptionsTags {
619 void TextClass::readClassOptions(Lexer & lexrc)
621 keyword_item classOptionsTags[] = {
623 {"fontsize", CO_FONTSIZE },
624 {"header", CO_HEADER },
625 {"other", CO_OTHER },
626 {"pagestyle", CO_PAGESTYLE }
629 lexrc.pushTable(classOptionsTags, CO_END);
631 while (!getout && lexrc.isOK()) {
632 int le = lexrc.lex();
634 case Lexer::LEX_UNDEF:
635 lexrc.printError("Unknown ClassOption tag `$$Token'");
639 switch (static_cast<ClassOptionsTags>(le)) {
642 opt_fontsize_ = rtrim(lexrc.getString());
646 opt_pagestyle_ = rtrim(lexrc.getString());
650 options_ = lexrc.getString();
654 class_header_ = subst(lexrc.getString(), """, "\"");
678 void TextClass::readFloat(Lexer & lexrc)
680 keyword_item floatTags[] = {
682 { "extension", FT_EXT },
683 { "guiname", FT_NAME },
684 { "latexbuiltin", FT_BUILTIN },
685 { "listname", FT_LISTNAME },
686 { "numberwithin", FT_WITHIN },
687 { "placement", FT_PLACEMENT },
688 { "style", FT_STYLE },
692 lexrc.pushTable(floatTags, FT_END);
701 bool builtin = false;
704 while (!getout && lexrc.isOK()) {
705 int le = lexrc.lex();
707 case Lexer::LEX_UNDEF:
708 lexrc.printError("Unknown float tag `$$Token'");
712 switch (static_cast<FloatTags>(le)) {
715 type = lexrc.getString();
716 if (floatlist_.typeExist(type)) {
717 Floating const & fl = floatlist_.getType(type);
718 placement = fl.placement();
720 within = fl.within();
723 listName = fl.listName();
724 builtin = fl.builtin();
729 name = lexrc.getString();
733 placement = lexrc.getString();
737 ext = lexrc.getString();
741 within = lexrc.getString();
742 if (within == "none")
747 style = lexrc.getString();
751 listName = lexrc.getString();
755 builtin = lexrc.getBool();
763 // Here if have a full float if getout == true
765 Floating fl(type, placement, ext, within,
766 style, name, listName, builtin);
767 floatlist_.newFloat(fl);
768 // each float has its own counter
769 counters_.newCounter(from_ascii(type), from_ascii(within),
770 docstring(), docstring());
771 // also define sub-float counters
772 docstring const subtype = "sub-" + from_ascii(type);
773 counters_.newCounter(subtype, from_ascii(type),
774 "\\alph{" + subtype + "}", docstring());
785 CT_LABELSTRING_APPENDIX,
790 void TextClass::readCounter(Lexer & lexrc)
792 keyword_item counterTags[] = {
794 { "labelstring", CT_LABELSTRING },
795 { "labelstringappendix", CT_LABELSTRING_APPENDIX },
797 { "within", CT_WITHIN }
800 lexrc.pushTable(counterTags, CT_END);
804 docstring labelstring;
805 docstring labelstring_appendix;
808 while (!getout && lexrc.isOK()) {
809 int le = lexrc.lex();
811 case Lexer::LEX_UNDEF:
812 lexrc.printError("Unknown counter tag `$$Token'");
816 switch (static_cast<CounterTags>(le)) {
819 name = lexrc.getDocString();
820 if (counters_.hasCounter(name))
821 LYXERR(Debug::TCLASS, "Reading existing counter " << to_utf8(name));
823 LYXERR(Debug::TCLASS, "Reading new counter " << to_utf8(name));
827 within = lexrc.getDocString();
828 if (within == "none")
833 labelstring = lexrc.getDocString();
834 labelstring_appendix = labelstring;
836 case CT_LABELSTRING_APPENDIX:
838 labelstring_appendix = lexrc.getDocString();
846 // Here if have a full counter if getout == true
848 counters_.newCounter(name, within,
849 labelstring, labelstring_appendix);
855 bool TextClass::hasLayout(docstring const & n) const
857 docstring const name = n.empty() ? defaultLayoutName() : n;
859 return find_if(layoutlist_.begin(), layoutlist_.end(),
860 LayoutNamesEqual(name))
861 != layoutlist_.end();
866 Layout const & TextClass::operator[](docstring const & name) const
868 BOOST_ASSERT(!name.empty());
871 find_if(begin(), end(), LayoutNamesEqual(name));
874 lyxerr << "We failed to find the layout '" << to_utf8(name)
875 << "' in the layout list. You MUST investigate!"
877 for (const_iterator cit = begin(); cit != end(); ++cit)
878 lyxerr << " " << to_utf8(cit->name()) << endl;
880 // we require the name to exist
888 Layout & TextClass::operator[](docstring const & name)
890 BOOST_ASSERT(!name.empty());
892 iterator it = find_if(begin(), end(), LayoutNamesEqual(name));
895 lyxerr << "We failed to find the layout '" << to_utf8(name)
896 << "' in the layout list. You MUST investigate!"
898 for (const_iterator cit = begin(); cit != end(); ++cit)
899 lyxerr << " " << to_utf8(cit->name()) << endl;
901 // we require the name to exist
909 bool TextClass::deleteLayout(docstring const & name)
911 if (name == defaultLayoutName() || name == emptyLayoutName())
914 LayoutList::iterator it =
915 remove_if(layoutlist_.begin(), layoutlist_.end(),
916 LayoutNamesEqual(name));
918 LayoutList::iterator end = layoutlist_.end();
919 bool const ret = (it != end);
920 layoutlist_.erase(it, end);
925 // Load textclass info if not loaded yet
926 bool TextClass::load(string const & path) const
931 // Read style-file, provided path is searched before the system ones
932 FileName layout_file;
934 layout_file = FileName(addName(path, name_ + ".layout"));
935 if (layout_file.empty() || !layout_file.exists())
936 layout_file = libFileSearch("layouts", name_, "layout");
937 loaded_ = const_cast<TextClass*>(this)->read(layout_file);
940 lyxerr << "Error reading `"
941 << to_utf8(makeDisplayPath(layout_file.absFilename()))
942 << "'\n(Check `" << name_
943 << "')\nCheck your installation and "
944 "try Options/Reconfigure..." << endl;
951 InsetLayout const & DocumentClass::insetLayout(docstring const & name) const
954 InsetLayouts::const_iterator cen = insetlayoutlist_.end();
956 InsetLayouts::const_iterator cit = insetlayoutlist_.lower_bound(n);
957 if (cit != cen && cit->first == n)
959 size_t i = n.find(':');
960 if (i == string::npos)
964 return empty_insetlayout_;
968 docstring const & TextClass::defaultLayoutName() const
970 // This really should come from the actual layout... (Lgb)
971 return defaultlayout_;
975 Layout const & TextClass::defaultLayout() const
977 return operator[](defaultLayoutName());
981 bool TextClass::isDefaultLayout(Layout const & lay) const
983 return lay.name() == defaultLayoutName();
987 bool TextClass::isEmptyLayout(Layout const & lay) const
989 return lay.name() == emptyLayoutName();
993 DocumentClass & DocumentClassBundle::newClass(LayoutFile const & baseClass)
995 DocumentClass * dc = new DocumentClass(baseClass);
996 tc_list_.push_back(dc);
997 return *tc_list_.back();
1001 DocumentClassBundle & DocumentClassBundle::get()
1003 static DocumentClassBundle singleton;
1008 DocumentClass::DocumentClass(LayoutFile const & tc)
1013 bool DocumentClass::hasLaTeXLayout(std::string const & lay) const
1015 LayoutList::const_iterator it = layoutlist_.begin();
1016 LayoutList::const_iterator end = layoutlist_.end();
1017 for (; it != end; ++it)
1018 if (it->latexname() == lay)
1024 bool DocumentClass::provides(string const & p) const
1026 return provides_.find(p) != provides_.end();
1030 bool DocumentClass::hasTocLevels() const
1032 return min_toclevel_ != Layout::NOT_IN_TOC;
1036 ostream & operator<<(ostream & os, PageSides p)