* This file is part of LyX, the document processor.
* Licence details can be found in the file COPYING.
*
- * \author Lars Gullik Bjønnes
+ * \author Lars Gullik Bjønnes
* \author John Levon
*
* Full author contact details are available in file CREDITS.
#include <config.h>
#include "LayoutFile.h"
-#include "Counters.h"
-#include "Floating.h"
-#include "FloatList.h"
-#include "Lexer.h"
#include "TextClass.h"
-#include "support/lassert.h"
#include "support/debug.h"
#include "support/FileName.h"
#include "support/filetools.h"
-#include "support/gettext.h"
-
-#include <boost/bind.hpp>
-#include <boost/regex.hpp>
+#include "support/lassert.h"
+#include "support/Lexer.h"
+#include "support/TempFile.h"
#include <fstream>
+#include <regex>
using namespace std;
using namespace lyx::support;
namespace lyx {
-using boost::bind;
-using boost::regex;
-using boost::smatch;
+
LayoutFile::LayoutFile(string const & fn, string const & cln,
- string const & desc, bool texClassAvail )
+ string const & desc, string const & prereq,
+ string const & category, bool texclassavail)
{
- name_ = fn;
+ name_ = onlyFileName(fn);
+ path_ = fn.rfind('/') == string::npos ? string() : onlyPath(fn);
latexname_ = cln;
description_ = desc;
- texClassAvail_ = texClassAvail;
+ prerequisites_ = prereq;
+ category_ = category;
+ tex_class_avail_ = texclassavail;
}
+
LayoutFileList::~LayoutFileList()
{
ClassMap::const_iterator it = classmap_.begin();
}
}
-LayoutFileList & LayoutFileList::get()
+
+LayoutFileList & LayoutFileList::get()
{
static LayoutFileList baseclasslist;
return baseclasslist;
LayoutFile const & LayoutFileList::operator[](string const & classname) const
{
- LASSERT(haveClass(classname), /**/);
+ LATTEST(haveClass(classname));
+ // safe to continue, since we will make an empty LayoutFile
return *classmap_[classname];
}
-LayoutFile &
- LayoutFileList::operator[](string const & classname)
+LayoutFile & LayoutFileList::operator[](string const & classname)
{
- LASSERT(haveClass(classname), /**/);
+ LATTEST(haveClass(classname));
+ // safe to continue, since we will make an empty LayoutFile
return *classmap_[classname];
}
// Reads LyX textclass definitions according to textclass config file
bool LayoutFileList::read()
{
+ bool success = false;
Lexer lex;
FileName const real_file = libFileSearch("", "textclass.lst");
- LYXERR(Debug::TCLASS, "Reading textclasses from `" << real_file << '\'');
+ LYXERR(Debug::TCLASS, "Reading textclasses from `" << real_file << "'.");
if (real_file.empty()) {
- lyxerr << "LayoutFileList::Read: unable to find "
- "textclass file `"
- << to_utf8(makeDisplayPath(real_file.absFilename(), 1000))
- << "'. Exiting." << endl;
- return false;
- // This causes LyX to end... Not a desirable behaviour. Lgb
- // What do you propose? That the user gets a file dialog
- // and is allowed to hunt for the file? (Asger)
- // more that we have a layout for minimal.cls statically
- // compiled in... (Lgb)
- }
-
- if (!lex.setFile(real_file)) {
- lyxerr << "LayoutFileList::Read: "
- "lyxlex was not able to set file: "
- << real_file << endl;
- }
-
- if (!lex.isOK()) {
- lyxerr << "LayoutFileList::Read: unable to open "
- "textclass file `"
- << to_utf8(makeDisplayPath(real_file.absFilename(), 1000))
- << "'\nCheck your installation. LyX can't continue."
- << endl;
- return false;
- }
-
- bool finished = false;
- // Parse config-file
- LYXERR(Debug::TCLASS, "Starting parsing of textclass.lst");
- while (lex.isOK() && !finished) {
- LYXERR(Debug::TCLASS, "\tline by line");
- switch (lex.lex()) {
- case Lexer::LEX_FEOF:
- finished = true;
- break;
- default:
- string const fname = lex.getString();
- LYXERR(Debug::TCLASS, "Fname: " << fname);
- if (lex.next()) {
+ LYXERR0("LayoutFileList::Read: unable to find textclass file "
+ << "`textclass.lst'.");
+ } else if (!lex.setFile(real_file)) {
+ LYXERR0("LayoutFileList::Read: lyxlex was not able to set file: "
+ << real_file << '.');
+ } else if (!lex.isOK()) {
+ LYXERR0("LayoutFileList::Read: unable to open textclass file `"
+ << makeDisplayPath(real_file.absFileName(), 1000)
+ << "'\nCheck your installation.");
+ } else {
+ // we have a file we can read.
+ bool finished = false;
+ LYXERR(Debug::TCLASS, "Starting parsing of textclass.lst");
+ while (lex.isOK() && !finished) {
+ LYXERR(Debug::TCLASS, "\tline by line");
+ switch (lex.lex()) {
+ case Lexer::LEX_FEOF:
+ finished = true;
+ break;
+ default:
+ string const fname = lex.getString();
+ LYXERR(Debug::TCLASS, "Fname: " << fname);
+ if (!lex.next())
+ break;
string const clname = lex.getString();
LYXERR(Debug::TCLASS, "Clname: " << clname);
- if (lex.next()) {
- string const desc = lex.getString();
- LYXERR(Debug::TCLASS, "Desc: " << desc);
- if (lex.next()) {
- bool avail = lex.getBool();
- LYXERR(Debug::TCLASS, "Avail: " << avail);
- // This code is run when we have
- // fname, clname, desc, and avail
- LayoutFile * tmpl = new LayoutFile(fname, clname, desc, avail);
- if (lyxerr.debugging(Debug::TCLASS)) {
- // only system layout files are loaded here so no
- // buffer path is needed.
- tmpl->load();
- }
- classmap_[fname] = tmpl;
- }
+ if (!lex.next())
+ break;
+ string const desc = lex.getString();
+ LYXERR(Debug::TCLASS, "Desc: " << desc);
+ if (!lex.next())
+ break;
+ bool avail = lex.getBool();
+ LYXERR(Debug::TCLASS, "Avail: " << avail);
+ if (!lex.next())
+ break;
+ string const prereq = lex.getString();
+ LYXERR(Debug::TCLASS, "Prereq: " << prereq);
+ if (!lex.next())
+ break;
+ string const category = lex.getString();
+ LYXERR(Debug::TCLASS, "Category: " << category);
+ // This code is run when we have
+ // fname, clname, desc, prereq, and avail
+ LayoutFile * tmpl = new LayoutFile(fname, clname, desc, prereq, category, avail);
+ if (lyxerr.debugging(Debug::TCLASS)) {
+ // only system layout files are loaded here so no
+ // buffer path is needed.
+ tmpl->load();
}
- }
- }
- }
- LYXERR(Debug::TCLASS, "End of parsing of textclass.lst");
-
- // lyx will start with an empty classmap_, but only reconfigure is allowed
- // in this case. This gives users a second chance to configure lyx if
- // initial configuration fails. (c.f. bug 2829)
+ classmap_[fname] = tmpl;
+ } // end of switch
+ } // end of while loop
+ LYXERR(Debug::TCLASS, "End parsing of textclass.lst");
+ success = true;
+ } // end of else
+
+ // LyX will start with an empty classmap_. This is OK because
+ // (a) we will give the user a chance to reconfigure (see bug 2829) and
+ // (b) even if that fails, we can use addEmptyClass() to get some basic
+ // functionality.
if (classmap_.empty())
- lyxerr << "LayoutFileList::Read: no textclasses found!"
- << endl;
- return true;
+ LYXERR0("LayoutFileList::Read: no textclasses found!");
+ return success;
}
}
-void LayoutFileList::reset(LayoutFileIndex const & classname) {
- LASSERT(haveClass(classname), /**/);
+void LayoutFileList::reset(LayoutFileIndex const & classname)
+{
+ LATTEST(haveClass(classname));
+ // safe to continue, since we will make an empty LayoutFile
LayoutFile * tc = classmap_[classname];
- LayoutFile * tmpl =
+ LayoutFile * tmpl =
new LayoutFile(tc->name(), tc->latexname(), tc->description(),
- tc->isTeXClassAvailable());
+ tc->prerequisites(), tc->category(),
+ tc->isTeXClassAvailable());
classmap_[classname] = tmpl;
delete tc;
}
-LayoutFileIndex LayoutFileList::addDefaultClass(string const & textclass)
-{
- if (haveClass(textclass))
- return textclass;
+namespace {
- FileName const tempLayout = FileName::tempName();
- ofstream ofs(tempLayout.toFilesystemEncoding().c_str());
- ofs << "# This layout is automatically generated\n"
- "# \\DeclareLaTeXClass{" << textclass << "}\n\n"
- "Format 7\n"
- "Input stdclass.inc\n\n"
- "Columns 1\n"
- "Sides 1\n"
- "SecNumDepth 2\n"
- "TocDepth 2\n"
+string layoutpost =
+ "Columns 1\n"
+ "Sides 1\n"
+ "SecNumDepth 2\n"
+ "TocDepth 2\n"
"DefaultStyle Standard\n\n"
"Style Standard\n"
" Category MainText\n"
" AlignPossible Block, Left, Right, Center\n"
" LabelType No_Label\n"
"End\n";
+
+}
+
+
+LayoutFileIndex LayoutFileList::addEmptyClass(string const & textclass)
+{
+ // FIXME This could be simplified a bit to call TextClass::read(string, ReadType).
+
+ TempFile tempfile("basicXXXXXX.layout");
+ FileName const tempLayout = tempfile.name();
+ ofstream ofs(tempLayout.toFilesystemEncoding().c_str());
+ // This writes a very basic class, but it also attempts to include
+ // stdclass.inc. That would give us something moderately usable.
+ ofs << "# This layout is automatically generated\n"
+ "# \\DeclareLaTeXClass{" << textclass << "}\n\n"
+ "Format " << LAYOUT_FORMAT << "\n"
+ "Input stdclass.inc\n\n"
+ << layoutpost;
ofs.close();
// We do not know if a LaTeX class is available for this document, but setting
// the last parameter to true will suppress a warning message about missing
// tex class.
- LayoutFile * tc = new LayoutFile(textclass, textclass, "Unknown text class " + textclass, true);
- if (!tc->load(tempLayout.absFilename()))
- // The only way this happens is because the hardcoded layout file above
- // is wrong.
- LASSERT(false, /**/);
+ LayoutFile * tc = new LayoutFile(textclass, textclass,
+ "Unknown text class " + textclass, textclass + ".cls", "", true);
+
+ if (!tc->load(tempLayout.absFileName())) {
+ // The only way this happens is because the hardcoded layout file
+ // above is wrong or stdclass.inc cannot be found. So try again
+ // without stdclass.inc and without stdinsets.inc.
+ ofstream ofs2(tempLayout.toFilesystemEncoding().c_str());
+ ofs2 << "# This layout is automatically generated\n"
+ "# \\DeclareLaTeXClass{" << textclass << "}\n\n"
+ "Format " << LAYOUT_FORMAT << "\n"
+ "Provides stdinsets 1\n"
+ << layoutpost;
+ ofs2.close();
+ if (!tc->load(tempLayout.absFileName())) {
+ // This can only happen if the hardcoded file above is wrong
+ // or there is some weird filesystem error.
+ LATTEST(false); // We will get an empty layout or something.
+ }
+ }
+
classmap_[textclass] = tc;
return textclass;
}
-LayoutFileIndex
- LayoutFileList::addLocalLayout(string const & textclass, string const & path)
+LayoutFileIndex LayoutFileList::addLocalLayout(
+ string const & textclass, string const & path, string const & oldpath)
{
// FIXME There is a bug here: 4593
//
// only check for textclass.layout file, .cls can be anywhere in $TEXINPUTS
- // NOTE: latex class name is defined in textclass.layout, which can be
+ // NOTE: latex class name is defined in textclass.layout, which can be
// different from textclass
string fullName = addName(path, textclass + ".layout");
-
- FileName const layout_file(fullName);
- if (layout_file.exists()) {
- LYXERR(Debug::TCLASS, "Adding class " << textclass << " from directory " << path);
- // Read .layout file and get description, real latex classname etc
- //
- // This is a C++ version of function processLayoutFile in configure.py,
- // which uses the following regex
- // \Declare(LaTeX|DocBook)Class\s*(\[([^,]*)(,.*)*\])*\s*{(.*)}
- ifstream ifs(layout_file.toFilesystemEncoding().c_str());
- static regex const reg("^#\\s*\\\\Declare(LaTeX|DocBook)Class\\s*"
- "(?:\\[([^,]*)(?:,.*)*\\])*\\s*\\{(.*)\\}\\s*");
- string line;
- while (getline(ifs, line)) {
- // look for the \DeclareXXXClass line
- smatch sub;
- if (regex_match(line, sub, reg)) {
- // returns: whole string, classtype (not used here), class name, description
- LASSERT(sub.size() == 4, /**/);
- // now, create a TextClass with description containing path information
- string className(sub.str(2) == "" ? textclass : sub.str(2));
- LayoutFile * tmpl =
- new LayoutFile(textclass, className, textclass, true);
- // This textclass is added on request so it will definitely be
- // used. Load it now because other load() calls may fail if they
- // are called in a context without buffer path information.
- tmpl->load(path);
- // There will be only one textclass with this name, even if different
- // layout files are loaded from different directories.
- if (haveClass(textclass)) {
- LYXERR0("Exisint textclass " << textclass << " is redefined by " << fullName);
- delete classmap_[textclass];
- }
- classmap_[textclass] = tmpl;
- return textclass;
- }
- }
+
+ FileName layout_file(fullName);
+ bool moved = false;
+
+ if (!layout_file.exists()) {
+ if (oldpath.empty())
+ return string();
+ // The document has been moved to a different directory.
+ // However, oldpath always points to the right spot, unless
+ // the user also moved the layout file.
+ fullName = addName(oldpath, textclass + ".layout");
+ layout_file.set(fullName);
+ layout_file.refresh();
+ if (!layout_file.exists())
+ return string();
+ moved = true;
}
- // If .layout is not in local directory, or an invalid layout is found, return null
- return string();
-}
+ LYXERR(Debug::TCLASS, "Adding class " << textclass << " from directory " << path);
+ // Read .layout file and get description, real latex classname etc
+ //
+ // This is a C++ version of function processLayoutFile in configure.py,
+ // which uses the following regex
+ // \Declare(LaTeX|DocBook)Class\s*(\[([^,]*)(,.*)*\])*\s*{(.*)}
+ ifstream ifs(layout_file.toFilesystemEncoding().c_str());
+ static regex const reg("^\\s*#\\s*\\\\Declare(LaTeX|DocBook)Class\\s*"
+ "(?:\\[([^,]*)(?:,.*)*\\])*\\s*\\{(.*)\\}\\s*");
+ static regex const catreg("^\\s*#\\s*\\\\DeclareCategory\\{(.*)\\}\\s*");
+ string line;
+ string class_name;
+ string class_prereq;
+ string category;
+ bool have_declaration = false;
+ while (getline(ifs, line)) {
+ // look for the \DeclareXXXClass line
+ smatch sub;
+ if (regex_match(line, sub, reg)) {
+ // returns: whole string, classtype (not used here), class name, description
+ // LASSERT: Why would this fail?
+ LASSERT(sub.size() == 4, /**/);
+ // now, create a TextClass with description containing path information
+ class_name = (sub.str(2) == "" ? textclass : sub.str(2));
+ class_prereq = class_name + ".cls";
+ have_declaration = true;
+ }
+ else if (regex_match(line, sub, catreg)) {
+ category = sub.str(1);
+ }
+ if (have_declaration && !category.empty())
+ break;
+ }
-LayoutFileIndex defaultBaseclass()
-{
- if (LayoutFileList::get().haveClass("article"))
- return string("article");
- if (LayoutFileList::get().empty())
+ if (!have_declaration)
return string();
- return LayoutFileList::get().classList().front();
-}
+ LayoutFile * tmpl =
+ new LayoutFile(addName(moved ? oldpath : path, textclass),
+ class_name, textclass, class_prereq, category, true);
+ //FIXME: The prerequisites are available from the layout file and
+ // can be extracted from the above regex, but for now this
+ // field is simply set to class_name + ".cls"
+ // This textclass is added on request so it will definitely be
+ // used. Load it now because other load() calls may fail if they
+ // are called in a context without buffer path information.
+ tmpl->load(moved ? oldpath : path);
+ // There will be only one textclass with this name, even if different
+ // layout files are loaded from different directories.
+ if (haveClass(textclass)) {
+ // Unconditionally issuing the warning may be confusing when
+ // saving the document with a different name, as it is exactly
+ // the same textclass that is being re-established.
+ LYXERR(Debug::TCLASS, "Existing textclass " << textclass << " is redefined by " << fullName);
+ delete classmap_[textclass];
+ }
+ classmap_[textclass] = tmpl;
+ return removeExtension(fullName);
+}
-// Reads the style files
-bool LyXSetStyle()
+bool LayoutFileList::load(string const & name, string const & buf_path)
{
- LYXERR(Debug::TCLASS, "LyXSetStyle: parsing configuration...");
-
- if (!LayoutFileList::get().read()) {
- LYXERR(Debug::TCLASS, "LyXSetStyle: an error occured "
- "during parsing.\n Exiting.");
+ if (!haveClass(name)) {
+ LYXERR0("Document class \"" << name << "\" does not exist.");
return false;
}
- LYXERR(Debug::TCLASS, "LyXSetStyle: configuration parsed.");
- return true;
+ LayoutFile * tc = classmap_[name];
+ return tc->load(buf_path);
}
+LayoutFileIndex defaultBaseclass()
+{
+ if (LayoutFileList::get().haveClass("article"))
+ return string("article");
+ if (LayoutFileList::get().empty())
+ // we'll call it that, since this gives the user a chance to
+ // have a functioning document when things improve.
+ return string("article");
+ return LayoutFileList::get().classList().front();
+}
+
} // namespace lyx