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