]> git.lyx.org Git - lyx.git/blob - src/LayoutFile.cpp
Avoid full metrics computation with Update:FitCursor
[lyx.git] / src / LayoutFile.cpp
1 /**
2  * \file LayoutFileList.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  * \author John Levon
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "LayoutFile.h"
15 #include "TextClass.h"
16
17 #include "support/debug.h"
18 #include "support/FileName.h"
19 #include "support/filetools.h"
20 #include "support/lassert.h"
21 #include "support/Lexer.h"
22 #include "support/TempFile.h"
23
24 #include <fstream>
25 #include <regex>
26
27 using namespace std;
28 using namespace lyx::support;
29
30 namespace lyx {
31
32
33
34 LayoutFile::LayoutFile(string const & fn, string const & cln,
35                        string const & desc, string const & prereq,
36                        string const & category, bool texclassavail)
37 {
38         name_ = onlyFileName(fn);
39         path_ = fn.rfind('/') == string::npos ? string() : onlyPath(fn);
40         latexname_ = cln;
41         description_ = desc;
42         prerequisites_ = prereq;
43         category_ = category;
44         tex_class_avail_ = texclassavail;
45 }
46
47
48 LayoutFileList::~LayoutFileList()
49 {
50         ClassMap::const_iterator it = classmap_.begin();
51         ClassMap::const_iterator en = classmap_.end();
52         for (; it != en; ++it) {
53                 delete it->second;
54         }
55 }
56
57
58 LayoutFileList & LayoutFileList::get()
59 {
60         static LayoutFileList baseclasslist;
61         return baseclasslist;
62 }
63
64
65 bool LayoutFileList::haveClass(string const & classname) const
66 {
67         ClassMap::const_iterator it = classmap_.begin();
68         ClassMap::const_iterator en = classmap_.end();
69         for (; it != en; ++it) {
70                 if (it->first == classname)
71                         return true;
72         }
73         return false;
74 }
75
76
77 LayoutFile const & LayoutFileList::operator[](string const & classname) const
78 {
79         LATTEST(haveClass(classname));
80         // safe to continue, since we will make an empty LayoutFile
81         return *classmap_[classname];
82 }
83
84
85 LayoutFile & LayoutFileList::operator[](string const & classname)
86 {
87         LATTEST(haveClass(classname));
88         // safe to continue, since we will make an empty LayoutFile
89         return *classmap_[classname];
90 }
91
92
93 // Reads LyX textclass definitions according to textclass config file
94 bool LayoutFileList::read()
95 {
96         bool success = false;
97         Lexer lex;
98         FileName const real_file = libFileSearch("", "textclass.lst");
99         LYXERR(Debug::TCLASS, "Reading textclasses from `" << real_file << "'.");
100
101         if (real_file.empty()) {
102                 LYXERR0("LayoutFileList::Read: unable to find textclass file  "
103                     << "`textclass.lst'.");
104         } else if (!lex.setFile(real_file)) {
105                 LYXERR0("LayoutFileList::Read: lyxlex was not able to set file: "
106                        << real_file << '.');
107         } else if (!lex.isOK()) {
108                 LYXERR0("LayoutFileList::Read: unable to open textclass file  `"
109                        << makeDisplayPath(real_file.absFileName(), 1000)
110                        << "'\nCheck your installation.");
111         } else {
112                 // we have a file we can read.
113                 bool finished = false;
114                 LYXERR(Debug::TCLASS, "Starting parsing of textclass.lst");
115                 while (lex.isOK() && !finished) {
116                         LYXERR(Debug::TCLASS, "\tline by line");
117                         switch (lex.lex()) {
118                         case Lexer::LEX_FEOF:
119                                 finished = true;
120                                 break;
121                         default:
122                                 string const fname = lex.getString();
123                                 LYXERR(Debug::TCLASS, "Fname: " << fname);
124                                 if (!lex.next())
125                                         break;
126                                 string const clname = lex.getString();
127                                 LYXERR(Debug::TCLASS, "Clname: " << clname);
128                                 if (!lex.next())
129                                         break;
130                                 string const desc = lex.getString();
131                                 LYXERR(Debug::TCLASS, "Desc: " << desc);
132                                 if (!lex.next())
133                                         break;
134                                 bool avail = lex.getBool();
135                                 LYXERR(Debug::TCLASS, "Avail: " << avail);
136                                 if (!lex.next())
137                                         break;
138                                 string const prereq = lex.getString();
139                                 LYXERR(Debug::TCLASS, "Prereq: " << prereq);
140                                 if (!lex.next())
141                                         break;
142                                 string const category = lex.getString();
143                                 LYXERR(Debug::TCLASS, "Category: " << category);
144                                 // This code is run when we have
145                                 // fname, clname, desc, prereq, and avail
146                                 LayoutFile * tmpl = new LayoutFile(fname, clname, desc, prereq, category, avail);
147                                 if (lyxerr.debugging(Debug::TCLASS)) {
148                                         // only system layout files are loaded here so no
149                                         // buffer path is needed.
150                                         tmpl->load();
151                                 }
152                                 classmap_[fname] = tmpl;
153                         } // end of switch
154                 } // end of while loop
155                 LYXERR(Debug::TCLASS, "End parsing of textclass.lst");
156                 success = true;
157         } // end of else
158
159         // LyX will start with an empty classmap_. This is OK because
160         // (a) we will give the user a chance to reconfigure (see bug 2829) and
161         // (b) even if that fails, we can use addEmptyClass() to get some basic
162         // functionality.
163         if (classmap_.empty())
164                 LYXERR0("LayoutFileList::Read: no textclasses found!");
165         return success;
166 }
167
168
169 std::vector<LayoutFileIndex> LayoutFileList::classList() const
170 {
171         std::vector<LayoutFileIndex> cl;
172         ClassMap::const_iterator it = classmap_.begin();
173         ClassMap::const_iterator en = classmap_.end();
174         for (; it != en; ++it)
175                 cl.push_back(it->first);
176         return cl;
177 }
178
179
180 void LayoutFileList::reset(LayoutFileIndex const & classname)
181 {
182         LATTEST(haveClass(classname));
183         // safe to continue, since we will make an empty LayoutFile
184         LayoutFile * tc = classmap_[classname];
185         LayoutFile * tmpl =
186                 new LayoutFile(tc->name(), tc->latexname(), tc->description(),
187                                tc->prerequisites(), tc->category(),
188                                tc->isTeXClassAvailable());
189         classmap_[classname] = tmpl;
190         delete tc;
191 }
192
193
194 namespace {
195
196 string layoutpost =
197                 "Columns      1\n"
198                 "Sides        1\n"
199                 "SecNumDepth  2\n"
200                 "TocDepth     2\n"
201                 "DefaultStyle   Standard\n\n"
202                 "Style Standard\n"
203                 "       Category              MainText\n"
204                 "       Margin                Static\n"
205                 "       LatexType             Paragraph\n"
206                 "       LatexName             dummy\n"
207                 "       ParIndent             MM\n"
208                 "       ParSkip               0.4\n"
209                 "       Align                 Block\n"
210                 "       AlignPossible         Block, Left, Right, Center\n"
211                 "       LabelType             No_Label\n"
212                 "End\n";
213
214 }
215
216
217 LayoutFileIndex LayoutFileList::addEmptyClass(string const & textclass)
218 {
219         // FIXME This could be simplified a bit to call TextClass::read(string, ReadType).
220
221         TempFile tempfile("basicXXXXXX.layout");
222         FileName const tempLayout = tempfile.name();
223         ofstream ofs(tempLayout.toFilesystemEncoding().c_str());
224         // This writes a very basic class, but it also attempts to include
225         // stdclass.inc. That would give us something moderately usable.
226         ofs << "# This layout is automatically generated\n"
227                "# \\DeclareLaTeXClass{" << textclass << "}\n\n"
228                "Format " << LAYOUT_FORMAT << "\n"
229                "Input stdclass.inc\n\n"
230             << layoutpost;
231         ofs.close();
232
233         // We do not know if a LaTeX class is available for this document, but setting
234         // the last parameter to true will suppress a warning message about missing
235         // tex class.
236         LayoutFile * tc = new LayoutFile(textclass, textclass,
237                         "Unknown text class " + textclass, textclass + ".cls", "", true);
238
239         if (!tc->load(tempLayout.absFileName())) {
240                 // The only way this happens is because the hardcoded layout file
241                 // above is wrong or stdclass.inc cannot be found. So try again
242                 // without stdclass.inc and without stdinsets.inc.
243                 ofstream ofs2(tempLayout.toFilesystemEncoding().c_str());
244                 ofs2 << "# This layout is automatically generated\n"
245                         "# \\DeclareLaTeXClass{" << textclass << "}\n\n"
246                         "Format " << LAYOUT_FORMAT << "\n"
247                         "Provides stdinsets 1\n"
248                      << layoutpost;
249                 ofs2.close();
250                 if (!tc->load(tempLayout.absFileName())) {
251                         // This can only happen if the hardcoded file above is wrong
252                         // or there is some weird filesystem error.
253                         LATTEST(false); // We will get an empty layout or something.
254                 }
255         }
256
257         classmap_[textclass] = tc;
258         return textclass;
259 }
260
261
262 LayoutFileIndex  LayoutFileList::addLocalLayout(
263         string const & textclass, string const & path, string const & oldpath)
264 {
265         // FIXME  There is a bug here: 4593
266         //
267         // only check for textclass.layout file, .cls can be anywhere in $TEXINPUTS
268         // NOTE: latex class name is defined in textclass.layout, which can be
269         // different from textclass
270         string fullName = addName(path, textclass + ".layout");
271
272         FileName layout_file(fullName);
273         bool moved = false;
274
275         if (!layout_file.exists()) {
276                 if (oldpath.empty())
277                         return string();
278                 // The document has been moved to a different directory.
279                 // However, oldpath always points to the right spot, unless
280                 // the user also moved the layout file.
281                 fullName = addName(oldpath, textclass + ".layout");
282                 layout_file.set(fullName);
283                 layout_file.refresh();
284                 if (!layout_file.exists())
285                         return string();
286                 moved = true;
287         }
288
289         LYXERR(Debug::TCLASS, "Adding class " << textclass << " from directory " << path);
290         // Read .layout file and get description, real latex classname etc
291         //
292         // This is a C++ version of function processLayoutFile in configure.py,
293         // which uses the following regex
294         //     \Declare(LaTeX|DocBook)Class\s*(\[([^,]*)(,.*)*\])*\s*{(.*)}
295         ifstream ifs(layout_file.toFilesystemEncoding().c_str());
296         static regex const reg("^\\s*#\\s*\\\\Declare(LaTeX|DocBook)Class\\s*"
297                 "(?:\\[([^,]*)(?:,.*)*\\])*\\s*\\{(.*)\\}\\s*");
298         static regex const catreg("^\\s*#\\s*\\\\DeclareCategory\\{(.*)\\}\\s*");
299         string line;
300         string class_name;
301         string class_prereq;
302         string category;
303         bool have_declaration = false;
304         while (getline(ifs, line)) {
305                 // look for the \DeclareXXXClass line
306                 smatch sub;
307                 if (regex_match(line, sub, reg)) {
308                         // returns: whole string, classtype (not used here), class name, description
309                         // LASSERT: Why would this fail?
310                         LASSERT(sub.size() == 4, /**/);
311                         // now, create a TextClass with description containing path information
312                         class_name = (sub.str(2) == "" ? textclass : sub.str(2));
313                         class_prereq = class_name + ".cls";
314                         have_declaration = true;
315                 }
316                 else if (regex_match(line, sub, catreg)) {
317                         category = sub.str(1);
318                 }
319                 if (have_declaration && !category.empty())
320                         break;
321         }
322
323         if (!have_declaration)
324                 return string();
325
326         LayoutFile * tmpl =
327                 new LayoutFile(addName(moved ? oldpath : path, textclass),
328                         class_name, textclass, class_prereq, category, true);
329         //FIXME: The prerequisites are available from the layout file and
330         //       can be extracted from the above regex, but for now this
331         //       field is simply set to class_name + ".cls"
332         // This textclass is added on request so it will definitely be
333         // used. Load it now because other load() calls may fail if they
334         // are called in a context without buffer path information.
335         tmpl->load(moved ? oldpath : path);
336         // There will be only one textclass with this name, even if different
337         // layout files are loaded from different directories.
338         if (haveClass(textclass)) {
339                 // Unconditionally issuing the warning may be confusing when
340                 // saving the document with a different name, as it is exactly
341                 // the same textclass that is being re-established.
342                 LYXERR(Debug::TCLASS, "Existing textclass " << textclass << " is redefined by " << fullName);
343                 delete classmap_[textclass];
344         }
345         classmap_[textclass] = tmpl;
346         return removeExtension(fullName);
347 }
348
349
350 bool LayoutFileList::load(string const & name, string const & buf_path)
351 {
352         if (!haveClass(name)) {
353                 LYXERR0("Document class \"" << name << "\" does not exist.");
354                 return false;
355         }
356
357         LayoutFile * tc = classmap_[name];
358         return tc->load(buf_path);
359 }
360
361
362 LayoutFileIndex defaultBaseclass()
363 {
364         if (LayoutFileList::get().haveClass("article"))
365                 return string("article");
366         if (LayoutFileList::get().empty())
367                 // we'll call it that, since this gives the user a chance to
368                 // have a functioning document when things improve.
369                 return string("article");
370         return LayoutFileList::get().classList().front();
371 }
372
373 } // namespace lyx