]> git.lyx.org Git - lyx.git/blob - src/TextClass.cpp
5a51cef31557d25a037e4d28e6e97f2cbd622c0a
[lyx.git] / src / TextClass.cpp
1 /**
2  * \file TextClass.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 Jean-Marc Lasgouttes
8  * \author Angus Leeming
9  * \author John Levon
10  * \author André Pönitz
11  *
12  * Full author contact details are available in file CREDITS.
13  */
14
15 #include <config.h>
16
17 #include "TextClass.h"
18
19 #include "LayoutFile.h"
20 #include "Color.h"
21 #include "Counters.h"
22 #include "Floating.h"
23 #include "FloatList.h"
24 #include "Layout.h"
25 #include "Lexer.h"
26 #include "Font.h"
27 #include "ModuleList.h"
28
29 #include "frontends/alert.h"
30
31 #include "support/lassert.h"
32 #include "support/debug.h"
33 #include "support/ExceptionMessage.h"
34 #include "support/FileName.h"
35 #include "support/filetools.h"
36 #include "support/gettext.h"
37 #include "support/lstrings.h"
38 #include "support/os.h"
39
40 #include <algorithm>
41 #include <fstream>
42 #include <sstream>
43
44 #ifdef ERROR
45 #undef ERROR
46 #endif
47
48 using namespace std;
49 using namespace lyx::support;
50
51 namespace lyx {
52
53 namespace {
54
55 class LayoutNamesEqual : public unary_function<Layout, bool> {
56 public:
57         LayoutNamesEqual(docstring const & name)
58                 : name_(name)
59         {}
60         bool operator()(Layout const & c) const
61         {
62                 return c.name() == name_;
63         }
64 private:
65         docstring name_;
66 };
67
68 // Keep the changes documented in the Customization manual. 
69 int const FORMAT = 26;
70
71
72 bool layout2layout(FileName const & filename, FileName const & tempfile)
73 {
74         FileName const script = libFileSearch("scripts", "layout2layout.py");
75         if (script.empty()) {
76                 LYXERR0("Could not find layout conversion "
77                           "script layout2layout.py.");
78                 return false;
79         }
80
81         ostringstream command;
82         command << os::python() << ' ' << quoteName(script.toFilesystemEncoding())
83                 << ' ' << quoteName(filename.toFilesystemEncoding())
84                 << ' ' << quoteName(tempfile.toFilesystemEncoding());
85         string const command_str = command.str();
86
87         LYXERR(Debug::TCLASS, "Running `" << command_str << '\'');
88
89         cmd_ret const ret = runCommand(command_str);
90         if (ret.first != 0) {
91                 LYXERR0("Could not run layout conversion script layout2layout.py.");
92                 return false;
93         }
94         return true;
95 }
96
97
98 string translateRT(TextClass::ReadType rt) 
99 {
100         switch (rt) {
101         case TextClass::BASECLASS:
102                 return "textclass";
103         case TextClass::MERGE:
104                 return "input file";
105         case TextClass::MODULE:
106                 return "module file";
107         case TextClass::VALIDATION:
108                 return "validation";
109         }
110         // shutup warning
111         return string();
112 }
113
114 } // namespace anon
115
116
117 // This string should not be translated here, 
118 // because it is a layout identifier.
119 docstring const TextClass::plain_layout_ = from_ascii("Plain Layout");
120
121
122 InsetLayout DocumentClass::plain_insetlayout_;
123
124
125 /////////////////////////////////////////////////////////////////////////
126 //
127 // TextClass
128 //
129 /////////////////////////////////////////////////////////////////////////
130
131 TextClass::TextClass()
132 {
133         outputType_ = LATEX;
134         outputFormat_ = "latex";
135         columns_ = 1;
136         sides_ = OneSide;
137         secnumdepth_ = 3;
138         tocdepth_ = 3;
139         pagestyle_ = "default";
140         defaultfont_ = sane_font;
141         opt_fontsize_ = "10|11|12";
142         opt_pagestyle_ = "empty|plain|headings|fancy";
143         titletype_ = TITLE_COMMAND_AFTER;
144         titlename_ = "maketitle";
145         loaded_ = false;
146         _("Plain Layout"); // a hack to make this translatable
147 }
148
149
150 bool TextClass::readStyle(Lexer & lexrc, Layout & lay) const
151 {
152         LYXERR(Debug::TCLASS, "Reading style " << to_utf8(lay.name()));
153         if (!lay.read(lexrc, *this)) {
154                 LYXERR0("Error parsing style `" << to_utf8(lay.name()) << '\'');
155                 return false;
156         }
157         // Resolve fonts
158         lay.resfont = lay.font;
159         lay.resfont.realize(defaultfont_);
160         lay.reslabelfont = lay.labelfont;
161         lay.reslabelfont.realize(defaultfont_);
162         return true; // no errors
163 }
164
165
166 enum TextClassTags {
167         TC_OUTPUTTYPE = 1,
168         TC_OUTPUTFORMAT,
169         TC_INPUT,
170         TC_STYLE,
171         TC_IFSTYLE,
172         TC_DEFAULTSTYLE,
173         TC_INSETLAYOUT,
174         TC_NOSTYLE,
175         TC_COLUMNS,
176         TC_SIDES,
177         TC_PAGESTYLE,
178         TC_DEFAULTFONT,
179         TC_SECNUMDEPTH,
180         TC_TOCDEPTH,
181         TC_CLASSOPTIONS,
182         TC_PREAMBLE,
183         TC_HTMLPREAMBLE,
184         TC_PROVIDES,
185         TC_REQUIRES,
186         TC_LEFTMARGIN,
187         TC_RIGHTMARGIN,
188         TC_FLOAT,
189         TC_COUNTER,
190         TC_IFCOUNTER,
191         TC_NOFLOAT,
192         TC_TITLELATEXNAME,
193         TC_TITLELATEXTYPE,
194         TC_FORMAT,
195         TC_ADDTOPREAMBLE,
196         TC_ADDTOHTMLPREAMBLE,
197         TC_DEFAULTMODULE,
198         TC_PROVIDESMODULE,
199         TC_EXCLUDESMODULE,
200         TC_HTMLTOCSECTION,
201         TC_CITEFORMAT
202 };
203
204
205 namespace {
206
207         LexerKeyword textClassTags[] = {
208                 { "addtohtmlpreamble", TC_ADDTOHTMLPREAMBLE },
209                 { "addtopreamble",     TC_ADDTOPREAMBLE },
210                 { "citeformat",        TC_CITEFORMAT },
211                 { "classoptions",      TC_CLASSOPTIONS },
212                 { "columns",           TC_COLUMNS },
213                 { "counter",           TC_COUNTER },
214                 { "defaultfont",       TC_DEFAULTFONT },
215                 { "defaultmodule",     TC_DEFAULTMODULE },
216                 { "defaultstyle",      TC_DEFAULTSTYLE },
217                 { "excludesmodule",    TC_EXCLUDESMODULE },
218                 { "float",             TC_FLOAT },
219                 { "format",            TC_FORMAT },
220                 { "htmlpreamble",      TC_HTMLPREAMBLE },
221                 { "htmltocsection",    TC_HTMLTOCSECTION },
222                 { "ifcounter",         TC_IFCOUNTER },
223                 { "ifstyle",           TC_IFSTYLE },
224                 { "input",             TC_INPUT },
225                 { "insetlayout",       TC_INSETLAYOUT },
226                 { "leftmargin",        TC_LEFTMARGIN },
227                 { "nofloat",           TC_NOFLOAT },
228                 { "nostyle",           TC_NOSTYLE },
229                 { "outputformat",      TC_OUTPUTFORMAT },
230                 { "outputtype",        TC_OUTPUTTYPE },
231                 { "pagestyle",         TC_PAGESTYLE },
232                 { "preamble",          TC_PREAMBLE },
233                 { "provides",          TC_PROVIDES },
234                 { "providesmodule",    TC_PROVIDESMODULE },
235                 { "requires",          TC_REQUIRES },
236                 { "rightmargin",       TC_RIGHTMARGIN },
237                 { "secnumdepth",       TC_SECNUMDEPTH },
238                 { "sides",             TC_SIDES },
239                 { "style",             TC_STYLE },
240                 { "titlelatexname",    TC_TITLELATEXNAME },
241                 { "titlelatextype",    TC_TITLELATEXTYPE },
242                 { "tocdepth",          TC_TOCDEPTH }
243         };
244         
245 } //namespace anon
246
247
248 bool TextClass::convertLayoutFormat(support::FileName const & filename, ReadType rt)
249 {
250         LYXERR(Debug::TCLASS, "Converting layout file to " << FORMAT);
251         FileName const tempfile = FileName::tempName("convert_layout");
252         bool success = layout2layout(filename, tempfile);
253         if (success)
254                 success = readWithoutConv(tempfile, rt) == OK;
255         tempfile.removeFile();
256         return success;
257 }
258
259
260 TextClass::ReturnValues TextClass::readWithoutConv(FileName const & filename, ReadType rt)
261 {
262         if (!filename.isReadableFile()) {
263                 lyxerr << "Cannot read layout file `" << filename << "'."
264                        << endl;
265                 return ERROR;
266         }
267
268         LYXERR(Debug::TCLASS, "Reading " + translateRT(rt) + ": " +
269                 to_utf8(makeDisplayPath(filename.absFilename())));
270
271         // Define the plain layout used in table cells, ert, etc. Note that 
272         // we do this before loading any layout file, so that classes can 
273         // override features of this layout if they should choose to do so.
274         if (rt == BASECLASS && !hasLayout(plain_layout_))
275                 layoutlist_.push_back(createBasicLayout(plain_layout_));
276
277         Lexer lexrc(textClassTags);
278         lexrc.setFile(filename);
279         ReturnValues retval = read(lexrc, rt);
280         
281         LYXERR(Debug::TCLASS, "Finished reading " + translateRT(rt) + ": " +
282                         to_utf8(makeDisplayPath(filename.absFilename())));
283
284         return retval;
285 }
286
287
288 bool TextClass::read(FileName const & filename, ReadType rt)
289 {
290         ReturnValues const retval = readWithoutConv(filename, rt);
291         if (retval != FORMAT_MISMATCH)
292                 return retval == OK;
293
294         bool const worx = convertLayoutFormat(filename, rt);
295         if (!worx) {
296                 LYXERR0 ("Unable to convert " << filename << 
297                         " to format " << FORMAT);
298                 return false;
299         }
300         return true;
301 }
302
303
304 bool TextClass::validate(std::string const & str)
305 {
306         TextClass tc;
307         return tc.read(str, VALIDATION);
308 }
309
310
311 bool TextClass::read(std::string const & str, ReadType rt) 
312 {
313         Lexer lexrc(textClassTags);
314         istringstream is(str);
315         lexrc.setStream(is);
316         ReturnValues retval = read(lexrc, rt);
317
318         if (retval != FORMAT_MISMATCH) 
319                 return retval == OK;
320
321         // write the layout string to a temporary file
322         FileName const tempfile = FileName::tempName("TextClass_read");
323         ofstream os(tempfile.toFilesystemEncoding().c_str());
324         if (!os) {
325                 LYXERR0("Unable to create temporary file");
326                 return false;
327         }
328         os << str;
329         os.close();
330
331         // now try to convert it
332         bool const worx = convertLayoutFormat(tempfile, rt);
333         if (!worx) {
334                 LYXERR0("Unable to convert internal layout information to format " 
335                         << FORMAT);
336         }
337         tempfile.removeFile();
338         return worx;
339 }
340
341
342 // Reads a textclass structure from file.
343 TextClass::ReturnValues TextClass::read(Lexer & lexrc, ReadType rt) 
344 {
345         bool error = !lexrc.isOK();
346
347         // Format of files before the 'Format' tag was introduced
348         int format = 1;
349
350         // parsing
351         while (lexrc.isOK() && !error) {
352                 int le = lexrc.lex();
353
354                 switch (le) {
355                 case Lexer::LEX_FEOF:
356                         continue;
357
358                 case Lexer::LEX_UNDEF:
359                         lexrc.printError("Unknown TextClass tag `$$Token'");
360                         error = true;
361                         continue;
362
363                 default:
364                         break;
365                 }
366
367                 // used below to track whether we are in an IfStyle or IfCounter tag.
368                 bool ifstyle    = false;
369                 bool ifcounter  = false;
370
371                 switch (static_cast<TextClassTags>(le)) {
372
373                 case TC_FORMAT:
374                         if (lexrc.next())
375                                 format = lexrc.getInteger();
376                         break;
377
378                 case TC_OUTPUTFORMAT:
379                         if (lexrc.next())
380                                 outputFormat_ = lexrc.getString();
381                         break;
382
383                 case TC_OUTPUTTYPE:
384                         readOutputType(lexrc);
385                         switch(outputType_) {
386                         case LATEX:
387                                 outputFormat_ = "latex";
388                                 break;
389                         case DOCBOOK:
390                                 outputFormat_ = "docbook";
391                                 break;
392                         case LITERATE:
393                                 outputFormat_ = "literate";
394                                 break;
395                         }
396                         break;
397
398                 case TC_INPUT: // Include file
399                         if (lexrc.next()) {
400                                 string const inc = lexrc.getString();
401                                 FileName tmp = libFileSearch("layouts", inc,
402                                                             "layout");
403
404                                 if (tmp.empty()) {
405                                         lexrc.printError("Could not find input file: " + inc);
406                                         error = true;
407                                 } else if (!read(tmp, MERGE)) {
408                                         lexrc.printError("Error reading input"
409                                                          "file: " + tmp.absFilename());
410                                         error = true;
411                                 }
412                         }
413                         break;
414
415                 case TC_DEFAULTSTYLE:
416                         if (lexrc.next()) {
417                                 docstring const name = from_utf8(subst(lexrc.getString(),
418                                                           '_', ' '));
419                                 defaultlayout_ = name;
420                         }
421                         break;
422
423                 case TC_IFSTYLE:
424                         ifstyle = true;
425                         // fall through
426                 case TC_STYLE: {
427                         if (!lexrc.next()) {
428                                 lexrc.printError("No name given for style: `$$Token'.");
429                                 error = true;
430                                 break;
431                         }
432                         docstring const name = from_utf8(subst(lexrc.getString(),
433                                                         '_', ' '));
434                         if (name.empty()) {
435                                 string s = "Could not read name for style: `$$Token' "
436                                         + lexrc.getString() + " is probably not valid UTF-8!";
437                                 lexrc.printError(s.c_str());
438                                 Layout lay;
439                                 // Since we couldn't read the name, we just scan the rest
440                                 // of the style and discard it.
441                                 error = !readStyle(lexrc, lay);
442                         } else if (hasLayout(name)) {
443                                 Layout & lay = operator[](name);
444                                 error = !readStyle(lexrc, lay);
445                         } else if (!ifstyle) {
446                                 Layout layout;
447                                 layout.setName(name);
448                                 error = !readStyle(lexrc, layout);
449                                 if (!error)
450                                         layoutlist_.push_back(layout);
451
452                                 if (defaultlayout_.empty()) {
453                                         // We do not have a default layout yet, so we choose
454                                         // the first layout we encounter.
455                                         defaultlayout_ = name;
456                                 }
457                         }
458                         else {
459                                 // scan the rest and discard it
460                                 Layout lay;
461                                 readStyle(lexrc, lay);
462                                 error = false;
463                         }
464
465                         // reset flag
466                         ifstyle = false;
467                         break;
468                 }
469
470                 case TC_NOSTYLE:
471                         if (lexrc.next()) {
472                                 docstring const style = from_utf8(subst(lexrc.getString(),
473                                                      '_', ' '));
474                                 if (!deleteLayout(style))
475                                         lyxerr << "Cannot delete style `"
476                                                << to_utf8(style) << '\'' << endl;
477                         }
478                         break;
479
480                 case TC_COLUMNS:
481                         if (lexrc.next())
482                                 columns_ = lexrc.getInteger();
483                         break;
484
485                 case TC_SIDES:
486                         if (lexrc.next()) {
487                                 switch (lexrc.getInteger()) {
488                                 case 1: sides_ = OneSide; break;
489                                 case 2: sides_ = TwoSides; break;
490                                 default:
491                                         lyxerr << "Impossible number of page"
492                                                 " sides, setting to one."
493                                                << endl;
494                                         sides_ = OneSide;
495                                         break;
496                                 }
497                         }
498                         break;
499
500                 case TC_PAGESTYLE:
501                         lexrc.next();
502                         pagestyle_ = rtrim(lexrc.getString());
503                         break;
504
505                 case TC_DEFAULTFONT:
506                         defaultfont_ = lyxRead(lexrc);
507                         if (!defaultfont_.resolved()) {
508                                 lexrc.printError("Warning: defaultfont should "
509                                                  "be fully instantiated!");
510                                 defaultfont_.realize(sane_font);
511                         }
512                         break;
513
514                 case TC_SECNUMDEPTH:
515                         lexrc.next();
516                         secnumdepth_ = lexrc.getInteger();
517                         break;
518
519                 case TC_TOCDEPTH:
520                         lexrc.next();
521                         tocdepth_ = lexrc.getInteger();
522                         break;
523
524                 // First step to support options
525                 case TC_CLASSOPTIONS:
526                         readClassOptions(lexrc);
527                         break;
528
529                 case TC_PREAMBLE:
530                         preamble_ = from_utf8(lexrc.getLongString("EndPreamble"));
531                         break;
532
533                 case TC_HTMLPREAMBLE:
534                         htmlpreamble_ = from_utf8(lexrc.getLongString("EndPreamble"));
535                         break;
536                 
537                 case TC_HTMLTOCSECTION:
538                         html_toc_section_ = from_utf8(trim(lexrc.getString()));
539                         break;
540
541                 case TC_ADDTOPREAMBLE:
542                         preamble_ += from_utf8(lexrc.getLongString("EndPreamble"));
543                         break;
544
545                 case TC_ADDTOHTMLPREAMBLE:
546                         htmlpreamble_ += from_utf8(lexrc.getLongString("EndPreamble"));
547                         break;
548
549                 case TC_PROVIDES: {
550                         lexrc.next();
551                         string const feature = lexrc.getString();
552                         lexrc.next();
553                         if (lexrc.getInteger())
554                                 provides_.insert(feature);
555                         else
556                                 provides_.erase(feature);
557                         break;
558                 }
559
560                 case TC_REQUIRES: {
561                         lexrc.eatLine();
562                         vector<string> const req 
563                                 = getVectorFromString(lexrc.getString());
564                         requires_.insert(req.begin(), req.end());
565                         break;
566                 }
567
568                 case TC_DEFAULTMODULE: {
569                         lexrc.next();
570                         string const module = lexrc.getString();
571                         if (find(default_modules_.begin(), default_modules_.end(), module) == default_modules_.end())
572                                 default_modules_.push_back(module);
573                         break;
574                 }
575
576                 case TC_PROVIDESMODULE: {
577                         lexrc.next();
578                         string const module = lexrc.getString();
579                         if (find(provided_modules_.begin(), provided_modules_.end(), module) == provided_modules_.end())
580                                 provided_modules_.push_back(module);
581                         break;
582                 }
583
584                 case TC_EXCLUDESMODULE: {
585                         lexrc.next();
586                         string const module = lexrc.getString();
587                         // modules already have their own way to exclude other modules
588                         if (rt == MODULE) {
589                                 LYXERR0("ExcludesModule tag cannot be used in a module!");
590                                 break;
591                         }
592                         if (find(excluded_modules_.begin(), excluded_modules_.end(), module) == excluded_modules_.end())
593                                 excluded_modules_.push_back(module);
594                         break;
595                 }
596
597                 case TC_LEFTMARGIN:     // left margin type
598                         if (lexrc.next())
599                                 leftmargin_ = lexrc.getDocString();
600                         break;
601
602                 case TC_RIGHTMARGIN:    // right margin type
603                         if (lexrc.next())
604                                 rightmargin_ = lexrc.getDocString();
605                         break;
606
607                 case TC_INSETLAYOUT: {
608                         if (!lexrc.next()) {
609                                 lexrc.printError("No name given for InsetLayout: `$$Token'.");
610                                 error = true;
611                                 break;
612                         }
613                         docstring const name = subst(lexrc.getDocString(), '_', ' ');
614                         if (name.empty()) {
615                                 string s = "Could not read name for InsetLayout: `$$Token' "
616                                         + lexrc.getString() + " is probably not valid UTF-8!";
617                                 lexrc.printError(s.c_str());
618                                 InsetLayout il;
619                                 // Since we couldn't read the name, we just scan the rest
620                                 // of the style and discard it.
621                                 il.read(lexrc, *this);
622                                 error = true;
623                         } else if (hasInsetLayout(name)) {
624                                 InsetLayout & il = insetlayoutlist_[name];
625                                 error = !il.read(lexrc, *this);
626                         } else {
627                                 InsetLayout il;
628                                 il.setName(name);
629                                 error = !il.read(lexrc, *this);
630                                 if (!error)
631                                         insetlayoutlist_[name] = il;
632                         }
633                         break;
634                 }
635
636                 case TC_FLOAT:
637                         readFloat(lexrc);
638                         break;
639                 
640                 case TC_CITEFORMAT:
641                         readCiteFormat(lexrc);
642                         break;
643
644                 case TC_IFCOUNTER:
645                         ifcounter = true;
646                 case TC_COUNTER:
647                         if (lexrc.next()) {
648                                 docstring const name = lexrc.getDocString();
649                                 if (name.empty()) {
650                                         string s = "Could not read name for counter: `$$Token' "
651                                                         + lexrc.getString() + " is probably not valid UTF-8!";
652                                         lexrc.printError(s.c_str());
653                                         Counter c;
654                                         // Since we couldn't read the name, we just scan the rest
655                                         // and discard it.
656                                         c.read(lexrc);
657                                 } else
658                                         error = !counters_.read(lexrc, name, !ifcounter);
659                         }
660                         else {
661                                 lexrc.printError("No name given for style: `$$Token'.");
662                                 error = true;
663                         }
664                         // reset flag
665                         ifcounter = false;
666                         break;
667
668                 case TC_TITLELATEXTYPE:
669                         readTitleType(lexrc);
670                         break;
671
672                 case TC_TITLELATEXNAME:
673                         if (lexrc.next())
674                                 titlename_ = lexrc.getString();
675                         break;
676
677                 case TC_NOFLOAT:
678                         if (lexrc.next()) {
679                                 string const nofloat = lexrc.getString();
680                                 floatlist_.erase(nofloat);
681                         }
682                         break;
683                 } // end of switch
684
685                 //Note that this is triggered the first time through the loop unless
686                 //we hit a format tag.
687                 if (format != FORMAT)
688                         break;
689         }
690
691         if (format != FORMAT)
692                 return FORMAT_MISMATCH;
693
694         if (rt != BASECLASS) 
695                 return (error ? ERROR : OK);
696
697         if (defaultlayout_.empty()) {
698                 LYXERR0("Error: Textclass '" << name_
699                                                 << "' is missing a defaultstyle.");
700                 error = true;
701         }
702                 
703         // Try to erase "stdinsets" from the provides_ set. 
704         // The
705         //   Provides stdinsets 1
706         // declaration simply tells us that the standard insets have been
707         // defined. (It's found in stdinsets.inc but could also be used in
708         // user-defined files.) There isn't really any such package. So we
709         // might as well go ahead and erase it.
710         // If we do not succeed, then it was not there, which means that
711         // the textclass did not provide the definitions of the standard
712         // insets. So we need to try to load them.
713         int erased = provides_.erase("stdinsets");
714         if (!erased) {
715                 FileName tmp = libFileSearch("layouts", "stdinsets.inc");
716
717                 if (tmp.empty()) {
718                         throw ExceptionMessage(WarningException, _("Missing File"),
719                                 _("Could not find stdinsets.inc! This may lead to data loss!"));
720                         error = true;
721                 } else if (!read(tmp, MERGE)) {
722                         throw ExceptionMessage(WarningException, _("Corrupt File"),
723                                 _("Could not read stdinsets.inc! This may lead to data loss!"));
724                         error = true;
725                 }
726         }
727
728         min_toclevel_ = Layout::NOT_IN_TOC;
729         max_toclevel_ = Layout::NOT_IN_TOC;
730         const_iterator lit = begin();
731         const_iterator len = end();
732         for (; lit != len; ++lit) {
733                 int const toclevel = lit->toclevel;
734                 if (toclevel != Layout::NOT_IN_TOC) {
735                         if (min_toclevel_ == Layout::NOT_IN_TOC)
736                                 min_toclevel_ = toclevel;
737                         else
738                                 min_toclevel_ = min(min_toclevel_, toclevel);
739                         max_toclevel_ = max(max_toclevel_, toclevel);
740                 }
741         }
742         LYXERR(Debug::TCLASS, "Minimum TocLevel is " << min_toclevel_
743                 << ", maximum is " << max_toclevel_);
744
745         return (error ? ERROR : OK);
746 }
747
748
749 void TextClass::readTitleType(Lexer & lexrc)
750 {
751         LexerKeyword titleTypeTags[] = {
752                 { "commandafter", TITLE_COMMAND_AFTER },
753                 { "environment",  TITLE_ENVIRONMENT }
754         };
755
756         PushPopHelper pph(lexrc, titleTypeTags);
757
758         int le = lexrc.lex();
759         switch (le) {
760         case Lexer::LEX_UNDEF:
761                 lexrc.printError("Unknown output type `$$Token'");
762                 break;
763         case TITLE_COMMAND_AFTER:
764         case TITLE_ENVIRONMENT:
765                 titletype_ = static_cast<TitleLatexType>(le);
766                 break;
767         default:
768                 LYXERR0("Unhandled value " << le << " in TextClass::readTitleType.");
769                 break;
770         }
771 }
772
773
774 void TextClass::readOutputType(Lexer & lexrc)
775 {
776         LexerKeyword outputTypeTags[] = {
777                 { "docbook",  DOCBOOK },
778                 { "latex",    LATEX },
779                 { "literate", LITERATE }
780         };
781
782         PushPopHelper pph(lexrc, outputTypeTags);
783
784         int le = lexrc.lex();
785         switch (le) {
786         case Lexer::LEX_UNDEF:
787                 lexrc.printError("Unknown output type `$$Token'");
788                 return;
789         case LATEX:
790         case DOCBOOK:
791         case LITERATE:
792                 outputType_ = static_cast<OutputType>(le);
793                 break;
794         default:
795                 LYXERR0("Unhandled value " << le);
796                 break;
797         }
798 }
799
800
801 void TextClass::readClassOptions(Lexer & lexrc)
802 {
803         enum {
804                 CO_FONTSIZE = 1,
805                 CO_PAGESTYLE,
806                 CO_OTHER,
807                 CO_HEADER,
808                 CO_END
809         };
810
811         LexerKeyword classOptionsTags[] = {
812                 {"end",       CO_END },
813                 {"fontsize",  CO_FONTSIZE },
814                 {"header",    CO_HEADER },
815                 {"other",     CO_OTHER },
816                 {"pagestyle", CO_PAGESTYLE }
817         };
818
819         lexrc.pushTable(classOptionsTags);
820         bool getout = false;
821         while (!getout && lexrc.isOK()) {
822                 int le = lexrc.lex();
823                 switch (le) {
824                 case Lexer::LEX_UNDEF:
825                         lexrc.printError("Unknown ClassOption tag `$$Token'");
826                         continue;
827                 default: break;
828                 }
829                 switch (le) {
830                 case CO_FONTSIZE:
831                         lexrc.next();
832                         opt_fontsize_ = rtrim(lexrc.getString());
833                         break;
834                 case CO_PAGESTYLE:
835                         lexrc.next();
836                         opt_pagestyle_ = rtrim(lexrc.getString());
837                         break;
838                 case CO_OTHER:
839                         lexrc.next();
840                         options_ = lexrc.getString();
841                         break;
842                 case CO_HEADER:
843                         lexrc.next();
844                         class_header_ = subst(lexrc.getString(), "&quot;", "\"");
845                         break;
846                 case CO_END:
847                         getout = true;
848                         break;
849                 }
850         }
851         lexrc.popTable();
852 }
853
854
855 void TextClass::readCiteFormat(Lexer & lexrc)
856 {
857         string etype;
858         string definition;
859         while (lexrc.isOK()) {
860                 lexrc.next();
861                 etype = lexrc.getString();
862                 if (!lexrc.isOK() || compare_ascii_no_case(etype, "end") == 0)
863                         break;
864                 lexrc.eatLine();
865                 definition = lexrc.getString();
866                 cite_formats_[etype] = definition;
867         }
868 }
869
870
871 void TextClass::readFloat(Lexer & lexrc)
872 {
873         enum {
874                 FT_TYPE = 1,
875                 FT_NAME,
876                 FT_PLACEMENT,
877                 FT_EXT,
878                 FT_WITHIN,
879                 FT_STYLE,
880                 FT_LISTNAME,
881                 FT_NEEDSFLOAT,
882                 FT_HTMLSTYLE,
883                 FT_HTMLATTR,
884                 FT_HTMLTAG,
885                 FT_LISTCOMMAND,
886                 FT_REFPREFIX,
887                 FT_END
888         };
889
890         LexerKeyword floatTags[] = {
891                 { "end", FT_END },
892                 { "extension", FT_EXT },
893                 { "guiname", FT_NAME },
894                 { "htmlattr", FT_HTMLATTR },
895                 { "htmlstyle", FT_HTMLSTYLE },
896                 { "htmltag", FT_HTMLTAG },
897                 { "listcommand", FT_LISTCOMMAND },
898                 { "listname", FT_LISTNAME },
899                 { "needsfloatpkg", FT_NEEDSFLOAT },
900                 { "numberwithin", FT_WITHIN },
901                 { "placement", FT_PLACEMENT },
902                 { "refprefix", FT_REFPREFIX },
903                 { "style", FT_STYLE },
904                 { "type", FT_TYPE }
905         };
906
907         lexrc.pushTable(floatTags);
908
909         string ext;
910         string htmlattr;
911         string htmlstyle;
912         string htmltag;
913         string listname;
914         string listcommand;
915         string name;
916         string placement;
917         string refprefix;
918         string style;
919         string type;
920         string within;
921         bool needsfloat = true;
922
923         bool getout = false;
924         while (!getout && lexrc.isOK()) {
925                 int le = lexrc.lex();
926                 switch (le) {
927                 case Lexer::LEX_UNDEF:
928                         lexrc.printError("Unknown float tag `$$Token'");
929                         continue;
930                 default: break;
931                 }
932                 switch (le) {
933                 case FT_TYPE:
934                         lexrc.next();
935                         type = lexrc.getString();
936                         if (floatlist_.typeExist(type)) {
937                                 Floating const & fl = floatlist_.getType(type);
938                                 placement = fl.placement();
939                                 ext = fl.ext();
940                                 within = fl.within();
941                                 style = fl.style();
942                                 name = fl.name();
943                                 listname = fl.listName();
944                                 needsfloat = fl.needsFloatPkg();
945                                 listcommand = fl.listCommand();
946                                 refprefix = fl.refPrefix();
947                         } 
948                         break;
949                 case FT_NAME:
950                         lexrc.next();
951                         name = lexrc.getString();
952                         break;
953                 case FT_PLACEMENT:
954                         lexrc.next();
955                         placement = lexrc.getString();
956                         break;
957                 case FT_EXT:
958                         lexrc.next();
959                         ext = lexrc.getString();
960                         break;
961                 case FT_WITHIN:
962                         lexrc.next();
963                         within = lexrc.getString();
964                         if (within == "none")
965                                 within.erase();
966                         break;
967                 case FT_STYLE:
968                         lexrc.next();
969                         style = lexrc.getString();
970                         break;
971                 case FT_LISTCOMMAND:
972                         lexrc.next();
973                         listcommand = lexrc.getString();
974                         break;
975                 case FT_REFPREFIX:
976                         lexrc.next();
977                         refprefix = lexrc.getString();
978                         break;
979                 case FT_LISTNAME:
980                         lexrc.next();
981                         listname = lexrc.getString();
982                         break;
983                 case FT_NEEDSFLOAT:
984                         lexrc.next();
985                         needsfloat = lexrc.getBool();
986                         break;
987                 case FT_HTMLATTR:
988                         lexrc.next();
989                         htmlattr = lexrc.getString();
990                         break;
991                 case FT_HTMLSTYLE:
992                         lexrc.next();
993                         htmlstyle = lexrc.getLongString("EndHTMLStyle");
994                         break;
995                 case FT_HTMLTAG:
996                         lexrc.next();
997                         htmltag = lexrc.getString();
998                         break;
999                 case FT_END:
1000                         getout = true;
1001                         break;
1002                 }
1003         }
1004
1005         // Here we have a full float if getout == true
1006         if (getout) {
1007                 if (!needsfloat && listcommand.empty())
1008                         LYXERR0("The layout does not provide a list command " <<
1009                                 "for the builtin float `" << type << "'. LyX will " <<
1010                                 "not be able to produce a float list.");
1011                 Floating fl(type, placement, ext, within, style, name, 
1012                                 listname, listcommand, refprefix, 
1013                                 htmltag, htmlattr, htmlstyle, needsfloat);
1014                 floatlist_.newFloat(fl);
1015                 // each float has its own counter
1016                 counters_.newCounter(from_ascii(type), from_ascii(within),
1017                                       docstring(), docstring());
1018                 // also define sub-float counters
1019                 docstring const subtype = "sub-" + from_ascii(type);
1020                 counters_.newCounter(subtype, from_ascii(type),
1021                                       "\\alph{" + subtype + "}", docstring());
1022         }
1023
1024         lexrc.popTable();
1025 }
1026
1027
1028 string const & TextClass::prerequisites() const
1029
1030         if (contains(prerequisites_, ',')) {
1031                 vector<string> const pres = getVectorFromString(prerequisites_);
1032                 prerequisites_ = getStringFromVector(pres, "\n\t");
1033         }
1034         return prerequisites_; 
1035 }
1036
1037 bool TextClass::hasLayout(docstring const & n) const
1038 {
1039         docstring const name = n.empty() ? defaultLayoutName() : n;
1040
1041         return find_if(layoutlist_.begin(), layoutlist_.end(),
1042                        LayoutNamesEqual(name))
1043                 != layoutlist_.end();
1044 }
1045
1046
1047 bool TextClass::hasInsetLayout(docstring const & n) const
1048 {
1049         if (n.empty()) 
1050                 return false;
1051         InsetLayouts::const_iterator it = insetlayoutlist_.begin();
1052         InsetLayouts::const_iterator en = insetlayoutlist_.end();
1053         for (; it != en; ++it)
1054                 if (n == it->first)
1055                         return true;
1056         return false;
1057 }
1058
1059
1060 Layout const & TextClass::operator[](docstring const & name) const
1061 {
1062         LASSERT(!name.empty(), /**/);
1063
1064         const_iterator it = 
1065                 find_if(begin(), end(), LayoutNamesEqual(name));
1066
1067         if (it == end()) {
1068                 lyxerr << "We failed to find the layout '" << to_utf8(name)
1069                        << "' in the layout list. You MUST investigate!"
1070                        << endl;
1071                 for (const_iterator cit = begin(); cit != end(); ++cit)
1072                         lyxerr  << " " << to_utf8(cit->name()) << endl;
1073
1074                 // we require the name to exist
1075                 LASSERT(false, /**/);
1076         }
1077
1078         return *it;
1079 }
1080
1081
1082 Layout & TextClass::operator[](docstring const & name)
1083 {
1084         LASSERT(!name.empty(), /**/);
1085
1086         iterator it = find_if(begin(), end(), LayoutNamesEqual(name));
1087
1088         if (it == end()) {
1089                 LYXERR0("We failed to find the layout '" << to_utf8(name)
1090                        << "' in the layout list. You MUST investigate!");
1091                 for (const_iterator cit = begin(); cit != end(); ++cit)
1092                         LYXERR0(" " << to_utf8(cit->name()));
1093
1094                 // we require the name to exist
1095                 LASSERT(false, /**/);
1096         }
1097
1098         return *it;
1099 }
1100
1101
1102 bool TextClass::deleteLayout(docstring const & name)
1103 {
1104         if (name == defaultLayoutName() || name == plainLayoutName())
1105                 return false;
1106
1107         LayoutList::iterator it =
1108                 remove_if(layoutlist_.begin(), layoutlist_.end(),
1109                           LayoutNamesEqual(name));
1110
1111         LayoutList::iterator end = layoutlist_.end();
1112         bool const ret = (it != end);
1113         layoutlist_.erase(it, end);
1114         return ret;
1115 }
1116
1117
1118 // Load textclass info if not loaded yet
1119 bool TextClass::load(string const & path) const
1120 {
1121         if (loaded_)
1122                 return true;
1123
1124         // Read style-file, provided path is searched before the system ones
1125         // If path is a file, it is loaded directly.
1126         FileName layout_file(path);
1127         if (!path.empty() && !layout_file.isReadableFile())
1128                 layout_file = FileName(addName(path, name_ + ".layout"));
1129         if (layout_file.empty() || !layout_file.exists())
1130                 layout_file = libFileSearch("layouts", name_, "layout");
1131         loaded_ = const_cast<TextClass*>(this)->read(layout_file);
1132
1133         if (!loaded_) {
1134                 lyxerr << "Error reading `"
1135                        << to_utf8(makeDisplayPath(layout_file.absFilename()))
1136                        << "'\n(Check `" << name_
1137                        << "')\nCheck your installation and "
1138                         "try Options/Reconfigure..." << endl;
1139         }
1140
1141         return loaded_;
1142 }
1143
1144
1145 void DocumentClass::addLayoutIfNeeded(docstring const & n) const
1146 {
1147         if (!hasLayout(n))
1148                 layoutlist_.push_back(createBasicLayout(n, true));
1149 }
1150
1151
1152 InsetLayout const & DocumentClass::insetLayout(docstring const & name) const 
1153 {
1154         // FIXME The fix for the InsetLayout part of 4812 would be here:
1155         // Add the InsetLayout to the document class if it is not found.
1156         docstring n = name;
1157         InsetLayouts::const_iterator cen = insetlayoutlist_.end();
1158         while (!n.empty()) {
1159                 InsetLayouts::const_iterator cit = insetlayoutlist_.lower_bound(n);
1160                 if (cit != cen && cit->first == n)
1161                         return cit->second;
1162                 size_t i = n.find(':');
1163                 if (i == string::npos)
1164                         break;
1165                 n = n.substr(0, i);
1166         }
1167         return plain_insetlayout_;
1168 }
1169
1170
1171 docstring const & TextClass::defaultLayoutName() const
1172 {
1173         // This really should come from the actual layout... (Lgb)
1174         return defaultlayout_;
1175 }
1176
1177
1178 Layout const & TextClass::defaultLayout() const
1179 {
1180         return operator[](defaultLayoutName());
1181 }
1182
1183
1184 bool TextClass::isDefaultLayout(Layout const & layout) const 
1185 {
1186         return layout.name() == defaultLayoutName();
1187 }
1188
1189
1190 bool TextClass::isPlainLayout(Layout const & layout) const 
1191 {
1192         return layout.name() == plainLayoutName();
1193 }
1194
1195
1196 Layout TextClass::createBasicLayout(docstring const & name, bool unknown) const
1197 {
1198         static Layout * defaultLayout = NULL;
1199
1200         if (defaultLayout) {
1201                 defaultLayout->setUnknown(unknown);
1202                 defaultLayout->setName(name);
1203                 return *defaultLayout;
1204         }
1205
1206         static char const * s = "Margin Static\n"
1207                         "LatexType Paragraph\n"
1208                         "LatexName dummy\n"
1209                         "Align Block\n"
1210                         "AlignPossible Left, Right, Center\n"
1211                         "LabelType No_Label\n"
1212                         "End";
1213         istringstream ss(s);
1214         Lexer lex(textClassTags);
1215         lex.setStream(ss);
1216         defaultLayout = new Layout;
1217         defaultLayout->setUnknown(unknown);
1218         defaultLayout->setName(name);
1219         if (!readStyle(lex, *defaultLayout)) {
1220                 // The only way this happens is because the hardcoded layout above
1221                 // is wrong.
1222                 LASSERT(false, /**/);
1223         };
1224         return *defaultLayout;
1225 }
1226
1227
1228 /////////////////////////////////////////////////////////////////////////
1229 //
1230 // DocumentClassBundle
1231 //
1232 /////////////////////////////////////////////////////////////////////////
1233
1234 DocumentClassBundle::~DocumentClassBundle()
1235 {
1236         for (size_t i = 0; i != documentClasses_.size(); ++i)
1237                 delete documentClasses_[i];
1238         documentClasses_.clear();
1239 }
1240
1241 DocumentClass & DocumentClassBundle::newClass(LayoutFile const & baseClass)
1242 {
1243         DocumentClass * dc = new DocumentClass(baseClass);
1244         documentClasses_.push_back(dc);
1245         return *documentClasses_.back();
1246 }
1247
1248
1249 DocumentClassBundle & DocumentClassBundle::get()
1250 {
1251         static DocumentClassBundle singleton; 
1252         return singleton; 
1253 }
1254
1255
1256 DocumentClass & DocumentClassBundle::makeDocumentClass(
1257                 LayoutFile const & baseClass, LayoutModuleList const & modlist)
1258 {
1259         DocumentClass & doc_class = newClass(baseClass);
1260         LayoutModuleList::const_iterator it = modlist.begin();
1261         LayoutModuleList::const_iterator en = modlist.end();
1262         for (; it != en; it++) {
1263                 string const modName = *it;
1264                 LyXModule * lm = theModuleList[modName];
1265                 if (!lm) {
1266                         docstring const msg =
1267                                                 bformat(_("The module %1$s has been requested by\n"
1268                                                 "this document but has not been found in the list of\n"
1269                                                 "available modules. If you recently installed it, you\n"
1270                                                 "probably need to reconfigure LyX.\n"), from_utf8(modName));
1271                         ExceptionMessage(WarningException,_("Module not available"),
1272                                         msg + _("Some layouts may not be available."));
1273                         continue;
1274                 }
1275                 if (!lm->isAvailable()) {
1276                         docstring const msg =
1277                                                 bformat(_("The module %1$s requires a package that is\n"
1278                                                 "not available in your LaTeX installation. LaTeX output\n"
1279                                                 "may not be possible.\n"), from_utf8(modName));
1280                         ExceptionMessage(WarningException, _("Package not available"), msg);
1281                 }
1282                 FileName layout_file = libFileSearch("layouts", lm->getFilename());
1283                 if (!doc_class.read(layout_file, TextClass::MODULE)) {
1284                         docstring const msg =
1285                                                 bformat(_("Error reading module %1$s\n"), from_utf8(modName));
1286                         throw ExceptionMessage(WarningException, _("Read Error"), msg);
1287                 }
1288         }
1289         return doc_class;
1290 }
1291
1292
1293 /////////////////////////////////////////////////////////////////////////
1294 //
1295 // DocumentClass
1296 //
1297 /////////////////////////////////////////////////////////////////////////
1298
1299 DocumentClass::DocumentClass(LayoutFile const & tc)
1300         : TextClass(tc)
1301 {}
1302
1303
1304 bool DocumentClass::hasLaTeXLayout(std::string const & lay) const
1305 {
1306         LayoutList::const_iterator it  = layoutlist_.begin();
1307         LayoutList::const_iterator end = layoutlist_.end();
1308         for (; it != end; ++it)
1309                 if (it->latexname() == lay)
1310                         return true;
1311         return false;
1312 }
1313
1314
1315 bool DocumentClass::provides(string const & p) const
1316 {
1317         return provides_.find(p) != provides_.end();
1318 }
1319
1320
1321 bool DocumentClass::hasTocLevels() const
1322 {
1323         return min_toclevel_ != Layout::NOT_IN_TOC;
1324 }
1325
1326
1327 Layout const & DocumentClass::htmlTOCLayout() const
1328 {
1329         if (html_toc_section_.empty()) {
1330                 // we're going to look for the layout with the minimum toclevel
1331                 TextClass::LayoutList::const_iterator lit = begin();
1332                 TextClass::LayoutList::const_iterator const len = end();
1333                 int minlevel = 1000;
1334                 Layout const * lay = NULL;
1335                 for (; lit != len; ++lit) {
1336                         int const level = lit->toclevel;
1337                         // we don't want Part
1338                         if (level == Layout::NOT_IN_TOC || level < 0 || level >= minlevel)
1339                                 continue;
1340                         lay = &*lit;
1341                         minlevel = level;
1342                 }
1343                 if (lay)
1344                         html_toc_section_ = lay->name();
1345                 else
1346                         // hmm. that is very odd, so we'll do our best
1347                         html_toc_section_ = defaultLayoutName();
1348         }
1349         return operator[](html_toc_section_);
1350 }
1351
1352
1353 string const & DocumentClass::getCiteFormat(string const & entry_type) const
1354 {
1355         static string default_format = "{%author%[[%author%, ]][[{%editor%[[%editor%, %ed_text%, ]]}]]}\"%title%\"{%journal%[[, {!<i>!}%journal%{!</i>!}]][[{%publisher%[[, %publisher%]][[{%institution%[[, %institution%]]}]]}]]}{%year%[[ (%year%)]]}{%pages%[[, %pages%]]}.";
1356         
1357         map<string, string>::const_iterator it = cite_formats_.find(entry_type);
1358         if (it != cite_formats_.end())
1359                 return it->second;
1360         return default_format;
1361 }
1362
1363
1364 /////////////////////////////////////////////////////////////////////////
1365 //
1366 // PageSides
1367 //
1368 /////////////////////////////////////////////////////////////////////////
1369
1370 ostream & operator<<(ostream & os, PageSides p)
1371 {
1372         switch (p) {
1373         case OneSide:
1374                 os << '1';
1375                 break;
1376         case TwoSides:
1377                 os << '2';
1378                 break;
1379         }
1380         return os;
1381 }
1382
1383
1384 } // namespace lyx