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 for (size_t i = 0; i != layoutCount(); ++i) {
543 int const toclevel = layout(i)->toclevel;
544 if (toclevel != Layout::NOT_IN_TOC) {
545 if (min_toclevel_ == Layout::NOT_IN_TOC)
546 min_toclevel_ = toclevel;
548 min_toclevel_ = min(min_toclevel_, toclevel);
549 max_toclevel_ = max(max_toclevel_, toclevel);
552 LYXERR(Debug::TCLASS, "Minimum TocLevel is " << min_toclevel_
553 << ", maximum is " << max_toclevel_);
559 void TextClass::readTitleType(Lexer & lexrc)
561 keyword_item titleTypeTags[] = {
562 { "commandafter", TITLE_COMMAND_AFTER },
563 { "environment", TITLE_ENVIRONMENT }
566 PushPopHelper pph(lexrc, titleTypeTags, TITLE_ENVIRONMENT);
568 int le = lexrc.lex();
570 case Lexer::LEX_UNDEF:
571 lexrc.printError("Unknown output type `$$Token'");
573 case TITLE_COMMAND_AFTER:
574 case TITLE_ENVIRONMENT:
575 titletype_ = static_cast<TitleLatexType>(le);
578 lyxerr << "Unhandled value " << le
579 << " in TextClass::readTitleType." << endl;
586 void TextClass::readOutputType(Lexer & lexrc)
588 keyword_item outputTypeTags[] = {
589 { "docbook", DOCBOOK },
591 { "literate", LITERATE }
594 PushPopHelper pph(lexrc, outputTypeTags, LITERATE);
596 int le = lexrc.lex();
598 case Lexer::LEX_UNDEF:
599 lexrc.printError("Unknown output type `$$Token'");
604 outputType_ = static_cast<OutputType>(le);
607 lyxerr << "Unhandled value " << le
608 << " in TextClass::readOutputType." << endl;
615 enum ClassOptionsTags {
624 void TextClass::readClassOptions(Lexer & lexrc)
626 keyword_item classOptionsTags[] = {
628 {"fontsize", CO_FONTSIZE },
629 {"header", CO_HEADER },
630 {"other", CO_OTHER },
631 {"pagestyle", CO_PAGESTYLE }
634 lexrc.pushTable(classOptionsTags, CO_END);
636 while (!getout && lexrc.isOK()) {
637 int le = lexrc.lex();
639 case Lexer::LEX_UNDEF:
640 lexrc.printError("Unknown ClassOption tag `$$Token'");
644 switch (static_cast<ClassOptionsTags>(le)) {
647 opt_fontsize_ = rtrim(lexrc.getString());
651 opt_pagestyle_ = rtrim(lexrc.getString());
655 options_ = lexrc.getString();
659 class_header_ = subst(lexrc.getString(), """, "\"");
683 void TextClass::readFloat(Lexer & lexrc)
685 keyword_item floatTags[] = {
687 { "extension", FT_EXT },
688 { "guiname", FT_NAME },
689 { "latexbuiltin", FT_BUILTIN },
690 { "listname", FT_LISTNAME },
691 { "numberwithin", FT_WITHIN },
692 { "placement", FT_PLACEMENT },
693 { "style", FT_STYLE },
697 lexrc.pushTable(floatTags, FT_END);
706 bool builtin = false;
709 while (!getout && lexrc.isOK()) {
710 int le = lexrc.lex();
712 case Lexer::LEX_UNDEF:
713 lexrc.printError("Unknown float tag `$$Token'");
717 switch (static_cast<FloatTags>(le)) {
720 type = lexrc.getString();
721 if (floatlist_->typeExist(type)) {
722 Floating const & fl = floatlist_->getType(type);
723 placement = fl.placement();
725 within = fl.within();
728 listName = fl.listName();
729 builtin = fl.builtin();
734 name = lexrc.getString();
738 placement = lexrc.getString();
742 ext = lexrc.getString();
746 within = lexrc.getString();
747 if (within == "none")
752 style = lexrc.getString();
756 listName = lexrc.getString();
760 builtin = lexrc.getBool();
768 // Here if have a full float if getout == true
770 Floating fl(type, placement, ext, within,
771 style, name, listName, builtin);
772 floatlist_->newFloat(fl);
773 // each float has its own counter
774 counters_->newCounter(from_ascii(type), from_ascii(within),
775 docstring(), docstring());
786 CT_LABELSTRING_APPENDIX,
791 void TextClass::readCounter(Lexer & lexrc)
793 keyword_item counterTags[] = {
795 { "labelstring", CT_LABELSTRING },
796 { "labelstringappendix", CT_LABELSTRING_APPENDIX },
798 { "within", CT_WITHIN }
801 lexrc.pushTable(counterTags, CT_END);
805 docstring labelstring;
806 docstring labelstring_appendix;
809 while (!getout && lexrc.isOK()) {
810 int le = lexrc.lex();
812 case Lexer::LEX_UNDEF:
813 lexrc.printError("Unknown counter tag `$$Token'");
817 switch (static_cast<CounterTags>(le)) {
820 name = lexrc.getDocString();
821 if (counters_->hasCounter(name))
822 LYXERR(Debug::TCLASS, "Reading existing counter " << to_utf8(name));
824 LYXERR(Debug::TCLASS, "Reading new counter " << to_utf8(name));
828 within = lexrc.getDocString();
829 if (within == "none")
834 labelstring = lexrc.getDocString();
835 labelstring_appendix = labelstring;
837 case CT_LABELSTRING_APPENDIX:
839 labelstring_appendix = lexrc.getDocString();
847 // Here if have a full counter if getout == true
849 counters_->newCounter(name, within,
850 labelstring, labelstring_appendix);
856 FontInfo const & TextClass::defaultfont() const
862 docstring const & TextClass::leftmargin() const
868 docstring const & TextClass::rightmargin() const
874 bool TextClass::hasLayout(docstring const & n) const
876 docstring const name = n.empty() ? defaultLayoutName() : n;
878 return find_if(layoutlist_.begin(), layoutlist_.end(),
879 LayoutNamesEqual(name))
880 != layoutlist_.end();
885 LayoutPtr const & TextClass::operator[](docstring const & name) const
887 BOOST_ASSERT(!name.empty());
889 LayoutList::const_iterator cit =
890 find_if(layoutlist_.begin(),
892 LayoutNamesEqual(name));
894 if (cit == layoutlist_.end()) {
895 lyxerr << "We failed to find the layout '" << to_utf8(name)
896 << "' in the layout list. You MUST investigate!"
898 for (LayoutList::const_iterator it = layoutlist_.begin();
899 it != layoutlist_.end(); ++it)
900 lyxerr << " " << to_utf8(it->get()->name()) << endl;
902 // we require the name to exist
910 bool TextClass::deleteLayout(docstring const & name)
912 if (name == defaultLayoutName() || name == emptyLayoutName())
915 LayoutList::iterator it =
916 remove_if(layoutlist_.begin(), layoutlist_.end(),
917 LayoutNamesEqual(name));
919 LayoutList::iterator end = layoutlist_.end();
920 bool const ret = (it != end);
921 layoutlist_.erase(it, end);
926 // Load textclass info if not loaded yet
927 bool TextClass::load(string const & path) const
932 // Read style-file, provided path is searched before the system ones
933 FileName layout_file;
935 layout_file = FileName(addName(path, name_ + ".layout"));
936 if (layout_file.empty() || !layout_file.exists())
937 layout_file = libFileSearch("layouts", name_, "layout");
938 loaded_ = const_cast<TextClass*>(this)->read(layout_file);
941 lyxerr << "Error reading `"
942 << to_utf8(makeDisplayPath(layout_file.absFilename()))
943 << "'\n(Check `" << name_
944 << "')\nCheck your installation and "
945 "try Options/Reconfigure..." << endl;
952 FloatList & TextClass::floats()
954 return *floatlist_.get();
958 FloatList const & TextClass::floats() const
960 return *floatlist_.get();
964 Counters & TextClass::counters() const
966 return *counters_.get();
970 // Return the layout object of an inset given by name. If the name
971 // is not found as such, the part after the ':' is stripped off, and
972 // searched again. In this way, an error fallback can be provided:
973 // An erroneous 'CharStyle:badname' (e.g., after a documentclass switch)
974 // will invoke the layout object defined by name = 'CharStyle'.
975 // If that doesn't work either, an empty object returns (shouldn't
976 // happen). -- Idea JMarc, comment MV
977 InsetLayout const & TextClass::insetLayout(docstring const & name) const
981 if (insetlayoutlist_.count(n) > 0)
982 return insetlayoutlist_[n];
983 size_t i = n.find(':');
984 if (i == string::npos)
988 return empty_insetlayout_;
992 docstring const & TextClass::defaultLayoutName() const
994 // This really should come from the actual layout... (Lgb)
995 return defaultlayout_;
999 LayoutPtr const & TextClass::defaultLayout() const
1001 return operator[](defaultLayoutName());
1005 string const & TextClass::name() const
1011 string const & TextClass::latexname() const
1013 const_cast<TextClass*>(this)->load();
1018 string const & TextClass::description() const
1020 return description_;
1024 string const & TextClass::opt_fontsize() const
1026 return opt_fontsize_;
1030 string const & TextClass::opt_pagestyle() const
1032 return opt_pagestyle_;
1036 string const & TextClass::options() const
1042 string const & TextClass::class_header() const
1044 return class_header_;
1048 string const & TextClass::pagestyle() const
1054 docstring const & TextClass::preamble() const
1060 PageSides TextClass::sides() const
1066 int TextClass::secnumdepth() const
1068 return secnumdepth_;
1072 int TextClass::tocdepth() const
1078 OutputType TextClass::outputType() const
1084 bool TextClass::provides(string const & p) const
1086 return provides_.find(p) != provides_.end();
1090 unsigned int TextClass::columns() const
1096 TitleLatexType TextClass::titletype() const
1102 string const & TextClass::titlename() const
1108 int TextClass::size() const
1110 return layoutlist_.size();
1114 int TextClass::min_toclevel() const
1116 return min_toclevel_;
1120 int TextClass::max_toclevel() const
1122 return max_toclevel_;
1126 bool TextClass::hasTocLevels() const
1128 return min_toclevel_ != Layout::NOT_IN_TOC;
1132 ostream & operator<<(ostream & os, PageSides p)