]> git.lyx.org Git - lyx.git/blob - src/TextClass.cpp
Use rich text in XHTML output, too.
[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 string const & TextClass::prerequisites() const
1007
1008         if (contains(prerequisites_, ',')) {
1009                 vector<string> const pres = getVectorFromString(prerequisites_);
1010                 prerequisites_ = getStringFromVector(pres, "\n\t");
1011         }
1012         return prerequisites_; 
1013 }
1014
1015 bool TextClass::hasLayout(docstring const & n) const
1016 {
1017         docstring const name = n.empty() ? defaultLayoutName() : n;
1018
1019         return find_if(layoutlist_.begin(), layoutlist_.end(),
1020                        LayoutNamesEqual(name))
1021                 != layoutlist_.end();
1022 }
1023
1024
1025 bool TextClass::hasInsetLayout(docstring const & n) const
1026 {
1027         if (n.empty()) 
1028                 return false;
1029         InsetLayouts::const_iterator it = insetlayoutlist_.begin();
1030         InsetLayouts::const_iterator en = insetlayoutlist_.end();
1031         for (; it != en; ++it)
1032                 if (n == it->first)
1033                         return true;
1034         return false;
1035 }
1036
1037
1038 Layout const & TextClass::operator[](docstring const & name) const
1039 {
1040         LASSERT(!name.empty(), /**/);
1041
1042         const_iterator it = 
1043                 find_if(begin(), end(), LayoutNamesEqual(name));
1044
1045         if (it == end()) {
1046                 lyxerr << "We failed to find the layout '" << to_utf8(name)
1047                        << "' in the layout list. You MUST investigate!"
1048                        << endl;
1049                 for (const_iterator cit = begin(); cit != end(); ++cit)
1050                         lyxerr  << " " << to_utf8(cit->name()) << endl;
1051
1052                 // we require the name to exist
1053                 LASSERT(false, /**/);
1054         }
1055
1056         return *it;
1057 }
1058
1059
1060 Layout & TextClass::operator[](docstring const & name)
1061 {
1062         LASSERT(!name.empty(), /**/);
1063
1064         iterator it = find_if(begin(), end(), LayoutNamesEqual(name));
1065
1066         if (it == end()) {
1067                 LYXERR0("We failed to find the layout '" << to_utf8(name)
1068                        << "' in the layout list. You MUST investigate!");
1069                 for (const_iterator cit = begin(); cit != end(); ++cit)
1070                         LYXERR0(" " << to_utf8(cit->name()));
1071
1072                 // we require the name to exist
1073                 LASSERT(false, /**/);
1074         }
1075
1076         return *it;
1077 }
1078
1079
1080 bool TextClass::deleteLayout(docstring const & name)
1081 {
1082         if (name == defaultLayoutName() || name == plainLayoutName())
1083                 return false;
1084
1085         LayoutList::iterator it =
1086                 remove_if(layoutlist_.begin(), layoutlist_.end(),
1087                           LayoutNamesEqual(name));
1088
1089         LayoutList::iterator end = layoutlist_.end();
1090         bool const ret = (it != end);
1091         layoutlist_.erase(it, end);
1092         return ret;
1093 }
1094
1095
1096 // Load textclass info if not loaded yet
1097 bool TextClass::load(string const & path) const
1098 {
1099         if (loaded_)
1100                 return true;
1101
1102         // Read style-file, provided path is searched before the system ones
1103         // If path is a file, it is loaded directly.
1104         FileName layout_file(path);
1105         if (!path.empty() && !layout_file.isReadableFile())
1106                 layout_file = FileName(addName(path, name_ + ".layout"));
1107         if (layout_file.empty() || !layout_file.exists())
1108                 layout_file = libFileSearch("layouts", name_, "layout");
1109         loaded_ = const_cast<TextClass*>(this)->read(layout_file);
1110
1111         if (!loaded_) {
1112                 lyxerr << "Error reading `"
1113                        << to_utf8(makeDisplayPath(layout_file.absFilename()))
1114                        << "'\n(Check `" << name_
1115                        << "')\nCheck your installation and "
1116                         "try Options/Reconfigure..." << endl;
1117         }
1118
1119         return loaded_;
1120 }
1121
1122
1123 void DocumentClass::addLayoutIfNeeded(docstring const & n) const
1124 {
1125         if (!hasLayout(n))
1126                 layoutlist_.push_back(createBasicLayout(n, true));
1127 }
1128
1129
1130 InsetLayout const & DocumentClass::insetLayout(docstring const & name) const 
1131 {
1132         // FIXME The fix for the InsetLayout part of 4812 would be here:
1133         // Add the InsetLayout to the document class if it is not found.
1134         docstring n = name;
1135         InsetLayouts::const_iterator cen = insetlayoutlist_.end();
1136         while (!n.empty()) {
1137                 InsetLayouts::const_iterator cit = insetlayoutlist_.lower_bound(n);
1138                 if (cit != cen && cit->first == n)
1139                         return cit->second;
1140                 size_t i = n.find(':');
1141                 if (i == string::npos)
1142                         break;
1143                 n = n.substr(0, i);
1144         }
1145         return plain_insetlayout_;
1146 }
1147
1148
1149 docstring const & TextClass::defaultLayoutName() const
1150 {
1151         // This really should come from the actual layout... (Lgb)
1152         return defaultlayout_;
1153 }
1154
1155
1156 Layout const & TextClass::defaultLayout() const
1157 {
1158         return operator[](defaultLayoutName());
1159 }
1160
1161
1162 bool TextClass::isDefaultLayout(Layout const & layout) const 
1163 {
1164         return layout.name() == defaultLayoutName();
1165 }
1166
1167
1168 bool TextClass::isPlainLayout(Layout const & layout) const 
1169 {
1170         return layout.name() == plainLayoutName();
1171 }
1172
1173
1174 Layout TextClass::createBasicLayout(docstring const & name, bool unknown) const
1175 {
1176         static Layout * defaultLayout = NULL;
1177
1178         if (defaultLayout) {
1179                 defaultLayout->setUnknown(unknown);
1180                 defaultLayout->setName(name);
1181                 return *defaultLayout;
1182         }
1183
1184         static char const * s = "Margin Static\n"
1185                         "LatexType Paragraph\n"
1186                         "LatexName dummy\n"
1187                         "Align Block\n"
1188                         "AlignPossible Left, Right, Center\n"
1189                         "LabelType No_Label\n"
1190                         "End";
1191         istringstream ss(s);
1192         Lexer lex(textClassTags);
1193         lex.setStream(ss);
1194         defaultLayout = new Layout;
1195         defaultLayout->setUnknown(unknown);
1196         defaultLayout->setName(name);
1197         if (!readStyle(lex, *defaultLayout)) {
1198                 // The only way this happens is because the hardcoded layout above
1199                 // is wrong.
1200                 LASSERT(false, /**/);
1201         };
1202         return *defaultLayout;
1203 }
1204
1205
1206 /////////////////////////////////////////////////////////////////////////
1207 //
1208 // DocumentClassBundle
1209 //
1210 /////////////////////////////////////////////////////////////////////////
1211
1212 DocumentClassBundle::~DocumentClassBundle()
1213 {
1214         for (size_t i = 0; i != documentClasses_.size(); ++i)
1215                 delete documentClasses_[i];
1216         documentClasses_.clear();
1217 }
1218
1219 DocumentClass & DocumentClassBundle::newClass(LayoutFile const & baseClass)
1220 {
1221         DocumentClass * dc = new DocumentClass(baseClass);
1222         documentClasses_.push_back(dc);
1223         return *documentClasses_.back();
1224 }
1225
1226
1227 DocumentClassBundle & DocumentClassBundle::get()
1228 {
1229         static DocumentClassBundle singleton; 
1230         return singleton; 
1231 }
1232
1233
1234 DocumentClass & DocumentClassBundle::makeDocumentClass(
1235                 LayoutFile const & baseClass, LayoutModuleList const & modlist)
1236 {
1237         DocumentClass & doc_class = newClass(baseClass);
1238         LayoutModuleList::const_iterator it = modlist.begin();
1239         LayoutModuleList::const_iterator en = modlist.end();
1240         for (; it != en; it++) {
1241                 string const modName = *it;
1242                 LyXModule * lm = theModuleList[modName];
1243                 if (!lm) {
1244                         docstring const msg =
1245                                                 bformat(_("The module %1$s has been requested by\n"
1246                                                 "this document but has not been found in the list of\n"
1247                                                 "available modules. If you recently installed it, you\n"
1248                                                 "probably need to reconfigure LyX.\n"), from_utf8(modName));
1249                         ExceptionMessage(WarningException,_("Module not available"),
1250                                         msg + _("Some layouts may not be available."));
1251                         continue;
1252                 }
1253                 if (!lm->isAvailable()) {
1254                         docstring const msg =
1255                                                 bformat(_("The module %1$s requires a package that is\n"
1256                                                 "not available in your LaTeX installation. LaTeX output\n"
1257                                                 "may not be possible.\n"), from_utf8(modName));
1258                         ExceptionMessage(WarningException, _("Package not available"), msg);
1259                 }
1260                 FileName layout_file = libFileSearch("layouts", lm->getFilename());
1261                 if (!doc_class.read(layout_file, TextClass::MODULE)) {
1262                         docstring const msg =
1263                                                 bformat(_("Error reading module %1$s\n"), from_utf8(modName));
1264                         throw ExceptionMessage(WarningException, _("Read Error"), msg);
1265                 }
1266         }
1267         return doc_class;
1268 }
1269
1270
1271 /////////////////////////////////////////////////////////////////////////
1272 //
1273 // DocumentClass
1274 //
1275 /////////////////////////////////////////////////////////////////////////
1276
1277 DocumentClass::DocumentClass(LayoutFile const & tc)
1278         : TextClass(tc)
1279 {}
1280
1281
1282 bool DocumentClass::hasLaTeXLayout(std::string const & lay) const
1283 {
1284         LayoutList::const_iterator it  = layoutlist_.begin();
1285         LayoutList::const_iterator end = layoutlist_.end();
1286         for (; it != end; ++it)
1287                 if (it->latexname() == lay)
1288                         return true;
1289         return false;
1290 }
1291
1292
1293 bool DocumentClass::provides(string const & p) const
1294 {
1295         return provides_.find(p) != provides_.end();
1296 }
1297
1298
1299 bool DocumentClass::hasTocLevels() const
1300 {
1301         return min_toclevel_ != Layout::NOT_IN_TOC;
1302 }
1303
1304
1305 Layout const & DocumentClass::htmlTOCLayout() const
1306 {
1307         if (html_toc_section_.empty()) {
1308                 // we're going to look for the layout with the minimum toclevel
1309                 TextClass::LayoutList::const_iterator lit = begin();
1310                 TextClass::LayoutList::const_iterator const len = end();
1311                 int minlevel = 1000;
1312                 Layout const * lay = NULL;
1313                 for (; lit != len; ++lit) {
1314                         int const level = lit->toclevel;
1315                         // we don't want Part
1316                         if (level == Layout::NOT_IN_TOC || level < 0 || level >= minlevel)
1317                                 continue;
1318                         lay = &*lit;
1319                         minlevel = level;
1320                 }
1321                 if (lay)
1322                         html_toc_section_ = lay->name();
1323                 else
1324                         // hmm. that is very odd, so we'll do our best
1325                         html_toc_section_ = defaultLayoutName();
1326         }
1327         return operator[](html_toc_section_);
1328 }
1329
1330
1331 /////////////////////////////////////////////////////////////////////////
1332 //
1333 // PageSides
1334 //
1335 /////////////////////////////////////////////////////////////////////////
1336
1337 ostream & operator<<(ostream & os, PageSides p)
1338 {
1339         switch (p) {
1340         case OneSide:
1341                 os << '1';
1342                 break;
1343         case TwoSides:
1344                 os << '2';
1345                 break;
1346         }
1347         return os;
1348 }
1349
1350
1351 } // namespace lyx