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