3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Lars Gullik Bjønnes
7 * \author Jean-Marc Lasgouttes
10 * Full author contact details are available in file CREDITS.
20 #include "output_xhtml.h"
21 #include "TextClass.h"
23 #include "support/debug.h"
24 #include "support/lassert.h"
25 #include "support/lstrings.h"
26 #include "support/Messages.h"
27 #include "support/regex.h"
28 #include "support/textutils.h"
32 using namespace lyx::support;
36 /// Special value of toclevel for layouts that to not belong in a TOC
37 const int Layout::NOT_IN_TOC = -1000;
39 // The order of the LayoutTags enum is no more important. [asierra300396]
55 LT_PARBREAK_IS_NEWLINE,
64 LT_LABELSTRING_APPENDIX,
106 LT_INTITLE // keep this last!
109 /////////////////////
114 margintype = MARGIN_STATIC;
115 latextype = LATEX_PARAGRAPH;
121 labelfont = inherit_font;
123 reslabelfont = sane_font;
124 nextnoindent = false;
129 labelbottomsep = 0.0;
131 align = LYX_ALIGN_BLOCK;
132 alignpossible = LYX_ALIGN_NONE | LYX_ALIGN_LAYOUT;
133 labeltype = LABEL_NO_LABEL;
134 endlabeltype = END_LABEL_NO_LABEL;
135 // Should or should not. That is the question.
136 // spacing.set(Spacing::OneHalf);
137 newline_allowed = true;
138 free_spacing = false;
140 parbreak_is_newline = false;
141 toclevel = NOT_IN_TOC;
143 htmllabelfirst_ = false;
144 htmlforcecss_ = false;
152 bool Layout::read(Lexer & lex, TextClass const & tclass)
154 // This table is sorted alphabetically [asierra 30March96]
155 LexerKeyword layoutTags[] = {
156 { "align", LT_ALIGN },
157 { "alignpossible", LT_ALIGNPOSSIBLE },
158 { "babelpreamble", LT_BABELPREAMBLE },
159 { "bottomsep", LT_BOTTOMSEP },
160 { "category", LT_CATEGORY },
161 { "commanddepth", LT_COMMANDDEPTH },
162 { "copystyle", LT_COPYSTYLE },
163 { "dependson", LT_DEPENDSON },
165 { "endlabelstring", LT_ENDLABELSTRING },
166 { "endlabeltype", LT_ENDLABELTYPE },
168 { "freespacing", LT_FREE_SPACING },
169 { "htmlattr", LT_HTMLATTR },
170 { "htmlforcecss", LT_HTMLFORCECSS },
171 { "htmlitem", LT_HTMLITEM },
172 { "htmlitemattr", LT_HTMLITEMATTR },
173 { "htmllabel", LT_HTMLLABEL },
174 { "htmllabelattr", LT_HTMLLABELATTR },
175 { "htmllabelfirst", LT_HTMLLABELFIRST },
176 { "htmlpreamble", LT_HTMLPREAMBLE },
177 { "htmlstyle", LT_HTMLSTYLE },
178 { "htmltag", LT_HTMLTAG },
179 { "htmltitle", LT_HTMLTITLE },
180 { "innertag", LT_INNERTAG },
181 { "inpreamble", LT_INPREAMBLE },
182 { "intitle", LT_INTITLE },
183 { "itemsep", LT_ITEMSEP },
184 { "itemtag", LT_ITEMTAG },
185 { "keepempty", LT_KEEPEMPTY },
186 { "labelbottomsep", LT_LABEL_BOTTOMSEP },
187 { "labelcounter", LT_LABELCOUNTER },
188 { "labelfont", LT_LABELFONT },
189 { "labelindent", LT_LABELINDENT },
190 { "labelsep", LT_LABELSEP },
191 { "labelstring", LT_LABELSTRING },
192 { "labelstringappendix", LT_LABELSTRING_APPENDIX },
193 { "labeltag", LT_LABELTAG },
194 { "labeltype", LT_LABELTYPE },
195 { "langpreamble", LT_LANGPREAMBLE },
196 { "latexname", LT_LATEXNAME },
197 { "latexparam", LT_LATEXPARAM },
198 { "latextype", LT_LATEXTYPE },
199 { "leftmargin", LT_LEFTMARGIN },
200 { "margin", LT_MARGIN },
201 { "needprotect", LT_NEED_PROTECT },
202 { "newline", LT_NEWLINE },
203 { "nextnoindent", LT_NEXTNOINDENT },
204 { "obsoletedby", LT_OBSOLETEDBY },
205 { "optionalargs", LT_OPTARGS },
206 { "parbreakisnewline", LT_PARBREAK_IS_NEWLINE },
207 { "parindent", LT_PARINDENT },
208 { "parsep", LT_PARSEP },
209 { "parskip", LT_PARSKIP },
210 { "passthru", LT_PASS_THRU },
211 { "preamble", LT_PREAMBLE },
212 { "refprefix", LT_REFPREFIX },
213 { "requiredargs", LT_REQARGS },
214 { "requires", LT_REQUIRES },
215 { "rightmargin", LT_RIGHTMARGIN },
216 { "spacing", LT_SPACING },
217 { "spellcheck", LT_SPELLCHECK },
218 { "textfont", LT_TEXTFONT },
219 { "toclevel", LT_TOCLEVEL },
220 { "topsep", LT_TOPSEP }
224 bool finished = false;
225 lex.pushTable(layoutTags);
227 // parse style section
228 while (!finished && lex.isOK() && !error) {
230 // See comment in LyXRC.cpp.
232 case Lexer::LEX_FEOF:
235 case Lexer::LEX_UNDEF:
237 lex.printError("Unknown layout tag `$$Token'");
244 switch (static_cast<LayoutTags>(le)) {
256 style = subst(style, '_', ' ');
258 if (tclass.hasLayout(style)) {
259 docstring const tmpname = name_;
260 this->operator=(tclass[style]);
263 LYXERR0("Cannot copy unknown style `"
265 << "All layouts so far:");
266 DocumentClass::const_iterator lit = tclass.begin();
267 DocumentClass::const_iterator len = tclass.end();
268 for (; lit != len; ++lit)
269 LYXERR0(lit->name());
274 case LT_OBSOLETEDBY: {
277 style = subst(style, '_', ' ');
279 if (tclass.hasLayout(style)) {
280 docstring const tmpname = name_;
281 this->operator=(tclass[style]);
283 if (obsoleted_by().empty())
284 obsoleted_by_ = style;
286 LYXERR0("Cannot replace with unknown style `"
289 //lex.printError("Cannot replace with"
298 depends_on_ = subst(depends_on_, '_', ' ');
329 case LT_NEED_PROTECT:
338 font = lyxRead(lex, font);
343 font = lyxRead(lex, font);
347 labelfont = lyxRead(lex, labelfont);
350 case LT_NEXTNOINDENT:
354 case LT_COMMANDDEPTH:
364 latexparam_ = subst(latexparam_, """, "\"");
380 preamble_ = from_utf8(lex.getLongString("EndPreamble"));
383 case LT_LANGPREAMBLE:
384 langpreamble_ = from_utf8(lex.getLongString("EndLangPreamble"));
387 case LT_BABELPREAMBLE:
388 babelpreamble_ = from_utf8(lex.getLongString("EndBabelPreamble"));
395 case LT_ENDLABELTYPE:
396 readEndLabelType(lex);
431 case LT_LABEL_BOTTOMSEP:
432 lex >> labelbottomsep;
437 labelsep = subst(labelsep, 'x', ' ');
445 lex >> newline_allowed;
452 case LT_ALIGNPOSSIBLE:
453 readAlignPossible(lex);
457 // FIXME: this means LT_ENDLABELSTRING may only
458 // occur after LT_LABELSTRING
460 labelstring_ = trim(labelstring_);
461 labelstring_appendix_ = labelstring_;
464 case LT_ENDLABELSTRING:
465 lex >> endlabelstring_;
466 endlabelstring_ = trim(endlabelstring_);
469 case LT_LABELSTRING_APPENDIX:
470 lex >> labelstring_appendix_;
471 labelstring_appendix_ = trim(labelstring_appendix_);
474 case LT_LABELCOUNTER:
476 counter = trim(counter);
479 case LT_FREE_SPACING:
487 case LT_PARBREAK_IS_NEWLINE:
488 lex >> parbreak_is_newline;
497 vector<string> const req =
498 getVectorFromString(lex.getString());
499 requires_.insert(req.begin(), req.end());
525 case LT_HTMLITEMATTR:
526 lex >> htmlitemattr_;
530 lex >> htmllabeltag_;
533 case LT_HTMLLABELATTR:
534 lex >> htmllabelattr_;
537 case LT_HTMLLABELFIRST:
538 lex >> htmllabelfirst_;
542 htmlstyle_ = from_utf8(lex.getLongString("EndHTMLStyle"));
545 case LT_HTMLFORCECSS:
546 lex >> htmlforcecss_;
549 case LT_HTMLPREAMBLE:
550 htmlpreamble_ = from_utf8(lex.getLongString("EndPreamble"));
563 // make sure we only have inpreamble = true for commands
564 if (inpreamble && latextype != LATEX_COMMAND && latextype != LATEX_PARAGRAPH) {
565 LYXERR0("InPreamble not permitted except with command and paragraph layouts.");
566 LYXERR0("Layout name: " << name());
570 return finished && !error;
583 LexerKeyword alignTags[] = {
584 { "block", AT_BLOCK },
585 { "center", AT_CENTER },
586 { "layout", AT_LAYOUT },
588 { "right", AT_RIGHT }
592 void Layout::readAlign(Lexer & lex)
594 PushPopHelper pph(lex, alignTags);
597 case Lexer::LEX_UNDEF:
598 lex.printError("Unknown alignment `$$Token'");
604 align = LYX_ALIGN_BLOCK;
607 align = LYX_ALIGN_LEFT;
610 align = LYX_ALIGN_RIGHT;
613 align = LYX_ALIGN_CENTER;
616 align = LYX_ALIGN_LAYOUT;
622 void Layout::readAlignPossible(Lexer & lex)
624 lex.pushTable(alignTags);
625 alignpossible = LYX_ALIGN_NONE | LYX_ALIGN_LAYOUT;
626 int lineno = lex.lineNumber();
630 case Lexer::LEX_UNDEF:
631 lex.printError("Unknown alignment `$$Token'");
637 alignpossible |= LYX_ALIGN_BLOCK;
640 alignpossible |= LYX_ALIGN_LEFT;
643 alignpossible |= LYX_ALIGN_RIGHT;
646 alignpossible |= LYX_ALIGN_CENTER;
649 alignpossible |= LYX_ALIGN_LAYOUT;
652 } while (lineno == lex.lineNumber());
657 void Layout::readLabelType(Lexer & lex)
663 LA_CENTERED_TOP_ENVIRONMENT,
673 LexerKeyword labelTypeTags[] = {
674 { "bibliography", LA_BIBLIO },
675 { "centered_top_environment", LA_CENTERED_TOP_ENVIRONMENT },
676 { "counter", LA_COUNTER },
677 { "enumerate", LA_ENUMERATE },
678 { "itemize", LA_ITEMIZE },
679 { "manual", LA_MANUAL },
680 { "no_label", LA_NO_LABEL },
681 { "sensitive", LA_SENSITIVE },
682 { "static", LA_STATIC },
683 { "top_environment", LA_TOP_ENVIRONMENT }
686 PushPopHelper pph(lex, labelTypeTags);
689 case Lexer::LEX_UNDEF:
690 lex.printError("Unknown labeltype tag `$$Token'");
696 labeltype = LABEL_NO_LABEL;
699 labeltype = LABEL_MANUAL;
701 case LA_TOP_ENVIRONMENT:
702 labeltype = LABEL_TOP_ENVIRONMENT;
704 case LA_CENTERED_TOP_ENVIRONMENT:
705 labeltype = LABEL_CENTERED_TOP_ENVIRONMENT;
708 labeltype = LABEL_STATIC;
711 labeltype = LABEL_SENSITIVE;
714 labeltype = LABEL_COUNTER;
717 labeltype = LABEL_ENUMERATE;
720 labeltype = LABEL_ITEMIZE;
723 labeltype = LABEL_BIBLIO;
729 void Layout::readEndLabelType(Lexer & lex)
731 static LexerKeyword endlabelTypeTags[] = {
732 { "box", END_LABEL_BOX },
733 { "filled_box", END_LABEL_FILLED_BOX },
734 { "no_label", END_LABEL_NO_LABEL },
735 { "static", END_LABEL_STATIC }
738 PushPopHelper pph(lex, endlabelTypeTags);
741 case Lexer::LEX_UNDEF:
742 lex.printError("Unknown labeltype tag `$$Token'");
744 case END_LABEL_STATIC:
746 case END_LABEL_FILLED_BOX:
747 case END_LABEL_NO_LABEL:
748 endlabeltype = static_cast<EndLabelType>(le);
751 LYXERR0("Unhandled value " << le);
757 void Layout::readMargin(Lexer & lex)
759 LexerKeyword marginTags[] = {
760 { "dynamic", MARGIN_DYNAMIC },
761 { "first_dynamic", MARGIN_FIRST_DYNAMIC },
762 { "manual", MARGIN_MANUAL },
763 { "right_address_box", MARGIN_RIGHT_ADDRESS_BOX },
764 { "static", MARGIN_STATIC }
767 PushPopHelper pph(lex, marginTags);
771 case Lexer::LEX_UNDEF:
772 lex.printError("Unknown margin type tag `$$Token'");
777 case MARGIN_FIRST_DYNAMIC:
778 case MARGIN_RIGHT_ADDRESS_BOX:
779 margintype = static_cast<MarginType>(le);
782 LYXERR0("Unhandled value " << le);
788 void Layout::readLatexType(Lexer & lex)
790 LexerKeyword latexTypeTags[] = {
791 { "bib_environment", LATEX_BIB_ENVIRONMENT },
792 { "command", LATEX_COMMAND },
793 { "environment", LATEX_ENVIRONMENT },
794 { "item_environment", LATEX_ITEM_ENVIRONMENT },
795 { "list_environment", LATEX_LIST_ENVIRONMENT },
796 { "paragraph", LATEX_PARAGRAPH }
799 PushPopHelper pph(lex, latexTypeTags);
802 case Lexer::LEX_UNDEF:
803 lex.printError("Unknown latextype tag `$$Token'");
805 case LATEX_PARAGRAPH:
807 case LATEX_ENVIRONMENT:
808 case LATEX_ITEM_ENVIRONMENT:
809 case LATEX_BIB_ENVIRONMENT:
810 case LATEX_LIST_ENVIRONMENT:
811 latextype = static_cast<LatexType>(le);
814 LYXERR0("Unhandled value " << le);
820 void Layout::readSpacing(Lexer & lex)
823 ST_SPACING_SINGLE = 1,
829 LexerKeyword spacingTags[] = {
830 {"double", ST_SPACING_DOUBLE },
831 {"onehalf", ST_SPACING_ONEHALF },
832 {"other", ST_OTHER },
833 {"single", ST_SPACING_SINGLE }
836 PushPopHelper pph(lex, spacingTags);
839 case Lexer::LEX_UNDEF:
840 lex.printError("Unknown spacing token `$$Token'");
845 case ST_SPACING_SINGLE:
846 spacing.set(Spacing::Single);
848 case ST_SPACING_ONEHALF:
849 spacing.set(Spacing::Onehalf);
851 case ST_SPACING_DOUBLE:
852 spacing.set(Spacing::Double);
856 spacing.set(Spacing::Other, lex.getString());
864 docstring const i18npreamble(Language const * lang, Encoding const & enc,
865 docstring const & templ, bool const polyglossia)
870 string preamble = polyglossia ?
871 subst(to_utf8(templ), "$$lang", lang->polyglossia()) :
872 subst(to_utf8(templ), "$$lang", lang->babel());
875 // tex2lyx does not have getMessages()
876 LASSERT(false, /**/);
879 string const langenc = lang->encoding()->iconvName();
880 string const texenc = lang->encoding()->latexName();
881 string const bufenc = enc.iconvName();
882 // First and second character of plane 15 (Private Use Area)
883 string const s1 = "\xf3\xb0\x80\x80"; // U+F0000
884 string const s2 = "\xf3\xb0\x80\x81"; // U+F0001
886 // lyx::regex is not unicode-safe.
887 // Should use QRegExp or (boost::u32regex, but that requires ICU)
888 static regex const reg("_\\(([^\\)]+)\\)");
890 while (regex_search(preamble, sub, reg)) {
891 string const key = sub.str(1);
892 string translated = to_utf8(lang->translateLayout(key));
893 if (langenc != bufenc)
894 translated = "\\inputencoding{" + texenc + "}"
895 + s1 + langenc + s2 + translated
897 preamble = subst(preamble, sub.str(), translated);
900 return from_utf8(preamble);
906 docstring const Layout::langpreamble(Language const * lang,
907 Encoding const & enc, bool const polyglossia) const
909 return i18npreamble(lang, enc, langpreamble_, polyglossia);
913 docstring const Layout::babelpreamble(Language const * lang,
914 Encoding const & enc, bool const polyglossia) const
916 return i18npreamble(lang, enc, babelpreamble_, polyglossia);
920 string const & Layout::htmltag() const
922 if (htmltag_.empty())
928 string const & Layout::htmlattr() const
930 if (htmlattr_.empty())
931 htmlattr_ = "class=\"" + defaultCSSClass() + "\"";
936 string const & Layout::htmlitemtag() const
938 if (htmlitemtag_.empty())
939 htmlitemtag_ = "div";
944 string const & Layout::htmlitemattr() const
946 if (htmlitemattr_.empty())
947 htmlitemattr_ = "class=\"" + defaultCSSItemClass() + "\"";
948 return htmlitemattr_;
952 string const & Layout::htmllabeltag() const
954 if (htmllabeltag_.empty()) {
955 if (labeltype != LABEL_TOP_ENVIRONMENT &&
956 labeltype != LABEL_CENTERED_TOP_ENVIRONMENT)
957 htmllabeltag_ = "span";
959 htmllabeltag_ = "div";
961 return htmllabeltag_;
965 string const & Layout::htmllabelattr() const
967 if (htmllabelattr_.empty())
968 htmllabelattr_ = "class=\"" + defaultCSSLabelClass() + "\"";
969 return htmllabelattr_;
973 docstring Layout::htmlstyle() const {
974 if (!htmlstyle_.empty() && !htmlforcecss_)
976 if (htmldefaultstyle_.empty())
978 docstring retval = htmldefaultstyle_;
979 if (!htmlstyle_.empty())
980 retval += '\n' + htmlstyle_;
985 string Layout::defaultCSSClass() const
987 if (!defaultcssclass_.empty())
988 return defaultcssclass_;
990 docstring::const_iterator it = name().begin();
991 docstring::const_iterator en = name().end();
992 for (; it != en; ++it) {
993 char_type const c = *it;
994 if (!isAlphaASCII(c)) {
996 // make sure we don't start with an underscore,
997 // as that sometimes causes problems.
998 d = from_ascii("lyx_");
1001 } else if (islower(c))
1004 // this is slow, so do it only if necessary
1007 defaultcssclass_ = to_utf8(d);
1008 return defaultcssclass_;
1013 string makeMarginValue(char const * side, double d) {
1015 os << "margin-" << side << ": " << d << "ex;\n";
1021 void Layout::makeDefaultCSS() const {
1022 // this never needs to be redone, since reloading layouts will
1023 // wipe out what we did before.
1024 if (!htmldefaultstyle_.empty())
1028 htmldefaultstyle_ = font.asCSS();
1033 tmp += makeMarginValue("top", topsep);
1035 tmp += makeMarginValue("bottom", bottomsep);
1036 if (!leftmargin.empty()) {
1037 // we can't really do what LyX does with the margin, so
1038 // we'll just figure out how many characters it is
1039 int const len = leftmargin.length();
1040 tmp += makeMarginValue("left", len);
1042 if (!rightmargin.empty()) {
1043 int const len = rightmargin.length();
1044 tmp += makeMarginValue("right", len);
1048 if (!htmldefaultstyle_.empty())
1049 htmldefaultstyle_ += from_ascii("\n");
1050 htmldefaultstyle_ += from_ascii(tmp);
1053 // tex2lyx does not see output_xhtml.cpp
1056 string where = alignmentToCSS(align);
1057 if (!where.empty()) {
1058 htmldefaultstyle_ += from_ascii("text-align: " + where + ";\n");
1062 // wrap up what we have, if anything
1063 if (!htmldefaultstyle_.empty())
1065 from_ascii(htmltag() + "." + defaultCSSClass() + " {\n") +
1066 htmldefaultstyle_ + from_ascii("\n}\n");
1068 if (labeltype == LABEL_NO_LABEL || htmllabeltag() == "NONE")
1074 if (labelfont != font)
1075 labelCSS = labelfont.asCSS() + from_ascii("\n");
1076 if (labeltype == LABEL_CENTERED_TOP_ENVIRONMENT)
1077 labelCSS += from_ascii("text-align: center;\n");
1079 if (!labelCSS.empty())
1080 htmldefaultstyle_ +=
1081 from_ascii(htmllabeltag() + "." + defaultCSSLabelClass() + " {\n") +
1082 labelCSS + from_ascii("\n}\n");
1086 bool Layout::operator==(Layout const & rhs) const
1088 // This is enough for the applications we actually make,
1089 // at least at the moment. But we could check more.
1090 return name() == rhs.name()
1091 && latexname() == rhs.latexname()
1092 && latextype == rhs.latextype;