/** * \file lyxtextclass.C * This file is part of LyX, the document processor. * Licence details can be found in the file COPYING. * * \author Lars Gullik Bjønnes * \author Jean-Marc Lasgouttes * \author Angus Leeming * \author John Levon * \author André Pönitz * * Full author contact details are available in file CREDITS. */ #include #include "lyxtextclass.h" #include "debug.h" #include "lyxlex.h" #include "counters.h" #include "Floating.h" #include "FloatList.h" #include "support/lstrings.h" #include "support/lyxlib.h" #include "support/filetools.h" #include "support/os.h" #include namespace fs = boost::filesystem; #include using lyx::docstring; using lyx::support::libFileSearch; using lyx::support::makeDisplayPath; using lyx::support::quoteName; using lyx::support::rtrim; using lyx::support::subst; using lyx::support::addName; using std::endl; using std::find_if; using std::remove_if; using std::string; using std::ostream; namespace { class LayoutNamesEqual : public std::unary_function { public: LayoutNamesEqual(string const & name) : name_(name) {} bool operator()(LyXLayout_ptr const & c) const { return c->name() == name_; } private: string name_; }; int const FORMAT = 2; bool layout2layout(string const & filename, string const & tempfile) { string const script = libFileSearch("scripts", "layout2layout.py"); if (script.empty()) { lyxerr << "Could not find layout conversion " "script layout2layout.py." << endl; return false; } std::ostringstream command; command << lyx::support::os::python() << ' ' << quoteName(script) << ' ' << quoteName(filename) << ' ' << quoteName(tempfile); string const command_str = command.str(); lyxerr[Debug::TCLASS] << "Running `" << command_str << '\'' << endl; lyx::support::cmd_ret const ret = lyx::support::runCommand(command_str); if (ret.first != 0) { lyxerr << "Could not run layout conversion " "script layout2layout.py." << endl; return false; } return true; } } // namespace anon LyXTextClass::LyXTextClass(string const & fn, string const & cln, string const & desc, bool texClassAvail ) : name_(fn), latexname_(cln), description_(desc), floatlist_(new FloatList), ctrs_(new Counters), texClassAvail_(texClassAvail) { outputType_ = LATEX; columns_ = 1; sides_ = OneSide; secnumdepth_ = 3; tocdepth_ = 3; pagestyle_ = "default"; defaultfont_ = LyXFont(LyXFont::ALL_SANE); opt_fontsize_ = "10|11|12"; opt_pagestyle_ = "empty|plain|headings|fancy"; provides_ = nothing; titletype_ = TITLE_COMMAND_AFTER; titlename_ = "maketitle"; loaded_ = false; } bool LyXTextClass::isTeXClassAvailable() const { return texClassAvail_; } bool LyXTextClass::do_readStyle(LyXLex & lexrc, LyXLayout & lay) { lyxerr[Debug::TCLASS] << "Reading style " << lay.name() << endl; if (!lay.read(lexrc, *this)) { // Resolve fonts lay.resfont = lay.font; lay.resfont.realize(defaultfont()); lay.reslabelfont = lay.labelfont; lay.reslabelfont.realize(defaultfont()); return false; // no errors } lyxerr << "Error parsing style `" << lay.name() << '\'' << endl; return true; } enum TextClassTags { TC_OUTPUTTYPE = 1, TC_INPUT, TC_STYLE, TC_DEFAULTSTYLE, TC_CHARSTYLE, TC_ENVIRONMENT, TC_NOSTYLE, TC_COLUMNS, TC_SIDES, TC_PAGESTYLE, TC_DEFAULTFONT, TC_SECNUMDEPTH, TC_TOCDEPTH, TC_CLASSOPTIONS, TC_PREAMBLE, TC_PROVIDESAMSMATH, TC_PROVIDESNATBIB, TC_PROVIDESMAKEIDX, TC_PROVIDESURL, TC_LEFTMARGIN, TC_RIGHTMARGIN, TC_FLOAT, TC_COUNTER, TC_NOFLOAT, TC_TITLELATEXNAME, TC_TITLELATEXTYPE, TC_FORMAT }; // Reads a textclass structure from file. bool LyXTextClass::read(string const & filename, bool merge) { if (!lyx::support::isFileReadable(filename)) { lyxerr << "Cannot read layout file `" << filename << "'." << endl; return true; } keyword_item textClassTags[] = { { "charstyle", TC_CHARSTYLE }, { "classoptions", TC_CLASSOPTIONS }, { "columns", TC_COLUMNS }, { "counter", TC_COUNTER }, { "defaultfont", TC_DEFAULTFONT }, { "defaultstyle", TC_DEFAULTSTYLE }, { "environment", TC_ENVIRONMENT }, { "float", TC_FLOAT }, { "format", TC_FORMAT }, { "input", TC_INPUT }, { "leftmargin", TC_LEFTMARGIN }, { "nofloat", TC_NOFLOAT }, { "nostyle", TC_NOSTYLE }, { "outputtype", TC_OUTPUTTYPE }, { "pagestyle", TC_PAGESTYLE }, { "preamble", TC_PREAMBLE }, { "providesamsmath", TC_PROVIDESAMSMATH }, { "providesmakeidx", TC_PROVIDESMAKEIDX }, { "providesnatbib", TC_PROVIDESNATBIB }, { "providesurl", TC_PROVIDESURL }, { "rightmargin", TC_RIGHTMARGIN }, { "secnumdepth", TC_SECNUMDEPTH }, { "sides", TC_SIDES }, { "style", TC_STYLE }, { "titlelatexname", TC_TITLELATEXNAME }, { "titlelatextype", TC_TITLELATEXTYPE }, { "tocdepth", TC_TOCDEPTH } }; if (!merge) lyxerr[Debug::TCLASS] << "Reading textclass " << lyx::to_utf8(makeDisplayPath(filename)) << endl; else lyxerr[Debug::TCLASS] << "Reading input file " << lyx::to_utf8(makeDisplayPath(filename)) << endl; LyXLex lexrc(textClassTags, sizeof(textClassTags) / sizeof(textClassTags[0])); lexrc.setFile(filename); bool error = !lexrc.isOK(); // Format of files before the 'Format' tag was introduced int format = 1; // parsing while (lexrc.isOK() && !error) { int le = lexrc.lex(); switch (le) { case LyXLex::LEX_FEOF: continue; case LyXLex::LEX_UNDEF: lexrc.printError("Unknown TextClass tag `$$Token'"); error = true; continue; default: break; } switch (static_cast(le)) { case TC_FORMAT: if (lexrc.next()) format = lexrc.getInteger(); break; case TC_OUTPUTTYPE: // output type definition readOutputType(lexrc); break; case TC_INPUT: // Include file if (lexrc.next()) { string const inc = lexrc.getString(); string tmp = libFileSearch("layouts", inc, "layout"); if (tmp.empty()) { lexrc.printError("Could not find input" "file: " + inc); error = true; } else if (read(tmp, true)) { lexrc.printError("Error reading input" "file: "+tmp); error = true; } } break; case TC_DEFAULTSTYLE: if (lexrc.next()) { string const name = subst(lexrc.getString(), '_', ' '); defaultlayout_ = name; } break; case TC_ENVIRONMENT: case TC_STYLE: if (lexrc.next()) { string const name = subst(lexrc.getString(), '_', ' '); if (hasLayout(name)) { LyXLayout * lay = operator[](name).get(); error = do_readStyle(lexrc, *lay); } else { LyXLayout lay; lay.setName(name); if (le == TC_ENVIRONMENT) lay.is_environment = true; error = do_readStyle(lexrc, lay); if (!error) layoutlist_.push_back( boost::shared_ptr(new LyXLayout(lay)) ); if (defaultlayout_.empty()) { // We do not have a default // layout yet, so we choose // the first layout we // encounter. defaultlayout_ = name; } } } else { lexrc.printError("No name given for style: `$$Token'."); error = true; } break; case TC_NOSTYLE: if (lexrc.next()) { string const style = subst(lexrc.getString(), '_', ' '); if (!delete_layout(style)) lyxerr << "Cannot delete style `" << style << '\'' << endl; // lexrc.printError("Cannot delete style" // " `$$Token'"); } break; case TC_COLUMNS: if (lexrc.next()) columns_ = lexrc.getInteger(); break; case TC_SIDES: if (lexrc.next()) { switch (lexrc.getInteger()) { case 1: sides_ = OneSide; break; case 2: sides_ = TwoSides; break; default: lyxerr << "Impossible number of page" " sides, setting to one." << endl; sides_ = OneSide; break; } } break; case TC_PAGESTYLE: lexrc.next(); pagestyle_ = rtrim(lexrc.getString()); break; case TC_DEFAULTFONT: defaultfont_.lyxRead(lexrc); if (!defaultfont_.resolved()) { lexrc.printError("Warning: defaultfont should " "be fully instantiated!"); defaultfont_.realize(LyXFont(LyXFont::ALL_SANE)); } break; case TC_SECNUMDEPTH: lexrc.next(); secnumdepth_ = lexrc.getInteger(); break; case TC_TOCDEPTH: lexrc.next(); tocdepth_ = lexrc.getInteger(); break; // First step to support options case TC_CLASSOPTIONS: readClassOptions(lexrc); break; case TC_PREAMBLE: preamble_ = lexrc.getLongString("EndPreamble"); break; case TC_PROVIDESAMSMATH: if (lexrc.next() && lexrc.getInteger()) provides_ |= amsmath; break; case TC_PROVIDESNATBIB: if (lexrc.next() && lexrc.getInteger()) provides_ |= natbib; break; case TC_PROVIDESMAKEIDX: if (lexrc.next() && lexrc.getInteger()) provides_ |= makeidx; break; case TC_PROVIDESURL: if (lexrc.next() && lexrc.getInteger()) provides_ |= url; break; case TC_LEFTMARGIN: // left margin type if (lexrc.next()) leftmargin_ = lexrc.getString(); break; case TC_RIGHTMARGIN: // right margin type if (lexrc.next()) rightmargin_ = lexrc.getString(); break; case TC_CHARSTYLE: if (lexrc.next()) { string const name = subst(lexrc.getString(), '_', ' '); readCharStyle(lexrc, name); } break; case TC_FLOAT: readFloat(lexrc); break; case TC_COUNTER: readCounter(lexrc); break; case TC_TITLELATEXTYPE: readTitleType(lexrc); break; case TC_TITLELATEXNAME: if (lexrc.next()) titlename_ = lexrc.getString(); break; case TC_NOFLOAT: if (lexrc.next()) { string const nofloat = lexrc.getString(); floatlist_->erase(nofloat); } break; } if (format != FORMAT) break; } if (format != FORMAT) { lyxerr[Debug::TCLASS] << "Converting layout file from format " << format << " to " << FORMAT << endl; string const tempfile = lyx::support::tempName(); error = !layout2layout(filename, tempfile); if (!error) error = read(tempfile, merge); lyx::support::unlink(tempfile); return error; } if (!merge) { // we are at top level here. lyxerr[Debug::TCLASS] << "Finished reading textclass " << lyx::to_utf8(makeDisplayPath(filename)) << endl; if (defaultlayout_.empty()) { lyxerr << "Error: Textclass '" << name_ << "' is missing a defaultstyle." << endl; error = true; } min_toclevel_ = LyXLayout::NOT_IN_TOC; max_toclevel_ = LyXLayout::NOT_IN_TOC; const_iterator cit = begin(); const_iterator the_end = end(); for ( ; cit != the_end ; ++cit) { int const toclevel = (*cit)->toclevel; if (toclevel != LyXLayout::NOT_IN_TOC) { if (min_toclevel_ == LyXLayout::NOT_IN_TOC) min_toclevel_ = toclevel; else min_toclevel_ = std::min(min_toclevel_, toclevel); max_toclevel_ = std::max(max_toclevel_, toclevel); } } lyxerr[Debug::TCLASS] << "Minimum TocLevel is " << min_toclevel_ << ", maximum is " << max_toclevel_ <(le); break; default: lyxerr << "Unhandled value " << le << " in LyXTextClass::readTitleType." << endl; break; } } void LyXTextClass::readOutputType(LyXLex & lexrc) { keyword_item outputTypeTags[] = { { "docbook", DOCBOOK }, { "latex", LATEX }, { "literate", LITERATE } }; pushpophelper pph(lexrc, outputTypeTags, LITERATE); int le = lexrc.lex(); switch (le) { case LyXLex::LEX_UNDEF: lexrc.printError("Unknown output type `$$Token'"); return; case LATEX: case DOCBOOK: case LITERATE: outputType_ = static_cast(le); break; default: lyxerr << "Unhandled value " << le << " in LyXTextClass::readOutputType." << endl; break; } } enum ClassOptionsTags { CO_FONTSIZE = 1, CO_PAGESTYLE, CO_OTHER, CO_HEADER, CO_END }; void LyXTextClass::readClassOptions(LyXLex & lexrc) { keyword_item classOptionsTags[] = { {"end", CO_END }, {"fontsize", CO_FONTSIZE }, {"header", CO_HEADER }, {"other", CO_OTHER }, {"pagestyle", CO_PAGESTYLE } }; lexrc.pushTable(classOptionsTags, CO_END); bool getout = false; while (!getout && lexrc.isOK()) { int le = lexrc.lex(); switch (le) { case LyXLex::LEX_UNDEF: lexrc.printError("Unknown ClassOption tag `$$Token'"); continue; default: break; } switch (static_cast(le)) { case CO_FONTSIZE: lexrc.next(); opt_fontsize_ = rtrim(lexrc.getString()); break; case CO_PAGESTYLE: lexrc.next(); opt_pagestyle_ = rtrim(lexrc.getString()); break; case CO_OTHER: lexrc.next(); options_ = lexrc.getString(); break; case CO_HEADER: lexrc.next(); class_header_ = subst(lexrc.getString(), """, "\""); break; case CO_END: getout = true; break; } } lexrc.popTable(); } enum CharStyleTags { CS_FONT = 1, CS_LABELFONT, CS_LATEXTYPE, CS_LATEXNAME, CS_LATEXPARAM, CS_PREAMBLE, CS_END }; void LyXTextClass::readCharStyle(LyXLex & lexrc, string const & name) { keyword_item elementTags[] = { { "end", CS_END }, { "font", CS_FONT }, { "labelfont", CS_LABELFONT }, { "latexname", CS_LATEXNAME }, { "latexparam", CS_LATEXPARAM }, { "latextype", CS_LATEXTYPE }, { "preamble", CS_PREAMBLE} }; lexrc.pushTable(elementTags, CS_END); string latextype; string latexname; string latexparam; LyXFont font(LyXFont::ALL_INHERIT); LyXFont labelfont(LyXFont::ALL_INHERIT); string preamble; bool getout = false; while (!getout && lexrc.isOK()) { int le = lexrc.lex(); switch (le) { case LyXLex::LEX_UNDEF: lexrc.printError("Unknown ClassOption tag `$$Token'"); continue; default: break; } switch (static_cast(le)) { case CS_LATEXTYPE: lexrc.next(); latextype = lexrc.getString(); break; case CS_LATEXNAME: lexrc.next(); latexname = lexrc.getString(); break; case CS_LATEXPARAM: lexrc.next(); latexparam = subst(lexrc.getString(), """, "\""); break; case CS_LABELFONT: labelfont.lyxRead(lexrc); break; case CS_FONT: font.lyxRead(lexrc); labelfont = font; break; case CS_PREAMBLE: preamble = lexrc.getLongString("EndPreamble"); break; case CS_END: getout = true; break; } } // // Here add element to list if getout == true if (getout) { CharStyle cs; cs.name = name; cs.latextype = latextype; cs.latexname = latexname; cs.latexparam = latexparam; cs.font = font; cs.labelfont = labelfont; cs.preamble = preamble; charstyles().push_back(cs); } lexrc.popTable(); } enum FloatTags { FT_TYPE = 1, FT_NAME, FT_PLACEMENT, FT_EXT, FT_WITHIN, FT_STYLE, FT_LISTNAME, FT_BUILTIN, FT_END }; void LyXTextClass::readFloat(LyXLex & lexrc) { keyword_item floatTags[] = { { "end", FT_END }, { "extension", FT_EXT }, { "guiname", FT_NAME }, { "latexbuiltin", FT_BUILTIN }, { "listname", FT_LISTNAME }, { "numberwithin", FT_WITHIN }, { "placement", FT_PLACEMENT }, { "style", FT_STYLE }, { "type", FT_TYPE } }; lexrc.pushTable(floatTags, FT_END); string type; string placement; string ext; string within; string style; string name; string listname; bool builtin = false; bool getout = false; while (!getout && lexrc.isOK()) { int le = lexrc.lex(); switch (le) { case LyXLex::LEX_UNDEF: lexrc.printError("Unknown ClassOption tag `$$Token'"); continue; default: break; } switch (static_cast(le)) { case FT_TYPE: lexrc.next(); type = lexrc.getString(); // Here we could check if this type is already defined // and modify it with the rest of the vars instead. break; case FT_NAME: lexrc.next(); name = lexrc.getString(); break; case FT_PLACEMENT: lexrc.next(); placement = lexrc.getString(); break; case FT_EXT: lexrc.next(); ext = lexrc.getString(); break; case FT_WITHIN: lexrc.next(); within = lexrc.getString(); if (within == "none") within.erase(); break; case FT_STYLE: lexrc.next(); style = lexrc.getString(); break; case FT_LISTNAME: lexrc.next(); listname = lexrc.getString(); break; case FT_BUILTIN: lexrc.next(); builtin = lexrc.getBool(); break; case FT_END: getout = true; break; } } // Here if have a full float if getout == true if (getout) { Floating newfloat(type, placement, ext, within, style, name, listname, builtin); floatlist_->newFloat(newfloat); } lexrc.popTable(); } enum CounterTags { CT_NAME = 1, CT_WITHIN, CT_END }; void LyXTextClass::readCounter(LyXLex & lexrc) { keyword_item counterTags[] = { { "end", CT_END }, { "name", CT_NAME }, { "within", CT_WITHIN } }; lexrc.pushTable(counterTags, CT_END); string name; string within; bool getout = false; while (!getout && lexrc.isOK()) { int le = lexrc.lex(); switch (le) { case LyXLex::LEX_UNDEF: lexrc.printError("Unknown ClassOption tag `$$Token'"); continue; default: break; } switch (static_cast(le)) { case CT_NAME: lexrc.next(); name = lexrc.getString(); break; case CT_WITHIN: lexrc.next(); within = lexrc.getString(); if (within == "none") within.erase(); break; case CT_END: getout = true; break; } } // Here if have a full counter if getout == true if (getout) { // FIXME UNICODE if (within.empty()) { ctrs_->newCounter(lyx::from_ascii(name)); } else { ctrs_->newCounter(lyx::from_ascii(name), lyx::from_ascii(within)); } } lexrc.popTable(); } LyXFont const & LyXTextClass::defaultfont() const { return defaultfont_; } string const & LyXTextClass::leftmargin() const { return leftmargin_; } string const & LyXTextClass::rightmargin() const { return rightmargin_; } bool LyXTextClass::hasLayout(string const & n) const { string const name = (n.empty() ? defaultLayoutName() : n); return find_if(layoutlist_.begin(), layoutlist_.end(), LayoutNamesEqual(name)) != layoutlist_.end(); } LyXLayout_ptr const & LyXTextClass::operator[](string const & name) const { BOOST_ASSERT(!name.empty()); LayoutList::const_iterator cit = find_if(layoutlist_.begin(), layoutlist_.end(), LayoutNamesEqual(name)); if (cit == layoutlist_.end()) { lyxerr << "We failed to find the layout '" << name << "' in the layout list. You MUST investigate!" << endl; for (LayoutList::const_iterator it = layoutlist_.begin(); it != layoutlist_.end(); ++it) lyxerr << " " << it->get()->name() << endl; // we require the name to exist BOOST_ASSERT(false); } return (*cit); } bool LyXTextClass::delete_layout(string const & name) { if (name == defaultLayoutName()) return false; LayoutList::iterator it = remove_if(layoutlist_.begin(), layoutlist_.end(), LayoutNamesEqual(name)); LayoutList::iterator end = layoutlist_.end(); bool const ret = (it != end); layoutlist_.erase(it, end); return ret; } // Load textclass info if not loaded yet bool LyXTextClass::load(string const & path) const { if (loaded_) return true; // Read style-file, provided path is searched before the system ones string layout_file; if (!path.empty()) layout_file = addName(path, name_ + ".layout"); if (layout_file.empty() || !fs::exists(layout_file)) layout_file = libFileSearch("layouts", name_, "layout"); loaded_ = const_cast(this)->read(layout_file) == 0; if (!loaded_) { lyxerr << "Error reading `" << lyx::to_utf8(makeDisplayPath(layout_file)) << "'\n(Check `" << name_ << "')\nCheck your installation and " "try Options/Reconfigure..." << endl; } return loaded_; } FloatList & LyXTextClass::floats() { return *floatlist_.get(); } FloatList const & LyXTextClass::floats() const { return *floatlist_.get(); } Counters & LyXTextClass::counters() const { return *ctrs_.get(); } CharStyles::iterator LyXTextClass::charstyle(string const & s) const { CharStyles::iterator cs = charstyles().begin(); CharStyles::iterator csend = charstyles().end(); for (; cs != csend; ++cs) { if (cs->name == s) return cs; } return csend; } string const & LyXTextClass::defaultLayoutName() const { // This really should come from the actual layout... (Lgb) return defaultlayout_; } LyXLayout_ptr const & LyXTextClass::defaultLayout() const { return operator[](defaultLayoutName()); } string const & LyXTextClass::name() const { return name_; } string const & LyXTextClass::latexname() const { const_cast(this)->load(); return latexname_; } string const & LyXTextClass::description() const { return description_; } string const & LyXTextClass::opt_fontsize() const { return opt_fontsize_; } string const & LyXTextClass::opt_pagestyle() const { return opt_pagestyle_; } string const & LyXTextClass::options() const { return options_; } string const & LyXTextClass::class_header() const { return class_header_; } string const & LyXTextClass::pagestyle() const { return pagestyle_; } string const & LyXTextClass::preamble() const { return preamble_; } LyXTextClass::PageSides LyXTextClass::sides() const { return sides_; } int LyXTextClass::secnumdepth() const { return secnumdepth_; } int LyXTextClass::tocdepth() const { return tocdepth_; } OutputType LyXTextClass::outputType() const { return outputType_; } bool LyXTextClass::provides(LyXTextClass::Provides p) const { return provides_ & p; } unsigned int LyXTextClass::columns() const { return columns_; } LYX_TITLE_LATEX_TYPES LyXTextClass::titletype() const { return titletype_; } string const & LyXTextClass::titlename() const { return titlename_; } int LyXTextClass::size() const { return layoutlist_.size(); } int LyXTextClass::min_toclevel() const { return min_toclevel_; } int LyXTextClass::max_toclevel() const { return max_toclevel_; } bool LyXTextClass::hasTocLevels() const { return min_toclevel_ != LyXLayout::NOT_IN_TOC; } ostream & operator<<(ostream & os, LyXTextClass::PageSides p) { switch (p) { case LyXTextClass::OneSide: os << '1'; break; case LyXTextClass::TwoSides: os << '2'; break; } return os; }