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"
22 #include "FloatList.h"
27 #include "frontends/alert.h"
29 #include "support/debug.h"
30 #include "support/ExceptionMessage.h"
31 #include "support/FileName.h"
32 #include "support/filetools.h"
33 #include "support/gettext.h"
34 #include "support/lstrings.h"
35 #include "support/os.h"
40 using namespace lyx::support;
46 class LayoutNamesEqual : public unary_function<LayoutPtr, bool> {
48 LayoutNamesEqual(docstring const & name)
51 bool operator()(LayoutPtr const & c) const
53 return c->name() == name_;
63 bool layout2layout(FileName const & filename, FileName const & tempfile)
65 FileName const script = libFileSearch("scripts", "layout2layout.py");
67 lyxerr << "Could not find layout conversion "
68 "script layout2layout.py." << endl;
72 ostringstream command;
73 command << os::python() << ' ' << quoteName(script.toFilesystemEncoding())
74 << ' ' << quoteName(filename.toFilesystemEncoding())
75 << ' ' << quoteName(tempfile.toFilesystemEncoding());
76 string const command_str = command.str();
78 LYXERR(Debug::TCLASS, "Running `" << command_str << '\'');
81 runCommand(command_str);
83 lyxerr << "Could not run layout conversion "
84 "script layout2layout.py." << endl;
91 std::string translateRT(TextClass::ReadType rt)
94 case TextClass::BASECLASS:
96 case TextClass::MERGE:
98 case TextClass::MODULE:
108 TextClass::TextClass(string const & fn, string const & cln,
109 string const & desc, bool texClassAvail )
110 : name_(fn), latexname_(cln), description_(desc),
111 floatlist_(new FloatList), counters_(new Counters),
112 texClassAvail_(texClassAvail)
120 pagestyle_ = "default";
121 defaultfont_ = sane_font;
122 opt_fontsize_ = "10|11|12";
123 opt_pagestyle_ = "empty|plain|headings|fancy";
124 titletype_ = TITLE_COMMAND_AFTER;
125 titlename_ = "maketitle";
127 // a hack to make this available for translation
128 // i'm sure there must be a better way (rgh)
133 docstring const TextClass::emptylayout_ = from_ascii("PlainLayout");
136 InsetLayout TextClass::empty_insetlayout_;
139 bool TextClass::isTeXClassAvailable() const
141 return texClassAvail_;
145 bool TextClass::readStyle(Lexer & lexrc, Layout & lay)
147 LYXERR(Debug::TCLASS, "Reading style " << to_utf8(lay.name()));
148 if (!lay.read(lexrc, *this)) {
149 lyxerr << "Error parsing style `" << to_utf8(lay.name()) << '\'' << endl;
153 lay.resfont = lay.font;
154 lay.resfont.realize(defaultfont());
155 lay.reslabelfont = lay.labelfont;
156 lay.reslabelfont.realize(defaultfont());
157 return true; // no errors
190 // Reads a textclass structure from file.
191 bool TextClass::read(FileName const & filename, ReadType rt)
193 if (!filename.isReadableFile()) {
194 lyxerr << "Cannot read layout file `" << filename << "'."
199 keyword_item textClassTags[] = {
200 { "classoptions", TC_CLASSOPTIONS },
201 { "columns", TC_COLUMNS },
202 { "counter", TC_COUNTER },
203 { "defaultfont", TC_DEFAULTFONT },
204 { "defaultstyle", TC_DEFAULTSTYLE },
205 { "environment", TC_ENVIRONMENT },
206 { "float", TC_FLOAT },
207 { "format", TC_FORMAT },
208 { "input", TC_INPUT },
209 { "insetlayout", TC_INSETLAYOUT },
210 { "leftmargin", TC_LEFTMARGIN },
211 { "nofloat", TC_NOFLOAT },
212 { "nostyle", TC_NOSTYLE },
213 { "outputtype", TC_OUTPUTTYPE },
214 { "pagestyle", TC_PAGESTYLE },
215 { "preamble", TC_PREAMBLE },
216 { "provides", TC_PROVIDES },
217 { "requires", TC_REQUIRES },
218 { "rightmargin", TC_RIGHTMARGIN },
219 { "secnumdepth", TC_SECNUMDEPTH },
220 { "sides", TC_SIDES },
221 { "style", TC_STYLE },
222 { "titlelatexname", TC_TITLELATEXNAME },
223 { "titlelatextype", TC_TITLELATEXTYPE },
224 { "tocdepth", TC_TOCDEPTH }
227 LYXERR(Debug::TCLASS, "Reading " + translateRT(rt) + ": " +
228 to_utf8(makeDisplayPath(filename.absFilename())));
230 // Define the `empty' layout used in table cells, ert, etc. Note that
231 // we do this before loading any layout file, so that classes can
232 // override features of this layout if they should choose to do so.
233 if (rt == BASECLASS) {
234 static char const * s = "Margin Static\n"
235 "LatexType Paragraph\n"
238 "AlignPossible Left, Right, Center\n"
239 "LabelType No_Label\n"
242 Lexer lex(textClassTags, sizeof(textClassTags) / sizeof(textClassTags[0]));
245 lay.setName(emptylayout_);
246 if (!readStyle(lex, lay)) {
247 // The only way this happens is because the hardcoded layout above
251 layoutlist_.push_back(boost::shared_ptr<Layout>(new Layout(lay)));
254 Lexer lexrc(textClassTags,
255 sizeof(textClassTags) / sizeof(textClassTags[0]));
257 lexrc.setFile(filename);
258 bool error = !lexrc.isOK();
260 // Format of files before the 'Format' tag was introduced
264 while (lexrc.isOK() && !error) {
265 int le = lexrc.lex();
268 case Lexer::LEX_FEOF:
271 case Lexer::LEX_UNDEF:
272 lexrc.printError("Unknown TextClass tag `$$Token'");
280 switch (static_cast<TextClassTags>(le)) {
284 format = lexrc.getInteger();
287 case TC_OUTPUTTYPE: // output type definition
288 readOutputType(lexrc);
291 case TC_INPUT: // Include file
293 string const inc = lexrc.getString();
294 FileName tmp = libFileSearch("layouts", inc,
298 lexrc.printError("Could not find input file: " + inc);
300 } else if (!read(tmp, MERGE)) {
301 lexrc.printError("Error reading input"
302 "file: " + tmp.absFilename());
308 case TC_DEFAULTSTYLE:
310 docstring const name = from_utf8(subst(lexrc.getString(),
312 defaultlayout_ = name;
319 docstring const name = from_utf8(subst(lexrc.getString(),
322 string s = "Could not read name for style: `$$Token' "
323 + lexrc.getString() + " is probably not valid UTF-8!";
324 lexrc.printError(s.c_str());
326 //FIXME If we're just dropping this layout, do we really
327 //care whether there's an error?? Or should we just set
328 //error to true, since we couldn't even read the name?
329 error = !readStyle(lexrc, lay);
330 } else if (hasLayout(name)) {
331 Layout * lay = operator[](name).get();
332 error = !readStyle(lexrc, *lay);
336 if (le == TC_ENVIRONMENT)
337 lay.is_environment = true;
338 error = !readStyle(lexrc, lay);
340 layoutlist_.push_back(boost::shared_ptr<Layout>(new Layout(lay)));
342 if (defaultlayout_.empty()) {
343 // We do not have a default layout yet, so we choose
344 // the first layout we encounter.
345 defaultlayout_ = name;
350 //FIXME Should we also eat the style here? viz:
352 //readStyle(lexrc, lay);
354 lexrc.printError("No name given for style: `$$Token'.");
361 docstring const style = from_utf8(subst(lexrc.getString(),
363 if (!deleteLayout(style))
364 lyxerr << "Cannot delete style `"
365 << to_utf8(style) << '\'' << endl;
371 columns_ = lexrc.getInteger();
376 switch (lexrc.getInteger()) {
377 case 1: sides_ = OneSide; break;
378 case 2: sides_ = TwoSides; break;
380 lyxerr << "Impossible number of page"
381 " sides, setting to one."
391 pagestyle_ = rtrim(lexrc.getString());
395 defaultfont_ = lyxRead(lexrc);
396 if (!defaultfont_.resolved()) {
397 lexrc.printError("Warning: defaultfont should "
398 "be fully instantiated!");
399 defaultfont_.realize(sane_font);
405 secnumdepth_ = lexrc.getInteger();
410 tocdepth_ = lexrc.getInteger();
413 // First step to support options
414 case TC_CLASSOPTIONS:
415 readClassOptions(lexrc);
419 preamble_ = from_utf8(lexrc.getLongString("EndPreamble"));
424 string const feature = lexrc.getString();
426 if (lexrc.getInteger())
427 provides_.insert(feature);
429 provides_.erase(feature);
435 vector<string> const req
436 = getVectorFromString(lexrc.getString());
437 requires_.insert(req.begin(), req.end());
441 case TC_LEFTMARGIN: // left margin type
443 leftmargin_ = lexrc.getDocString();
446 case TC_RIGHTMARGIN: // right margin type
448 rightmargin_ = lexrc.getDocString();
454 if (il.read(lexrc)) {
455 insetlayoutlist_[il.name()] = il;
457 // else there was an error, so forget it
469 case TC_TITLELATEXTYPE:
470 readTitleType(lexrc);
473 case TC_TITLELATEXNAME:
475 titlename_ = lexrc.getString();
480 string const nofloat = lexrc.getString();
481 floatlist_->erase(nofloat);
486 //Note that this is triggered the first time through the loop unless
487 //we hit a format tag.
488 if (format != FORMAT)
492 if (format != FORMAT) {
493 LYXERR(Debug::TCLASS, "Converting layout file from format "
494 << format << " to " << FORMAT);
495 FileName const tempfile = FileName::tempName();
496 error = !layout2layout(filename, tempfile);
498 error = read(tempfile, rt);
499 tempfile.removeFile();
503 LYXERR(Debug::TCLASS, "Finished reading " + translateRT(rt) + ": " +
504 to_utf8(makeDisplayPath(filename.absFilename())));
509 if (defaultlayout_.empty()) {
510 lyxerr << "Error: Textclass '" << name_
511 << "' is missing a defaultstyle." << endl;
515 //Try to erase "stdinsets" from the provides_ set.
517 // Provides stdinsets 1
518 //declaration simply tells us that the standard insets have been
519 //defined. (It's found in stdinsets.inc but could also be used in
520 //user-defined files.) There isn't really any such package. So we
521 //might as well go ahead and erase it.
522 //If we do not succeed, then it was not there, which means that
523 //the textclass did not provide the definitions of the standard
524 //insets. So we need to try to load them.
525 int erased = provides_.erase("stdinsets");
527 FileName tmp = libFileSearch("layouts", "stdinsets.inc");
530 throw ExceptionMessage(WarningException, _("Missing File"),
531 _("Could not find stdinsets.inc! This may lead to data loss!"));
533 } else if (!read(tmp, MERGE)) {
534 throw ExceptionMessage(WarningException, _("Corrupt File"),
535 _("Could not read stdinsets.inc! This may lead to data loss!"));
540 min_toclevel_ = Layout::NOT_IN_TOC;
541 max_toclevel_ = Layout::NOT_IN_TOC;
542 const_iterator cit = begin();
543 const_iterator the_end = end();
544 for ( ; cit != the_end ; ++cit) {
545 int const toclevel = (*cit)->toclevel;
546 if (toclevel != Layout::NOT_IN_TOC) {
547 if (min_toclevel_ == Layout::NOT_IN_TOC)
548 min_toclevel_ = toclevel;
550 min_toclevel_ = min(min_toclevel_,
552 max_toclevel_ = max(max_toclevel_,
556 LYXERR(Debug::TCLASS, "Minimum TocLevel is " << min_toclevel_
557 << ", maximum is " << max_toclevel_);
563 void TextClass::readTitleType(Lexer & lexrc)
565 keyword_item titleTypeTags[] = {
566 { "commandafter", TITLE_COMMAND_AFTER },
567 { "environment", TITLE_ENVIRONMENT }
570 PushPopHelper pph(lexrc, titleTypeTags, TITLE_ENVIRONMENT);
572 int le = lexrc.lex();
574 case Lexer::LEX_UNDEF:
575 lexrc.printError("Unknown output type `$$Token'");
577 case TITLE_COMMAND_AFTER:
578 case TITLE_ENVIRONMENT:
579 titletype_ = static_cast<TitleLatexType>(le);
582 lyxerr << "Unhandled value " << le
583 << " in TextClass::readTitleType." << endl;
590 void TextClass::readOutputType(Lexer & lexrc)
592 keyword_item outputTypeTags[] = {
593 { "docbook", DOCBOOK },
595 { "literate", LITERATE }
598 PushPopHelper pph(lexrc, outputTypeTags, LITERATE);
600 int le = lexrc.lex();
602 case Lexer::LEX_UNDEF:
603 lexrc.printError("Unknown output type `$$Token'");
608 outputType_ = static_cast<OutputType>(le);
611 lyxerr << "Unhandled value " << le
612 << " in TextClass::readOutputType." << endl;
619 enum ClassOptionsTags {
628 void TextClass::readClassOptions(Lexer & lexrc)
630 keyword_item classOptionsTags[] = {
632 {"fontsize", CO_FONTSIZE },
633 {"header", CO_HEADER },
634 {"other", CO_OTHER },
635 {"pagestyle", CO_PAGESTYLE }
638 lexrc.pushTable(classOptionsTags, CO_END);
640 while (!getout && lexrc.isOK()) {
641 int le = lexrc.lex();
643 case Lexer::LEX_UNDEF:
644 lexrc.printError("Unknown ClassOption tag `$$Token'");
648 switch (static_cast<ClassOptionsTags>(le)) {
651 opt_fontsize_ = rtrim(lexrc.getString());
655 opt_pagestyle_ = rtrim(lexrc.getString());
659 options_ = lexrc.getString();
663 class_header_ = subst(lexrc.getString(), """, "\"");
687 void TextClass::readFloat(Lexer & lexrc)
689 keyword_item floatTags[] = {
691 { "extension", FT_EXT },
692 { "guiname", FT_NAME },
693 { "latexbuiltin", FT_BUILTIN },
694 { "listname", FT_LISTNAME },
695 { "numberwithin", FT_WITHIN },
696 { "placement", FT_PLACEMENT },
697 { "style", FT_STYLE },
701 lexrc.pushTable(floatTags, FT_END);
710 bool builtin = false;
713 while (!getout && lexrc.isOK()) {
714 int le = lexrc.lex();
716 case Lexer::LEX_UNDEF:
717 lexrc.printError("Unknown float tag `$$Token'");
721 switch (static_cast<FloatTags>(le)) {
724 type = lexrc.getString();
725 if (floatlist_->typeExist(type)) {
726 Floating const & fl = floatlist_->getType(type);
727 placement = fl.placement();
729 within = fl.within();
732 listName = fl.listName();
733 builtin = fl.builtin();
738 name = lexrc.getString();
742 placement = lexrc.getString();
746 ext = lexrc.getString();
750 within = lexrc.getString();
751 if (within == "none")
756 style = lexrc.getString();
760 listName = lexrc.getString();
764 builtin = lexrc.getBool();
772 // Here if have a full float if getout == true
774 Floating fl(type, placement, ext, within,
775 style, name, listName, builtin);
776 floatlist_->newFloat(fl);
777 // each float has its own counter
778 counters_->newCounter(from_ascii(type), from_ascii(within),
779 docstring(), docstring());
790 CT_LABELSTRING_APPENDIX,
795 void TextClass::readCounter(Lexer & lexrc)
797 keyword_item counterTags[] = {
799 { "labelstring", CT_LABELSTRING },
800 { "labelstringappendix", CT_LABELSTRING_APPENDIX },
802 { "within", CT_WITHIN }
805 lexrc.pushTable(counterTags, CT_END);
809 docstring labelstring;
810 docstring labelstring_appendix;
813 while (!getout && lexrc.isOK()) {
814 int le = lexrc.lex();
816 case Lexer::LEX_UNDEF:
817 lexrc.printError("Unknown counter tag `$$Token'");
821 switch (static_cast<CounterTags>(le)) {
824 name = lexrc.getDocString();
825 if (counters_->hasCounter(name))
826 LYXERR(Debug::TCLASS, "Reading existing counter " << to_utf8(name));
828 LYXERR(Debug::TCLASS, "Reading new counter " << to_utf8(name));
832 within = lexrc.getDocString();
833 if (within == "none")
838 labelstring = lexrc.getDocString();
839 labelstring_appendix = labelstring;
841 case CT_LABELSTRING_APPENDIX:
843 labelstring_appendix = lexrc.getDocString();
851 // Here if have a full counter if getout == true
853 counters_->newCounter(name, within,
854 labelstring, labelstring_appendix);
860 FontInfo const & TextClass::defaultfont() const
866 docstring const & TextClass::leftmargin() const
872 docstring const & TextClass::rightmargin() const
878 bool TextClass::hasLayout(docstring const & n) const
880 docstring const name = n.empty() ? defaultLayoutName() : n;
882 return find_if(layoutlist_.begin(), layoutlist_.end(),
883 LayoutNamesEqual(name))
884 != layoutlist_.end();
889 LayoutPtr const & TextClass::operator[](docstring const & name) const
891 BOOST_ASSERT(!name.empty());
893 LayoutList::const_iterator cit =
894 find_if(layoutlist_.begin(),
896 LayoutNamesEqual(name));
898 if (cit == layoutlist_.end()) {
899 lyxerr << "We failed to find the layout '" << to_utf8(name)
900 << "' in the layout list. You MUST investigate!"
902 for (LayoutList::const_iterator it = layoutlist_.begin();
903 it != layoutlist_.end(); ++it)
904 lyxerr << " " << to_utf8(it->get()->name()) << endl;
906 // we require the name to exist
914 bool TextClass::deleteLayout(docstring const & name)
916 if (name == defaultLayoutName() || name == emptyLayoutName())
919 LayoutList::iterator it =
920 remove_if(layoutlist_.begin(), layoutlist_.end(),
921 LayoutNamesEqual(name));
923 LayoutList::iterator end = layoutlist_.end();
924 bool const ret = (it != end);
925 layoutlist_.erase(it, end);
930 // Load textclass info if not loaded yet
931 bool TextClass::load(string const & path) const
936 // Read style-file, provided path is searched before the system ones
937 FileName layout_file;
939 layout_file = FileName(addName(path, name_ + ".layout"));
940 if (layout_file.empty() || !layout_file.exists())
941 layout_file = libFileSearch("layouts", name_, "layout");
942 loaded_ = const_cast<TextClass*>(this)->read(layout_file);
945 lyxerr << "Error reading `"
946 << to_utf8(makeDisplayPath(layout_file.absFilename()))
947 << "'\n(Check `" << name_
948 << "')\nCheck your installation and "
949 "try Options/Reconfigure..." << endl;
956 FloatList & TextClass::floats()
958 return *floatlist_.get();
962 FloatList const & TextClass::floats() const
964 return *floatlist_.get();
968 Counters & TextClass::counters() const
970 return *counters_.get();
974 // Return the layout object of an inset given by name. If the name
975 // is not found as such, the part after the ':' is stripped off, and
976 // searched again. In this way, an error fallback can be provided:
977 // An erroneous 'CharStyle:badname' (e.g., after a documentclass switch)
978 // will invoke the layout object defined by name = 'CharStyle'.
979 // If that doesn't work either, an empty object returns (shouldn't
980 // happen). -- Idea JMarc, comment MV
981 InsetLayout const & TextClass::insetlayout(docstring const & name) const
985 if (insetlayoutlist_.count(n) > 0)
986 return insetlayoutlist_[n];
987 docstring::size_type i = n.find(':');
988 if (i == string::npos)
992 return empty_insetlayout_;
996 docstring const & TextClass::defaultLayoutName() const
998 // This really should come from the actual layout... (Lgb)
999 return defaultlayout_;
1003 LayoutPtr const & TextClass::defaultLayout() const
1005 return operator[](defaultLayoutName());
1009 string const & TextClass::name() const
1015 string const & TextClass::latexname() const
1017 const_cast<TextClass*>(this)->load();
1022 string const & TextClass::description() const
1024 return description_;
1028 string const & TextClass::opt_fontsize() const
1030 return opt_fontsize_;
1034 string const & TextClass::opt_pagestyle() const
1036 return opt_pagestyle_;
1040 string const & TextClass::options() const
1046 string const & TextClass::class_header() const
1048 return class_header_;
1052 string const & TextClass::pagestyle() const
1058 docstring const & TextClass::preamble() const
1064 PageSides TextClass::sides() const
1070 int TextClass::secnumdepth() const
1072 return secnumdepth_;
1076 int TextClass::tocdepth() const
1082 OutputType TextClass::outputType() const
1088 bool TextClass::provides(string const & p) const
1090 return provides_.find(p) != provides_.end();
1094 unsigned int TextClass::columns() const
1100 TitleLatexType TextClass::titletype() const
1106 string const & TextClass::titlename() const
1112 int TextClass::size() const
1114 return layoutlist_.size();
1118 int TextClass::min_toclevel() const
1120 return min_toclevel_;
1124 int TextClass::max_toclevel() const
1126 return max_toclevel_;
1130 bool TextClass::hasTocLevels() const
1132 return min_toclevel_ != Layout::NOT_IN_TOC;
1136 ostream & operator<<(ostream & os, PageSides p)