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