From 3883b85f49054c109cb8a9a293721a5e41edb68d Mon Sep 17 00:00:00 2001 From: Thibaut Cuvelier Date: Mon, 8 Jun 2020 23:27:49 +0200 Subject: [PATCH] New DocBook support --- src/BiblioInfo.cpp | 75 ++- src/BiblioInfo.h | 3 + src/Buffer.cpp | 103 ++- src/Buffer.h | 2 +- src/Converter.cpp | 6 +- src/Converter.h | 6 +- src/Floating.cpp | 44 +- src/Floating.h | 18 +- src/Layout.cpp | 236 ++++++- src/Layout.h | 63 ++ src/OutputParams.cpp | 4 +- src/OutputParams.h | 19 + src/Paragraph.cpp | 352 ++++++++-- src/Paragraph.h | 28 +- src/TextClass.cpp | 47 +- src/TextClass.h | 11 +- src/insets/Inset.cpp | 5 +- src/insets/Inset.h | 2 +- src/insets/InsetArgument.h | 2 +- src/insets/InsetBibtex.cpp | 342 +++++++++- src/insets/InsetBibtex.h | 2 + src/insets/InsetBox.cpp | 4 +- src/insets/InsetBox.h | 2 +- src/insets/InsetBranch.cpp | 11 +- src/insets/InsetBranch.h | 2 +- src/insets/InsetCaption.cpp | 23 +- src/insets/InsetCaption.h | 4 +- src/insets/InsetCaptionable.cpp | 13 + src/insets/InsetCaptionable.h | 2 + src/insets/InsetCitation.cpp | 29 +- src/insets/InsetCitation.h | 2 +- src/insets/InsetCommand.cpp | 4 +- src/insets/InsetCommand.h | 2 +- src/insets/InsetCounter.cpp | 3 +- src/insets/InsetCounter.h | 2 +- src/insets/InsetERT.cpp | 16 +- src/insets/InsetERT.h | 2 +- src/insets/InsetExternal.cpp | 41 +- src/insets/InsetExternal.h | 4 +- src/insets/InsetFloat.cpp | 200 +++++- src/insets/InsetFloat.h | 2 +- src/insets/InsetFloatList.h | 2 +- src/insets/InsetFoot.cpp | 12 +- src/insets/InsetFoot.h | 2 +- src/insets/InsetGraphics.cpp | 72 +- src/insets/InsetGraphics.h | 2 +- src/insets/InsetHyperlink.cpp | 12 +- src/insets/InsetHyperlink.h | 2 +- src/insets/InsetIPA.cpp | 8 + src/insets/InsetIPA.h | 2 + src/insets/InsetIPAMacro.cpp | 56 +- src/insets/InsetIPAMacro.h | 4 +- src/insets/InsetInclude.cpp | 105 +-- src/insets/InsetInclude.h | 2 +- src/insets/InsetIndex.cpp | 182 +++++- src/insets/InsetIndex.h | 2 +- src/insets/InsetLabel.cpp | 12 +- src/insets/InsetLabel.h | 2 +- src/insets/InsetLine.cpp | 6 +- src/insets/InsetLine.h | 3 +- src/insets/InsetListings.cpp | 40 ++ src/insets/InsetListings.h | 2 + src/insets/InsetMarginal.cpp | 16 +- src/insets/InsetMarginal.h | 2 +- src/insets/InsetNewline.cpp | 10 +- src/insets/InsetNewline.h | 2 +- src/insets/InsetNewpage.cpp | 7 +- src/insets/InsetNewpage.h | 2 +- src/insets/InsetNomencl.cpp | 113 ++-- src/insets/InsetNomencl.h | 7 +- src/insets/InsetNote.cpp | 22 +- src/insets/InsetNote.h | 2 +- src/insets/InsetPhantom.cpp | 17 +- src/insets/InsetPhantom.h | 2 +- src/insets/InsetQuotes.cpp | 7 +- src/insets/InsetQuotes.h | 2 +- src/insets/InsetRef.cpp | 67 +- src/insets/InsetRef.h | 2 +- src/insets/InsetScript.cpp | 10 +- src/insets/InsetScript.h | 2 +- src/insets/InsetSeparator.cpp | 6 +- src/insets/InsetSeparator.h | 2 +- src/insets/InsetSpace.cpp | 41 +- src/insets/InsetSpace.h | 2 +- src/insets/InsetSpecialChar.cpp | 45 +- src/insets/InsetSpecialChar.h | 2 +- src/insets/InsetTOC.cpp | 7 +- src/insets/InsetTOC.h | 2 +- src/insets/InsetTabular.cpp | 257 ++++---- src/insets/InsetTabular.h | 9 +- src/insets/InsetText.cpp | 53 +- src/insets/InsetText.h | 6 +- src/insets/InsetVSpace.cpp | 7 +- src/insets/InsetVSpace.h | 2 +- src/insets/InsetWrap.cpp | 14 +- src/insets/InsetWrap.h | 2 +- src/mathed/InsetMathHull.cpp | 71 +- src/mathed/InsetMathHull.h | 2 +- src/mathed/InsetMathRef.cpp | 20 +- src/mathed/InsetMathRef.h | 2 +- src/output_docbook.cpp | 1087 +++++++++++++++++++++++-------- src/output_docbook.h | 20 +- src/xml.cpp | 21 +- src/xml.h | 20 +- 104 files changed, 3190 insertions(+), 1070 deletions(-) diff --git a/src/BiblioInfo.cpp b/src/BiblioInfo.cpp index f43d4149ab..4c2f5497db 100644 --- a/src/BiblioInfo.cpp +++ b/src/BiblioInfo.cpp @@ -22,7 +22,7 @@ #include "Encoding.h" #include "InsetIterator.h" #include "Language.h" -#include "output_xhtml.h" +#include "xml.h" #include "Paragraph.h" #include "TextClass.h" #include "TocBackend.h" @@ -1591,4 +1591,77 @@ string citationStyleToString(const CitationStyle & cs, bool const latex) return cmd; } + +docstring authorsToDocBookAuthorGroup(docstring const & authorsString, XMLStream & xs, Buffer const & buf) +{ + // This function closely mimics getAuthorList, but produces DocBook instead of text. + // It has been greatly simplified, as the complete list of authors is always produced. No separators are required, + // as the output has a database-like shape. + // constructName has also been merged within, as it becomes really simple and leads to no copy-paste. + + if (authorsString.empty()) { + return docstring(); + } + + // Split the input list of authors into individual authors. + vector const authors = getAuthors(authorsString); + + // Retrieve the "et al." variation. + string const etal = buf.params().documentClass().getCiteMacro(buf.params().citeEngineType(), "_etal"); + + // Output the list of authors. + xs << xml::StartTag("authorgroup"); + auto it = authors.cbegin(); + auto en = authors.cend(); + for (size_t i = 0; it != en; ++it, ++i) { + xs << xml::StartTag("author"); + xs << xml::CR(); + xs << xml::StartTag("personname"); + xs << xml::CR(); + docstring name = *it; + + // All authors go in a . If more structure is known, use it; otherwise (just "et al."), print it as such. + if (name == "others") { + xs << buf.B_(etal); + } else { + name_parts parts = nameParts(name); + if (! parts.prefix.empty()) { + xs << xml::StartTag("honorific"); + xs << parts.prefix; + xs << xml::EndTag("honorific"); + xs << xml::CR(); + } + if (! parts.prename.empty()) { + xs << xml::StartTag("firstname"); + xs << parts.prename; + xs << xml::EndTag("firstname"); + xs << xml::CR(); + } + if (! parts.surname.empty()) { + xs << xml::StartTag("surname"); + xs << parts.surname; + xs << xml::EndTag("surname"); + xs << xml::CR(); + } + if (! parts.suffix.empty()) { + xs << xml::StartTag("othername", "role=\"suffix\""); + xs << parts.suffix; + xs << xml::EndTag("othername"); + xs << xml::CR(); + } + } + + xs << xml::EndTag("personname"); + xs << xml::CR(); + xs << xml::EndTag("author"); + xs << xml::CR(); + + // Could add an affiliation after , but not stored in BibTeX. + } + xs << xml::EndTag("authorgroup"); + xs << xml::CR(); + + return docstring(); +} + } // namespace lyx diff --git a/src/BiblioInfo.h b/src/BiblioInfo.h index 3ef1ead5f3..4509101fd3 100644 --- a/src/BiblioInfo.h +++ b/src/BiblioInfo.h @@ -35,6 +35,9 @@ CitationStyle citationStyleFromString(std::string const & latex_str, /// the other way round std::string citationStyleToString(CitationStyle const &, bool const latex = false); +/// Transforms the information about authors into a (directly written to a XMLStream). +docstring authorsToDocBookAuthorGroup(docstring const & authorsString, XMLStream & xs, Buffer const & buf); + /// Class to represent information about a BibTeX or /// bibliography entry. diff --git a/src/Buffer.cpp b/src/Buffer.cpp index eb2a4ef6d1..bfb0dbb33d 100644 --- a/src/Buffer.cpp +++ b/src/Buffer.cpp @@ -46,11 +46,11 @@ #include "LyX.h" #include "LyXRC.h" #include "LyXVC.h" -#include "output_docbook.h" #include "output.h" #include "output_latex.h" -#include "output_xhtml.h" +#include "output_docbook.h" #include "output_plaintext.h" +#include "output_xhtml.h" #include "Paragraph.h" #include "ParagraphParameters.h" #include "ParIterator.h" @@ -2112,7 +2112,7 @@ Buffer::ExportStatus Buffer::makeDocBookFile(FileName const & fname, updateMacroInstances(OutputUpdate); ExportStatus const retval = - writeDocBookSource(ofs, fname.absFileName(), runparams, output); + writeDocBookSource(ofs, runparams, output); if (retval == ExportKilled) return ExportKilled; @@ -2123,85 +2123,56 @@ Buffer::ExportStatus Buffer::makeDocBookFile(FileName const & fname, } -Buffer::ExportStatus Buffer::writeDocBookSource(odocstream & os, string const & fname, +Buffer::ExportStatus Buffer::writeDocBookSource(odocstream & os, OutputParams const & runparams, OutputWhat output) const { LaTeXFeatures features(*this, params(), runparams); validate(features); + d->bibinfo_.makeCitationLabels(*this); d->texrow.reset(); DocumentClass const & tclass = params().documentClass(); - string const & top_element = tclass.latexname(); bool const output_preamble = output == FullSource || output == OnlyPreamble; bool const output_body = output == FullSource || output == OnlyBody; + XMLStream xs(os); + if (output_preamble) { - if (runparams.flavor == OutputParams::DOCBOOK5) - os << "\n"; + // XML preamble, no doctype needed. + // Not using XMLStream for this, as the root tag would be in the tag stack and make troubles with the error + // detection mechanisms (these are called before the end tag is output, and thus interact with the canary + // parsep in output_docbook.cpp). + os << "\n" + << "\n"; - // FIXME UNICODE - os << "code(); + string params = "xml:lang=\"" + languageCode + '"' + + " xmlns=\"http://docbook.org/ns/docbook\"" + + " xmlns:xlink=\"http://www.w3.org/1999/xlink\"" + + " xmlns:m=\"http://www.w3.org/1998/Math/MathML\"" + + " xmlns:xi=\"http://www.w3.org/2001/XInclude\"" + + " version=\"5.2\""; - // FIXME UNICODE - if (! tclass.class_header().empty()) - os << from_ascii(tclass.class_header()); - else if (runparams.flavor == OutputParams::DOCBOOK5) - os << "PUBLIC \"-//OASIS//DTD DocBook XML V4.2//EN\" " - << "\"https://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd\""; - else - os << " PUBLIC \"-//OASIS//DTD DocBook V4.2//EN\""; - - docstring preamble = params().preamble; - if (runparams.flavor != OutputParams::DOCBOOK5 ) { - preamble += "\n"; - preamble += "\n"; - preamble += "\n"; - preamble += "\n"; - } - - string const name = runparams.nice - ? changeExtension(absFileName(), ".sgml") : fname; - preamble += features.getIncludedFiles(name); - preamble += features.getLyXSGMLEntities(); - - if (!preamble.empty()) { - os << "\n [ " << preamble << " ]"; - } - os << ">\n\n"; + os << "<" << from_ascii(tclass.docbookroot()) << " " << from_ascii(params) << ">\n"; } if (output_body) { - string top = top_element; - top += " lang=\""; - if (runparams.flavor == OutputParams::DOCBOOK5) - top += params().language->code(); - else - top += params().language->code().substr(0, 2); - top += '"'; - - if (!params().options.empty()) { - top += ' '; - top += params().options; - } - - os << "\n"; - params().documentClass().counters().reset(); - xml::openTag(os, top); - os << '\n'; - try { - docbookParagraphs(text(), *this, os, runparams); - } - catch (ConversionException const &) { return ExportKilled; } - xml::closeTag(os, top_element); + // Start to output the document. + docbookParagraphs(text(), *this, xs, runparams); + } + + if (output_preamble) { + // Close the root element. + os << "\n"; } return ExportSuccess; } @@ -4132,8 +4103,9 @@ unique_ptr Buffer::getSourceCode(odocstream & os, string const & format, // Probably should have some routine with a signature like them. writePlaintextParagraph(*this, text().paragraphs()[par_begin], os, runparams, dummy); - } else if (params().isDocBook()) { - docbookParagraphs(text(), *this, os, runparams); + } else if (runparams.flavor == OutputParams::DOCBOOK5) { + XMLStream xs{os}; + docbookParagraphs(text(), *this, xs, runparams); } else { // If we are previewing a paragraph, even if this is the // child of some other buffer, let's cut the link here, @@ -4185,8 +4157,8 @@ unique_ptr Buffer::getSourceCode(odocstream & os, string const & format, os << "% "<< _("Plain text does not have a preamble."); } else writePlaintextFile(*this, os, runparams); - } else if (params().isDocBook()) { - writeDocBookSource(os, absFileName(), runparams, output); + } else if (runparams.flavor == OutputParams::DOCBOOK5) { + writeDocBookSource(os, runparams, output); } else { // latex or literate otexstream ots(os); @@ -4495,8 +4467,9 @@ Buffer::ExportStatus Buffer::doExport(string const & target, bool put_in_tempdir return ExportKilled; } else if (backend_format == "lyx") writeFile(FileName(filename)); - // Docbook backend - else if (params().isDocBook()) { + // DocBook backend + else if (backend_format == "docbook5") { + runparams.flavor = OutputParams::DOCBOOK5; runparams.nice = !put_in_tempdir; if (makeDocBookFile(FileName(filename), runparams) == ExportKilled) return ExportKilled; diff --git a/src/Buffer.h b/src/Buffer.h index 8eb6e06494..40d4e11a30 100644 --- a/src/Buffer.h +++ b/src/Buffer.h @@ -335,7 +335,7 @@ public: OutputParams const & runparams_in, OutputWhat output = FullSource) const; /// - ExportStatus writeDocBookSource(odocstream & os, std::string const & filename, + ExportStatus writeDocBookSource(odocstream & os, OutputParams const & runparams_in, OutputWhat output = FullSource) const; /// diff --git a/src/Converter.cpp b/src/Converter.cpp index 5de38b6cca..59604d3127 100644 --- a/src/Converter.cpp +++ b/src/Converter.cpp @@ -105,7 +105,7 @@ private: Converter::Converter(string const & f, string const & t, string const & c, string const & l) : from_(f), to_(t), command_(c), flags_(l), - From_(nullptr), To_(nullptr), latex_(false), xml_(false), + From_(nullptr), To_(nullptr), latex_(false), docbook_(false), need_aux_(false), nice_(false), need_auth_(false) {} @@ -122,7 +122,7 @@ void Converter::readFlags() latex_flavor_ = flag_value.empty() ? "latex" : flag_value; } else if (flag_name == "xml") - xml_ = true; + docbook_ = true; else if (flag_name == "needaux") { need_aux_ = true; latex_flavor_ = flag_value.empty() ? @@ -276,7 +276,7 @@ OutputParams::FLAVOR Converters::getFlavor(Graph::EdgePath const & path, if (conv.latex_flavor() == "pdflatex") return OutputParams::PDFLATEX; } - if (conv.xml()) + if (conv.docbook()) return OutputParams::DOCBOOK5; } return buffer ? buffer->params().getOutputFlavor() diff --git a/src/Converter.h b/src/Converter.h index 684895e984..fe95be6ed8 100644 --- a/src/Converter.h +++ b/src/Converter.h @@ -74,7 +74,7 @@ public: /// std::string const latex_flavor() const { return latex_flavor_; } /// - bool xml() const { return xml_; } + bool docbook() const { return docbook_; } /// bool need_aux() const { return need_aux_; } /// Return whether or not the needauth option is set for this converter @@ -108,8 +108,8 @@ private: bool latex_; /// The latex derivate trivstring latex_flavor_; - /// The converter is xml - bool xml_; + /// The converter is DocBook + bool docbook_; /// This converter needs the .aux files bool need_aux_; /// we need a "nice" file from the backend, c.f. OutputParams.nice. diff --git a/src/Floating.cpp b/src/Floating.cpp index 3dbc0a9f94..ebd1e41c09 100644 --- a/src/Floating.cpp +++ b/src/Floating.cpp @@ -30,7 +30,8 @@ Floating::Floating(string const & type, string const & placement, string const & listName, std::string const & listCmd, string const & refPrefix, std::string const & allowedplacement, string const & htmlTag, string const & htmlAttrib, - docstring const & htmlStyle, string const & required, + docstring const & htmlStyle, string const & docbookTag, + string const & docbookAttr, string const & required, bool usesfloat, bool ispredefined, bool allowswide, bool allowssideways) : floattype_(type), placement_(placement), ext_(ext), within_(within), @@ -38,7 +39,8 @@ Floating::Floating(string const & type, string const & placement, refprefix_(refPrefix), allowedplacement_(allowedplacement), required_(required), usesfloatpkg_(usesfloat), ispredefined_(ispredefined), allowswide_(allowswide), allowssideways_(allowssideways), - html_tag_(htmlTag), html_attrib_(htmlAttrib), html_style_(htmlStyle) + html_tag_(htmlTag), html_attrib_(htmlAttrib), html_style_(htmlStyle), + docbook_tag_(docbookTag), docbook_attr_(docbookAttr) {} @@ -80,4 +82,42 @@ string Floating::defaultCSSClass() const } +string const & Floating::docbookAttr() const +{ + return docbook_attr_; +} + + +string const & Floating::docbookTag(bool hasTitle) const +{ + docbook_tag_ = ""; + if (floattype_ == "figure") { + docbook_tag_ = hasTitle ? "figure" : "informalfigure"; + } else if (floattype_ == "table") { + docbook_tag_ = hasTitle ? "table" : "informaltable"; + } else if (floattype_ == "algorithm") { + // TODO: no good translation for now! Figures are the closest match, as they can contain text. + // Solvable as soon as https://github.com/docbook/docbook/issues/157 has a definitive answer. + docbook_tag_ = "figure"; + } + return docbook_tag_; +} + + +string const & Floating::docbookCaption() const +{ + docbook_caption_ = ""; + if (floattype_ == "figure") { + docbook_caption_ = "title"; + } else if (floattype_ == "table") { + docbook_caption_ = "caption"; + } else if (floattype_ == "algorithm") { + // TODO: no good translation for now! Figures are the closest match, as they can contain text. + // Solvable as soon as https://github.com/docbook/docbook/issues/157 has a definitive answer. + docbook_caption_ = "title"; + } + return docbook_caption_; +} + + } // namespace lyx diff --git a/src/Floating.h b/src/Floating.h index f013ed2270..d10694f92b 100644 --- a/src/Floating.h +++ b/src/Floating.h @@ -37,9 +37,9 @@ public: std::string const & listName, std::string const & listCmd, std::string const & refPrefix, std::string const & allowedplacement, std::string const & htmlType, std::string const & htmlClass, - docstring const & htmlStyle, std::string const & required, - bool usesfloat, bool isprefined, - bool allowswide, bool allowssideways); + docstring const & htmlStyle, std::string const & docbookTag, + std::string const & docbookAttr, std::string const & required, + bool usesfloat, bool isprefined, bool allowswide, bool allowssideways); /// std::string const & floattype() const { return floattype_; } /// @@ -79,6 +79,12 @@ public: std::string const & htmlAttrib() const; /// tag type, defaults to "div" std::string const & htmlTag() const; + /// + std::string const & docbookTag(bool hasTitle = false) const; + /// + std::string const & docbookAttr() const; + /// + std::string const & docbookCaption() const; private: /// std::string defaultCSSClass() const; @@ -120,6 +126,12 @@ private: mutable std::string defaultcssclass_; /// docstring html_style_; + /// DocBook tag + mutable std::string docbook_tag_; + /// attribute (mostly, role) + mutable std::string docbook_caption_; + /// caption tag (mostly, either caption or title) + std::string docbook_attr_; }; diff --git a/src/Layout.cpp b/src/Layout.cpp index 877464f240..04cd74792f 100644 --- a/src/Layout.cpp +++ b/src/Layout.cpp @@ -31,7 +31,7 @@ using namespace lyx::support; namespace lyx { -/// Special value of toclevel for layouts that to not belong in a TOC +/// Special value of toclevel for layouts that do not belong to a TOC const int Layout::NOT_IN_TOC = -1000; // The order of the LayoutTags enum is no more important. [asierra300396] @@ -104,6 +104,21 @@ enum LayoutTags { LT_HTMLPREAMBLE, LT_HTMLSTYLE, LT_HTMLFORCECSS, + LT_DOCBOOKTAG, + LT_DOCBOOKATTR, + LT_DOCBOOKININFO, + LT_DOCBOOKWRAPPERTAG, + LT_DOCBOOKWRAPPERATTR, + LT_DOCBOOKSECTIONTAG, + LT_DOCBOOKITEMWRAPPERTAG, + LT_DOCBOOKITEMWRAPPERATTR, + LT_DOCBOOKITEMTAG, + LT_DOCBOOKITEMATTR, + LT_DOCBOOKITEMLABELTAG, + LT_DOCBOOKITEMLABELATTR, + LT_DOCBOOKITEMINNERTAG, + LT_DOCBOOKITEMINNERATTR, + LT_DOCBOOKFORCEABSTRACTTAG, LT_INPREAMBLE, LT_HTMLTITLE, LT_SPELLCHECK, @@ -204,6 +219,21 @@ bool Layout::readIgnoreForcelocal(Lexer & lex, TextClass const & tclass) { "commanddepth", LT_COMMANDDEPTH }, { "copystyle", LT_COPYSTYLE }, { "dependson", LT_DEPENDSON }, + { "docbookattr", LT_DOCBOOKATTR }, + { "docbookforceabstracttag", LT_DOCBOOKFORCEABSTRACTTAG }, + { "docbookininfo", LT_DOCBOOKININFO }, + { "docbookitemattr", LT_DOCBOOKITEMATTR }, + { "docbookiteminnerattr", LT_DOCBOOKITEMINNERATTR }, + { "docbookiteminnertag", LT_DOCBOOKITEMINNERTAG }, + { "docbookitemlabelattr", LT_DOCBOOKITEMLABELATTR }, + { "docbookitemlabeltag", LT_DOCBOOKITEMLABELTAG }, + { "docbookitemtag", LT_DOCBOOKITEMTAG }, + { "docbookitemwrapperattr", LT_DOCBOOKITEMWRAPPERATTR }, + { "docbookitemwrappertag", LT_DOCBOOKITEMWRAPPERTAG }, + { "docbooksectiontag", LT_DOCBOOKSECTIONTAG }, + { "docbooktag", LT_DOCBOOKTAG }, + { "docbookwrapperattr", LT_DOCBOOKWRAPPERATTR }, + { "docbookwrappertag", LT_DOCBOOKWRAPPERTAG }, { "end", LT_END }, { "endlabelstring", LT_ENDLABELSTRING }, { "endlabeltype", LT_ENDLABELTYPE }, @@ -689,6 +719,66 @@ bool Layout::readIgnoreForcelocal(Lexer & lex, TextClass const & tclass) lex >> htmltitle_; break; + case LT_DOCBOOKTAG: + lex >> docbooktag_; + break; + + case LT_DOCBOOKATTR: + lex >> docbookattr_; + break; + + case LT_DOCBOOKFORCEABSTRACTTAG: + lex >> docbookforceabstracttag_; + break; + + case LT_DOCBOOKININFO: + lex >> docbookininfo_; + break; + + case LT_DOCBOOKWRAPPERTAG: + lex >> docbookwrappertag_; + break; + + case LT_DOCBOOKWRAPPERATTR: + lex >> docbookwrapperattr_; + break; + + case LT_DOCBOOKSECTIONTAG: + lex >> docbooksectiontag_; + break; + + case LT_DOCBOOKITEMWRAPPERTAG: + lex >> docbookitemwrappertag_; + break; + + case LT_DOCBOOKITEMWRAPPERATTR: + lex >> docbookitemwrapperattr_; + break; + + case LT_DOCBOOKITEMTAG: + lex >> docbookitemtag_; + break; + + case LT_DOCBOOKITEMATTR: + lex >> docbookitemattr_; + break; + + case LT_DOCBOOKITEMLABELTAG: + lex >> docbookitemlabeltag_; + break; + + case LT_DOCBOOKITEMLABELATTR: + lex >> docbookitemlabelattr_; + break; + + case LT_DOCBOOKITEMINNERTAG: + lex >> docbookiteminnertag_; + break; + + case LT_DOCBOOKITEMINNERATTR: + lex >> docbookiteminnerattr_; + break; + case LT_SPELLCHECK: lex >> spellcheck; break; @@ -1499,8 +1589,38 @@ void Layout::write(ostream & os) const os << "\tHTMLPreamble\n" << to_utf8(rtrim(htmlpreamble_, "\n")) << "\n\tEndPreamble\n"; - os << "\tHTMLTitle " << htmltitle_ << "\n" - "\tSpellcheck " << spellcheck << "\n" + os << "\tHTMLTitle " << htmltitle_ << "\n"; + if(!docbooktag_.empty()) + os << "\tDocBookTag " << docbooktag_ << '\n'; + if(!docbookattr_.empty()) + os << "\tDocBookAttr " << docbookattr_ << '\n'; + if(!docbookininfo_.empty()) + os << "\tDocBookInInfo " << docbookininfo_ << '\n'; + if(!docbookwrappertag_.empty()) + os << "\tDocBookWrapperTag " << docbookwrappertag_ << '\n'; + if(!docbookwrapperattr_.empty()) + os << "\tDocBookWrapperAttr " << docbookwrapperattr_ << '\n'; + if(!docbooksectiontag_.empty()) + os << "\tDocBookSectionTag " << docbooksectiontag_ << '\n'; + if(!docbookitemtag_.empty()) + os << "\tDocBookItemTag " << docbookitemtag_ << '\n'; + if(!docbookitemattr_.empty()) + os << "\tDocBookItemAttr " << docbookitemattr_ << '\n'; + if(!docbookitemwrappertag_.empty()) + os << "\tDocBookItemTag " << docbookitemwrappertag_ << '\n'; + if(!docbookitemwrapperattr_.empty()) + os << "\tDocBookItemWrapperAttr " << docbookitemwrapperattr_ << '\n'; + if(!docbookitemlabeltag_.empty()) + os << "\tDocBookItemLabelTag " << docbookitemlabeltag_ << '\n'; + if(!docbookitemlabelattr_.empty()) + os << "\tDocBookItemLabelAttr " << docbookitemlabelattr_ << '\n'; + if(!docbookiteminnertag_.empty()) + os << "\tDocBookItemInnerTag " << docbookiteminnertag_ << '\n'; + if(!docbookiteminnerattr_.empty()) + os << "\tDocBookItemInnerAttr " << docbookiteminnerattr_ << '\n'; + if(!docbookforceabstracttag_.empty()) + os << "\tDocBookForceAbstractTag " << docbookforceabstracttag_ << '\n'; + os << "\tSpellcheck " << spellcheck << "\n" "\tForceLocal " << forcelocal << "\n" "End\n"; } @@ -1648,6 +1768,116 @@ string Layout::defaultCSSClass() const } +string const & Layout::docbooktag() const +{ + // No sensible default value, unhappily... + if (docbooktag_.empty()) + docbooktag_ = to_utf8(name_); + return docbooktag_; +} + + +string const & Layout::docbookattr() const +{ + // Perfectly OK to return no attributes, so docbookattr_ does not need to be filled. + return docbookattr_; +} + + +string const & Layout::docbookininfo() const +{ + // Indeed, a trilean. Only titles should be "maybe": otherwise, metadata is "always", content is "never". + if (docbookininfo_.empty() || (docbookininfo_ != "never" && docbookininfo_ != "always" && docbookininfo_ != "maybe")) + docbookininfo_ = "never"; + return docbookininfo_; +} + + +string const & Layout::docbookwrappertag() const +{ + if (docbookwrappertag_.empty()) + docbookwrappertag_ = "NONE"; + return docbookwrappertag_; +} + + +string const & Layout::docbookwrapperattr() const +{ + return docbookwrapperattr_; +} + + +string const & Layout::docbooksectiontag() const +{ + if (docbooksectiontag_.empty()) + docbooksectiontag_ = "section"; + return docbooksectiontag_; +} + + +string const & Layout::docbookitemwrappertag() const +{ + if (docbookitemwrappertag_.empty()) + docbookitemwrappertag_ = "NONE"; + return docbookitemwrappertag_; +} + + +string const & Layout::docbookitemwrapperattr() const +{ + return docbookitemwrapperattr_; +} + + +string const & Layout::docbookitemtag() const +{ + return docbookitemtag_; +} + + +string const & Layout::docbookitemattr() const +{ + return docbookitemattr_; +} + + +string const & Layout::docbookitemlabeltag() const +{ + if (docbookitemlabeltag_.empty()) + docbookitemlabeltag_ = "NONE"; + return docbookitemlabeltag_; +} + + +string const & Layout::docbookitemlabelattr() const +{ + return docbookitemlabelattr_; +} + + +string const & Layout::docbookiteminnertag() const +{ + if (docbookiteminnertag_.empty()) + docbookiteminnertag_ = "NONE"; + return docbookiteminnertag_; +} + + +string const & Layout::docbookiteminnerattr() const +{ + return docbookiteminnerattr_; +} + + +std::string const & Layout::docbookforceabstracttag() const +{ + if (docbookforceabstracttag_.empty()) + docbookforceabstracttag_ = "NONE"; + return docbookforceabstracttag_; +} + + + namespace { string makeMarginValue(char const * side, double d) diff --git a/src/Layout.h b/src/Layout.h index ffc976d8ff..7e9409ad44 100644 --- a/src/Layout.h +++ b/src/Layout.h @@ -193,6 +193,36 @@ public: /// bool htmltitle() const { return htmltitle_; } /// + std::string const & docbooktag() const; + /// + std::string const & docbookattr() const; + /// + std::string const & docbookininfo() const; + /// + std::string const & docbookwrappertag() const; + /// + std::string const & docbookwrapperattr() const; + /// + std::string const & docbooksectiontag() const; + /// + std::string const & docbookitemwrappertag() const; + /// + std::string const & docbookitemwrapperattr() const; + /// + std::string const & docbookitemlabeltag() const; + /// + std::string const & docbookitemlabelattr() const; + /// + std::string const & docbookiteminnertag() const; + /// + std::string const & docbookiteminnerattr() const; + /// + std::string const & docbookitemtag() const; + /// + std::string const & docbookitemattr() const; + /// + std::string const & docbookforceabstracttag() const; + /// bool isParagraph() const { return latextype == LATEX_PARAGRAPH; } /// bool isCommand() const { return latextype == LATEX_COMMAND; } @@ -457,6 +487,39 @@ private: bool htmllabelfirst_; /// CSS information needed by this layout. docstring htmlstyle_; + /// DocBook tag corresponding to this layout. + mutable std::string docbooktag_; + /// Roles to add to docbooktag_, if any (default: none). + mutable std::string docbookattr_; + /// DocBook tag corresponding to this item (mainly for lists). + mutable std::string docbookitemtag_; + /// Roles to add to docbookitemtag_, if any (default: none). + mutable std::string docbookitemattr_; + /// DocBook tag corresponding to the wrapper around an item (mainly for lists). + mutable std::string docbookitemwrappertag_; + /// Roles to add to docbookitemwrappertag_, if any (default: none). + mutable std::string docbookitemwrapperattr_; + /// DocBook tag corresponding to this label (only for description lists; + /// labels in the common sense do not exist with DocBook). + mutable std::string docbookitemlabeltag_; + /// Roles to add to docbooklabeltag_, if any (default: none). + mutable std::string docbookitemlabelattr_; + /// DocBook tag to add within the item, around its direct content (mainly for lists). + mutable std::string docbookiteminnertag_; + /// Roles to add to docbookiteminnertag_, if any (default: none). + mutable std::string docbookiteminnerattr_; + /// DocBook tag corresponding to this wrapper around the main tag. + mutable std::string docbookwrappertag_; + /// Roles to add to docbookwrappertag_, if any (default: none). + mutable std::string docbookwrapperattr_; + /// Outer tag for this section, only if this layout represent a sectionning item, including chapters (default: section). + mutable std::string docbooksectiontag_; + /// Whether this tag must/can/can't go into an tag (default: never, as it only makes sense for metadata). + mutable std::string docbookininfo_; + /// whether this element (root or not) does not accept text without a section(i.e. the first text that is met + /// in LyX must be considered as the abstract if this is true); this text must be output with the specific tag + /// held by this attribute + mutable std::string docbookforceabstracttag_; /// Should we generate the default CSS for this layout, even if HTMLStyle /// has been given? Default is false. /// Note that the default CSS is output first, then the user CSS, so it is diff --git a/src/OutputParams.cpp b/src/OutputParams.cpp index 0409634be3..e2e3332069 100644 --- a/src/OutputParams.cpp +++ b/src/OutputParams.cpp @@ -33,7 +33,9 @@ OutputParams::OutputParams(Encoding const * enc) par_begin(0), par_end(0), lastid(-1), lastpos(0), isLastPar(false), dryrun(false), silent(false), pass_thru(false), html_disable_captions(false), html_in_par(false), - html_make_pars(true), for_toc(false), for_tooltip(false), + html_make_pars(true), docbook_in_par(false), docbook_make_pars(true), + docbook_force_pars(false), docbook_in_float(false), docbook_anchors_to_ignore(std::unordered_set()), + docbook_in_listing(false), for_toc(false), for_tooltip(false), for_search(false), for_preview(false), includeall(false) { // Note: in PreviewLoader::Impl::dumpPreamble diff --git a/src/OutputParams.h b/src/OutputParams.h index f26aa52263..9b4fdbf000 100644 --- a/src/OutputParams.h +++ b/src/OutputParams.h @@ -16,6 +16,7 @@ #include "Changes.h" #include +#include namespace lyx { @@ -350,6 +351,24 @@ public: /// Does the present context even permit paragraphs? bool html_make_pars; + /// Are we already in a paragraph? + bool docbook_in_par; + + /// Does the present context even permit paragraphs? + bool docbook_make_pars; + + /// Are paragraphs mandatory in this context? + bool docbook_force_pars; + + /// Anchors that should not be output (LyX-side identifier, not DocBook-side). + std::unordered_set docbook_anchors_to_ignore; + + /// Is the current context a float (such as a table or a figure)? + bool docbook_in_float; + + /// Is the current context a listing? + bool docbook_in_listing; + /// Are we generating this material for inclusion in a TOC-like entity? bool for_toc; diff --git a/src/Paragraph.cpp b/src/Paragraph.cpp index 5d1a7fe820..2ab27da77e 100644 --- a/src/Paragraph.cpp +++ b/src/Paragraph.cpp @@ -64,6 +64,7 @@ #include "support/lassert.h" #include "support/lstrings.h" #include "support/textutils.h" +#include "output_docbook.h" #include #include @@ -2054,13 +2055,6 @@ docstring Paragraph::expandLabel(Layout const & layout, } -docstring Paragraph::expandDocBookLabel(Layout const & layout, - BufferParams const & bparams) const -{ - return expandParagraphLabel(layout, bparams, false); -} - - docstring Paragraph::expandParagraphLabel(Layout const & layout, BufferParams const & bparams, bool process_appendix) const { @@ -2952,18 +2946,17 @@ string Paragraph::getID(Buffer const &, OutputParams const &) } -pos_type Paragraph::firstWordDocBook(odocstream & os, OutputParams const & runparams) - const +pos_type Paragraph::firstWordDocBook(XMLStream & xs, OutputParams const & runparams) const { pos_type i; for (i = 0; i < size(); ++i) { if (Inset const * inset = getInset(i)) { - inset->docbook(os, runparams); + inset->docbook(xs, runparams); } else { char_type c = d->text_[i]; if (c == ' ') break; - os << xml::escapeChar(c, XMLStream::ESCAPE_ALL); + xs << c; } } return i; @@ -3005,57 +2998,312 @@ bool Paragraph::Private::onlyText(Buffer const & buf, Font const & outerfont, po } -void Paragraph::simpleDocBookOnePar(Buffer const & buf, - odocstream & os, - OutputParams const & runparams, - Font const & outerfont, - pos_type initial) const +namespace { + +void doFontSwitchDocBook(vector & tagsToOpen, + vector & tagsToClose, + bool & flag, FontState curstate, xml::FontTypes type) { - bool emph_flag = false; + if (curstate == FONT_ON) { + tagsToOpen.push_back(docbookStartFontTag(type)); + flag = true; + } else if (flag) { + tagsToClose.push_back(docbookEndFontTag(type)); + flag = false; + } +} - Layout const & style = *d->layout_; - FontInfo font_old = - style.labeltype == LABEL_MANUAL ? style.labelfont : style.font; +class OptionalFontType { +public: + bool has_value; + xml::FontTypes ft; - if (style.pass_thru && !d->onlyText(buf, outerfont, initial)) - os << "]]>"; + OptionalFontType(): has_value(false), ft(xml::FT_EMPH) {} // A possible value at random for ft. + OptionalFontType(xml::FontTypes ft): has_value(true), ft(ft) {} +}; - // parsing main loop - for (pos_type i = initial; i < size(); ++i) { - Font font = getFont(buf.params(), i, outerfont); +OptionalFontType fontShapeToXml(FontShape fs) +{ + switch (fs) { + case ITALIC_SHAPE: + return {xml::FT_ITALIC}; + case SLANTED_SHAPE: + return {xml::FT_SLANTED}; + case SMALLCAPS_SHAPE: + return {xml::FT_SMALLCAPS}; + case UP_SHAPE: + case INHERIT_SHAPE: + return {}; + default: + // the other tags are for internal use + LATTEST(false); + return {}; + } +} - // handle tag - if (font_old.emph() != font.fontInfo().emph()) { - if (font.fontInfo().emph() == FONT_ON) { - os << ""; - emph_flag = true; - } else if (i != initial) { - os << ""; - emph_flag = false; +OptionalFontType fontFamilyToXml(FontFamily fm) +{ + switch (fm) { + case ROMAN_FAMILY: + return {xml::FT_ROMAN}; + break; + case SANS_FAMILY: + return {xml::FT_SANS}; + break; + case TYPEWRITER_FAMILY: + return {xml::FT_TYPE}; + break; + case INHERIT_FAMILY: + return {}; + default: + // the other tags are for internal use + LATTEST(false); + return {}; + } +} + +OptionalFontType fontSizeToXml(FontSize fs) +{ + switch (fs) { + case TINY_SIZE: + return {xml::FT_SIZE_TINY}; + case SCRIPT_SIZE: + return {xml::FT_SIZE_SCRIPT}; + case FOOTNOTE_SIZE: + return {xml::FT_SIZE_FOOTNOTE}; + case SMALL_SIZE: + return {xml::FT_SIZE_SMALL}; + case LARGE_SIZE: + return {xml::FT_SIZE_LARGE}; + case LARGER_SIZE: + return {xml::FT_SIZE_LARGER}; + case LARGEST_SIZE: + return {xml::FT_SIZE_LARGEST}; + case HUGE_SIZE: + return {xml::FT_SIZE_HUGE}; + case HUGER_SIZE: + return {xml::FT_SIZE_HUGER}; + case INCREASE_SIZE: + return {xml::FT_SIZE_INCREASE}; + case DECREASE_SIZE: + return {xml::FT_SIZE_DECREASE}; + case INHERIT_SIZE: + case NORMAL_SIZE: + return {}; + default: + // the other tags are for internal use + LATTEST(false); + return {}; + } + +} + +}// anonymous namespace + + +void Paragraph::simpleDocBookOnePar(Buffer const & buf, + XMLStream & xs, + OutputParams const & runparams, + Font const & outerfont, + bool start_paragraph, bool close_paragraph, + pos_type initial) const +{ + // track whether we have opened these tags + bool emph_flag = false; + bool bold_flag = false; + bool noun_flag = false; + bool ubar_flag = false; + bool dbar_flag = false; + bool sout_flag = false; + bool wave_flag = false; + // shape tags + bool shap_flag = false; + // family tags + bool faml_flag = false; + // size tags + bool size_flag = false; + + Layout const & style = *d->layout_; + + if (start_paragraph) + xs.startDivision(allowEmpty()); + + FontInfo font_old = + style.labeltype == LABEL_MANUAL ? style.labelfont : style.font; + + FontShape curr_fs = INHERIT_SHAPE; + FontFamily curr_fam = INHERIT_FAMILY; + FontSize curr_size = INHERIT_SIZE; + + string const default_family = + buf.masterBuffer()->params().fonts_default_family; + + vector tagsToOpen; + vector tagsToClose; + + // parsing main loop + for (pos_type i = initial; i < size(); ++i) { + // let's not show deleted material in the output + if (isDeleted(i)) + continue; + + Font const font = getFont(buf.masterBuffer()->params(), i, outerfont); + + // emphasis + FontState curstate = font.fontInfo().emph(); + if (font_old.emph() != curstate) + doFontSwitchDocBook(tagsToOpen, tagsToClose, emph_flag, curstate, xml::FT_EMPH); + + // noun + curstate = font.fontInfo().noun(); + if (font_old.noun() != curstate) + doFontSwitchDocBook(tagsToOpen, tagsToClose, noun_flag, curstate, xml::FT_NOUN); + + // underbar + curstate = font.fontInfo().underbar(); + if (font_old.underbar() != curstate) + doFontSwitchDocBook(tagsToOpen, tagsToClose, ubar_flag, curstate, xml::FT_UBAR); + + // strikeout + curstate = font.fontInfo().strikeout(); + if (font_old.strikeout() != curstate) + doFontSwitchDocBook(tagsToOpen, tagsToClose, sout_flag, curstate, xml::FT_SOUT); + + // double underbar + curstate = font.fontInfo().uuline(); + if (font_old.uuline() != curstate) + doFontSwitchDocBook(tagsToOpen, tagsToClose, dbar_flag, curstate, xml::FT_DBAR); + + // wavy line + curstate = font.fontInfo().uwave(); + if (font_old.uwave() != curstate) + doFontSwitchDocBook(tagsToOpen, tagsToClose, wave_flag, curstate, xml::FT_WAVE); + + // bold + // a little hackish, but allows us to reuse what we have. + curstate = (font.fontInfo().series() == BOLD_SERIES ? FONT_ON : FONT_OFF); + if (font_old.series() != font.fontInfo().series()) + doFontSwitchDocBook(tagsToOpen, tagsToClose, bold_flag, curstate, xml::FT_BOLD); + + // Font shape + curr_fs = font.fontInfo().shape(); + FontShape old_fs = font_old.shape(); + if (old_fs != curr_fs) { + if (shap_flag) { + OptionalFontType tag = fontShapeToXml(old_fs); + if (tag.has_value) { + tagsToClose.push_back(docbookEndFontTag(tag.ft)); + } + shap_flag = false; } - } - if (Inset const * inset = getInset(i)) { - inset->docbook(os, runparams); - } else { - char_type c = d->text_[i]; + OptionalFontType tag = fontShapeToXml(curr_fs); + if (tag.has_value) { + tagsToOpen.push_back(docbookStartFontTag(tag.ft)); + } + } - if (style.pass_thru) - os.put(c); - else - os << xml::escapeChar(c, XMLStream::EscapeSettings::ESCAPE_ALL); - } - font_old = font.fontInfo(); - } + // Font family + curr_fam = font.fontInfo().family(); + FontFamily old_fam = font_old.family(); + if (old_fam != curr_fam) { + if (faml_flag) { + OptionalFontType tag = fontFamilyToXml(old_fam); + if (tag.has_value) { + tagsToClose.push_back(docbookEndFontTag(tag.ft)); + } + faml_flag = false; + } + switch (curr_fam) { + case ROMAN_FAMILY: + // we will treat a "default" font family as roman, since we have + // no other idea what to do. + if (default_family != "rmdefault" && default_family != "default") { + tagsToOpen.push_back(docbookStartFontTag(xml::FT_ROMAN)); + faml_flag = true; + } + break; + case SANS_FAMILY: + if (default_family != "sfdefault") { + tagsToOpen.push_back(docbookStartFontTag(xml::FT_SANS)); + faml_flag = true; + } + break; + case TYPEWRITER_FAMILY: + if (default_family != "ttdefault") { + tagsToOpen.push_back(docbookStartFontTag(xml::FT_TYPE)); + faml_flag = true; + } + break; + case INHERIT_FAMILY: + break; + default: + // the other tags are for internal use + LATTEST(false); + break; + } + } - if (emph_flag) { - os << ""; - } + // Font size + curr_size = font.fontInfo().size(); + FontSize old_size = font_old.size(); + if (old_size != curr_size) { + if (size_flag) { + OptionalFontType tag = fontSizeToXml(old_size); + if (tag.has_value) { + tagsToClose.push_back(docbookEndFontTag(tag.ft)); + } + size_flag = false; + } - if (style.free_spacing) - os << '\n'; - if (style.pass_thru && !d->onlyText(buf, outerfont, initial)) - os << "::const_iterator cit = tagsToClose.begin(); + vector::const_iterator cen = tagsToClose.end(); + for (; cit != cen; ++cit) + xs << *cit; + + vector::const_iterator sit = tagsToOpen.begin(); + vector::const_iterator sen = tagsToOpen.end(); + for (; sit != sen; ++sit) + xs << *sit; + + tagsToClose.clear(); + tagsToOpen.clear(); + + if (Inset const * inset = getInset(i)) { + if (!runparams.for_toc || inset->isInToc()) { + OutputParams np = runparams; + np.local_font = &font; + // If the paragraph has size 1, then we are in the "special + // case" where we do not output the containing paragraph info. + if (!inset->getLayout().htmlisblock() && size() != 1) // TODO: htmlisblock here too! + np.docbook_in_par = true; + inset->docbook(xs, np); + } + } else { + char_type c = getUChar(buf.masterBuffer()->params(), runparams, i); + xs << c; + } + font_old = font.fontInfo(); + } + + // FIXME XHTML + // I'm worried about what happens if a branch, say, is itself + // wrapped in some font stuff. I think that will not work. + xs.closeFontTags(); + if (runparams.docbook_in_listing) + xs << xml::CR(); + if (close_paragraph) + xs.endDivision(); } diff --git a/src/Paragraph.h b/src/Paragraph.h index 60b829e813..fb18352dbc 100644 --- a/src/Paragraph.h +++ b/src/Paragraph.h @@ -200,27 +200,29 @@ public: std::string getID(Buffer const & buf, OutputParams const & runparams) const; /// Output the first word of a paragraph, return the position where it left. - pos_type firstWordDocBook(odocstream & os, OutputParams const & runparams) const; + pos_type firstWordDocBook(XMLStream & xs, OutputParams const & runparams) const; /// Output the first word of a paragraph, return the position where it left. pos_type firstWordLyXHTML(XMLStream & xs, OutputParams const & runparams) const; - /// Writes to stream the docbook representation + /// Writes to stream the DocBook representation void simpleDocBookOnePar(Buffer const & buf, - odocstream &, - OutputParams const & runparams, - Font const & outerfont, - pos_type initial = 0) const; + XMLStream &, + OutputParams const & runparams, + Font const & outerfont, + bool start_paragraph = true, + bool close_paragraph = true, + pos_type initial = 0) const; /// \return any material that has had to be deferred until after the /// paragraph has closed. docstring simpleLyXHTMLOnePar(Buffer const & buf, - XMLStream & xs, - OutputParams const & runparams, - Font const & outerfont, - bool start_paragraph = true, - bool close_paragraph = true, - pos_type initial = 0) const; + XMLStream & xs, + OutputParams const & runparams, + Font const & outerfont, + bool start_paragraph = true, + bool close_paragraph = true, + pos_type initial = 0) const; /// bool hasSameLayout(Paragraph const & par) const; @@ -307,8 +309,6 @@ public: /// docstring expandLabel(Layout const &, BufferParams const &) const; /// - docstring expandDocBookLabel(Layout const &, BufferParams const &) const; - /// docstring const & labelString() const; /// the next two functions are for the manual labels docstring const getLabelWidthString() const; diff --git a/src/TextClass.cpp b/src/TextClass.cpp index 91f68f7291..a39ab34f2a 100644 --- a/src/TextClass.cpp +++ b/src/TextClass.cpp @@ -62,7 +62,7 @@ namespace lyx { // You should also run the development/tools/updatelayouts.py script, // to update the format of all of our layout files. // -int const LAYOUT_FORMAT = 81; // rikiheck: GuiName for counters +int const LAYOUT_FORMAT = 82; // dourouc05: DocBook additions. // Layout format for the current lyx file format. Controls which format is @@ -143,7 +143,7 @@ TextClass::TextClass() outputFormat_("latex"), has_output_format_(false), defaultfont_(sane_font), titletype_(TITLE_COMMAND_AFTER), titlename_("maketitle"), min_toclevel_(0), max_toclevel_(0), maxcitenames_(2), - cite_full_author_list_(true), bibintoc_(false) + cite_full_author_list_(true), bibintoc_(false), docbookroot_("article"), docbookforceabstract_(false) { } @@ -216,7 +216,9 @@ enum TextClassTags { TC_FULLAUTHORLIST, TC_OUTLINERNAME, TC_TABLESTYLE, - TC_BIBINTOC + TC_BIBINTOC, + TC_DOCBOOKROOT, + TC_DOCBOOKFORCEABSTRACT }; @@ -239,6 +241,8 @@ LexerKeyword textClassTags[] = { { "defaultfont", TC_DEFAULTFONT }, { "defaultmodule", TC_DEFAULTMODULE }, { "defaultstyle", TC_DEFAULTSTYLE }, + { "docbookforceabstract", TC_DOCBOOKFORCEABSTRACT }, + { "docbookroot", TC_DOCBOOKROOT }, { "excludesmodule", TC_EXCLUDESMODULE }, { "float", TC_FLOAT }, { "format", TC_FORMAT }, @@ -868,10 +872,20 @@ TextClass::ReturnValues TextClass::read(Lexer & lexrc, ReadType rt) error = !readOutlinerName(lexrc); break; - case TC_TABLESTYLE: + case TC_TABLESTYLE: lexrc.next(); tablestyle_ = rtrim(lexrc.getString()); break; + + case TC_DOCBOOKROOT: + if (lexrc.next()) + docbookroot_ = lexrc.getString(); + break; + + case TC_DOCBOOKFORCEABSTRACT: + if (lexrc.next()) + docbookforceabstract_ = lexrc.getBool(); + break; } // end of switch } @@ -994,7 +1008,6 @@ void TextClass::readClassOptions(Lexer & lexrc) CO_PAGESIZE_FORMAT, CO_PAGESTYLE, CO_OTHER, - CO_HEADER, CO_END }; @@ -1002,7 +1015,6 @@ void TextClass::readClassOptions(Lexer & lexrc) {"end", CO_END }, {"fontsize", CO_FONTSIZE }, {"fontsizeformat", CO_FONTSIZE_FORMAT }, - {"header", CO_HEADER }, {"other", CO_OTHER }, {"pagesize", CO_PAGESIZE }, {"pagesizeformat", CO_PAGESIZE_FORMAT }, @@ -1048,10 +1060,6 @@ void TextClass::readClassOptions(Lexer & lexrc) else options_ += ',' + lexrc.getString(); break; - case CO_HEADER: - lexrc.next(); - class_header_ = subst(lexrc.getString(), """, "\""); - break; case CO_END: getout = true; break; @@ -1373,6 +1381,8 @@ bool TextClass::readFloat(Lexer & lexrc) FT_HTMLSTYLE, FT_HTMLATTR, FT_HTMLTAG, + FT_DOCBOOKATTR, + FT_DOCBOOKTAG, FT_LISTCOMMAND, FT_REFPREFIX, FT_ALLOWED_PLACEMENT, @@ -1386,6 +1396,8 @@ bool TextClass::readFloat(Lexer & lexrc) { "allowedplacement", FT_ALLOWED_PLACEMENT }, { "allowssideways", FT_ALLOWS_SIDEWAYS }, { "allowswide", FT_ALLOWS_WIDE }, + { "docbookattr", FT_DOCBOOKATTR }, + { "docbooktag", FT_DOCBOOKTAG }, { "end", FT_END }, { "extension", FT_EXT }, { "guiname", FT_NAME }, @@ -1410,6 +1422,8 @@ bool TextClass::readFloat(Lexer & lexrc) string htmlattr; docstring htmlstyle; string htmltag; + string docbookattr; + string docbooktag; string listname; string listcommand; string name; @@ -1523,6 +1537,14 @@ bool TextClass::readFloat(Lexer & lexrc) lexrc.next(); htmltag = lexrc.getString(); break; + case FT_DOCBOOKATTR: + lexrc.next(); + docbookattr = lexrc.getString(); + break; + case FT_DOCBOOKTAG: + lexrc.next(); + docbooktag = lexrc.getString(); + break; case FT_END: getout = true; break; @@ -1550,8 +1572,9 @@ bool TextClass::readFloat(Lexer & lexrc) } Floating fl(type, placement, ext, within, style, name, listname, listcommand, refprefix, allowed_placement, - htmltag, htmlattr, htmlstyle, required, usesfloat, - ispredefined, allowswide, allowssideways); + htmltag, htmlattr, htmlstyle, docbooktag, docbookattr, + required, usesfloat, ispredefined, allowswide, + allowssideways); floatlist_.newFloat(fl); // each float has its own counter counters_.newCounter(from_ascii(type), from_ascii(within), diff --git a/src/TextClass.h b/src/TextClass.h index 207b868a4b..bf061acd0f 100644 --- a/src/TextClass.h +++ b/src/TextClass.h @@ -303,8 +303,13 @@ protected: docstring htmlpreamble_; /// same, but specifically for CSS information docstring htmlstyles_; - /// the paragraph style to use for TOCs, Bibliography, etc + /// the paragraph style to use for TOCs, bibliography, etc. mutable docstring html_toc_section_; + /// root element when exporting as DocBook + std::string docbookroot_; + /// whether this root element does not accept text without a section (i.e. the first text that is met in LyX must + /// be considered as the abstract if this is true); this text must be output within and + bool docbookforceabstract_; /// latex packages loaded by document class. std::set provides_; /// latex packages requested by document class. @@ -484,6 +489,10 @@ public: docstring const & htmlpreamble() const { return htmlpreamble_; } /// docstring const & htmlstyles() const { return htmlstyles_; } + /// + bool const & docbookforceabstract() const { return docbookforceabstract_; } + /// + std::string const & docbookroot() const { return docbookroot_; } /// Looks for the layout of "highest level", other than Part (or other /// layouts with a negative toc number), for use in constructing TOCs and /// similar information. diff --git a/src/insets/Inset.cpp b/src/insets/Inset.cpp index 904ac81686..b561533998 100644 --- a/src/insets/Inset.cpp +++ b/src/insets/Inset.cpp @@ -29,6 +29,7 @@ #include "FuncStatus.h" #include "MetricsInfo.h" #include "output_xhtml.h" +#include "xml.h" #include "Text.h" #include "TextClass.h" #include "TocBackend.h" @@ -466,9 +467,9 @@ bool Inset::idxUpDown(Cursor &, bool) const } -int Inset::docbook(odocstream &, OutputParams const &) const +void Inset::docbook(XMLStream & xs, OutputParams const &) const { - return 0; + xs << "[[Inset: " << from_ascii(insetName(lyxCode())) << "]]"; } diff --git a/src/insets/Inset.h b/src/insets/Inset.h index 653123073a..4ef73db0ac 100644 --- a/src/insets/Inset.h +++ b/src/insets/Inset.h @@ -341,7 +341,7 @@ public: virtual int plaintext(odocstringstream &, OutputParams const &, size_t max_length = INT_MAX) const = 0; /// docbook output - virtual int docbook(odocstream & os, OutputParams const &) const; + virtual void docbook(XMLStream &, OutputParams const &) const; /// XHTML output /// the inset is expected to write XHTML to the XMLStream /// \return any "deferred" material that should be written outside the diff --git a/src/insets/InsetArgument.h b/src/insets/InsetArgument.h index 8cc3e08722..933d66b32d 100644 --- a/src/insets/InsetArgument.h +++ b/src/insets/InsetArgument.h @@ -54,7 +54,7 @@ public: /// int plaintext(odocstringstream &, OutputParams const &, size_t) const { return 0; } /// - int docbook(odocstream &, OutputParams const &) const { return 0; } + void docbook(XMLStream &, OutputParams const &) const { return; } /// docstring xhtml(XMLStream &, OutputParams const &) const { return docstring(); } diff --git a/src/insets/InsetBibtex.cpp b/src/insets/InsetBibtex.cpp index 2e41a4f3e4..3e59d654ce 100644 --- a/src/insets/InsetBibtex.cpp +++ b/src/insets/InsetBibtex.cpp @@ -27,7 +27,6 @@ #include "FuncStatus.h" #include "LaTeXFeatures.h" #include "output_latex.h" -#include "output_xhtml.h" #include "xml.h" #include "OutputParams.h" #include "PDFOptions.h" @@ -44,6 +43,7 @@ #include "support/ExceptionMessage.h" #include "support/FileNameList.h" #include "support/filetools.h" +#include "support/regex.h" #include "support/gettext.h" #include "support/lstrings.h" #include "support/os.h" @@ -51,6 +51,10 @@ #include "support/textutils.h" #include +#include +#include + +#include using namespace std; using namespace lyx::support; @@ -1075,6 +1079,342 @@ docstring InsetBibtex::xhtml(XMLStream & xs, OutputParams const &) const } +void InsetBibtex::docbook(XMLStream & xs, OutputParams const &) const +{ + BiblioInfo const & bibinfo = buffer().masterBibInfo(); + bool const all_entries = getParam("btprint") == "btPrintAll"; + vector const & cites = + all_entries ? bibinfo.getKeys() : bibinfo.citedEntries(); + + docstring const reflabel = buffer().B_("References"); + + // Tell BiblioInfo our purpose (i.e. generate HTML rich text). + CiteItem ci; + ci.context = CiteItem::Export; + ci.richtext = true; + ci.max_key_size = UINT_MAX; + + // Header for bibliography (title required). + xs << xml::StartTag("bibliography"); + xs << xml::CR(); + xs << xml::StartTag("title"); + xs << reflabel; + xs << xml::EndTag("title") << xml::CR(); + + // Translation between keys in each entry and DocBook tags. + // IDs for publications; list: http://tdg.docbook.org/tdg/5.2/biblioid.html. + vector> biblioId = { // + make_pair("doi", "doi"), + make_pair("isbn", "isbn"), + make_pair("issn", "issn"), + make_pair("isrn", "isrn"), + make_pair("istc", "istc"), + make_pair("lccn", "libraryofcongress"), + make_pair("number", "pubsnumber"), + make_pair("url", "uri") + }; + // Relations between documents. + vector> relations = { // + make_pair("journal", "journal"), + make_pair("booktitle", "book"), + make_pair("series", "series") + }; + // Various things that do not fit DocBook. + vector misc = { "language", "school", "note" }; + + // Store the mapping between BibTeX and DocBook. + map toDocBookTag; + toDocBookTag["fullnames:author"] = "SPECIFIC"; // No direct translation to DocBook: . + toDocBookTag["publisher"] = "SPECIFIC"; // No direct translation to DocBook: . + toDocBookTag["address"] = "SPECIFIC"; // No direct translation to DocBook: . + toDocBookTag["editor"] = "editor"; + toDocBookTag["institution"] = "SPECIFIC"; // No direct translation to DocBook: . + + toDocBookTag["title"] = "title"; + toDocBookTag["volume"] = "volumenum"; + toDocBookTag["edition"] = "edition"; + toDocBookTag["pages"] = "artpagenums"; + + toDocBookTag["abstract"] = "SPECIFIC"; // No direct translation to DocBook: . + toDocBookTag["keywords"] = "SPECIFIC"; // No direct translation to DocBook: . + toDocBookTag["year"] = "SPECIFIC"; // No direct translation to DocBook: . + toDocBookTag["month"] = "SPECIFIC"; // No direct translation to DocBook: . + + toDocBookTag["journal"] = "SPECIFIC"; // No direct translation to DocBook: . + toDocBookTag["booktitle"] = "SPECIFIC"; // No direct translation to DocBook: . + toDocBookTag["series"] = "SPECIFIC"; // No direct translation to DocBook: . + + for (auto const & id: biblioId) + toDocBookTag[id.first] = "SPECIFIC"; // No direct translation to DocBook: . + for (auto const & id: relations) + toDocBookTag[id.first] = "SPECIFIC"; // No direct translation to DocBook: . + for (auto const & id: misc) + toDocBookTag[id] = "SPECIFIC"; // No direct translation to DocBook: . + + // Loop over the entries. If there are no entries, add a comment to say so. + auto vit = cites.begin(); + auto ven = cites.end(); + + if (vit == ven) { + xs << XMLStream::ESCAPE_NONE << ""; + } + + for (; vit != ven; ++vit) { + BiblioInfo::const_iterator const biit = bibinfo.find(*vit); + if (biit == bibinfo.end()) + continue; + + BibTeXInfo const & entry = biit->second; + string const attr = "xml:id=\"" + to_utf8(xml::cleanID(entry.key())) + "\""; + xs << xml::StartTag("biblioentry", attr); + xs << xml::CR(); + + // FIXME Right now, we are calling BibInfo::getInfo on the key, + // which will give us all the cross-referenced info. But for every + // entry, so there's a lot of repetition. This should be fixed. + + // Parse the results of getInfo and emit the corresponding DocBook tags. Interesting pieces have the form + // "STH", the rest of the text may be discarded. + // Could have written a DocBook version of expandFormat (that parses a citation into HTML), but it implements + // some kind of recursion. Still, a (static) conversion step between the citation format and DocBook would have + // been required. All in all, both codes approaches would have been similar, but this parsing allows relying + // on existing building blocks. + + string html = to_utf8(bibinfo.getInfo(entry.key(), buffer(), ci)); + regex tagRegex("([^<]*)"); + smatch match; + auto tagIt = std::sregex_iterator(html.cbegin(), html.cend(), tagRegex, regex_constants::match_default); + auto tagEnd = std::sregex_iterator(); + map delayedTags; + + // Read all tags from HTML and convert those that have a 1:1 matching. + while (tagIt != tagEnd) { + string tag = tagIt->str(); // regex_match cannot work with temporary strings. + ++tagIt; + std::regex_match(tag, match, tagRegex); + + if (toDocBookTag.find(match[1]) == toDocBookTag.end()) { + LYXERR0("The BibTeX field " << match[1].str() << " is unknown."); + xs << XMLStream::ESCAPE_NONE << from_utf8("\n"); + continue; + } + + if (toDocBookTag[match[1]] == "SPECIFIC") { + delayedTags[match[1]] = match[2]; + } else { + xs << xml::StartTag(toDocBookTag[match[1]]); + xs << from_utf8(match[2].str()); + xs << xml::EndTag(toDocBookTag[match[1]]); + } + } + + // Type of document (book, journal paper, etc.). + xs << xml::StartTag("bibliomisc", "role=\"type\""); + xs << entry.entryType(); + xs << xml::EndTag("bibliomisc"); + xs << xml::CR(); + + // Handle tags that have complex transformations. + if (! delayedTags.empty()) { + unsigned long remainingTags = delayedTags.size(); // Used as a workaround. With GCC 7, when erasing all + // elements one by one, some elements may still pop in later on (even though they were deleted previously). + auto hasTag = [&delayedTags](string key) { return delayedTags.find(key) != delayedTags.end(); }; + auto getTag = [&delayedTags](string key) { return from_utf8(delayedTags[key]); }; + auto eraseTag = [&delayedTags, &remainingTags](string key) { + remainingTags -= 1; + delayedTags.erase(key); + }; + + // Notes on order of checks. + // - address goes with publisher if there is one, so check this first. Otherwise, the address goes with + // the entry without other details. + + // + if (hasTag("publisher")) { + xs << xml::StartTag("publisher"); + xs << xml::CR(); + xs << xml::StartTag("publishername"); + xs << getTag("publisher"); + xs << xml::EndTag("publishername"); + xs << xml::CR(); + + if (hasTag("address")) { + xs << xml::StartTag("address"); + xs << getTag("address"); + xs << xml::EndTag("address"); + eraseTag("address"); + } + + xs << xml::EndTag("publisher"); + xs << xml::CR(); + eraseTag("publisher"); + } + + if (hasTag("address")) { + xs << xml::StartTag("address"); + xs << getTag("address"); + xs << xml::EndTag("address"); + eraseTag("address"); + } + + // + if (hasTag("keywords")) { + // Split the keywords on comma. + docstring keywordSet = getTag("keywords"); + vector keywords; + if (keywordSet.find(from_utf8(",")) == string::npos) { + keywords = { keywordSet }; + } else { + size_t pos = 0; + while ((pos = keywordSet.find(from_utf8(","))) != string::npos) { + keywords.push_back(keywordSet.substr(0, pos)); + keywordSet.erase(0, pos + 1); + } + keywords.push_back(keywordSet); + } + + xs << xml::StartTag("keywordset") << xml::CR(); + for (auto & kw: keywords) { + kw.erase(kw.begin(), std::find_if(kw.begin(), kw.end(), + [](int c) {return !std::isspace(c);})); + xs << xml::StartTag("keyword"); + xs << kw; + xs << xml::EndTag("keyword"); + xs << xml::CR(); + } + xs << xml::EndTag("keywordset") << xml::CR(); + eraseTag("keywords"); + } + + // + // Example: http://tdg.docbook.org/tdg/5.1/biblioset.html + if (hasTag("year")) { + docstring value = getTag("year"); + eraseTag("year"); + + // Follow xsd:gYearMonth format (http://books.xmlschemata.org/relaxng/ch19-77135.html). + if (hasTag("month")) { + value += "-" + getTag("month"); + eraseTag("month"); + } + + xs << xml::StartTag("pubdate"); + xs << value; + xs << xml::EndTag("pubdate"); + xs << xml::CR(); + } + + // + if (hasTag("institution")) { + xs << xml::StartTag("org"); + xs << xml::CR(); + xs << xml::StartTag("orgname"); + xs << getTag("institution"); + xs << xml::EndTag("orgname"); + xs << xml::CR(); + xs << xml::EndTag("org"); + xs << xml::CR(); + eraseTag("institution"); + } + + // + // Example: http://tdg.docbook.org/tdg/5.1/biblioset.html + for (auto const & id: relations) { + if (hasTag(id.first)) { + xs << xml::StartTag("biblioset", "relation=\"" + id.second + "\""); + xs << xml::CR(); + xs << xml::StartTag("title"); + xs << getTag(id.first); + xs << xml::EndTag("title"); + xs << xml::CR(); + xs << xml::EndTag("biblioset"); + xs << xml::CR(); + eraseTag(id.first); + } + } + + // + // Example: http://tdg.docbook.org/tdg/5.1/authorgroup.html + if (hasTag("fullnames:author")) { + // Perform full parsing of the BibTeX string, dealing with the many corner cases that might + // be encountered. + authorsToDocBookAuthorGroup(getTag("fullnames:author"), xs, buffer()); + eraseTag("fullnames:author"); + } + + // + if (hasTag("abstract")) { + // Split the paragraphs on new line. + docstring abstract = getTag("abstract"); + vector paragraphs; + if (abstract.find(from_utf8("\n")) == string::npos) { + paragraphs = { abstract }; + } else { + size_t pos = 0; + while ((pos = abstract.find(from_utf8(","))) != string::npos) { + paragraphs.push_back(abstract.substr(0, pos)); + abstract.erase(0, pos + 1); + } + paragraphs.push_back(abstract); + } + + xs << xml::StartTag("abstract"); + xs << xml::CR(); + for (auto const & para: paragraphs) { + if (para.empty()) + continue; + xs << xml::StartTag("para"); + xs << para; + xs << xml::EndTag("para"); + } + xs << xml::CR(); + xs << xml::EndTag("abstract"); + xs << xml::CR(); + eraseTag("abstract"); + } + + // + for (auto const & id: biblioId) { + if (hasTag(id.first)) { + xs << xml::StartTag("biblioid", "class=\"" + id.second + "\""); + xs << getTag(id.first); + xs << xml::EndTag("biblioid"); + xs << xml::CR(); + eraseTag(id.first); + } + } + + // + for (auto const & id: misc) { + if (hasTag(id)) { + xs << xml::StartTag("bibliomisc", "role=\"" + id + "\""); + xs << getTag(id); + xs << xml::EndTag("bibliomisc"); + xs << xml::CR(); + eraseTag(id); + } + } + + // After all tags are processed, check for errors. + if (remainingTags > 0) { + LYXERR0("Still delayed tags not yet handled."); + xs << XMLStream::ESCAPE_NONE << from_utf8("\n"); + } + } + + xs << xml::EndTag("biblioentry"); + xs << xml::CR(); + } + + // Footer for bibliography. + xs << xml::EndTag("bibliography"); +} + + void InsetBibtex::write(ostream & os) const { params().Write(os, &buffer()); diff --git a/src/insets/InsetBibtex.h b/src/insets/InsetBibtex.h index 073b376016..932fb0f48a 100644 --- a/src/insets/InsetBibtex.h +++ b/src/insets/InsetBibtex.h @@ -66,6 +66,8 @@ public: /// docstring xhtml(XMLStream &, OutputParams const &) const; /// + void docbook(XMLStream &, OutputParams const &) const; + /// std::string contextMenuName() const; //@} diff --git a/src/insets/InsetBox.cpp b/src/insets/InsetBox.cpp index 3e8612d4fa..c61efe6d01 100644 --- a/src/insets/InsetBox.cpp +++ b/src/insets/InsetBox.cpp @@ -715,9 +715,9 @@ int InsetBox::plaintext(odocstringstream & os, } -int InsetBox::docbook(odocstream & os, OutputParams const & runparams) const +void InsetBox::docbook(XMLStream & xs, OutputParams const & runparams) const { - return InsetText::docbook(os, runparams); + InsetText::docbook(xs, runparams); } diff --git a/src/insets/InsetBox.h b/src/insets/InsetBox.h index a1f4466958..418a242772 100644 --- a/src/insets/InsetBox.h +++ b/src/insets/InsetBox.h @@ -133,7 +133,7 @@ public: int plaintext(odocstringstream & ods, OutputParams const & op, size_t max_length = INT_MAX) const; /// - int docbook(odocstream &, OutputParams const &) const; + void docbook(XMLStream &, OutputParams const &) const; /// docstring xhtml(XMLStream &, OutputParams const &) const; /// diff --git a/src/insets/InsetBranch.cpp b/src/insets/InsetBranch.cpp index db987ba0d3..a70ff27463 100644 --- a/src/insets/InsetBranch.cpp +++ b/src/insets/InsetBranch.cpp @@ -24,6 +24,7 @@ #include "Lexer.h" #include "LyX.h" #include "OutputParams.h" +#include "output_docbook.h" #include "output_xhtml.h" #include "TextClass.h" #include "TocBackend.h" @@ -329,10 +330,14 @@ int InsetBranch::plaintext(odocstringstream & os, } -int InsetBranch::docbook(odocstream & os, - OutputParams const & runparams) const +void InsetBranch::docbook(XMLStream & xs, OutputParams const & runparams) const { - return producesOutput() ? InsetText::docbook(os, runparams) : 0; + if (producesOutput()) { + OutputParams rp = runparams; + rp.par_begin = 0; + rp.par_end = text().paragraphs().size(); + docbookParagraphs(text(), buffer(), xs, rp); + } } diff --git a/src/insets/InsetBranch.h b/src/insets/InsetBranch.h index 1723fd1484..42b11d215d 100644 --- a/src/insets/InsetBranch.h +++ b/src/insets/InsetBranch.h @@ -76,7 +76,7 @@ private: int plaintext(odocstringstream & ods, OutputParams const & op, size_t max_length = INT_MAX) const; /// - int docbook(odocstream &, OutputParams const &) const; + void docbook(XMLStream &, OutputParams const &) const; /// docstring xhtml(XMLStream &, OutputParams const &) const; /// diff --git a/src/insets/InsetCaption.cpp b/src/insets/InsetCaption.cpp index 42f951989c..029162563a 100644 --- a/src/insets/InsetCaption.cpp +++ b/src/insets/InsetCaption.cpp @@ -27,6 +27,7 @@ #include "Language.h" #include "LyXRC.h" #include "MetricsInfo.h" +#include "xml.h" #include "output_latex.h" #include "output_xhtml.h" #include "OutputParams.h" @@ -291,14 +292,9 @@ int InsetCaption::plaintext(odocstringstream & os, } -int InsetCaption::docbook(odocstream & os, - OutputParams const & runparams) const +void InsetCaption::docbook(XMLStream &, OutputParams const &) const { - int ret; - os << ""; - ret = InsetText::docbook(os, runparams); - os << "\n"; - return ret; + // This function should never be called (rather InsetFloat::docbook, the titles should be skipped in floats). } @@ -364,6 +360,19 @@ int InsetCaption::getCaptionAsPlaintext(odocstream & os, } +void InsetCaption::getCaptionAsDocBook(XMLStream & xs, + OutputParams const & runparams) const +{ + if (runparams.docbook_in_float) + return; + + // Ignore full_label_, as the DocBook processor will deal with the numbering. + InsetText::XHTMLOptions const opts = + InsetText::WriteLabel | InsetText::WriteInnerTag; + InsetText::docbook(xs, runparams, opts); +} + + docstring InsetCaption::getCaptionAsHTML(XMLStream & xs, OutputParams const & runparams) const { diff --git a/src/insets/InsetCaption.h b/src/insets/InsetCaption.h index 2b0af0dd08..1fb1022faa 100644 --- a/src/insets/InsetCaption.h +++ b/src/insets/InsetCaption.h @@ -30,6 +30,8 @@ public: void getArgument(otexstream & os, OutputParams const &) const; /// return the caption text int getCaptionAsPlaintext(odocstream & os, OutputParams const &) const; + /// write the caption text as DocBook in os + void getCaptionAsDocBook(XMLStream & os, OutputParams const &) const; /// return the caption text as HTML docstring getCaptionAsHTML(XMLStream & os, OutputParams const &) const; /// @@ -76,7 +78,7 @@ private: int plaintext(odocstringstream & ods, OutputParams const & op, size_t max_length = INT_MAX) const; /// - int docbook(odocstream & os, OutputParams const & runparams) const; + void docbook(XMLStream &, OutputParams const &) const; /// docstring xhtml(XMLStream & os, OutputParams const & runparams) const; /// diff --git a/src/insets/InsetCaptionable.cpp b/src/insets/InsetCaptionable.cpp index 61c9acc3b8..a8a2108ce9 100644 --- a/src/insets/InsetCaptionable.cpp +++ b/src/insets/InsetCaptionable.cpp @@ -80,6 +80,19 @@ docstring InsetCaptionable::getCaptionText(OutputParams const & runparams) const } +docstring InsetCaptionable::getCaptionDocBook(OutputParams const & runparams) const +{ + InsetCaption const * ins = getCaptionInset(); + if (ins == nullptr) + return docstring(); + + odocstringstream ods; + XMLStream xs(ods); + ins->getCaptionAsDocBook(xs, runparams); + return ods.str(); +} + + docstring InsetCaptionable::getCaptionHTML(OutputParams const & runparams) const { InsetCaption const * ins = getCaptionInset(); diff --git a/src/insets/InsetCaptionable.h b/src/insets/InsetCaptionable.h index 870489888c..3190c9a01c 100644 --- a/src/insets/InsetCaptionable.h +++ b/src/insets/InsetCaptionable.h @@ -38,6 +38,8 @@ protected: /// docstring getCaptionHTML(OutputParams const &) const; /// + docstring getCaptionDocBook(OutputParams const &) const; + /// virtual void setCaptionType(std::string const & type); /// are our captions subcaptions? virtual bool hasSubCaptions(ParIterator const &) const { return false; } diff --git a/src/insets/InsetCitation.cpp b/src/insets/InsetCitation.cpp index 744368d2e3..337c4bb2dd 100644 --- a/src/insets/InsetCitation.cpp +++ b/src/insets/InsetCitation.cpp @@ -24,6 +24,7 @@ #include "FuncStatus.h" #include "LaTeXFeatures.h" #include "output_xhtml.h" +#include #include "ParIterator.h" #include "texstream.h" #include "TocBackend.h" @@ -545,19 +546,33 @@ static docstring const cleanupWhitespace(docstring const & citelist) } -int InsetCitation::docbook(odocstream & os, OutputParams const &) const +void InsetCitation::docbook(XMLStream & xs, OutputParams const &) const { - os << from_ascii("") - << cleanupWhitespace(getParam("key")) - << from_ascii(""); - return 0; + if (getCmdName() == "nocite") + return; + + // Split the different citations (on ","), so that one tag can be output for each of them. + string citations = to_utf8(getParam("key")); // Citation strings are not supposed to be too fancy. + if (citations.find(',') == string::npos) { + xs << xml::CompTag("biblioref", "endterm=\"" + citations + "\""); + } else { + size_t pos = 0; + while (pos != string::npos) { + pos = citations.find(','); + xs << xml::CompTag("biblioref", "endterm=\"" + citations.substr(0, pos) + "\""); + citations.erase(0, pos + 1); + + if (pos != string::npos) { + xs << ", "; + } + } + } } docstring InsetCitation::xhtml(XMLStream & xs, OutputParams const &) const { - string const & cmd = getCmdName(); - if (cmd == "nocite") + if (getCmdName() == "nocite") return docstring(); // have to output this raw, because generateLabel() will include tags diff --git a/src/insets/InsetCitation.h b/src/insets/InsetCitation.h index c7cd8dcb60..bc15e11a6f 100644 --- a/src/insets/InsetCitation.h +++ b/src/insets/InsetCitation.h @@ -56,7 +56,7 @@ public: int plaintext(odocstringstream & ods, OutputParams const & op, size_t max_length = INT_MAX) const; /// - int docbook(odocstream &, OutputParams const &) const; + void docbook(XMLStream &, OutputParams const &) const; /// docstring xhtml(XMLStream &, OutputParams const &) const; /// diff --git a/src/insets/InsetCommand.cpp b/src/insets/InsetCommand.cpp index 47affcc543..6d15c6886a 100644 --- a/src/insets/InsetCommand.cpp +++ b/src/insets/InsetCommand.cpp @@ -162,9 +162,9 @@ int InsetCommand::plaintext(odocstringstream & os, } -int InsetCommand::docbook(odocstream &, OutputParams const &) const +void InsetCommand::docbook(XMLStream &, OutputParams const &) const { - return 0; + return; } diff --git a/src/insets/InsetCommand.h b/src/insets/InsetCommand.h index 58d2345841..0c0cd1a954 100644 --- a/src/insets/InsetCommand.h +++ b/src/insets/InsetCommand.h @@ -89,7 +89,7 @@ public: int plaintext(odocstringstream & ods, OutputParams const & op, size_t max_length = INT_MAX) const; /// - int docbook(odocstream &, OutputParams const & runparams) const; + void docbook(XMLStream &, OutputParams const &) const; /// void validate(LaTeXFeatures & features) const; /// diff --git a/src/insets/InsetCounter.cpp b/src/insets/InsetCounter.cpp index 85efd3add2..015d458e62 100644 --- a/src/insets/InsetCounter.cpp +++ b/src/insets/InsetCounter.cpp @@ -184,13 +184,12 @@ void InsetCounter::trackCounters(string const & cmd) const } } -int InsetCounter::docbook(odocstream &, OutputParams const &) const +void InsetCounter::docbook(odocstream &, OutputParams const &) const { // Here, we need to track counter values ourselves, // since unlike in the LaTeX case, there is no external // mechanism for doing that. trackCounters(getCmdName()); - return 0; } diff --git a/src/insets/InsetCounter.h b/src/insets/InsetCounter.h index 65334a3836..3020fdb424 100644 --- a/src/insets/InsetCounter.h +++ b/src/insets/InsetCounter.h @@ -39,7 +39,7 @@ public: int plaintext(odocstringstream & ods, OutputParams const & op, size_t max_length = INT_MAX) const; /// - int docbook(odocstream &, OutputParams const &) const; + void docbook(odocstream &, OutputParams const &) const; /// docstring xhtml(XMLStream &, OutputParams const &) const; /// diff --git a/src/insets/InsetERT.cpp b/src/insets/InsetERT.cpp index 403df75032..eb4567175e 100644 --- a/src/insets/InsetERT.cpp +++ b/src/insets/InsetERT.cpp @@ -25,6 +25,7 @@ #include "Lexer.h" #include "LyXAction.h" #include "OutputParams.h" +#include "xml.h" #include "ParagraphParameters.h" #include "Paragraph.h" @@ -90,25 +91,24 @@ int InsetERT::plaintext(odocstringstream & os, } -int InsetERT::docbook(odocstream & os, OutputParams const &) const +void InsetERT::docbook(XMLStream & xs, OutputParams const &) const { // FIXME can we do the same thing here as for LaTeX? ParagraphList::const_iterator par = paragraphs().begin(); ParagraphList::const_iterator end = paragraphs().end(); - int lines = 0; + xs << XMLStream::ESCAPE_NONE << ""; } diff --git a/src/insets/InsetERT.h b/src/insets/InsetERT.h index 1ee17b3a15..b24c93d628 100644 --- a/src/insets/InsetERT.h +++ b/src/insets/InsetERT.h @@ -52,7 +52,7 @@ private: int plaintext(odocstringstream & ods, OutputParams const & op, size_t max_length = INT_MAX) const; /// - int docbook(odocstream &, OutputParams const &) const; + void docbook(XMLStream &, OutputParams const &) const; /// docstring xhtml(XMLStream &, OutputParams const &) const; /// diff --git a/src/insets/InsetExternal.cpp b/src/insets/InsetExternal.cpp index 8531e56694..a2f7216eaa 100644 --- a/src/insets/InsetExternal.cpp +++ b/src/insets/InsetExternal.cpp @@ -770,42 +770,35 @@ int InsetExternal::plaintext(odocstringstream & os, } -int InsetExternal::docbook(odocstream & os, - OutputParams const & runparams) const +void InsetExternal::generateXML(XMLStream & xs, OutputParams const & runparams, std::string const & format) const { bool const external_in_tmpdir = !runparams.nice; bool const dryrun = runparams.dryrun || runparams.inComment; odocstringstream ods; otexstream ots(ods); external::RetVal retval = - external::writeExternal(params_, "DocBook", buffer(), ots, - *(runparams.exportdata), external_in_tmpdir, dryrun); - if (retval == external::KILLED) { - LYXERR0("External template preparation killed."); - if (buffer().isClone() && buffer().isExporting()) - throw ConversionException(); - } - os << ods.str(); - return int(count(ods.str().begin(), ods.str().end(), '\n')); -} - - -docstring InsetExternal::xhtml(XMLStream & xs, - OutputParams const & runparams) const -{ - bool const external_in_tmpdir = !runparams.nice; - bool const dryrun = runparams.dryrun || runparams.inComment; - odocstringstream ods; - otexstream ots(ods); - external::RetVal retval = - external::writeExternal(params_, "XHTML", buffer(), ots, - *(runparams.exportdata), external_in_tmpdir, dryrun); + external::writeExternal(params_, format, buffer(), ots, + *(runparams.exportdata), external_in_tmpdir, dryrun); if (retval == external::KILLED) { LYXERR0("External template preparation killed."); if (buffer().isClone() && buffer().isExporting()) throw ConversionException(); } xs << XMLStream::ESCAPE_NONE << ods.str(); +} + + +void InsetExternal::docbook(XMLStream & xs, + OutputParams const & runparams) const +{ + generateXML(xs, runparams, "DocBook"); +} + + +docstring InsetExternal::xhtml(XMLStream & xs, + OutputParams const & runparams) const +{ + generateXML(xs, runparams, "XHTML"); return docstring(); } diff --git a/src/insets/InsetExternal.h b/src/insets/InsetExternal.h index b03e416de8..8fb95b33d8 100644 --- a/src/insets/InsetExternal.h +++ b/src/insets/InsetExternal.h @@ -146,7 +146,9 @@ public: int plaintext(odocstringstream & ods, OutputParams const & op, size_t max_length = INT_MAX) const; /// - int docbook(odocstream &, OutputParams const &) const; + void generateXML(XMLStream &, OutputParams const &, std::string const &) const; + /// + void docbook(XMLStream &, OutputParams const &) const; /// For now, this does nothing. Someone who knows about this /// should see what needs doing for XHTML output. docstring xhtml(XMLStream &, OutputParams const &) const; diff --git a/src/insets/InsetFloat.cpp b/src/insets/InsetFloat.cpp index c1dc9856a9..6644d71693 100644 --- a/src/insets/InsetFloat.cpp +++ b/src/insets/InsetFloat.cpp @@ -10,11 +10,17 @@ * Full author contact details are available in file CREDITS. */ +#include + #include +#include #include -#include "InsetFloat.h" +#include "InsetBox.h" #include "InsetCaption.h" +#include "InsetFloat.h" +#include "InsetGraphics.h" +#include "InsetLabel.h" #include "Buffer.h" #include "BufferParams.h" @@ -480,15 +486,193 @@ int InsetFloat::plaintext(odocstringstream & os, OutputParams const & runparams, } -int InsetFloat::docbook(odocstream & os, OutputParams const & runparams) const +std::vector findSubfiguresInParagraph(const Paragraph &par) { - // FIXME Implement subfloat! - // FIXME UNICODE - os << '<' << from_ascii(params_.type) << '>'; - int const i = InsetText::docbook(os, runparams); - os << "'; - return i; + // Don't make the hypothesis that all subfigures are in the same paragraph. + // Similarly, there may be several subfigures in the same paragraph (most likely case, based on the documentation). + // Any box is considered as a subfigure, even though the most likely case is \minipage. + std::vector subfigures; + for (pos_type pos = 0; pos < par.size(); ++pos) { + const Inset *inset = par.getInset(pos); + if (!inset) + continue; + if (const auto box = dynamic_cast(inset)) + subfigures.push_back(box); + } + return subfigures; +} + + +const InsetLabel* findLabelInParagraph(const Paragraph &par) +{ + for (pos_type pos = 0; pos < par.size(); ++pos) { + // If this inset is a subfigure, skip it. + const Inset *inset = par.getInset(pos); + if (dynamic_cast(inset)) { + continue; + } + + // Maybe an inset is directly a label, in which case no more work is needed. + if (inset && dynamic_cast(inset)) + return dynamic_cast(inset); + + // More likely, the label is hidden in an inset of a paragraph (only if a subtype of InsetText). + if (!dynamic_cast(inset)) + continue; + + auto insetAsText = dynamic_cast(inset); + auto itIn = insetAsText->paragraphs().begin(); + auto endIn = insetAsText->paragraphs().end(); + for (; itIn != endIn; ++itIn) { + for (pos_type posIn = 0; posIn < itIn->size(); ++posIn) { + const Inset *insetIn = itIn->getInset(posIn); + if (insetIn && dynamic_cast(insetIn)) { + return dynamic_cast(insetIn); + } + } + } + + // Obviously, this solution does not scale with more levels of paragraphs-insets, but this should be enough. + } + + return nullptr; +} + + +const InsetCaption* findCaptionInParagraph(const Paragraph &par) +{ + // Don't dive too deep, otherwise, this could be a subfigure caption. + for (pos_type pos = 0; pos < par.size(); ++pos) { + // If this inset is a subfigure, skip it. + const Inset *inset = par.getInset(pos); + if (dynamic_cast(inset)) + continue; + + if (inset && dynamic_cast(inset)) + return dynamic_cast(inset); + } + + return nullptr; +} + + +void InsetFloat::docbook(XMLStream & xs, OutputParams const & runparams) const +{ + // Determine whether the float has a title or not. For this, iterate through the paragraphs and look + // for an InsetCaption. Do the same for labels and subfigures. + // The caption and the label for each subfigure is handled by recursive calls. + const InsetCaption* caption = nullptr; + const InsetLabel* label = nullptr; + std::vector subfigures; + + auto end = paragraphs().end(); + for (auto it = paragraphs().begin(); it != end; ++it) { + std::vector foundSubfigures = findSubfiguresInParagraph(*it); + if (!foundSubfigures.empty()) { + subfigures.reserve(subfigures.size() + foundSubfigures.size()); + subfigures.insert(subfigures.end(), foundSubfigures.begin(), foundSubfigures.end()); + } + + if (!caption) + caption = findCaptionInParagraph(*it); + if (!label) + label = findLabelInParagraph(*it); + } + + // Gather a few things from global environment that are shared between all following cases. + FloatList const &floats = buffer().params().documentClass().floats(); + Floating const &ftype = floats.getType(params_.type); + string const &titleTag = ftype.docbookCaption(); + + // Ensure there is no label output, it is supposed to be handled as xml:id. + OutputParams rpNoLabel = runparams; + if (label) + rpNoLabel.docbook_anchors_to_ignore.emplace(label->screenLabel()); + + // Ensure the float does not output its caption, as it is handled here (DocBook mandates a specific place for + // captions, they cannot appear at the end of the float, albeit LyX is happy with that). + OutputParams rpNoTitle = runparams; + rpNoTitle.docbook_in_float = true; + + // Deal with subfigures. + if (!subfigures.empty()) { + // First, open the formal group. + docstring attr = docstring(); + if (label) + attr += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\""; + + xs.startDivision(false); + xs << xml::StartTag("formalgroup", attr); + xs << xml::CR(); + + xs << xml::StartTag("title", attr); + if (caption) { + caption->getCaptionAsDocBook(xs, rpNoLabel); + } else { + xs << "No caption"; + // No caption has been detected, but this tag is required for the document to be valid DocBook. + } + xs << xml::EndTag("title"); + xs << xml::CR(); + + // Deal with each subfigure individually. This should also deal with their caption and their label. + // This should be a recursive call to InsetFloat. + for (const InsetBox *subfigure: subfigures) { + // If there is no InsetFloat in the paragraphs, output a warning. + bool foundInsetFloat = false; + for (auto it = subfigure->paragraphs().begin(); it != subfigure->paragraphs().end(); ++it) { + for (pos_type posIn = 0; posIn < it->size(); ++posIn) { + const Inset *inset = it->getInset(posIn); + if (inset && dynamic_cast(inset)) { + foundInsetFloat = true; + break; + } + } + + if (foundInsetFloat) + break; + } + + if (!foundInsetFloat) + xs << XMLStream::ESCAPE_NONE << "Error: no float found in the box. " + "To use subfigures in DocBook, elements must be wrapped in a float " + "inset and have a title/caption."; + // TODO: could also output a table, that would ensure that the document is correct and *displays* correctly (but without the right semantics), instead of just an error. + + // Finally, recurse. + subfigure->docbook(xs, runparams); + } + + // Every subfigure is done: close the formal group. + xs << xml::EndTag("formalgroup"); + xs << xml::CR(); + xs.endDivision(); + } + + // Here, ensured not to have subfigures. + + // Organisation: <contents without title/> </float> + docstring attr = docstring(); + if (label) + attr += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\""; + if (!ftype.docbookAttr().empty()) { + if (!attr.empty()) + attr += " "; + attr += from_utf8(ftype.docbookAttr()); + } + + xs << xml::StartTag(ftype.docbookTag(caption != nullptr), attr); + xs << xml::CR(); + if (caption != nullptr) { + xs << xml::StartTag(titleTag); + caption->getCaptionAsDocBook(xs, rpNoLabel); + xs << xml::EndTag(titleTag); + xs << xml::CR(); + } + InsetText::docbook(xs, rpNoTitle); + xs << xml::EndTag(ftype.docbookTag(caption != nullptr)); + xs << xml::CR(); } diff --git a/src/insets/InsetFloat.h b/src/insets/InsetFloat.h index b43147e725..bdf68089a9 100644 --- a/src/insets/InsetFloat.h +++ b/src/insets/InsetFloat.h @@ -99,7 +99,7 @@ private: int plaintext(odocstringstream & ods, OutputParams const & op, size_t max_length = INT_MAX) const; /// - int docbook(odocstream &, OutputParams const &) const; + void docbook(XMLStream &, OutputParams const &) const; /// docstring xhtml(XMLStream &, OutputParams const &) const; /// diff --git a/src/insets/InsetFloatList.h b/src/insets/InsetFloatList.h index 20c8e22d62..e0ae669de0 100644 --- a/src/insets/InsetFloatList.h +++ b/src/insets/InsetFloatList.h @@ -40,7 +40,7 @@ public: /// void latex(otexstream &, OutputParams const &) const; /// - int docbook(odocstream &, OutputParams const &) const { return 0; } + void docbook(XMLStream &, OutputParams const &) const { return; } /// int plaintext(odocstringstream & ods, OutputParams const & op, size_t max_length = INT_MAX) const; diff --git a/src/insets/InsetFoot.cpp b/src/insets/InsetFoot.cpp index 5865c7d77e..ad3fc3a641 100644 --- a/src/insets/InsetFoot.cpp +++ b/src/insets/InsetFoot.cpp @@ -10,6 +10,7 @@ */ #include <config.h> +#include <output_docbook.h> #include "InsetFoot.h" #include "InsetBox.h" @@ -122,13 +123,12 @@ int InsetFoot::plaintext(odocstringstream & os, } -int InsetFoot::docbook(odocstream & os, OutputParams const & runparams) const +void InsetFoot::docbook(XMLStream & xs, OutputParams const & runparams) const { - os << "<footnote>"; - int const i = InsetText::docbook(os, runparams); - os << "</footnote>"; - - return i; + OutputParams rp = runparams; + rp.docbook_force_pars = true; + rp.docbook_in_par = false; + InsetText::docbook(xs, rp); } diff --git a/src/insets/InsetFoot.h b/src/insets/InsetFoot.h index 194661a9b2..e938927385 100644 --- a/src/insets/InsetFoot.h +++ b/src/insets/InsetFoot.h @@ -35,7 +35,7 @@ private: int plaintext(odocstringstream & ods, OutputParams const & op, size_t max_length = INT_MAX) const; /// - int docbook(odocstream &, OutputParams const &) const; + void docbook(XMLStream &, OutputParams const &) const; /// void validate(LaTeXFeatures & features) const; /// Update the counters of this inset and of its contents diff --git a/src/insets/InsetGraphics.cpp b/src/insets/InsetGraphics.cpp index 0daae4c921..2b19d50665 100644 --- a/src/insets/InsetGraphics.cpp +++ b/src/insets/InsetGraphics.cpp @@ -95,7 +95,7 @@ TODO #include <algorithm> #include <sstream> -#include <tuple> +#include <output_docbook.h> using namespace std; using namespace lyx::support; @@ -125,8 +125,9 @@ string findTargetFormat(string const & format, OutputParams const & runparams) // Convert everything else to png return "png"; } - // for HTML, we leave the known formats and otherwise convert to png - if (runparams.flavor == OutputParams::HTML) { + + // for HTML and DocBook, we leave the known formats and otherwise convert to png + if (runparams.flavor == OutputParams::HTML || runparams.flavor == OutputParams::DOCBOOK5) { Format const * const f = theFormats().getFormat(format); // Convert vector graphics to svg if (f && f->vectorFormat() && theConverters().isReachable(format, "svg")) @@ -522,7 +523,6 @@ docstring InsetGraphics::createDocBookAttributes() const } } - if (!params().special.empty()) options << from_ascii(params().special) << " "; @@ -935,62 +935,24 @@ int InsetGraphics::plaintext(odocstringstream & os, } -static int writeImageObject(char const * format, odocstream & os, - OutputParams const & runparams, docstring const & graphic_label, - docstring const & attributes) -{ - if (runparams.flavor != OutputParams::DOCBOOK5) - os << "<![ %output.print." << format - << "; [" << endl; - - os <<"<imageobject><imagedata fileref=\"&" - << graphic_label - << ";." - << format - << "\" " - << attributes; - - if (runparams.flavor == OutputParams::DOCBOOK5) - os << " role=\"" << format << "\"/>" ; - else - os << " format=\"" << format << "\">" ; - - os << "</imageobject>"; - - if (runparams.flavor != OutputParams::DOCBOOK5) - os << endl << "]]>" ; - - return runparams.flavor == OutputParams::DOCBOOK5 ? 0 : 2; -} - - // For explanation on inserting graphics into DocBook checkout: // http://en.tldp.org/LDP/LDP-Author-Guide/html/inserting-pictures.html // See also the docbook guide at http://www.docbook.org/ -int InsetGraphics::docbook(odocstream & os, - OutputParams const & runparams) const +void InsetGraphics::docbook(XMLStream & xs, OutputParams const & runparams) const { - // In DocBook v5.0, the graphic tag will be eliminated from DocBook, will - // need to switch to MediaObject. However, for now this is sufficient and - // easier to use. - if (runparams.flavor == OutputParams::DOCBOOK5) - runparams.exportdata->addExternalFile("docbook-xml", - params().filename); - else - runparams.exportdata->addExternalFile("docbook", - params().filename); + string fn = params().filename.relFileName(runparams.export_folder); + string tag = runparams.docbook_in_float ? "mediaobject" : "inlinemediaobject"; - os << "<inlinemediaobject>"; - - int r = 0; - docstring attributes = createDocBookAttributes(); - r += writeImageObject("png", os, runparams, graphic_label, attributes); - r += writeImageObject("pdf", os, runparams, graphic_label, attributes); - r += writeImageObject("eps", os, runparams, graphic_label, attributes); - r += writeImageObject("bmp", os, runparams, graphic_label, attributes); - - os << "</inlinemediaobject>"; - return r; + xs << xml::StartTag(tag); + xs << xml::CR(); + xs << xml::StartTag("imageobject"); + xs << xml::CR(); + xs << xml::CompTag("imagedata", "fileref=\"" + fn + "\" " + to_utf8(createDocBookAttributes())); + xs << xml::CR(); + xs << xml::EndTag("imageobject"); + xs << xml::CR(); + xs << xml::EndTag(tag); + xs << xml::CR(); } diff --git a/src/insets/InsetGraphics.h b/src/insets/InsetGraphics.h index 8c07c0f969..c987ed0615 100644 --- a/src/insets/InsetGraphics.h +++ b/src/insets/InsetGraphics.h @@ -78,7 +78,7 @@ public: int plaintext(odocstringstream & ods, OutputParams const & op, size_t max_length = INT_MAX) const; /// - int docbook(odocstream &, OutputParams const &) const; + void docbook(XMLStream &, OutputParams const &) const; /// docstring xhtml(XMLStream & os, OutputParams const &) const; /** Tell LyX what the latex features you need i.e. what latex packages diff --git a/src/insets/InsetHyperlink.cpp b/src/insets/InsetHyperlink.cpp index 6246ff840a..1444fc19a2 100644 --- a/src/insets/InsetHyperlink.cpp +++ b/src/insets/InsetHyperlink.cpp @@ -10,6 +10,7 @@ */ #include <config.h> +#include <output_docbook.h> #include "InsetHyperlink.h" @@ -213,14 +214,11 @@ int InsetHyperlink::plaintext(odocstringstream & os, } -int InsetHyperlink::docbook(odocstream & os, OutputParams const &) const +void InsetHyperlink::docbook(XMLStream & xs, OutputParams const &) const { - os << "<ulink url=\"" - << subst(getParam("target"), from_ascii("&"), from_ascii("&")) - << "\">" - << xml::escapeString(getParam("name")) - << "</ulink>"; - return 0; + xs << xml::StartTag("link", "xlink:href=\"" + subst(getParam("target"), from_ascii("&"), from_ascii("&")) + "\""); + xs << xml::escapeString(getParam("name")); + xs << xml::EndTag("link"); } diff --git a/src/insets/InsetHyperlink.h b/src/insets/InsetHyperlink.h index 0e0e8d3446..90ae05b94f 100644 --- a/src/insets/InsetHyperlink.h +++ b/src/insets/InsetHyperlink.h @@ -51,7 +51,7 @@ public: int plaintext(odocstringstream & ods, OutputParams const & op, size_t max_length = INT_MAX) const; /// - int docbook(odocstream &, OutputParams const &) const; + void docbook(XMLStream &, OutputParams const &) const; /// docstring xhtml(XMLStream &, OutputParams const &) const; //@} diff --git a/src/insets/InsetIPA.cpp b/src/insets/InsetIPA.cpp index e11f0cc1f5..02dad4d455 100644 --- a/src/insets/InsetIPA.cpp +++ b/src/insets/InsetIPA.cpp @@ -233,6 +233,14 @@ void InsetIPA::latex(otexstream & os, OutputParams const & runparams_in) const } +void InsetIPA::docbook(XMLStream & xs, OutputParams const & runparams) const +{ + OutputParams rp(runparams); + rp.inIPA = true; + InsetText::docbook(xs, rp); +} + + docstring InsetIPA::xhtml(XMLStream & xs, OutputParams const & runparams_in) const { OutputParams runparams(runparams_in); diff --git a/src/insets/InsetIPA.h b/src/insets/InsetIPA.h index 35f2ea4b5d..695179738f 100644 --- a/src/insets/InsetIPA.h +++ b/src/insets/InsetIPA.h @@ -77,6 +77,8 @@ public: /// void latex(otexstream &, OutputParams const &) const; /// + void docbook(XMLStream &, OutputParams const &) const; + /// docstring xhtml(XMLStream & xs, OutputParams const &) const; /// void validate(LaTeXFeatures & features) const; diff --git a/src/insets/InsetIPAMacro.cpp b/src/insets/InsetIPAMacro.cpp index 91c5c88176..f96ee7a5e8 100644 --- a/src/insets/InsetIPAMacro.cpp +++ b/src/insets/InsetIPAMacro.cpp @@ -22,7 +22,7 @@ #include "LaTeXFeatures.h" #include "Lexer.h" #include "MetricsInfo.h" -#include "output_xhtml.h" +#include "xml.h" #include "texstream.h" #include "frontends/FontMetrics.h" @@ -305,10 +305,22 @@ int InsetIPADeco::plaintext(odocstringstream & os, } -int InsetIPADeco::docbook(odocstream & os, OutputParams const & runparams) const +void InsetIPADeco::docbook(XMLStream & xs, OutputParams const & runparams) const { - // FIXME: Any docbook option here? - return InsetCollapsible::docbook(os, runparams); + // The special combining character must be put in the middle, between the two other characters. + // It will not work if there is anything else than two pure characters, so going back to plaintext. + odocstringstream ods; + int h = (int)(InsetText::plaintext(ods, runparams) / 2); + docstring result = ods.str(); + docstring const before = result.substr(0, h); + docstring const after = result.substr(h, result.size()); + + xs << XMLStream::ESCAPE_NONE << before; + if (params_.type == InsetIPADecoParams::Toptiebar) + xs << XMLStream::ESCAPE_NONE << "͡"; + else if (params_.type == InsetIPADecoParams::Bottomtiebar) + xs << XMLStream::ESCAPE_NONE << "͜"; + xs << XMLStream::ESCAPE_NONE << after; } @@ -540,19 +552,31 @@ int InsetIPAChar::plaintext(odocstringstream & os, OutputParams const &, size_t) } -int InsetIPAChar::docbook(odocstream & /*os*/, OutputParams const &) const +void InsetIPAChar::docbook(XMLStream & xs, OutputParams const &) const { - switch (kind_) { - case TONE_FALLING: - case TONE_RISING: - case TONE_HIGH_RISING: - case TONE_LOW_RISING: - case TONE_HIGH_RISING_FALLING: - // FIXME - LYXERR0("IPA tone macros not yet implemented with DocBook!"); - break; - } - return 0; + switch (kind_) { + case TONE_FALLING: + xs << XMLStream::ESCAPE_NONE << "˥"; + xs << XMLStream::ESCAPE_NONE << "˩"; + break; + case TONE_RISING: + xs << XMLStream::ESCAPE_NONE << "˩"; + xs << XMLStream::ESCAPE_NONE << "˥"; + break; + case TONE_HIGH_RISING: + xs << XMLStream::ESCAPE_NONE << "˧"; + xs << XMLStream::ESCAPE_NONE << "˥"; + break; + case TONE_LOW_RISING: + xs << XMLStream::ESCAPE_NONE << "˩"; + xs << XMLStream::ESCAPE_NONE << "˧"; + break; + case TONE_HIGH_RISING_FALLING: + xs << XMLStream::ESCAPE_NONE << "˨"; + xs << XMLStream::ESCAPE_NONE << "˥"; + xs << XMLStream::ESCAPE_NONE << "˨"; + break; + } } diff --git a/src/insets/InsetIPAMacro.h b/src/insets/InsetIPAMacro.h index b985ef3b7b..273e572e25 100644 --- a/src/insets/InsetIPAMacro.h +++ b/src/insets/InsetIPAMacro.h @@ -79,7 +79,7 @@ private: int plaintext(odocstringstream & ods, OutputParams const & op, size_t max_length = INT_MAX) const; /// - int docbook(odocstream &, OutputParams const &) const; + void docbook(XMLStream &, OutputParams const &) const; /// docstring xhtml(XMLStream &, OutputParams const &) const; /// @@ -150,7 +150,7 @@ public: int plaintext(odocstringstream & ods, OutputParams const & op, size_t max_length = INT_MAX) const; /// - int docbook(odocstream &, OutputParams const &) const; + void docbook(XMLStream &, OutputParams const &) const; /// docstring xhtml(XMLStream &, OutputParams const &) const; /// diff --git a/src/insets/InsetInclude.cpp b/src/insets/InsetInclude.cpp index 6eba05abde..235023ed00 100644 --- a/src/insets/InsetInclude.cpp +++ b/src/insets/InsetInclude.cpp @@ -987,12 +987,11 @@ docstring InsetInclude::xhtml(XMLStream & xs, OutputParams const & rp) const op.par_begin = 0; op.par_end = 0; ibuf->writeLyXHTMLSource(xs.os(), op, Buffer::IncludedFile); - } else - xs << XMLStream::ESCAPE_NONE - << "<!-- Included file: " - << from_utf8(included_file.absFileName()) - << XMLStream::ESCAPE_NONE - << " -->"; + } else { + xs << XMLStream::ESCAPE_NONE << "<!-- Included file: "; + xs << from_utf8(included_file.absFileName()); + xs << XMLStream::ESCAPE_NONE << " -->"; + } return docstring(); } @@ -1037,56 +1036,68 @@ int InsetInclude::plaintext(odocstringstream & os, } -int InsetInclude::docbook(odocstream & os, OutputParams const & runparams) const +void InsetInclude::docbook(XMLStream & xs, OutputParams const & rp) const { - string incfile = ltrim(to_utf8(params()["filename"])); + if (rp.inComment) + return; - // Do nothing if no file name has been specified - if (incfile.empty()) - return 0; + // For verbatim and listings, we just include the contents of the file as-is. + bool const verbatim = isVerbatim(params()); + bool const listing = isListings(params()); + if (listing || verbatim) { + if (listing) + xs << xml::StartTag("programlisting"); + else if (verbatim) + xs << xml::StartTag("literallayout"); - string const included_file = includedFileName(buffer(), params()).absFileName(); - string exppath = incfile; - if (!runparams.export_folder.empty()) { - exppath = makeAbsPath(exppath, runparams.export_folder).realPath(); - FileName(exppath).onlyPath().createPath(); + // FIXME: We don't know the encoding of the file, default to UTF-8. + xs << includedFileName(buffer(), params()).fileContents("UTF-8"); + + if (listing) + xs << xml::EndTag("programlisting"); + else if (verbatim) + xs << xml::EndTag("literallayout"); + + return; } - // write it to a file (so far the complete file) - string const exportfile = changeExtension(exppath, ".sgml"); - DocFileName writefile(changeExtension(included_file, ".sgml")); - - Buffer * tmp = loadIfNeeded(); - if (tmp) { - if (recursion_error_) - return 0; - - string const mangled = writefile.mangledFileName(); - writefile = makeAbsPath(mangled, - buffer().masterBuffer()->temppath()); - if (!runparams.nice) - incfile = mangled; - - LYXERR(Debug::LATEX, "incfile:" << incfile); - LYXERR(Debug::LATEX, "exportfile:" << exportfile); - LYXERR(Debug::LATEX, "writefile:" << writefile); - - tmp->makeDocBookFile(writefile, runparams, Buffer::OnlyBody); + // We don't (yet) know how to Input or Include non-LyX files. + // (If we wanted to get really arcane, we could run some tex2html + // converter on the included file. But that's just masochistic.) + FileName const included_file = includedFileName(buffer(), params()); + if (!isLyXFileName(included_file.absFileName())) { + if (!rp.silent) + frontend::Alert::warning(_("Unsupported Inclusion"), + bformat(_("LyX does not know how to include non-LyX files when " + "generating DocBook output. Offending file:\n%1$s"), + ltrim(params()["filename"]))); + return; } - runparams.exportdata->addExternalFile("docbook", writefile, - exportfile); - runparams.exportdata->addExternalFile("docbook-xml", writefile, - exportfile); + // In the other cases, we will generate the HTML and include it. + Buffer const * const ibuf = loadIfNeeded(); + if (!ibuf) + return; - if (isVerbatim(params()) || isListings(params())) { - os << "<inlinegraphic fileref=\"" - << '&' << include_label << ';' - << "\" format=\"linespecific\">"; + if (recursion_error_) + return; + + // are we generating only some paragraphs, or all of them? + bool const all_pars = !rp.dryrun || + (rp.par_begin == 0 && + rp.par_end == (int) buffer().text().paragraphs().size()); + + OutputParams op = rp; + if (all_pars) { + op.par_begin = 0; + op.par_end = 0; + ibuf->writeDocBookSource(xs.os(), op, Buffer::IncludedFile); } else - os << '&' << include_label << ';'; - - return 0; + xs << XMLStream::ESCAPE_NONE + << "<!-- Included file: " + << from_utf8(included_file.absFileName()) + << XMLStream::ESCAPE_NONE + << " -->"; } diff --git a/src/insets/InsetInclude.h b/src/insets/InsetInclude.h index 08fe9434cc..db7beca5d4 100644 --- a/src/insets/InsetInclude.h +++ b/src/insets/InsetInclude.h @@ -94,7 +94,7 @@ public: int plaintext(odocstringstream & ods, OutputParams const & op, size_t max_length = INT_MAX) const; /// - int docbook(odocstream &, OutputParams const &) const; + void docbook(XMLStream &, OutputParams const &) const; /// docstring xhtml(XMLStream &, OutputParams const &) const; /// diff --git a/src/insets/InsetIndex.cpp b/src/insets/InsetIndex.cpp index 11a8545280..ed77d1d966 100644 --- a/src/insets/InsetIndex.cpp +++ b/src/insets/InsetIndex.cpp @@ -40,8 +40,11 @@ #include "frontends/alert.h" #include <algorithm> +#include <set> #include <ostream> +#include <QThreadStorage> + using namespace std; using namespace lyx::support; @@ -182,12 +185,181 @@ void InsetIndex::latex(otexstream & ios, OutputParams const & runparams_in) cons } -int InsetIndex::docbook(odocstream & os, OutputParams const & runparams) const +void InsetIndex::docbook(XMLStream & xs, OutputParams const & runparams) const { - os << "<indexterm><primary>"; - int const i = InsetText::docbook(os, runparams); - os << "</primary></indexterm>"; - return i; + // Get the content of the inset as LaTeX, as some things may be encoded as ERT (like {}). + odocstringstream odss; + otexstream ots(odss); + InsetText::latex(ots, runparams); + docstring latexString = trim(odss.str()); + + // Check whether there are unsupported things. + if (latexString.find(from_utf8("@")) != latexString.npos) { + docstring error = from_utf8("Unsupported feature: an index entry contains an @. " + "Complete entry: \"") + latexString + from_utf8("\""); + LYXERR0(error); + xs << XMLStream::ESCAPE_NONE << (from_utf8("<!-- Output Error: ") + error + from_utf8(" -->\n")); + } + + // Handle several indices. + docstring indexType = from_utf8(""); + if (buffer().masterBuffer()->params().use_indices) { + indexType += " type=\"" + params_.index + "\""; + } + + // Split the string into its main constituents: terms, and command (see, see also, range). + size_t positionVerticalBar = latexString.find(from_ascii("|")); // What comes before | is (sub)(sub)entries. + docstring indexTerms = latexString.substr(0, positionVerticalBar); + docstring command = latexString.substr(positionVerticalBar + 1); + + // Handle primary, secondary, and tertiary terms (entries, subentries, and subsubentries, for LaTeX). + vector<docstring> terms = getVectorFromString(indexTerms, from_ascii("!"), false); + + // Handle ranges. Happily, (| and |) can only be at the end of the string! However, | may be trapped by the + bool hasStartRange = latexString.find(from_ascii("|(")) != latexString.npos; + bool hasEndRange = latexString.find(from_ascii("|)")) != latexString.npos; + if (hasStartRange || hasEndRange) { + // Remove the ranges from the command if they do not appear at the beginning. + size_t index = 0; + while ((index = command.find(from_utf8("|("), index)) != std::string::npos) + command.erase(index, 1); + index = 0; + while ((index = command.find(from_utf8("|)"), index)) != std::string::npos) + command.erase(index, 1); + + // Remove the ranges when they are the only vertical bar in the complete string. + if (command[0] == '(' || command[0] == ')') + command.erase(0, 1); + } + + // Handle see and seealso. As "see" is a prefix of "seealso", the order of the comparisons is important. + // Both commands are mutually exclusive! + docstring see = from_utf8(""); + vector<docstring> seeAlsoes; + if (command.substr(0, 3) == "see") { + // Unescape brackets. + size_t index = 0; + while ((index = command.find(from_utf8("\\{"), index)) != std::string::npos) + command.erase(index, 1); + index = 0; + while ((index = command.find(from_utf8("\\}"), index)) != std::string::npos) + command.erase(index, 1); + + // Retrieve the part between brackets, and remove the complete seealso. + size_t positionOpeningBracket = command.find(from_ascii("{")); + size_t positionClosingBracket = command.find(from_ascii("}")); + docstring list = command.substr(positionOpeningBracket + 1, positionClosingBracket - positionOpeningBracket - 1); + + // Parse the list of referenced entries (or a single one for see). + if (command.substr(0, 7) == "seealso") { + seeAlsoes = getVectorFromString(list, from_ascii(","), false); + } else { + see = list; + + if (see.find(from_ascii(",")) != see.npos) { + docstring error = from_utf8("Several index terms found as \"see\"! Only one is acceptable. " + "Complete entry: \"") + latexString + from_utf8("\""); + LYXERR0(error); + xs << XMLStream::ESCAPE_NONE << (from_utf8("<!-- Output Error: ") + error + from_utf8(" -->\n")); + } + } + + // Remove the complete see/seealso from the commands, in case there is something else to parse. + command = command.substr(positionClosingBracket + 1); + } + + // Some parts of the strings are not parsed, as they do not have anything matching in DocBook: things like + // formatting the entry or the page number, other strings for sorting. https://wiki.lyx.org/Tips/Indexing + // If there are such things in the index entry, then this code may miserably fail. For example, for "Peter|(textbf", + // no range will be detected. + // TODO: Could handle formatting as significance="preferred"? + + // Write all of this down. + if (terms.empty() && !hasEndRange) { + docstring error = from_utf8("No index term found! Complete entry: \"") + latexString + from_utf8("\""); + LYXERR0(error); + xs << XMLStream::ESCAPE_NONE << (from_utf8("<!-- Output Error: ") + error + from_utf8(" -->\n")); + } else { + // Generate the attributes for ranges. It is based on the terms that are indexed, but the ID must be unique + // to this indexing area (xml::cleanID does not guarantee this: for each call with the same arguments, + // the same legal ID is produced; here, as the input would be the same, the output must be, by design). + // Hence the thread-local storage, as the numbers must strictly be unique, and thus cannot be shared across + // a paragraph (making the solution used for HTML worthless). This solution is very similar to the one used in + // xml::cleanID. + docstring attrs = indexType; + if (hasStartRange || hasEndRange) { + // Append an ID if uniqueness is not guaranteed across the document. + static QThreadStorage<set<docstring>> tKnownTermLists; + static QThreadStorage<int> tID; + + set<docstring> & knownTermLists = tKnownTermLists.localData(); + int & ID = tID.localData(); + + if (!tID.hasLocalData()) { + tID.localData() = 0; + } + + // Modify the index terms to add the unique ID if needed. + docstring newIndexTerms = indexTerms; + if (knownTermLists.find(indexTerms) != knownTermLists.end()) { + newIndexTerms += from_ascii(string("-") + to_string(ID)); + + // Only increment for the end of range, so that the same number is used for the start of range. + if (hasEndRange) { + ID++; + } + } + + // Term list not yet known: add it to the set AFTER the end of range. After + if (knownTermLists.find(indexTerms) == knownTermLists.end() && hasEndRange) { + knownTermLists.insert(indexTerms); + } + + // Generate the attributes. + docstring id = xml::cleanID(newIndexTerms); + if (hasStartRange) { + attrs += " class=\"startofrange\" xml:id=\"" + id + "\""; + } else { + attrs += " class=\"endofrange\" startref=\"" + id + "\""; + } + } + + // Handle the index terms (including the specific index for this entry). + xs << xml::StartTag("indexterm", attrs); + if (terms.size() > 0) { // hasEndRange has no content. + xs << xml::StartTag("primary"); + xs << terms[0]; + xs << xml::EndTag("primary"); + } + if (terms.size() > 1) { + xs << xml::StartTag("secondary"); + xs << terms[1]; + xs << xml::EndTag("secondary"); + } + if (terms.size() > 2) { + xs << xml::StartTag("tertiary"); + xs << terms[2]; + xs << xml::EndTag("tertiary"); + } + + // Handle see and see also. + if (!see.empty()) { + xs << xml::StartTag("see"); + xs << see; + xs << xml::EndTag("see"); + } + + if (!seeAlsoes.empty()) { + for (auto & entry : seeAlsoes) { + xs << xml::StartTag("seealso"); + xs << entry; + xs << xml::EndTag("seealso"); + } + } + + // Close the entry. + xs << xml::EndTag("indexterm"); + } } diff --git a/src/insets/InsetIndex.h b/src/insets/InsetIndex.h index 01831d33bc..a999ee5bb3 100644 --- a/src/insets/InsetIndex.h +++ b/src/insets/InsetIndex.h @@ -57,7 +57,7 @@ private: /// void read(Lexer & lex); /// - int docbook(odocstream &, OutputParams const &) const; + void docbook(XMLStream &, OutputParams const &) const; /// docstring xhtml(XMLStream &, OutputParams const &) const; /// diff --git a/src/insets/InsetLabel.cpp b/src/insets/InsetLabel.cpp index 4432493319..ec50d4feb6 100644 --- a/src/insets/InsetLabel.cpp +++ b/src/insets/InsetLabel.cpp @@ -25,7 +25,6 @@ #include "InsetIterator.h" #include "Language.h" #include "LyX.h" -#include "output_xhtml.h" #include "ParIterator.h" #include "xml.h" #include "texstream.h" @@ -351,12 +350,13 @@ int InsetLabel::plaintext(odocstringstream & os, } -int InsetLabel::docbook(odocstream & os, OutputParams const &) const +void InsetLabel::docbook(XMLStream & xs, OutputParams const & runparams) const { - os << "<!-- anchor id=\"" - << xml::cleanID(getParam("name")) - << "\" -->"; - return 0; + // Output an anchor only if it has not been processed before. + if (runparams.docbook_anchors_to_ignore.find(getParam("name")) == runparams.docbook_anchors_to_ignore.end()) { + docstring attr = from_utf8("xml:id=\"") + xml::cleanID(getParam("name")) + from_utf8("\""); + xs << xml::CompTag("anchor", to_utf8(attr)); + } } diff --git a/src/insets/InsetLabel.h b/src/insets/InsetLabel.h index 8d063168b6..4747bb5d3e 100644 --- a/src/insets/InsetLabel.h +++ b/src/insets/InsetLabel.h @@ -53,7 +53,7 @@ public: int plaintext(odocstringstream & ods, OutputParams const & op, size_t max_length = INT_MAX) const; /// - int docbook(odocstream &, OutputParams const &) const; + void docbook(XMLStream &, OutputParams const &) const; /// docstring xhtml(XMLStream &, OutputParams const &) const; /// diff --git a/src/insets/InsetLine.cpp b/src/insets/InsetLine.cpp index 1688af3ccf..2479be13a0 100644 --- a/src/insets/InsetLine.cpp +++ b/src/insets/InsetLine.cpp @@ -37,6 +37,7 @@ #include "support/lstrings.h" #include <cstdlib> +#include <output_docbook.h> using namespace std; @@ -183,10 +184,9 @@ int InsetLine::plaintext(odocstringstream & os, } -int InsetLine::docbook(odocstream & os, OutputParams const &) const +void InsetLine::docbook(XMLStream & xs, OutputParams const &) const { - os << '\n'; - return 0; + xs << xml::CR(); } diff --git a/src/insets/InsetLine.h b/src/insets/InsetLine.h index 7be587028f..4c918a872d 100644 --- a/src/insets/InsetLine.h +++ b/src/insets/InsetLine.h @@ -41,8 +41,7 @@ private: /// Inset inherited methods. //@{ InsetCode lyxCode() const { return LINE_CODE; } - int docbook(odocstream &, OutputParams const &) const; - /// Does nothing at the moment. + void docbook(XMLStream &, OutputParams const &) const; docstring xhtml(XMLStream &, OutputParams const &) const; bool hasSettings() const { return true; } void metrics(MetricsInfo &, Dimension &) const; diff --git a/src/insets/InsetListings.cpp b/src/insets/InsetListings.cpp index 2f1462a154..05048d4351 100644 --- a/src/insets/InsetListings.cpp +++ b/src/insets/InsetListings.cpp @@ -27,6 +27,7 @@ #include "LaTeXFeatures.h" #include "Lexer.h" #include "output_latex.h" +#include "output_docbook.h" #include "output_xhtml.h" #include "OutputParams.h" #include "TextClass.h" @@ -480,6 +481,45 @@ docstring InsetListings::xhtml(XMLStream & os, OutputParams const & rp) const } +void InsetListings::docbook(XMLStream & xs, OutputParams const & rp) const +{ + InsetLayout const & il = getLayout(); + + // Forge the attributes. + string attrs; + if (!il.docbookattr().empty()) + attrs += " role=\"" + il.docbookattr() + "\""; + string const lang = params().getParamValue("language"); + if (!lang.empty()) + attrs += " language=\"" + lang + "\""; + xs << xml::StartTag(il.docbooktag(), attrs); + xs.startDivision(false); + + // Deal with the caption. + docstring caption = getCaptionDocBook(rp); + if (!caption.empty()) { + xs << xml::StartTag("bridgehead"); + xs << XMLStream::ESCAPE_NONE; + xs << caption; + xs << xml::EndTag("bridgehead"); + } + + // Deal with the content of the listing. + OutputParams newrp = rp; + newrp.pass_thru = true; + newrp.docbook_make_pars = false; + newrp.par_begin = 0; + newrp.par_end = text().paragraphs().size(); + newrp.docbook_in_listing = true; + + docbookParagraphs(text(), buffer(), xs, newrp); + + // Done with the listing. + xs.endDivision(); + xs << xml::EndTag(il.docbooktag()); +} + + string InsetListings::contextMenuName() const { return "context-listings"; diff --git a/src/insets/InsetListings.h b/src/insets/InsetListings.h index 06c310739f..a74569c544 100644 --- a/src/insets/InsetListings.h +++ b/src/insets/InsetListings.h @@ -58,6 +58,8 @@ private: /// docstring xhtml(XMLStream &, OutputParams const &) const; /// + void docbook(XMLStream &, OutputParams const &) const; + /// void validate(LaTeXFeatures &) const; /// bool showInsetDialog(BufferView *) const; diff --git a/src/insets/InsetMarginal.cpp b/src/insets/InsetMarginal.cpp index 3dfcf7d048..54922fced5 100644 --- a/src/insets/InsetMarginal.cpp +++ b/src/insets/InsetMarginal.cpp @@ -10,6 +10,7 @@ */ #include <config.h> +#include <output_docbook.h> #include "InsetMarginal.h" @@ -41,14 +42,15 @@ int InsetMarginal::plaintext(odocstringstream & os, } -int InsetMarginal::docbook(odocstream & os, - OutputParams const & runparams) const +void InsetMarginal::docbook(XMLStream & xs, OutputParams const & runparams) const { - os << "<note role=\"margin\">"; - int const i = InsetText::docbook(os, runparams); - os << "</note>"; - - return i; + // Implementation as per http://www.sagehill.net/docbookxsl/SideFloats.html + // Unfortunately, only for XSL-FO output with the default style sheets, hence the role. + xs << xml::StartTag("sidebar", "role=\"margin\""); + xs << xml::CR(); + xs << "<?dbfo float-type=\"margin.note\"?>"; + InsetText::docbook(xs, runparams); + xs << xml::EndTag("sidebar"); } diff --git a/src/insets/InsetMarginal.h b/src/insets/InsetMarginal.h index db0f6db023..749a0c4152 100644 --- a/src/insets/InsetMarginal.h +++ b/src/insets/InsetMarginal.h @@ -34,7 +34,7 @@ public: int plaintext(odocstringstream & ods, OutputParams const & op, size_t max_length = INT_MAX) const; /// - int docbook(odocstream &, OutputParams const & runparams) const; + void docbook(XMLStream &, OutputParams const & runparams) const; /// Is the content of this inset part of the immediate (visible) text sequence? bool isPartOfTextSequence() const { return false; } private: diff --git a/src/insets/InsetNewline.cpp b/src/insets/InsetNewline.cpp index 4930dd902a..86f60f6b3a 100644 --- a/src/insets/InsetNewline.cpp +++ b/src/insets/InsetNewline.cpp @@ -10,6 +10,7 @@ */ #include <config.h> +#include <output_docbook.h> #include "InsetNewline.h" @@ -171,10 +172,13 @@ int InsetNewline::plaintext(odocstringstream & os, } -int InsetNewline::docbook(odocstream & os, OutputParams const &) const +void InsetNewline::docbook(XMLStream & xs, OutputParams const & runparams) const { - os << '\n'; - return 0; + if (runparams.docbook_in_par) { + xs.closeFontTags(); + xs << xml::EndTag("para"); + xs << xml::StartTag("para"); + } } diff --git a/src/insets/InsetNewline.h b/src/insets/InsetNewline.h index afa5fcdcc8..4209dfee38 100644 --- a/src/insets/InsetNewline.h +++ b/src/insets/InsetNewline.h @@ -65,7 +65,7 @@ private: int plaintext(odocstringstream & ods, OutputParams const & op, size_t max_length = INT_MAX) const; /// - int docbook(odocstream &, OutputParams const &) const; + void docbook(XMLStream &, OutputParams const &) const; /// docstring xhtml(XMLStream &, OutputParams const &) const; /// diff --git a/src/insets/InsetNewpage.cpp b/src/insets/InsetNewpage.cpp index b108885f9c..6e9f34211f 100644 --- a/src/insets/InsetNewpage.cpp +++ b/src/insets/InsetNewpage.cpp @@ -19,7 +19,7 @@ #include "Lexer.h" #include "MetricsInfo.h" #include "OutputParams.h" -#include "output_xhtml.h" +#include "xml.h" #include "texstream.h" #include "Text.h" #include "TextMetrics.h" @@ -248,10 +248,9 @@ int InsetNewpage::plaintext(odocstringstream & os, } -int InsetNewpage::docbook(odocstream & os, OutputParams const &) const +void InsetNewpage::docbook(XMLStream & os, OutputParams const &) const { - os << '\n'; - return 0; + os << xml::CR(); } diff --git a/src/insets/InsetNewpage.h b/src/insets/InsetNewpage.h index 7fc4cc49b1..1fb3436b62 100644 --- a/src/insets/InsetNewpage.h +++ b/src/insets/InsetNewpage.h @@ -66,7 +66,7 @@ private: int plaintext(odocstringstream & ods, OutputParams const & op, size_t max_length = INT_MAX) const; /// - int docbook(odocstream &, OutputParams const &) const; + void docbook(XMLStream &, OutputParams const &) const; /// docstring xhtml(XMLStream &, OutputParams const &) const; /// diff --git a/src/insets/InsetNomencl.cpp b/src/insets/InsetNomencl.cpp index 5e8813c398..e7fc8a7d98 100644 --- a/src/insets/InsetNomencl.cpp +++ b/src/insets/InsetNomencl.cpp @@ -29,7 +29,6 @@ #include "Length.h" #include "LyX.h" #include "OutputParams.h" -#include "output_xhtml.h" #include "xml.h" #include "texstream.h" #include "TocBackend.h" @@ -105,12 +104,12 @@ int InsetNomencl::plaintext(odocstringstream & os, } -int InsetNomencl::docbook(odocstream & os, OutputParams const &) const +void InsetNomencl::docbook(XMLStream & xs, OutputParams const &) const { - os << "<glossterm linkend=\"" << nomenclature_entry_id << "\">" - << xml::escapeString(getParam("symbol")) - << "</glossterm>"; - return 0; + docstring attr = "linkend=\"" + nomenclature_entry_id + "\""; + xs << xml::StartTag("glossterm", attr); + xs << xml::escapeString(getParam("symbol")); + xs << xml::EndTag("glossterm"); } @@ -120,20 +119,6 @@ docstring InsetNomencl::xhtml(XMLStream &, OutputParams const &) const } -int InsetNomencl::docbookGlossary(odocstream & os) const -{ - os << "<glossentry id=\"" << nomenclature_entry_id << "\">\n" - << "<glossterm>" - << xml::escapeString(getParam("symbol")) - << "</glossterm>\n" - << "<glossdef><para>" - << xml::escapeString(getParam("description")) - << "</para></glossdef>\n" - <<"</glossentry>\n"; - return 4; -} - - void InsetNomencl::validate(LaTeXFeatures & features) const { features.require("nomencl"); @@ -316,29 +301,75 @@ bool InsetPrintNomencl::getStatus(Cursor & cur, FuncRequest const & cmd, } -// FIXME This should be changed to use the TOC. Perhaps -// that could be done when XHTML output is added. -int InsetPrintNomencl::docbook(odocstream & os, OutputParams const &) const +void InsetPrintNomencl::docbook(XMLStream & xs, OutputParams const & runparams) const { - os << "<glossary>\n"; - int newlines = 2; - InsetIterator it = inset_iterator_begin(buffer().inset()); - while (it) { - if (it->lyxCode() == NOMENCL_CODE) { - newlines += static_cast<InsetNomencl const &>(*it).docbookGlossary(os); - ++it; - } else if (!it->producesOutput()) { - // Ignore contents of insets that are not in output - size_t const depth = it.depth(); - ++it; - while (it.depth() > depth) - ++it; - } else { - ++it; - } + shared_ptr<Toc const> toc = buffer().tocBackend().toc("nomencl"); + + EntryMap entries; + Toc::const_iterator it = toc->begin(); + Toc::const_iterator const en = toc->end(); + for (; it != en; ++it) { + DocIterator dit = it->dit(); + Paragraph const & par = dit.innerParagraph(); + Inset const * inset = par.getInset(dit.top().pos()); + if (!inset) + return; + InsetCommand const * ic = inset->asInsetCommand(); + if (!ic) + return; + + // FIXME We need a link to the paragraph here, so we + // need some kind of struct. + docstring const symbol = ic->getParam("symbol"); + docstring const desc = ic->getParam("description"); + docstring const prefix = ic->getParam("prefix"); + docstring const sortas = prefix.empty() ? symbol : prefix; + + entries[sortas] = NomenclEntry(symbol, desc, &par); } - os << "</glossary>\n"; - return newlines; + + if (entries.empty()) + return; + + // As opposed to XHTML, no need to defer everything until the end of time, so write directly to xs. + // TODO: At least, that's what was done before... + + docstring toclabel = translateIfPossible(from_ascii("Nomenclature"), + runparams.local_font->language()->lang()); + + xs << xml::StartTag("glossary"); + xs << xml::CR(); + xs << xml::StartTag("title"); + xs << toclabel; + xs << xml::EndTag("title"); + xs << xml::CR(); + + EntryMap::const_iterator eit = entries.begin(); + EntryMap::const_iterator const een = entries.end(); + for (; eit != een; ++eit) { + NomenclEntry const & ne = eit->second; + string const parid = ne.par->magicLabel(); + + xs << xml::StartTag("glossentry", "xml:id=\"" + parid + "\""); + xs << xml::CR(); + xs << xml::StartTag("glossterm"); + xs << ne.symbol; + xs << xml::EndTag("glossterm"); + xs << xml::CR(); + xs << xml::StartTag("glossdef"); + xs << xml::CR(); + xs << xml::StartTag("para"); + xs << ne.desc; + xs << xml::EndTag("para"); + xs << xml::CR(); + xs << xml::EndTag("glossdef"); + xs << xml::CR(); + xs << xml::EndTag("glossentry"); + xs << xml::CR(); + } + + xs << xml::EndTag("glossary"); + xs << xml::CR(); } diff --git a/src/insets/InsetNomencl.h b/src/insets/InsetNomencl.h index d023237865..b36527c7bb 100644 --- a/src/insets/InsetNomencl.h +++ b/src/insets/InsetNomencl.h @@ -28,9 +28,6 @@ public: /// InsetNomencl(Buffer * buf, InsetCommandParams const &); - /// - int docbookGlossary(odocstream &) const; - /// \name Public functions inherited from Inset class //@{ /// @@ -48,7 +45,7 @@ public: int plaintext(odocstringstream & ods, OutputParams const & op, size_t max_length = INT_MAX) const; /// - int docbook(odocstream &, OutputParams const &) const; + void docbook(XMLStream &, OutputParams const &) const; /// Does nothing at the moment. docstring xhtml(XMLStream &, OutputParams const &) const; //@} @@ -92,7 +89,7 @@ public: /// Updates needed features for this inset. void validate(LaTeXFeatures & features) const; /// - int docbook(odocstream &, OutputParams const &) const; + void docbook(XMLStream &, OutputParams const &) const; /// docstring xhtml(XMLStream &, OutputParams const &) const; /// diff --git a/src/insets/InsetNote.cpp b/src/insets/InsetNote.cpp index 31df791f59..85469f67ac 100644 --- a/src/insets/InsetNote.cpp +++ b/src/insets/InsetNote.cpp @@ -42,6 +42,7 @@ #include <algorithm> #include <sstream> +#include <output_docbook.h> using namespace std; @@ -260,27 +261,28 @@ int InsetNote::plaintext(odocstringstream & os, } -int InsetNote::docbook(odocstream & os, OutputParams const & runparams_in) const +void InsetNote::docbook(XMLStream & xs, OutputParams const & runparams_in) const { if (params_.type == InsetNoteParams::Note) - return 0; + return; OutputParams runparams(runparams_in); if (params_.type == InsetNoteParams::Comment) { - os << "<remark>\n"; + xs << xml::StartTag("remark"); + xs << xml::CR(); runparams.inComment = true; // Ignore files that are exported inside a comment runparams.exportdata.reset(new ExportData); } + // Greyed out text is output as such (no way to mark text as greyed out with DocBook). - int const n = InsetText::docbook(os, runparams); + InsetText::docbook(xs, runparams); - if (params_.type == InsetNoteParams::Comment) - os << "\n</remark>\n"; - - // Return how many newlines we issued. - //return int(count(str.begin(), str.end(), '\n')); - return n + 1 + 2; + if (params_.type == InsetNoteParams::Comment) { + xs << xml::CR(); + xs << xml::EndTag("remark"); + xs << xml::CR(); + } } diff --git a/src/insets/InsetNote.h b/src/insets/InsetNote.h index 3c04a10128..f76ce21ed6 100644 --- a/src/insets/InsetNote.h +++ b/src/insets/InsetNote.h @@ -86,7 +86,7 @@ private: int plaintext(odocstringstream & ods, OutputParams const & op, size_t max_length = INT_MAX) const; /// - int docbook(odocstream &, OutputParams const &) const; + void docbook(XMLStream &, OutputParams const &) const; /// docstring xhtml(XMLStream &, OutputParams const &) const; /// diff --git a/src/insets/InsetPhantom.cpp b/src/insets/InsetPhantom.cpp index 4771cb83ee..37367add2c 100644 --- a/src/insets/InsetPhantom.cpp +++ b/src/insets/InsetPhantom.cpp @@ -348,22 +348,9 @@ int InsetPhantom::plaintext(odocstringstream & os, } -int InsetPhantom::docbook(odocstream & os, OutputParams const & runparams) const +void InsetPhantom::docbook(XMLStream &, OutputParams const &) const { - docstring cmdname; - switch (params_.type) { - case InsetPhantomParams::Phantom: - case InsetPhantomParams::HPhantom: - case InsetPhantomParams::VPhantom: - default: - cmdname = from_ascii("phantom"); - break; - } - os << "<" + cmdname + ">"; - int const i = InsetCollapsible::docbook(os, runparams); - os << "</" + cmdname + ">"; - - return i; + return; } diff --git a/src/insets/InsetPhantom.h b/src/insets/InsetPhantom.h index 44ab078c81..89ccfb3d76 100644 --- a/src/insets/InsetPhantom.h +++ b/src/insets/InsetPhantom.h @@ -79,7 +79,7 @@ private: int plaintext(odocstringstream & ods, OutputParams const & op, size_t max_length = INT_MAX) const; /// - int docbook(odocstream &, OutputParams const &) const; + void docbook(XMLStream &, OutputParams const &) const; /// Makes no sense for XHTML. docstring xhtml(XMLStream &, OutputParams const &) const; /// diff --git a/src/insets/InsetQuotes.cpp b/src/insets/InsetQuotes.cpp index 5818cd7a96..5358bbaf19 100644 --- a/src/insets/InsetQuotes.cpp +++ b/src/insets/InsetQuotes.cpp @@ -28,10 +28,10 @@ #include "LyXRC.h" #include "MetricsInfo.h" #include "OutputParams.h" -#include "output_xhtml.h" #include "Paragraph.h" #include "ParIterator.h" #include "texstream.h" +#include "xml.h" #include "frontends/FontMetrics.h" #include "frontends/Painter.h" @@ -1041,10 +1041,9 @@ docstring InsetQuotes::getQuoteEntity(bool isHTML) const { } -int InsetQuotes::docbook(odocstream & os, OutputParams const &) const +void InsetQuotes::docbook(XMLStream & xs, OutputParams const &) const { - os << getQuoteEntity(false); - return 0; + xs << XMLStream::ESCAPE_NONE << getQuoteEntity(false); } diff --git a/src/insets/InsetQuotes.h b/src/insets/InsetQuotes.h index 939e6d61b4..09eadec3c7 100644 --- a/src/insets/InsetQuotes.h +++ b/src/insets/InsetQuotes.h @@ -146,7 +146,7 @@ public: int plaintext(odocstringstream & ods, OutputParams const & op, size_t max_length = INT_MAX) const; /// - int docbook(odocstream &, OutputParams const &) const; + void docbook(XMLStream &, OutputParams const &) const; /// docstring xhtml(XMLStream &, OutputParams const &) const; diff --git a/src/insets/InsetRef.cpp b/src/insets/InsetRef.cpp index d4cdaac30c..e9b2695397 100644 --- a/src/insets/InsetRef.cpp +++ b/src/insets/InsetRef.cpp @@ -309,28 +309,59 @@ int InsetRef::plaintext(odocstringstream & os, } -int InsetRef::docbook(odocstream & os, OutputParams const & runparams) const +void InsetRef::docbook(XMLStream & xs, OutputParams const &) const { + docstring const & ref = getParam("reference"); + InsetLabel const * il = buffer().insetLabel(ref, true); + string const & cmd = params().getCmdName(); + docstring linkend = xml::cleanID(ref); + + // A name is provided, LyX will provide it. This is supposed to be a very rare case. + // Link with linkend, as is it within the document (not outside, in which case xlink:href is better suited). docstring const & name = getParam("name"); - if (name.empty()) { - if (runparams.flavor == OutputParams::DOCBOOK5) { - os << "<xref linkend=\"" - << xml::cleanID(getParam("reference")) - << "\" />"; - } else { - os << "<xref linkend=\"" - << xml::cleanID(getParam("reference")) - << "\">"; - } - } else { - os << "<link linkend=\"" - << xml::cleanID(getParam("reference")) - << "\">" - << getParam("name") - << "</link>"; + if (!name.empty()) { + docstring attr = from_utf8("linkend=\"") + linkend + from_utf8("\""); + + xs << xml::StartTag("link", to_utf8(attr)); + xs << name; + xs << xml::EndTag("link"); + return; } - return 0; + // The DocBook processor will generate the name when required. + docstring display_before; + docstring display_after; + docstring role; + + if (il && !il->counterValue().empty()) { + // Try to construct a label from the InsetLabel we reference. + if (cmd == "vref" || cmd == "pageref" || cmd == "vpageref" || cmd == "nameref" || cmd == "formatted") { + // "ref on page #", "on page #", etc. The DocBook processor deals with generating the right text, + // including in the right language. + role = from_ascii(cmd); + + if (cmd == "formatted") { + // A formatted reference may have many parameters. Generate all of them as roles, the only + // way arbitrary parameters can be passed into DocBook. + if (buffer().params().use_refstyle && getParam("caps") == "true") + role += " refstyle-caps"; + if (buffer().params().use_refstyle && getParam("plural") == "true") + role += " refstyle-plural"; + } + } else if (cmd == "eqref") { + display_before = from_ascii("("); + display_after = from_ascii(")"); + } + // TODO: what about labelonly? I don't get how this is supposed to work... + } + + // No name, ask DocBook to generate one. + docstring attr = from_utf8("linkend=\"") + ref + from_utf8("\""); + if (!role.empty()) + attr += " role=\"" + role + "\""; + xs << display_before; + xs << xml::CompTag("xref", to_utf8(attr)); + xs << display_after; } diff --git a/src/insets/InsetRef.h b/src/insets/InsetRef.h index 253718450b..192ca0d696 100644 --- a/src/insets/InsetRef.h +++ b/src/insets/InsetRef.h @@ -61,7 +61,7 @@ public: int plaintext(odocstringstream & ods, OutputParams const & op, size_t max_length = INT_MAX) const; /// - int docbook(odocstream &, OutputParams const &) const; + void docbook(XMLStream &, OutputParams const &) const; /// docstring xhtml(XMLStream &, OutputParams const &) const; /// diff --git a/src/insets/InsetScript.cpp b/src/insets/InsetScript.cpp index 13b67e16e1..3dc9e1248a 100644 --- a/src/insets/InsetScript.cpp +++ b/src/insets/InsetScript.cpp @@ -40,6 +40,7 @@ #include "frontends/Painter.h" #include <algorithm> +#include <output_docbook.h> using namespace std; @@ -354,7 +355,7 @@ int InsetScript::plaintext(odocstringstream & os, } -int InsetScript::docbook(odocstream & os, OutputParams const & runparams) const +void InsetScript::docbook(XMLStream & xs, OutputParams const & runparams) const { docstring cmdname; switch (params_.type) { @@ -365,11 +366,10 @@ int InsetScript::docbook(odocstream & os, OutputParams const & runparams) const cmdname = from_ascii("superscript"); break; } - os << '<' + cmdname + '>'; - int const i = InsetText::docbook(os, runparams); - os << "</" + cmdname + '>'; - return i; + xs << xml::StartTag(cmdname); + InsetText::docbook(xs, runparams); + xs << xml::EndTag(cmdname); } diff --git a/src/insets/InsetScript.h b/src/insets/InsetScript.h index 7f9cd85278..45e766f943 100644 --- a/src/insets/InsetScript.h +++ b/src/insets/InsetScript.h @@ -99,7 +99,7 @@ public: int plaintext(odocstringstream & ods, OutputParams const & op, size_t max_length = INT_MAX) const; /// - int docbook(odocstream &, OutputParams const &) const; + void docbook(XMLStream &, OutputParams const &) const; /// void edit(Cursor & cur, bool front, EntryDirection entry_from = ENTRY_DIRECTION_IGNORE); diff --git a/src/insets/InsetSeparator.cpp b/src/insets/InsetSeparator.cpp index 42ff09e70a..b588b39439 100644 --- a/src/insets/InsetSeparator.cpp +++ b/src/insets/InsetSeparator.cpp @@ -9,6 +9,7 @@ */ #include <config.h> +#include <output_docbook.h> #include "InsetSeparator.h" @@ -167,10 +168,9 @@ int InsetSeparator::plaintext(odocstringstream & os, } -int InsetSeparator::docbook(odocstream & os, OutputParams const &) const +void InsetSeparator::docbook(XMLStream & xs, OutputParams const &) const { - os << '\n'; - return 0; + xs << xml::CR(); } diff --git a/src/insets/InsetSeparator.h b/src/insets/InsetSeparator.h index a57f45c461..85d9347ed8 100644 --- a/src/insets/InsetSeparator.h +++ b/src/insets/InsetSeparator.h @@ -79,7 +79,7 @@ private: int plaintext(odocstringstream & ods, OutputParams const & op, size_t max_length = INT_MAX) const; /// - int docbook(odocstream &, OutputParams const &) const; + void docbook(XMLStream &, OutputParams const &) const; /// docstring xhtml(XMLStream &, OutputParams const &) const; /// diff --git a/src/insets/InsetSpace.cpp b/src/insets/InsetSpace.cpp index 84c6a2599c..04b515cdcc 100644 --- a/src/insets/InsetSpace.cpp +++ b/src/insets/InsetSpace.cpp @@ -26,8 +26,8 @@ #include "Lexer.h" #include "MetricsInfo.h" #include "OutputParams.h" -#include "output_xhtml.h" #include "texstream.h" +#include "xml.h" #include "support/debug.h" #include "support/docstream.h" @@ -741,68 +741,57 @@ int InsetSpace::plaintext(odocstringstream & os, } -int InsetSpace::docbook(odocstream & os, OutputParams const &) const +void InsetSpace::docbook(XMLStream & xs, OutputParams const &) const { switch (params_.kind) { case InsetSpaceParams::NORMAL: - os << " "; + xs << XMLStream::ESCAPE_NONE << " "; break; case InsetSpaceParams::QUAD: - os << " "; + xs << XMLStream::ESCAPE_NONE << " "; // HTML:   break; case InsetSpaceParams::QQUAD: - os << "  "; + xs << XMLStream::ESCAPE_NONE << "  "; // HTML:    break; case InsetSpaceParams::ENSKIP: - os << " "; + xs << XMLStream::ESCAPE_NONE << " "; // HTML:   break; case InsetSpaceParams::PROTECTED: - os << " "; + xs << XMLStream::ESCAPE_NONE << " "; // HTML:   break; case InsetSpaceParams::VISIBLE: - os << "␣"; + xs << XMLStream::ESCAPE_NONE << "␣"; break; - case InsetSpaceParams::ENSPACE: - os << "⁠ ⁠"; + case InsetSpaceParams::ENSPACE: // HTML: ⁠ ⁠ (word joiners) + xs << XMLStream::ESCAPE_NONE << "⁠ ⁠"; break; case InsetSpaceParams::THIN: - os << " "; + xs << XMLStream::ESCAPE_NONE << " "; // HTML: &thinspace; break; case InsetSpaceParams::MEDIUM: - os << " "; + xs << XMLStream::ESCAPE_NONE << " "; // HTML:   break; case InsetSpaceParams::THICK: - os << " "; + xs << XMLStream::ESCAPE_NONE << " "; // HTML:   break; case InsetSpaceParams::NEGTHIN: case InsetSpaceParams::NEGMEDIUM: case InsetSpaceParams::NEGTHICK: - // FIXME - os << " "; + xs << XMLStream::ESCAPE_NONE << " "; // HTML:   break; case InsetSpaceParams::HFILL: case InsetSpaceParams::HFILL_PROTECTED: - os << '\n'; - break; case InsetSpaceParams::DOTFILL: - // FIXME - os << '\n'; - break; case InsetSpaceParams::HRULEFILL: - // FIXME - os << '\n'; - break; case InsetSpaceParams::LEFTARROWFILL: case InsetSpaceParams::RIGHTARROWFILL: case InsetSpaceParams::UPBRACEFILL: case InsetSpaceParams::DOWNBRACEFILL: case InsetSpaceParams::CUSTOM: case InsetSpaceParams::CUSTOM_PROTECTED: - // FIXME - os << '\n'; + xs << '\n'; break; } - return 0; } diff --git a/src/insets/InsetSpace.h b/src/insets/InsetSpace.h index 65b1dc6ac4..1111aa1e82 100644 --- a/src/insets/InsetSpace.h +++ b/src/insets/InsetSpace.h @@ -129,7 +129,7 @@ public: int plaintext(odocstringstream & ods, OutputParams const & op, size_t max_length = INT_MAX) const; /// - int docbook(odocstream &, OutputParams const &) const; + void docbook(XMLStream &, OutputParams const &) const; /// docstring xhtml(XMLStream &, OutputParams const &) const; /// diff --git a/src/insets/InsetSpecialChar.cpp b/src/insets/InsetSpecialChar.cpp index 2e51e95d3f..33d3d0bc19 100644 --- a/src/insets/InsetSpecialChar.cpp +++ b/src/insets/InsetSpecialChar.cpp @@ -22,6 +22,7 @@ #include "Lexer.h" #include "MetricsInfo.h" #include "output_xhtml.h" +#include "xml.h" #include "texstream.h" #include "frontends/FontMetrics.h" @@ -529,48 +530,54 @@ int InsetSpecialChar::plaintext(odocstringstream & os, } -int InsetSpecialChar::docbook(odocstream & os, OutputParams const &) const +void InsetSpecialChar::docbook(XMLStream & xs, OutputParams const &) const { switch (kind_) { - case HYPHENATION: - break; - case ALLOWBREAK: - // U+200B ZERO WIDTH SPACE (ZWSP) - os.put(0x200b); - break; + case HYPHENATION: + // Soft hyphen. + xs << XMLStream::ESCAPE_NONE << "­"; + break; + case ALLOWBREAK: + // Zero-width space + xs << XMLStream::ESCAPE_NONE << "​"; + break; case LIGATURE_BREAK: + // Zero width non-joiner + xs << XMLStream::ESCAPE_NONE << "‌"; break; case END_OF_SENTENCE: - os << '.'; + xs << '.'; break; case LDOTS: - os << "…"; + // … + xs << XMLStream::ESCAPE_NONE << "…"; break; case MENU_SEPARATOR: - os << "&lyxarrow;"; + // ⇒, right arrow. + xs << XMLStream::ESCAPE_NONE << "⇒"; break; case SLASH: - os << '/'; + // ⁄, fractional slash. + xs << XMLStream::ESCAPE_NONE << "⁄"; break; + // Non-breaking hyphen. case NOBREAKDASH: - os << '-'; + xs << XMLStream::ESCAPE_NONE << "‑"; break; case PHRASE_LYX: - os << "LyX"; + xs << "LyX"; break; case PHRASE_TEX: - os << "TeX"; + xs << "TeX"; break; case PHRASE_LATEX2E: - os << "LaTeX2"; - // ε U+03B5 GREEK SMALL LETTER EPSILON - os.put(0x03b5); + // Lower-case epsilon. + xs << "LaTeX2" << XMLStream::ESCAPE_NONE << "ε"; break; case PHRASE_LATEX: - os << "LaTeX"; + xs << "LaTeX"; break; } - return 0; } diff --git a/src/insets/InsetSpecialChar.h b/src/insets/InsetSpecialChar.h index b7c6e794d0..286e3b9907 100644 --- a/src/insets/InsetSpecialChar.h +++ b/src/insets/InsetSpecialChar.h @@ -78,7 +78,7 @@ public: int plaintext(odocstringstream & ods, OutputParams const & op, size_t max_length = INT_MAX) const; /// - int docbook(odocstream &, OutputParams const &) const; + void docbook(XMLStream &, OutputParams const &) const; /// docstring xhtml(XMLStream &, OutputParams const &) const; /// diff --git a/src/insets/InsetTOC.cpp b/src/insets/InsetTOC.cpp index c700e57640..4469b88939 100644 --- a/src/insets/InsetTOC.cpp +++ b/src/insets/InsetTOC.cpp @@ -128,11 +128,10 @@ int InsetTOC::plaintext(odocstringstream & os, } -int InsetTOC::docbook(odocstream & os, OutputParams const &) const +void InsetTOC::docbook(XMLStream &, OutputParams const &) const { - if (getCmdName() == "tableofcontents") - os << "<toc></toc>"; - return 0; + // TOC are generated automatically by the DocBook processor. + return; } diff --git a/src/insets/InsetTOC.h b/src/insets/InsetTOC.h index 8ab1cc84ac..4b92e329a2 100644 --- a/src/insets/InsetTOC.h +++ b/src/insets/InsetTOC.h @@ -44,7 +44,7 @@ public: int plaintext(odocstringstream & ods, OutputParams const & op, size_t max_length = INT_MAX) const; /// - int docbook(odocstream &, OutputParams const &) const; + void docbook(XMLStream &, OutputParams const &) const; /// docstring xhtml(XMLStream & xs, OutputParams const &) const; /// diff --git a/src/insets/InsetTabular.cpp b/src/insets/InsetTabular.cpp index fb9f64a7d2..e9d71f91ef 100644 --- a/src/insets/InsetTabular.cpp +++ b/src/insets/InsetTabular.cpp @@ -42,6 +42,7 @@ #include "LyXRC.h" #include "MetricsInfo.h" #include "OutputParams.h" +#include "xml.h" #include "output_xhtml.h" #include "Paragraph.h" #include "ParagraphParameters.h" @@ -3498,154 +3499,136 @@ void Tabular::latex(otexstream & os, OutputParams const & runparams) const } -int Tabular::docbookRow(odocstream & os, row_type row, - OutputParams const & runparams) const +void Tabular::docbookRow(XMLStream & xs, row_type row, + OutputParams const & runparams, bool header) const { - int ret = 0; + string const celltag = header ? "th" : "td"; idx_type cell = getFirstCellInRow(row); - os << "<row>\n"; + xs << xml::StartTag("tr"); + xs << xml::CR(); for (col_type c = 0; c < ncols(); ++c) { - if (isPartOfMultiColumn(row, c)) + if (isPartOfMultiColumn(row, c) || isPartOfMultiRow(row, c)) continue; - os << "<entry align=\""; + stringstream attr; + + Length const cwidth = column_info[c].p_width; + if (!cwidth.zero()) { + string const hwidth = cwidth.asHTMLString(); + attr << "style =\"width: " << hwidth << ";\" "; + } + + attr << "align='"; switch (getAlignment(cell)) { case LYX_ALIGN_LEFT: - os << "left"; + attr << "left"; break; case LYX_ALIGN_RIGHT: - os << "right"; + attr << "right"; break; default: - os << "center"; + attr << "center"; break; } - - os << "\" valign=\""; + attr << "'"; + attr << " valign='"; switch (getVAlignment(cell)) { case LYX_VALIGN_TOP: - os << "top"; + attr << "top"; break; case LYX_VALIGN_BOTTOM: - os << "bottom"; + attr << "bottom"; break; case LYX_VALIGN_MIDDLE: - os << "middle"; + attr << "middle"; } - os << '"'; + attr << "'"; - if (isMultiColumn(cell)) { - os << " namest=\"col" << c << "\" "; - os << "nameend=\"col" << c + columnSpan(cell) - 1 << '"'; - } + if (isMultiColumn(cell)) + attr << " colspan='" << columnSpan(cell) << "'"; + else if (isMultiRow(cell)) + attr << " rowspan='" << rowSpan(cell) << "'"; - os << '>'; - ret += cellInset(cell)->docbook(os, runparams); - os << "</entry>\n"; + xs << xml::StartTag(celltag, attr.str(), true); + cellInset(cell)->docbook(xs, runparams); + xs << xml::EndTag(celltag); + xs << xml::CR(); ++cell; } - os << "</row>\n"; - return ret; + xs << xml::EndTag("tr"); + xs<< xml::CR(); } -int Tabular::docbook(odocstream & os, OutputParams const & runparams) const +void Tabular::docbook(XMLStream & xs, OutputParams const & runparams) const { - int ret = 0; + docstring ret; - //+--------------------------------------------------------------------- - //+ first the opening preamble + - //+--------------------------------------------------------------------- - - os << "<tgroup cols=\"" << ncols() - << "\" colsep=\"1\" rowsep=\"1\">\n"; - - for (col_type c = 0; c < ncols(); ++c) { - os << "<colspec colname=\"col" << c << "\" align=\""; - switch (column_info[c].alignment) { - case LYX_ALIGN_LEFT: - os << "left"; - break; - case LYX_ALIGN_RIGHT: - os << "right"; - break; - default: - os << "center"; - break; - } - os << '"'; - if (runparams.flavor == OutputParams::DOCBOOK5) - os << '/'; - os << ">\n"; - ++ret; + // Some tables are inline. Likely limitation: cannot output a table within a table; is that really a limitation? + bool hasTableStarted = xs.isTagOpen(xml::StartTag("informaltable")) || xs.isTagOpen(xml::StartTag("table")); + if (!hasTableStarted) { + xs << xml::StartTag("informaltable"); + xs << xml::CR(); } - //+--------------------------------------------------------------------- - //+ Long Tabular case + - //+--------------------------------------------------------------------- - - // output caption info - // The caption flag wins over head/foot + // "Formal" tables have a caption and use the tag <table>; the distinction with <informaltable> is done outside. if (haveLTCaption()) { - os << "<caption>\n"; - ++ret; - for (row_type r = 0; r < nrows(); ++r) { - if (row_info[r].caption) { - ret += docbookRow(os, r, runparams); - } - } - os << "</caption>\n"; - ++ret; + xs << xml::StartTag("caption"); + for (row_type r = 0; r < nrows(); ++r) + if (row_info[r].caption) + docbookRow(xs, r, runparams); + xs << xml::EndTag("caption"); + xs << xml::CR(); } + // output header info - if (haveLTHead(false) || haveLTFirstHead(false)) { - os << "<thead>\n"; - ++ret; + bool const havefirsthead = haveLTFirstHead(false); + // if we have a first head, then we are going to ignore the + // headers for the additional pages, since there aren't any + // in XHTML. this test accomplishes that. + bool const havehead = !havefirsthead && haveLTHead(false); + if (havehead || havefirsthead) { + xs << xml::StartTag("thead") << xml::CR(); for (row_type r = 0; r < nrows(); ++r) { - if ((row_info[r].endhead || row_info[r].endfirsthead) && - !row_info[r].caption) { - ret += docbookRow(os, r, runparams); + if (((havefirsthead && row_info[r].endfirsthead) || + (havehead && row_info[r].endhead)) && + !row_info[r].caption) { + docbookRow(xs, r, runparams, true); } } - os << "</thead>\n"; - ++ret; + xs << xml::EndTag("thead"); + xs << xml::CR(); } // output footer info - if (haveLTFoot(false) || haveLTLastFoot(false)) { - os << "<tfoot>\n"; - ++ret; + bool const havelastfoot = haveLTLastFoot(false); + // as before. + bool const havefoot = !havelastfoot && haveLTFoot(false); + if (havefoot || havelastfoot) { + xs << xml::StartTag("tfoot") << xml::CR(); for (row_type r = 0; r < nrows(); ++r) { - if ((row_info[r].endfoot || row_info[r].endlastfoot) && - !row_info[r].caption) { - ret += docbookRow(os, r, runparams); + if (((havelastfoot && row_info[r].endlastfoot) || + (havefoot && row_info[r].endfoot)) && + !row_info[r].caption) { + docbookRow(xs, r, runparams); } } - os << "</tfoot>\n"; - ++ret; + xs << xml::EndTag("tfoot"); + xs << xml::CR(); } - //+--------------------------------------------------------------------- - //+ the single row and columns (cells) + - //+--------------------------------------------------------------------- + xs << xml::StartTag("tbody"); + xs << xml::CR(); + for (row_type r = 0; r < nrows(); ++r) + if (isValidRow(r)) + docbookRow(xs, r, runparams); + xs << xml::EndTag("tbody"); + xs << xml::CR(); - os << "<tbody>\n"; - ++ret; - for (row_type r = 0; r < nrows(); ++r) { - if (isValidRow(r)) { - ret += docbookRow(os, r, runparams); - } + if (!hasTableStarted) { + xs << xml::EndTag("informaltable"); + xs << xml::CR(); } - os << "</tbody>\n"; - ++ret; - //+--------------------------------------------------------------------- - //+ the closing of the tabular + - //+--------------------------------------------------------------------- - - os << "</tgroup>"; - ++ret; - - return ret; } @@ -3728,20 +3711,22 @@ docstring Tabular::xhtml(XMLStream & xs, OutputParams const & runparams) const align = "right"; break; } - xs << xml::StartTag("div", "class='longtable' style='text-align: " + align + ";'") - << xml::CR(); + xs << xml::StartTag("div", "class='longtable' style='text-align: " + align + ";'"); + xs << xml::CR(); // The caption flag wins over head/foot if (haveLTCaption()) { - xs << xml::StartTag("div", "class='longtable-caption' style='text-align: " + align + ";'") - << xml::CR(); + xs << xml::StartTag("div", "class='longtable-caption' style='text-align: " + align + ";'"); + xs << xml::CR(); for (row_type r = 0; r < nrows(); ++r) if (row_info[r].caption) ret += xhtmlRow(xs, r, runparams); - xs << xml::EndTag("div") << xml::CR(); + xs << xml::EndTag("div"); + xs << xml::CR(); } } - xs << xml::StartTag("table") << xml::CR(); + xs << xml::StartTag("table"); + xs << xml::CR(); // output header info bool const havefirsthead = haveLTFirstHead(false); @@ -3750,7 +3735,8 @@ docstring Tabular::xhtml(XMLStream & xs, OutputParams const & runparams) const // in XHTML. this test accomplishes that. bool const havehead = !havefirsthead && haveLTHead(false); if (havehead || havefirsthead) { - xs << xml::StartTag("thead") << xml::CR(); + xs << xml::StartTag("thead"); + xs << xml::CR(); for (row_type r = 0; r < nrows(); ++r) { if (((havefirsthead && row_info[r].endfirsthead) || (havehead && row_info[r].endhead)) && @@ -3758,7 +3744,8 @@ docstring Tabular::xhtml(XMLStream & xs, OutputParams const & runparams) const ret += xhtmlRow(xs, r, runparams, true); } } - xs << xml::EndTag("thead") << xml::CR(); + xs << xml::EndTag("thead"); + xs << xml::CR(); } // output footer info bool const havelastfoot = haveLTLastFoot(false); @@ -3773,21 +3760,22 @@ docstring Tabular::xhtml(XMLStream & xs, OutputParams const & runparams) const ret += xhtmlRow(xs, r, runparams); } } - xs << xml::EndTag("tfoot") << xml::CR(); + xs << xml::EndTag("tfoot"); + xs << xml::CR(); } xs << xml::StartTag("tbody") << xml::CR(); - for (row_type r = 0; r < nrows(); ++r) { - if (isValidRow(r)) { + for (row_type r = 0; r < nrows(); ++r) + if (isValidRow(r)) ret += xhtmlRow(xs, r, runparams); - } + xs << xml::EndTag("tbody"); + xs << xml::CR(); + xs << xml::EndTag("table"); + xs << xml::CR(); + if (is_long_tabular) { + xs << xml::EndTag("div"); + xs << xml::CR(); } - xs << xml::EndTag("tbody") - << xml::CR() - << xml::EndTag("table") - << xml::CR(); - if (is_long_tabular) - xs << xml::EndTag("div") << xml::CR(); return ret; } @@ -4175,6 +4163,12 @@ docstring InsetTableCell::xhtml(XMLStream & xs, OutputParams const & rp) const } +void InsetTableCell::docbook(XMLStream & xs, OutputParams const & runparams) const +{ + InsetText::docbook(xs, runparams); +} + + void InsetTableCell::metrics(MetricsInfo & mi, Dimension & dim) const { TextMetrics & tm = mi.base.bv->textMetrics(&text()); @@ -6010,30 +6004,9 @@ int InsetTabular::plaintext(odocstringstream & os, } -int InsetTabular::docbook(odocstream & os, OutputParams const & runparams) const +void InsetTabular::docbook(XMLStream & xs, OutputParams const & runparams) const { - int ret = 0; - Inset * master = 0; - - // FIXME: Why not pass a proper DocIterator here? -#if 0 - // if the table is inside a float it doesn't need the informaltable - // wrapper. Search for it. - for (master = owner(); master; master = master->owner()) - if (master->lyxCode() == FLOAT_CODE) - break; -#endif - - if (!master) { - os << "<informaltable>"; - ++ret; - } - ret += tabular.docbook(os, runparams); - if (!master) { - os << "</informaltable>"; - ++ret; - } - return ret; + tabular.docbook(xs, runparams); } diff --git a/src/insets/InsetTabular.h b/src/insets/InsetTabular.h index 0e76bdd9ae..ce684ec9ea 100644 --- a/src/insets/InsetTabular.h +++ b/src/insets/InsetTabular.h @@ -74,6 +74,8 @@ public: /// docstring xhtml(XMLStream &, OutputParams const &) const; /// + void docbook(XMLStream &, OutputParams const &) const; + /// void addToToc(DocIterator const & di, bool output_active, UpdateType utype, TocBackend & backend) const; /// @@ -571,7 +573,7 @@ public: /// void latex(otexstream &, OutputParams const &) const; /// - int docbook(odocstream & os, OutputParams const &) const; + void docbook(XMLStream &, OutputParams const &) const; /// docstring xhtml(XMLStream &, OutputParams const &) const; /// @@ -906,7 +908,8 @@ public: std::vector<unsigned int> const &, bool onlydata, size_t max_length) const; /// auxiliary function for docbook - int docbookRow(odocstream & os, row_type, OutputParams const &) const; + void docbookRow(XMLStream &, row_type, OutputParams const &, + bool header = false) const; /// docstring xhtmlRow(XMLStream & xs, row_type, OutputParams const &, bool header = false) const; @@ -979,7 +982,7 @@ public: int plaintext(odocstringstream & ods, OutputParams const & op, size_t max_length = INT_MAX) const; /// - int docbook(odocstream &, OutputParams const &) const; + void docbook(XMLStream &, OutputParams const &) const; /// docstring xhtml(XMLStream &, OutputParams const &) const; /// diff --git a/src/insets/InsetText.cpp b/src/insets/InsetText.cpp index aeb4c43d6b..7f881f708a 100644 --- a/src/insets/InsetText.cpp +++ b/src/insets/InsetText.cpp @@ -38,14 +38,13 @@ #include "MetricsInfo.h" #include "output_docbook.h" #include "output_latex.h" +#include "output_plaintext.h" #include "output_xhtml.h" #include "OutputParams.h" -#include "output_plaintext.h" #include "Paragraph.h" #include "ParagraphParameters.h" #include "ParIterator.h" #include "Row.h" -#include "xml.h" #include "TexRow.h" #include "texstream.h" #include "TextClass.h" @@ -589,20 +588,52 @@ int InsetText::plaintext(odocstringstream & os, } -int InsetText::docbook(odocstream & os, OutputParams const & runparams) const + +void InsetText::docbook(XMLStream & xs, OutputParams const & rp) const { - ParagraphList::const_iterator const beg = paragraphs().begin(); + docbook(xs, rp, WriteEverything); +} - if (!undefined()) - xml::openTag(os, getLayout().latexname(), - beg->getID(buffer(), runparams) + getLayout().latexparam()); - docbookParagraphs(text_, buffer(), os, runparams); +void InsetText::docbook(XMLStream & xs, OutputParams const & rp, XHTMLOptions opts) const +{ + // we will always want to output all our paragraphs when we are + // called this way. + OutputParams runparams = rp; + runparams.par_begin = 0; + runparams.par_end = text().paragraphs().size(); - if (!undefined()) - xml::closeTag(os, getLayout().latexname()); + if (undefined()) { + xs.startDivision(false); + docbookParagraphs(text_, buffer(), xs, runparams); + xs.endDivision(); + return; + } - return 0; + InsetLayout const & il = getLayout(); + if (opts & WriteOuterTag && !il.docbooktag().empty() && il.docbooktag() != "NONE") { + docstring attrs = docstring(); + if (!il.docbookattr().empty()) + attrs += from_ascii(il.docbookattr()); + if (il.docbooktag() == "link") + attrs += from_ascii(" xlink:href=\"") + text_.asString() + from_ascii("\""); + xs << xml::StartTag(il.docbooktag(), attrs); + } + + // No need for labels that are generated from counters. + + // With respect to XHTML, paragraphs are still allowed here. + if (!allowMultiPar()) + runparams.docbook_make_pars = false; + if (il.isPassThru()) + runparams.pass_thru = true; + + xs.startDivision(false); + docbookParagraphs(text_, buffer(), xs, runparams); + xs.endDivision(); + + if (opts & WriteOuterTag) + xs << xml::EndTag(il.docbooktag()); } diff --git a/src/insets/InsetText.h b/src/insets/InsetText.h index 59f1202a24..d86bfb75d3 100644 --- a/src/insets/InsetText.h +++ b/src/insets/InsetText.h @@ -81,8 +81,6 @@ public: int plaintext(odocstringstream & ods, OutputParams const & op, size_t max_length = INT_MAX) const; /// - int docbook(odocstream &, OutputParams const &) const; - /// docstring xhtml(XMLStream &, OutputParams const &) const; /// enum XHTMLOptions { @@ -96,6 +94,10 @@ public: docstring insetAsXHTML(XMLStream &, OutputParams const &, XHTMLOptions) const; /// + void docbook(XMLStream &, OutputParams const &, XHTMLOptions opts) const; + /// + void docbook(XMLStream &, OutputParams const &) const; + /// void validate(LaTeXFeatures & features) const; /// return the argument(s) only diff --git a/src/insets/InsetVSpace.cpp b/src/insets/InsetVSpace.cpp index 24ea9aa636..a6092cb8a3 100644 --- a/src/insets/InsetVSpace.cpp +++ b/src/insets/InsetVSpace.cpp @@ -23,7 +23,7 @@ #include "Lexer.h" #include "MetricsInfo.h" #include "OutputParams.h" -#include "output_xhtml.h" +#include "xml.h" #include "texstream.h" #include "Text.h" @@ -224,10 +224,9 @@ int InsetVSpace::plaintext(odocstringstream & os, } -int InsetVSpace::docbook(odocstream & os, OutputParams const &) const +void InsetVSpace::docbook(XMLStream & xs, OutputParams const &) const { - os << '\n'; - return 1; + xs << xml::CR(); } diff --git a/src/insets/InsetVSpace.h b/src/insets/InsetVSpace.h index 08e8ea882e..6e307561d5 100644 --- a/src/insets/InsetVSpace.h +++ b/src/insets/InsetVSpace.h @@ -50,7 +50,7 @@ private: int plaintext(odocstringstream & ods, OutputParams const & op, size_t max_length = INT_MAX) const; /// - int docbook(odocstream &, OutputParams const &) const; + void docbook(XMLStream &, OutputParams const &) const; /// Note that this returns the inset rather than writing it, /// so it will actually be written after the present paragraph. /// The normal case is that this inset will be on a line by diff --git a/src/insets/InsetWrap.cpp b/src/insets/InsetWrap.cpp index 4460f99c9b..b05d5dba47 100644 --- a/src/insets/InsetWrap.cpp +++ b/src/insets/InsetWrap.cpp @@ -26,7 +26,7 @@ #include "FuncStatus.h" #include "LaTeXFeatures.h" #include "Lexer.h" -#include "output_xhtml.h" +#include "xml.h" #include "texstream.h" #include "TextClass.h" @@ -207,13 +207,13 @@ int InsetWrap::plaintext(odocstringstream & os, } -int InsetWrap::docbook(odocstream & os, OutputParams const & runparams) const +void InsetWrap::docbook(XMLStream & xs, OutputParams const & runparams) const { - // FIXME UNICODE - os << '<' << from_ascii(params_.type) << '>'; - int const i = InsetText::docbook(os, runparams); - os << "</" << from_ascii(params_.type) << '>'; - return i; + InsetLayout const & il = getLayout(); + + xs << xml::StartTag(il.docbooktag(), il.docbookattr()); // TODO: Detect when there is a title. + InsetText::docbook(xs, runparams); + xs << xml::EndTag(il.docbooktag()); } diff --git a/src/insets/InsetWrap.h b/src/insets/InsetWrap.h index 8df38e97f5..934e3bd101 100644 --- a/src/insets/InsetWrap.h +++ b/src/insets/InsetWrap.h @@ -72,7 +72,7 @@ private: int plaintext(odocstringstream & ods, OutputParams const & op, size_t max_length = INT_MAX) const; /// - int docbook(odocstream &, OutputParams const &) const; + void docbook(XMLStream &, OutputParams const &) const; /// docstring xhtml(XMLStream &, OutputParams const &) const; /// diff --git a/src/mathed/InsetMathHull.cpp b/src/mathed/InsetMathHull.cpp index d9f314b3c7..9800866d6b 100644 --- a/src/mathed/InsetMathHull.cpp +++ b/src/mathed/InsetMathHull.cpp @@ -38,7 +38,7 @@ #include "InsetMathMacro.h" #include "InsetMathMacroTemplate.h" #include "MetricsInfo.h" -#include "output_xhtml.h" +#include "xml.h" #include "Paragraph.h" #include "ParIterator.h" #include "xml.h" @@ -2404,13 +2404,12 @@ int InsetMathHull::plaintext(odocstringstream & os, } -int InsetMathHull::docbook(odocstream & os, OutputParams const & runparams) const +void InsetMathHull::docbook(XMLStream & xs, OutputParams const & runparams) const { // With DocBook 5, MathML must be within its own namespace; defined in Buffer.cpp::writeDocBookSource as "m". // Output everything in a separate stream so that this does not interfere with the standard flow of DocBook tags. odocstringstream osmath; MathStream ms(osmath, "m", true); - int res = 0; // Choose the tag around the MathML equation. docstring name; @@ -2419,6 +2418,8 @@ int InsetMathHull::docbook(odocstream & os, OutputParams const & runparams) cons else name = from_ascii("informalequation"); + // DocBook also has <equation>, but it comes with a title. + docstring bname = name; for (row_type i = 0; i < nrows(); ++i) { if (!label(i).empty()) { @@ -2429,44 +2430,44 @@ int InsetMathHull::docbook(odocstream & os, OutputParams const & runparams) cons ++ms.tab(); ms.cr(); ms.os() << '<' << bname << '>'; + // Output the MathML subtree. odocstringstream ls; otexstream ols(ls); - if (runparams.flavor == OutputParams::DOCBOOK5) { - ms << MTag("alt role='tex' "); - // Workaround for db2latex: db2latex always includes equations with - // \ensuremath{} or \begin{display}\end{display} - // so we strip LyX' math environment - WriteStream wi(ols, false, false, WriteStream::wsDefault, runparams.encoding); - InsetMathGrid::write(wi); - ms << from_utf8(subst(subst(to_utf8(ls.str()), "&", "&"), "<", "<")); - ms << ETag("alt"); - ms << MTag("math"); - ms << ETag("alt"); - ms << MTag("math"); - InsetMathGrid::mathmlize(ms); - ms << ETag("math"); - } else { - ms << MTag("alt role='tex'"); - latex(ols, runparams); - res = ols.texrow().rows(); - ms << from_utf8(subst(subst(to_utf8(ls.str()), "&", "&"), "<", "<")); - ms << ETag("alt"); + + // TeX transcription. Avoid MTag/ETag so that there are no extraneous spaces. + ms << "<" << from_ascii("alt") << " role='tex'" << ">"; + // Workaround for db2latex: db2latex always includes equations with + // \ensuremath{} or \begin{display}\end{display} + // so we strip LyX' math environment + WriteStream wi(ols, false, false, WriteStream::wsDefault, runparams.encoding); + InsetMathGrid::write(wi); + ms << from_utf8(subst(subst(to_utf8(ls.str()), "&", "&"), "<", "<")); + ms << "</" << from_ascii("alt") << ">"; + + // Actual transformation of the formula into MathML. This translation may fail (for example, due to custom macros). + // The new output stream is required to deal with the errors: first write completely the formula into this + // temporary stream; then, if it is possible without error, then copy it back to the "real" stream. Otherwise, + // some incomplete tags might be put into the real stream. + try { + // First, generate the MathML expression. + odocstringstream ostmp; + MathStream mstmp(ostmp, ms.xmlns(), ms.xmlMode()); + InsetMathGrid::mathmlize(mstmp); + + // Then, output it (but only if the generation can be done without errors!). + ms << MTag("math"); + ms.cr(); + osmath << ostmp.str(); // osmath is not a XMLStream, so no need for XMLStream::ESCAPE_NONE. + ms << ETag("math"); + } catch (MathExportException const &) { + osmath << "MathML export failed. Please report this as a bug."; } - ms << from_ascii("<graphic fileref=\"eqn/"); - if (!label(0).empty()) - ms << xml::cleanID(label(0)); - else - ms << xml::uniqueID(from_ascii("anon")); - - if (runparams.flavor == OutputParams::DOCBOOK5) - ms << from_ascii("\"/>"); - else - ms << from_ascii("\">"); - + // Close the DocBook tag enclosing the formula. ms.cr(); --ms.tab(); ms.os() << "</" << name << '>'; - return ms.line() + res; + // Output the complete tag to the DocBook stream. + xs << XMLStream::ESCAPE_NONE << osmath.str(); } diff --git a/src/mathed/InsetMathHull.h b/src/mathed/InsetMathHull.h index 96dbc10be6..42144a3f4b 100644 --- a/src/mathed/InsetMathHull.h +++ b/src/mathed/InsetMathHull.h @@ -144,7 +144,7 @@ public: int plaintext(odocstringstream &, OutputParams const &, size_t max_length = INT_MAX) const; /// - int docbook(odocstream &, OutputParams const &) const; + void docbook(XMLStream &, OutputParams const &) const; /// docstring xhtml(XMLStream &, OutputParams const &) const; /// diff --git a/src/mathed/InsetMathRef.cpp b/src/mathed/InsetMathRef.cpp index 3e1bc0290a..99058d776f 100644 --- a/src/mathed/InsetMathRef.cpp +++ b/src/mathed/InsetMathRef.cpp @@ -182,24 +182,18 @@ void InsetMathRef::validate(LaTeXFeatures & features) const } -int InsetMathRef::docbook(odocstream & os, OutputParams const & runparams) const +void InsetMathRef::docbook(XMLStream & xs, OutputParams const &) const { if (cell(1).empty()) { - os << "<xref linkend=\"" - << xml::cleanID(asString(cell(0))); - if (runparams.flavor == OutputParams::DOCBOOK5) - os << "\"/>"; - else - os << "\">"; + docstring attr = from_utf8("linkend=\"") + xml::cleanID(asString(cell(0))) + from_utf8("\""); + xs << xml::CompTag("xref", to_utf8(attr)); } else { - os << "<link linkend=\"" - << xml::cleanID(asString(cell(0))) - << "\">" + // Link with linkend, as is it within the document (not outside, in which case xlink:href is better suited). + docstring attr = from_utf8("linkend=\"") + xml::cleanID(asString(cell(0))) + from_utf8("\""); + xs << xml::StartTag("link", to_utf8(attr)) << asString(cell(1)) - << "</link>"; + << xml::EndTag("link"); } - - return 0; } diff --git a/src/mathed/InsetMathRef.h b/src/mathed/InsetMathRef.h index cdcbb1cc15..1f5e0b8bb8 100644 --- a/src/mathed/InsetMathRef.h +++ b/src/mathed/InsetMathRef.h @@ -48,7 +48,7 @@ public: virtual InsetMathRef * asRefInset() { return this; } /// docbook output - int docbook(odocstream & os, OutputParams const &) const; + void docbook(XMLStream &, OutputParams const &) const; /// generate something that will be understood by the Dialogs. std::string const createDialogStr() const; diff --git a/src/output_docbook.cpp b/src/output_docbook.cpp index 45af41bece..b80aaddf9b 100644 --- a/src/output_docbook.cpp +++ b/src/output_docbook.cpp @@ -11,13 +11,12 @@ #include <config.h> -#include "output_docbook.h" - #include "Buffer.h" #include "buffer_funcs.h" #include "BufferParams.h" #include "Counters.h" #include "Font.h" +#include "InsetList.h" #include "Layout.h" #include "OutputParams.h" #include "Paragraph.h" @@ -27,12 +26,19 @@ #include "Text.h" #include "TextClass.h" -#include "support/lassert.h" -#include "support/debug.h" -#include "support/lstrings.h" -#include "support/lyxalgo.h" +#include "insets/InsetBibtex.h" +#include "insets/InsetLabel.h" +#include "insets/InsetNote.h" +#include "support/convert.h" +#include "support/debug.h" +#include "support/lassert.h" +#include "support/lstrings.h" +#include "support/textutils.h" + +#include <stack> #include <iostream> +#include <algorithm> using namespace std; using namespace lyx::support; @@ -80,335 +86,896 @@ std::string const fontToDocBookTag(xml::FontTypes type) { } } -ParagraphList::const_iterator searchParagraph( - ParagraphList::const_iterator p, - ParagraphList::const_iterator const & pend) -{ - for (++p; p != pend && p->layout().latextype == LATEX_PARAGRAPH; ++p) - ; +string fontToRole(xml::FontTypes type) { + // Specific fonts are achieved with roles. The only common ones are "" for basic emphasis, + // and "bold"/"strong" for bold. With some specific options, other roles are copied into + // HTML output (via the DocBook XSLT sheets); otherwise, if not recognised, they are just ignored. + // Hence, it is not a problem to have many roles by default here. + // See https://www.sourceware.org/ml/docbook/2003-05/msg00269.html + switch (type) { + case xml::FontTypes::FT_ITALIC: + case xml::FontTypes::FT_EMPH: + return ""; + case xml::FontTypes::FT_BOLD: + return "bold"; + case xml::FontTypes::FT_NOUN: + return ""; // Outputs a <person> + case xml::FontTypes::FT_TYPE: + return ""; // Outputs a <code> - return p; + // All other roles are non-standard for DocBook. + + case xml::FontTypes::FT_UBAR: + return "ubar"; + case xml::FontTypes::FT_WAVE: + return "wave"; + case xml::FontTypes::FT_DBAR: + return "dbar"; + case xml::FontTypes::FT_SOUT: + return "sout"; + case xml::FontTypes::FT_XOUT: + return "xout"; + case xml::FontTypes::FT_UPRIGHT: + return "upright"; + case xml::FontTypes::FT_SLANTED: + return "slanted"; + case xml::FontTypes::FT_SMALLCAPS: + return "smallcaps"; + case xml::FontTypes::FT_ROMAN: + return "roman"; + case xml::FontTypes::FT_SANS: + return "sans"; + case xml::FontTypes::FT_SIZE_TINY: + return "tiny"; + case xml::FontTypes::FT_SIZE_SCRIPT: + return "size_script"; + case xml::FontTypes::FT_SIZE_FOOTNOTE: + return "size_footnote"; + case xml::FontTypes::FT_SIZE_SMALL: + return "size_small"; + case xml::FontTypes::FT_SIZE_NORMAL: + return "size_normal"; + case xml::FontTypes::FT_SIZE_LARGE: + return "size_large"; + case xml::FontTypes::FT_SIZE_LARGER: + return "size_larger"; + case xml::FontTypes::FT_SIZE_LARGEST: + return "size_largest"; + case xml::FontTypes::FT_SIZE_HUGE: + return "size_huge"; + case xml::FontTypes::FT_SIZE_HUGER: + return "size_huger"; + case xml::FontTypes::FT_SIZE_INCREASE: + return "size_increase"; + case xml::FontTypes::FT_SIZE_DECREASE: + return "size_decrease"; + default: + return ""; + } +} + +string fontToAttribute(xml::FontTypes type) { + // If there is a role (i.e. nonstandard use of a tag), output the attribute. Otherwise, the sheer tag is sufficient + // for the font. + string role = fontToRole(type); + if (!role.empty()) { + return "role='" + role + "'"; + } else { + return ""; + } +} + +} // end anonymous namespace + + +xml::FontTag docbookStartFontTag(xml::FontTypes type) +{ + return xml::FontTag(from_utf8(fontToDocBookTag(type)), from_utf8(fontToAttribute(type)), type); } -ParagraphList::const_iterator searchCommand( - ParagraphList::const_iterator p, - ParagraphList::const_iterator const & pend) +xml::EndFontTag docbookEndFontTag(xml::FontTypes type) { - Layout const & bstyle = p->layout(); + return xml::EndFontTag(from_utf8(fontToDocBookTag(type)), type); +} - for (++p; p != pend; ++p) { - Layout const & style = p->layout(); - if (style.latextype == LATEX_COMMAND - && style.commanddepth <= bstyle.commanddepth) - return p; + +namespace { + +// convenience functions + +void openParTag(XMLStream &xs, Layout const &lay) { + if (lay.docbookwrappertag() != "NONE") { + xs << xml::StartTag(lay.docbookwrappertag(), lay.docbookwrapperattr()); + } + + string tag = lay.docbooktag(); + if (tag == "Plain Layout") + tag = "para"; + + xs << xml::ParTag(tag, lay.docbookattr()); +} + + +void closeTag(XMLStream &xs, Layout const &lay) { + string tag = lay.docbooktag(); + if (tag == "Plain Layout") + tag = "para"; + + xs << xml::EndTag(tag); + if (lay.docbookwrappertag() != "NONE") + xs << xml::EndTag(lay.docbookwrappertag()); +} + + +void openLabelTag(XMLStream & xs, Layout const & lay) // Mostly for definition lists. +{ + xs << xml::StartTag(lay.docbookitemlabeltag(), lay.docbookitemlabelattr()); +} + + +void closeLabelTag(XMLStream & xs, Layout const & lay) +{ + xs << xml::EndTag(lay.docbookitemlabeltag()); + xs << xml::CR(); +} + + +void openItemTag(XMLStream &xs, Layout const &lay) { + xs << xml::StartTag(lay.docbookitemtag(), lay.docbookitemattr()); +} + + +// Return true when new elements are output in a paragraph, false otherwise. +bool openInnerItemTag(XMLStream &xs, Layout const &lay) { + if (lay.docbookiteminnertag() != "NONE") { + xs << xml::CR(); + xs << xml::ParTag(lay.docbookiteminnertag(), lay.docbookiteminnerattr()); + + if (lay.docbookiteminnertag() == "para") { + return true; + } + } + return false; +} + + +void closeInnerItemTag(XMLStream &xs, Layout const &lay) { + if (lay.docbookiteminnertag()!= "NONE") { + xs << xml::EndTag(lay.docbookiteminnertag()); + xs << xml::CR(); } - return pend; } -ParagraphList::const_iterator searchEnvironment( - ParagraphList::const_iterator p, - ParagraphList::const_iterator const & pend) -{ - Layout const & bstyle = p->layout(); - size_t const depth = p->params().depth(); - for (++p; p != pend; ++p) { - Layout const & style = p->layout(); - if (style.latextype == LATEX_COMMAND) - return p; +inline void closeItemTag(XMLStream &xs, Layout const &lay) { + xs << xml::EndTag(lay.docbookitemtag()) << xml::CR(); +} - if (style.latextype == LATEX_PARAGRAPH) { - if (p->params().depth() > depth) - continue; - return p; - } +// end of convenience functions - if (p->params().depth() < depth) - return p; +ParagraphList::const_iterator findLastParagraph( + ParagraphList::const_iterator p, + ParagraphList::const_iterator const &pend) { + for (++p; p != pend && p->layout().latextype == LATEX_PARAGRAPH; ++p); - if (style.latexname() != bstyle.latexname() - && p->params().depth() == depth) - return p; - } - return pend; + return p; } -ParagraphList::const_iterator makeParagraph( - Buffer const & buf, - odocstream & os, - OutputParams const & runparams, - Text const & text, - ParagraphList::const_iterator const & pbegin, - ParagraphList::const_iterator const & pend) -{ - ParagraphList const & paragraphs = text.paragraphs(); - for (ParagraphList::const_iterator par = pbegin; par != pend; ++par) { - if (par != pbegin) - os << '\n'; - bool const default_or_plain = - (buf.params().documentClass().isDefaultLayout(par->layout()) - || buf.params().documentClass().isPlainLayout(par->layout())); - if (default_or_plain && par->emptyTag()) { - par->simpleDocBookOnePar(buf, os, runparams, - text.outerFont(distance(paragraphs.begin(), par))); - } else { - xml::openTag(buf, os, runparams, *par); - par->simpleDocBookOnePar(buf, os, runparams, - text.outerFont(distance(paragraphs.begin(), par))); - xml::closeTag(os, *par); - } - } - return pend; +ParagraphList::const_iterator findEndOfEnvironment( + ParagraphList::const_iterator const &pstart, + ParagraphList::const_iterator const &pend) { + ParagraphList::const_iterator p = pstart; + Layout const &bstyle = p->layout(); + size_t const depth = p->params().depth(); + for (++p; p != pend; ++p) { + Layout const &style = p->layout(); + // It shouldn't happen that e.g. a section command occurs inside + // a quotation environment, at a higher depth, but as of 6/2009, + // it can happen. We pretend that it's just at lowest depth. + if (style.latextype == LATEX_COMMAND) + return p; + + // If depth is down, we're done + if (p->params().depth() < depth) + return p; + + // If depth is up, we're not done + if (p->params().depth() > depth) + continue; + + // FIXME I am not sure about the first check. + // Surely we *could* have different layouts that count as + // LATEX_PARAGRAPH, right? + if (style.latextype == LATEX_PARAGRAPH || style != bstyle) + return p; + } + return pend; } -ParagraphList::const_iterator makeEnvironment( - Buffer const & buf, - odocstream & os, - OutputParams const & runparams, - Text const & text, - ParagraphList::const_iterator const & pbegin, - ParagraphList::const_iterator const & pend) -{ - ParagraphList const & paragraphs = text.paragraphs(); +ParagraphList::const_iterator makeParagraphs(Buffer const &buf, + XMLStream &xs, + OutputParams const &runparams, + Text const &text, + ParagraphList::const_iterator const &pbegin, + ParagraphList::const_iterator const &pend) { + ParagraphList::const_iterator const begin = text.paragraphs().begin(); + ParagraphList::const_iterator par = pbegin; + for (; par != pend; ++par) { + Layout const &lay = par->layout(); + if (!lay.counter.empty()) + buf.masterBuffer()->params(). + documentClass().counters().step(lay.counter, OutputUpdate); + + // We want to open the paragraph tag if: + // (i) the current layout permits multiple paragraphs + // (ii) we are either not already inside a paragraph (HTMLIsBlock) OR + // we are, but this is not the first paragraph + // + // But there is also a special case, and we first see whether we are in it. + // We do not want to open the paragraph tag if this paragraph contains + // only one item, and that item is "inline", i.e., not HTMLIsBlock (such + // as a branch). On the other hand, if that single item has a font change + // applied to it, then we still do need to open the paragraph. + // + // Obviously, this is very fragile. The main reason we need to do this is + // because of branches, e.g., a branch that contains an entire new section. + // We do not really want to wrap that whole thing in a <div>...</div>. + bool special_case = false; + Inset const *specinset = par->size() == 1 ? par->getInset(0) : 0; + if (specinset && !specinset->getLayout().htmlisblock()) { // TODO: Convert htmlisblock to a DocBook parameter? + Layout const &style = par->layout(); + FontInfo const first_font = style.labeltype == LABEL_MANUAL ? + style.labelfont : style.font; + FontInfo const our_font = + par->getFont(buf.masterBuffer()->params(), 0, + text.outerFont(distance(begin, par))).fontInfo(); + + if (first_font == our_font) + special_case = true; + } + + // Plain layouts must be ignored. + if (!special_case && buf.params().documentClass().isPlainLayout(lay) && !runparams.docbook_force_pars) + special_case = true; + // TODO: Could get rid of this with a DocBook equivalent to htmlisblock? + if (!special_case && par->size() == 1 && par->getInset(0)) { + Inset const * firstInset = par->getInset(0); + + // Floats cannot be in paragraphs. + special_case = to_ascii(firstInset->layoutName()).substr(0, 6) == "Float:"; + + // Bibliographies cannot be in paragraphs. + if (!special_case && firstInset->asInsetCommand()) + special_case = firstInset->asInsetCommand()->params().getCmdName() == "bibtex"; + + // Equations do not deserve their own paragraph (DocBook allows them outside paragraphs). + if (!special_case && firstInset->asInsetMath()) + special_case = true; + + // ERTs are in comments, not paragraphs. + if (!special_case && firstInset->lyxCode() == lyx::ERT_CODE) + special_case = true; + + // Listings should not get into their own paragraph. + if (!special_case && firstInset->lyxCode() == lyx::LISTINGS_CODE) + special_case = true; + } + + bool const open_par = runparams.docbook_make_pars + && (!runparams.docbook_in_par || par != pbegin) + && !special_case; + + // We want to issue the closing tag if either: + // (i) We opened it, and either docbook_in_par is false, + // or we're not in the last paragraph, anyway. + // (ii) We didn't open it and docbook_in_par is true, + // but we are in the first par, and there is a next par. + ParagraphList::const_iterator nextpar = par; + ++nextpar; + bool const close_par = + ((open_par && (!runparams.docbook_in_par || nextpar != pend)) + || (!open_par && runparams.docbook_in_par && par == pbegin && nextpar != pend)); + + if (open_par) { + openParTag(xs, lay); + } + + par->simpleDocBookOnePar(buf, xs, runparams, text.outerFont(distance(begin, par)), open_par, close_par, 0); + + if (close_par) { + closeTag(xs, lay); + xs << xml::CR(); + } + } + return pend; +} + + +bool isNormalEnv(Layout const &lay) { + return lay.latextype == LATEX_ENVIRONMENT + || lay.latextype == LATEX_BIB_ENVIRONMENT; +} + + +ParagraphList::const_iterator makeEnvironment(Buffer const &buf, + XMLStream &xs, + OutputParams const &runparams, + Text const &text, + ParagraphList::const_iterator const &pbegin, + ParagraphList::const_iterator const &pend) { + ParagraphList::const_iterator const begin = text.paragraphs().begin(); ParagraphList::const_iterator par = pbegin; + Layout const &bstyle = par->layout(); + depth_type const origdepth = pbegin->params().depth(); - Layout const & defaultstyle = buf.params().documentClass().defaultLayout(); - Layout const & bstyle = par->layout(); + // open tag for this environment + openParTag(xs, bstyle); + xs << xml::CR(); - // Opening outter tag - xml::openTag(buf, os, runparams, *pbegin); - os << '\n'; - if (bstyle.latextype == LATEX_ENVIRONMENT && bstyle.pass_thru) - os << "<![CDATA["; + // we will on occasion need to remember a layout from before. + Layout const *lastlay = nullptr; while (par != pend) { Layout const & style = par->layout(); + // the counter only gets stepped if we're in some kind of list, + // or if it's the first time through. + // note that enum, etc, are handled automatically. + // FIXME There may be a bug here about user defined enumeration + // types. If so, then we'll need to take the counter and add "i", + // "ii", etc, as with enum. + Counters & cnts = buf.masterBuffer()->params().documentClass().counters(); + docstring const & cntr = style.counter; + if (!style.counter.empty() + && (par == pbegin || !isNormalEnv(style)) + && cnts.hasCounter(cntr) + ) + cnts.step(cntr, OutputUpdate); ParagraphList::const_iterator send; - string id = par->getID(buf, runparams); - string wrapper = ""; - pos_type sep = 0; - - // Opening inner tag - switch (bstyle.latextype) { - case LATEX_ENVIRONMENT: - if (!bstyle.innertag().empty()) { - xml::openTag(os, bstyle.innertag(), id); - } - break; - - case LATEX_ITEM_ENVIRONMENT: - if (!bstyle.labeltag().empty()) { - xml::openTag(os, bstyle.innertag(), id); - xml::openTag(os, bstyle.labeltag()); - sep = par->firstWordDocBook(os, runparams) + 1; - xml::closeTag(os, bstyle.labeltag()); - } - wrapper = defaultstyle.latexname(); - // If a sub list (embedded list) appears next with a - // different depth, then there is no need to open - // another tag at the current depth. - if(par->params().depth() == pbegin->params().depth()) { - xml::openTag(os, bstyle.itemtag()); - } - break; - default: - break; - } + // Actual content of this paragraph. switch (style.latextype) { case LATEX_ENVIRONMENT: + case LATEX_LIST_ENVIRONMENT: case LATEX_ITEM_ENVIRONMENT: { - if (par->params().depth() == pbegin->params().depth()) { - xml::openTag(os, wrapper); - par->simpleDocBookOnePar(buf, os, runparams, - text.outerFont(distance(paragraphs.begin(), par)), sep); - xml::closeTag(os, wrapper); + // There are two possibilities in this case. + // One is that we are still in the environment in which we + // started---which we will be if the depth is the same. + if (par->params().depth() == origdepth) { + LATTEST(bstyle == style); + if (lastlay != nullptr) { + closeItemTag(xs, *lastlay); + lastlay = nullptr; + } + + // this will be positive, if we want to skip the + // initial word (if it's been taken for the label). + pos_type sep = 0; + + // Open a wrapper tag if needed. + if (style.docbookitemwrappertag() != "NONE") { + xs << xml::StartTag(style.docbookitemwrappertag(), style.docbookitemwrapperattr()); + xs << xml::CR(); + } + + // label output + if (style.labeltype != LABEL_NO_LABEL && + style.docbookitemlabeltag() != "NONE") { + + if (isNormalEnv(style)) { + // in this case, we print the label only for the first + // paragraph (as in a theorem or an abstract). + if (par == pbegin) { + docstring const lbl = pbegin->params().labelString(); + if (!lbl.empty()) { + openLabelTag(xs, style); + xs << lbl; + closeLabelTag(xs, style); + } + xs << xml::CR(); + } + } else { // some kind of list + if (style.labeltype == LABEL_MANUAL) { + // Only variablelist gets here. + + openLabelTag(xs, style); + sep = par->firstWordDocBook(xs, runparams); + closeLabelTag(xs, style); + xs << xml::CR(); + } else { + openLabelTag(xs, style); + xs << par->params().labelString(); + closeLabelTag(xs, style); + xs << xml::CR(); + } + } + } // end label output + + bool wasInParagraph = runparams.docbook_in_par; + openItemTag(xs, style); + bool getsIntoParagraph = openInnerItemTag(xs, style); + OutputParams rp = runparams; + rp.docbook_in_par = wasInParagraph | getsIntoParagraph; + + par->simpleDocBookOnePar(buf, xs, rp, text.outerFont(distance(begin, par)), true, true, sep); ++par; + if (getsIntoParagraph) + closeInnerItemTag(xs, style); + + // We may not want to close the tag yet, in particular: + // If we're not at the end of the item... + if (par != pend + // and are doing items... + && !isNormalEnv(style) + // and if the depth has changed... + && par->params().depth() != origdepth) { + // then we'll save this layout for later, and close it when + // we get another item. + lastlay = &style; + } else { + closeItemTag(xs, style); + + // Eventually, close the item wrapper. + if (style.docbookitemwrappertag() != "NONE") { + xs << xml::EndTag(style.docbookitemwrappertag()); + xs << xml::CR(); + } + } } + // The other possibility is that the depth has increased, in which + // case we need to recurse. else { - send = searchEnvironment(par, pend); - par = makeEnvironment(buf, os, runparams, text, par,send); + send = findEndOfEnvironment(par, pend); + par = makeEnvironment(buf, xs, runparams, text, par, send); } break; } case LATEX_PARAGRAPH: - send = searchParagraph(par, pend); - par = makeParagraph(buf, os, runparams, text, par,send); + send = findLastParagraph(par, pend); + par = makeParagraphs(buf, xs, runparams, text, par, send); break; - case LATEX_LIST_ENVIRONMENT: case LATEX_BIB_ENVIRONMENT: + // Handled in InsetBibtex. + break; case LATEX_COMMAND: - // FIXME This means that we are just skipping any paragraph that - // isn't implemented above, and this includes lists. ++par; break; } - - // Closing inner tag - switch (bstyle.latextype) { - case LATEX_ENVIRONMENT: - if (!bstyle.innertag().empty()) { - xml::closeTag(os, bstyle.innertag()); - os << '\n'; - } - break; - case LATEX_ITEM_ENVIRONMENT: - // If a sub list (embedded list) appears next, then - // there is no need to close the current tag. - // par should have already been incremented to the next - // element. So we can compare the depth of the next - // element with pbegin. - // We need to be careful, that we don't dereference par - // when par == pend but at the same time that the - // current tag is closed. - if((par != pend && par->params().depth() == pbegin->params().depth()) || par == pend) { - xml::closeTag(os, bstyle.itemtag()); - } - if (!bstyle.labeltag().empty()) - xml::closeTag(os, bstyle.innertag()); - break; - default: - break; - } } - if (bstyle.latextype == LATEX_ENVIRONMENT && bstyle.pass_thru) - os << "]]>"; - - // Closing outer tag - xml::closeTag(os, *pbegin); - + if (lastlay != 0) + closeItemTag(xs, *lastlay); + closeTag(xs, bstyle); + xs << xml::CR(); return pend; } -ParagraphList::const_iterator makeCommand( - Buffer const & buf, - odocstream & os, - OutputParams const & runparams, - Text const & text, - ParagraphList::const_iterator const & pbegin, - ParagraphList::const_iterator const & pend) -{ - ParagraphList const & paragraphs = text.paragraphs(); - ParagraphList::const_iterator par = pbegin; - Layout const & bstyle = par->layout(); +void makeCommand(Buffer const &buf, + XMLStream &xs, + OutputParams const &runparams, + Text const &text, + ParagraphList::const_iterator const &pbegin) { + Layout const &style = pbegin->layout(); + if (!style.counter.empty()) + buf.masterBuffer()->params(). + documentClass().counters().step(style.counter, OutputUpdate); - //Open outter tag - xml::openTag(buf, os, runparams, *pbegin); - os << '\n'; + // No need for labels, as they are handled by DocBook tags. - // Label around sectioning number: - if (!bstyle.labeltag().empty()) { - xml::openTag(os, bstyle.labeltag()); - // We don't care about appendix in DOCBOOK. - os << par->expandDocBookLabel(bstyle, buf.params()); - xml::closeTag(os, bstyle.labeltag()); - } + openParTag(xs, style); - // Opend inner tag and close inner tags - xml::openTag(os, bstyle.innertag()); - par->simpleDocBookOnePar(buf, os, runparams, - text.outerFont(distance(paragraphs.begin(), par))); - xml::closeTag(os, bstyle.innertag()); - os << '\n'; + ParagraphList::const_iterator const begin = text.paragraphs().begin(); + pbegin->simpleDocBookOnePar(buf, xs, runparams, + text.outerFont(distance(begin, pbegin))); + closeTag(xs, style); + xs << xml::CR(); +} - ++par; - while (par != pend) { - Layout const & style = par->layout(); - ParagraphList::const_iterator send; +pair<ParagraphList::const_iterator, ParagraphList::const_iterator> makeAny(Text const &text, + Buffer const &buf, + XMLStream &xs, + OutputParams const &ourparams, + ParagraphList::const_iterator par, + ParagraphList::const_iterator send, + ParagraphList::const_iterator pend) { + Layout const &style = par->layout(); - switch (style.latextype) { + switch (style.latextype) { case LATEX_COMMAND: { - send = searchCommand(par, pend); - par = makeCommand(buf, os, runparams, text, par,send); - break; - } - case LATEX_ENVIRONMENT: - case LATEX_ITEM_ENVIRONMENT: { - send = searchEnvironment(par, pend); - par = makeEnvironment(buf, os, runparams, text, par,send); - break; - } - case LATEX_PARAGRAPH: - send = searchParagraph(par, pend); - par = makeParagraph(buf, os, runparams, text, par,send); - break; - case LATEX_BIB_ENVIRONMENT: - case LATEX_LIST_ENVIRONMENT: - // FIXME This means that we are just skipping any paragraph that - // isn't implemented above. + // The files with which we are working never have more than + // one paragraph in a command structure. + // FIXME + // if (ourparams.docbook_in_par) + // fix it so we don't get sections inside standard, e.g. + // note that we may then need to make runparams not const, so we + // can communicate that back. + // FIXME Maybe this fix should be in the routines themselves, in case + // they are called from elsewhere. + makeCommand(buf, xs, ourparams, text, par); ++par; break; } + case LATEX_ENVIRONMENT: + case LATEX_LIST_ENVIRONMENT: + case LATEX_ITEM_ENVIRONMENT: { + // FIXME Same fix here. + send = findEndOfEnvironment(par, pend); + par = makeEnvironment(buf, xs, ourparams, text, par, send); + break; + } + case LATEX_BIB_ENVIRONMENT: { + // Handled in InsetBibtex. + break; + } + case LATEX_PARAGRAPH: { + send = findLastParagraph(par, pend); + par = makeParagraphs(buf, xs, ourparams, text, par, send); + break; + } } - // Close outter tag - xml::closeTag(os, *pbegin); - return pend; + return make_pair(par, send); } -} // namespace +} // end anonymous namespace -void docbookParagraphs(Text const & text, - Buffer const & buf, - odocstream & os, - OutputParams const & runparams) +using DocBookDocumentSectioning = tuple<bool, pit_type>; +using DocBookInfoTag = tuple<set<pit_type>, set<pit_type>, pit_type, pit_type>; + + +DocBookDocumentSectioning hasDocumentSectioning(ParagraphList const ¶graphs, pit_type bpit, pit_type const epit) { + bool documentHasSections = false; + + while (bpit < epit) { + Layout const &style = paragraphs[bpit].layout(); + documentHasSections |= style.category() == from_utf8("Sectioning"); + + if (documentHasSections) { + break; + } + bpit += 1; + } + // Paragraphs before the first section: [ runparams.par_begin ; eppit ) + + return make_tuple(documentHasSections, bpit); +} + + +DocBookInfoTag getParagraphsWithInfo(ParagraphList const ¶graphs, pit_type const bpit, pit_type const epit) { + set<pit_type> shouldBeInInfo; + set<pit_type> mustBeInInfo; + + pit_type cpit = bpit; + while (cpit < epit) { + Layout const &style = paragraphs[cpit].layout(); + if (style.docbookininfo() == "always") { + mustBeInInfo.emplace(cpit); + } else if (style.docbookininfo() == "maybe") { + shouldBeInInfo.emplace(cpit); + } else { + // Hypothesis: the <info> parts should be grouped together near the beginning bpit. + break; + } + cpit += 1; + } + // Now, bpit points to the last paragraph that has things that could go in <info>. + + return make_tuple(shouldBeInInfo, mustBeInInfo, bpit, cpit); +} + + +bool hasAbstractBetween(ParagraphList const ¶graphs, pit_type const bpitAbstract, pit_type const epitAbstract) { - LASSERT(runparams.par_begin <= runparams.par_end, - { os << "<!-- Docbook Output Error -->\n"; return; }); + // Hypothesis: the paragraphs between bpitAbstract and epitAbstract can be considered an abstract because they + // are just after a document or part title. + if (epitAbstract - bpitAbstract <= 0) + return false; - ParagraphList const & paragraphs = text.paragraphs(); - ParagraphList::const_iterator par = paragraphs.begin(); - ParagraphList::const_iterator pend = paragraphs.end(); + // If there is something between these paragraphs, check if it's compatible with an abstract (i.e. some text). + pit_type bpit = bpitAbstract; + while (bpit < epitAbstract) { + const Paragraph &p = paragraphs.at(bpit); - // if only part of the paragraphs will be outputed - if (runparams.par_begin != runparams.par_end) { - par = paragraphs.iterator_at(runparams.par_begin); - pend = paragraphs.iterator_at(runparams.par_end); - // runparams will be passed to nested paragraphs, so - // we have to reset the range parameters. - const_cast<OutputParams&>(runparams).par_begin = 0; - const_cast<OutputParams&>(runparams).par_end = 0; + if (p.layout().name() == from_ascii("Abstract")) + return true; + + if (!p.insetList().empty()) { + for (const auto &i : p.insetList()) { + if (i.inset->getText(0) != nullptr) { + return true; + } + } + } + bpit++; + } + return false; +} + + +pit_type generateDocBookParagraphWithoutSectioning(Text const &text, + Buffer const &buf, + XMLStream &xs, + OutputParams const &runparams, + ParagraphList const ¶graphs, + pit_type bpit, + pit_type epit) { + auto par = paragraphs.iterator_at(bpit); + auto lastStartedPar = par; + ParagraphList::const_iterator send; + auto const pend = + (epit == (int) paragraphs.size()) ? + paragraphs.end() : paragraphs.iterator_at(epit); + + while (bpit < epit) { + tie(par, send) = makeAny(text, buf, xs, runparams, par, send, pend); + bpit += distance(lastStartedPar, par); } - while (par != pend) { - Layout const & style = par->layout(); - ParagraphList::const_iterator lastpar = par; + return bpit; +} + + +void outputDocBookInfo(Text const &text, + Buffer const &buf, + XMLStream &xs, + OutputParams const &runparams, + ParagraphList const ¶graphs, + DocBookInfoTag const &info, + pit_type bpitAbstract, + pit_type const epitAbstract) { + // Consider everything between bpitAbstract and epitAbstract (excluded) as paragraphs for the abstract. + // Use bpitAbstract >= epitAbstract to indicate there is no abstract. + + set<pit_type> shouldBeInInfo; + set<pit_type> mustBeInInfo; + pit_type bpitInfo; + pit_type epitInfo; + tie(shouldBeInInfo, mustBeInInfo, bpitInfo, epitInfo) = info; + + // The abstract must go in <info>. + bool hasAbstract = hasAbstractBetween(paragraphs, bpitAbstract, epitAbstract); + bool needInfo = !mustBeInInfo.empty() || hasAbstract; + + // Start the <info> tag if required. + if (needInfo) { + xs.startDivision(false); + xs << xml::StartTag("info"); + xs << xml::CR(); + } + + // Output the elements that should go in <info>. + generateDocBookParagraphWithoutSectioning(text, buf, xs, runparams, paragraphs, bpitInfo, epitInfo); + + if (hasAbstract) { + string tag = paragraphs[bpitAbstract].layout().docbookforceabstracttag(); + if (tag == "NONE") + tag = "abstract"; + + xs << xml::StartTag(tag); + xs << xml::CR(); + xs.startDivision(false); + generateDocBookParagraphWithoutSectioning(text, buf, xs, runparams, paragraphs, bpitAbstract, epitAbstract); + xs.endDivision(); + xs << xml::EndTag(tag); + xs << xml::CR(); + } + + // End the <info> tag if it was started. + if (needInfo) { + xs << xml::EndTag("info"); + xs << xml::CR(); + xs.endDivision(); + } +} + + +void docbookFirstParagraphs(Text const &text, + Buffer const &buf, + XMLStream &xs, + OutputParams const &runparams, + pit_type epit) { + // Handle the beginning of the document, supposing it has sections. + // Major role: output the first <info> tag. + + ParagraphList const ¶graphs = text.paragraphs(); + pit_type bpit = runparams.par_begin; + DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit); + outputDocBookInfo(text, buf, xs, runparams, paragraphs, info, get<3>(info), epit); +} + + +bool isParagraphEmpty(const Paragraph &par) +{ + InsetList const &insets = par.insetList(); + size_t insetsLength = distance(insets.begin(), insets.end()); + bool hasParagraphOnlyNote = insetsLength == 1 && insets.get(0) && insets.get(0)->asInsetCollapsible() && + dynamic_cast<InsetNote *>(insets.get(0)); + return hasParagraphOnlyNote; +} + + +void docbookSimpleAllParagraphs(Text const &text, + Buffer const &buf, + XMLStream &xs, + OutputParams const &runparams) { + // Handle the document, supposing it has no sections (i.e. a "simple" document). + + // First, the <info> tag. + ParagraphList const ¶graphs = text.paragraphs(); + pit_type bpit = runparams.par_begin; + pit_type const epit = runparams.par_end; + DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit); + outputDocBookInfo(text, buf, xs, runparams, paragraphs, info, 0, 0); + bpit = get<3>(info); // Generate the content starting from the end of the <info> part. + + // Then, the content. + ParagraphList::const_iterator const pend = + (epit == (int) paragraphs.size()) ? + paragraphs.end() : paragraphs.iterator_at(epit); + + while (bpit < epit) { + auto par = paragraphs.iterator_at(bpit); + ParagraphList::const_iterator const lastStartedPar = par; ParagraphList::const_iterator send; - switch (style.latextype) { - case LATEX_COMMAND: { - send = searchCommand(par, pend); - par = makeCommand(buf, os, runparams, text, par, send); - break; - } - case LATEX_ENVIRONMENT: - case LATEX_ITEM_ENVIRONMENT: { - send = searchEnvironment(par, pend); - par = makeEnvironment(buf, os, runparams, text, par, send); - break; - } - case LATEX_PARAGRAPH: - send = searchParagraph(par, pend); - par = makeParagraph(buf, os, runparams, text, par, send); - break; - case LATEX_BIB_ENVIRONMENT: - case LATEX_LIST_ENVIRONMENT: - // FIXME This means that we are just skipping any paragraph that - // isn't implemented above. + if (isParagraphEmpty(*par)) { ++par; - break; + bpit += distance(lastStartedPar, par); + continue; } - // makeEnvironment may process more than one paragraphs and bypass pend - if (distance(lastpar, par) >= distance(lastpar, pend)) - break; + + // Generate this paragraph. + tie(par, send) = makeAny(text, buf, xs, runparams, par, send, pend); + bpit += distance(lastStartedPar, par); } } +void docbookParagraphs(Text const &text, + Buffer const &buf, + XMLStream &xs, + OutputParams const &runparams) { + ParagraphList const ¶graphs = text.paragraphs(); + if (runparams.par_begin == runparams.par_end) { + runparams.par_begin = 0; + runparams.par_end = paragraphs.size(); + } + pit_type bpit = runparams.par_begin; + pit_type const epit = runparams.par_end; + LASSERT(bpit < epit, + { + xs << XMLStream::ESCAPE_NONE << "<!-- DocBook output error! -->\n"; + return; + }); + + ParagraphList::const_iterator const pend = + (epit == (int) paragraphs.size()) ? + paragraphs.end() : paragraphs.iterator_at(epit); + std::stack<std::pair<int, string>> headerLevels; // Used to determine when to open/close sections: store the depth + // of the section and the tag that was used to open it. + + // Detect whether the document contains sections. If there are no sections, there can be no automatically + // discovered abstract. + bool documentHasSections; + pit_type eppit; + tie(documentHasSections, eppit) = hasDocumentSectioning(paragraphs, bpit, epit); + + if (documentHasSections) { + docbookFirstParagraphs(text, buf, xs, runparams, eppit); + bpit = eppit; + } else { + docbookSimpleAllParagraphs(text, buf, xs, runparams); + return; + } + + bool currentlyInAppendix = false; + + while (bpit < epit) { + OutputParams ourparams = runparams; + + auto par = paragraphs.iterator_at(bpit); + if (par->params().startOfAppendix()) + currentlyInAppendix = true; + Layout const &style = par->layout(); + ParagraphList::const_iterator const lastStartedPar = par; + ParagraphList::const_iterator send; + + if (isParagraphEmpty(*par)) { + ++par; + bpit += distance(lastStartedPar, par); + continue; + } + + // Think about adding <section> and/or </section>s. + const bool isLayoutSectioning = style.category() == from_utf8("Sectioning"); + if (isLayoutSectioning) { + int level = style.toclevel; + + // Need to close a previous section if it has the same level or a higher one (close <section> if opening a <h2> + // after a <h2>, <h3>, <h4>, <h5> or <h6>). More examples: + // - current: h2; back: h1; do not close any <section> + // - current: h1; back: h2; close two <section> (first the <h2>, then the <h1>, so a new <h1> can come) + while (!headerLevels.empty() && level <= headerLevels.top().first) { + int stackLevel = headerLevels.top().first; + docstring stackTag = from_utf8("</" + headerLevels.top().second + ">"); + headerLevels.pop(); + + // Output the tag only if it corresponds to a legit section. + if (stackLevel != Layout::NOT_IN_TOC) + xs << XMLStream::ESCAPE_NONE << stackTag << xml::CR(); + } + + // Open the new section: first push it onto the stack, then output it in DocBook. + string sectionTag = (currentlyInAppendix && style.docbooksectiontag() == "chapter") ? + "appendix" : style.docbooksectiontag(); + headerLevels.push(std::make_pair(level, sectionTag)); + + // Some sectioning-like elements should not be output (such as FrontMatter). + if (level != Layout::NOT_IN_TOC) { + // Look for a label in the title, i.e. a InsetLabel as a child. + docstring id = docstring(); + for (pos_type i = 0; i < par->size(); ++i) { + Inset const *inset = par->getInset(i); + if (inset && dynamic_cast<InsetLabel const *>(inset)) { + // Generate the attributes for the section if need be. + auto label = dynamic_cast<InsetLabel const *>(inset); + id += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\""; + + // Don't output the ID as a DocBook <anchor>. + ourparams.docbook_anchors_to_ignore.emplace(label->screenLabel()); + + // Cannot have multiple IDs per tag. + break; + } + } + + // Write the open tag for this section. + docstring tag = from_utf8("<" + sectionTag); + if (!id.empty()) + tag += from_utf8(" ") + id; + tag += from_utf8(">"); + xs << XMLStream::ESCAPE_NONE << tag; + xs << xml::CR(); + } + } + + // Close all sections before the bibliography. + // TODO: Only close all when the bibliography is at the end of the document? Or force to output the bibliography at the end of the document? Or don't care (as allowed by DocBook)? + auto insetsLength = distance(par->insetList().begin(), par->insetList().end()); + if (insetsLength > 0) { + Inset const *firstInset = par->getInset(0); + if (firstInset && dynamic_cast<InsetBibtex const *>(firstInset)) { + while (!headerLevels.empty()) { + int level = headerLevels.top().first; + docstring tag = from_utf8("</" + headerLevels.top().second + ">"); + headerLevels.pop(); + + // Output the tag only if it corresponds to a legit section. + if (level != Layout::NOT_IN_TOC) { + xs << XMLStream::ESCAPE_NONE << tag; + xs << xml::CR(); + } + } + } + } + + // Generate this paragraph. + tie(par, send) = makeAny(text, buf, xs, ourparams, par, send, pend); + bpit += distance(lastStartedPar, par); + } + + // If need be, close <section>s, but only at the end of the document (otherwise, dealt with at the beginning + // of the loop). + while (!headerLevels.empty() && headerLevels.top().first > Layout::NOT_IN_TOC) { + docstring tag = from_utf8("</" + headerLevels.top().second + ">"); + headerLevels.pop(); + xs << XMLStream::ESCAPE_NONE << tag; + xs << xml::CR(); + } +} + } // namespace lyx diff --git a/src/output_docbook.h b/src/output_docbook.h index 819c2035e4..4dcc853df2 100644 --- a/src/output_docbook.h +++ b/src/output_docbook.h @@ -6,6 +6,8 @@ * * \author Lars Gullik Bjønnes * \author José Matos + * \author Thibaut Cuvelier + * \author Richard Heck * * Full author contact details are available in file CREDITS. */ @@ -13,22 +15,30 @@ #ifndef OUTPUT_DOCBOOK_H #define OUTPUT_DOCBOOK_H +#include "LayoutEnums.h" + +#include "support/docstream.h" #include "support/strfwd.h" #include "xml.h" namespace lyx { -std::string const fontToDocBookTag(xml::FontTypes type); - class Buffer; class OutputParams; class Text; +/// +std::string const fontToDocBookTag(xml::FontTypes type); +/// +xml::FontTag docbookStartFontTag(xml::FontTypes type); +/// +xml::EndFontTag docbookEndFontTag(xml::FontTypes type); + /// void docbookParagraphs(Text const & text, - Buffer const & buf, - odocstream & os, - OutputParams const & runparams); + Buffer const & buf, + XMLStream & os, + OutputParams const & runparams); } // namespace lyx diff --git a/src/xml.cpp b/src/xml.cpp index 2c30f1548d..7733bf0260 100644 --- a/src/xml.cpp +++ b/src/xml.cpp @@ -24,6 +24,7 @@ #include "support/convert.h" #include "support/docstream.h" +#include "support/lassert.h" #include "support/lstrings.h" #include "support/textutils.h" @@ -31,7 +32,6 @@ #include <map> #include <functional> #include <QThreadStorage> -#include <support/lassert.h> using namespace std; using namespace lyx::support; @@ -351,37 +351,44 @@ XMLStream &XMLStream::operator<<(xml::FontTag const &tag) { XMLStream &XMLStream::operator<<(xml::CR const &) { + clearTagDeque(); os_ << from_ascii("\n"); return *this; } -bool XMLStream::isTagOpen(xml::StartTag const &stag) const { +bool XMLStream::isTagOpen(xml::StartTag const &stag, int maxdepth) const { auto sit = tag_stack_.begin(); auto sen = tag_stack_.cend(); - for (; sit != sen; ++sit) + for (; sit != sen && maxdepth != 0; ++sit) { if (**sit == stag) return true; + maxdepth -= 1; + } return false; } -bool XMLStream::isTagOpen(xml::EndTag const &etag) const { +bool XMLStream::isTagOpen(xml::EndTag const &etag, int maxdepth) const { auto sit = tag_stack_.begin(); auto sen = tag_stack_.cend(); - for (; sit != sen; ++sit) + for (; sit != sen && maxdepth != 0; ++sit) { if (etag == **sit) return true; + maxdepth -= 1; + } return false; } -bool XMLStream::isTagPending(xml::StartTag const &stag) const { +bool XMLStream::isTagPending(xml::StartTag const &stag, int maxdepth) const { auto sit = pending_tags_.begin(); auto sen = pending_tags_.cend(); - for (; sit != sen; ++sit) + for (; sit != sen && maxdepth != 0; ++sit) { if (**sit == stag) return true; + maxdepth -= 1; + } return false; } diff --git a/src/xml.h b/src/xml.h index b4339ba7ff..c7f1e5076e 100644 --- a/src/xml.h +++ b/src/xml.h @@ -91,16 +91,16 @@ public: /// for it is disabled by default, however, so you will need /// to enable it if you want to use it. void dumpTagStack(std::string const & msg); + /// + bool isTagOpen(xml::StartTag const &, int maxdepth = -1) const; + /// + bool isTagOpen(xml::EndTag const &, int maxdepth = -1) const; + /// + bool isTagPending(xml::StartTag const &, int maxdepth = -1) const; private: /// void clearTagDeque(); /// - bool isTagOpen(xml::StartTag const &) const; - /// - bool isTagOpen(xml::EndTag const &) const; - /// - bool isTagPending(xml::StartTag const &) const; - /// void writeError(std::string const &) const; /// void writeError(docstring const &) const; @@ -183,7 +183,7 @@ struct StartTag /// </tag_> virtual docstring writeEndTag() const; /// - virtual FontTag const * asFontTag() const { return 0; } + virtual FontTag const * asFontTag() const { return nullptr; } /// virtual bool operator==(StartTag const & rhs) const { return tag_ == rhs.tag_; } @@ -303,8 +303,12 @@ struct FontTag : public StartTag /// FontTag(docstring const & tag, FontTypes type): StartTag(tag), font_type_(type) {} /// + FontTag(std::string const & tag, FontTypes type): StartTag(from_utf8(tag)), font_type_(type) {} + /// FontTag(docstring const & tag, docstring const & attr, FontTypes type): StartTag(tag, attr), font_type_(type) {} /// + FontTag(std::string const & tag, std::string const & attr, FontTypes type): StartTag(from_utf8(tag), from_utf8(attr)), font_type_(type) {} + /// FontTag const * asFontTag() const override { return this; } /// bool operator==(StartTag const &) const override; @@ -319,6 +323,8 @@ struct EndFontTag : public EndTag /// EndFontTag(docstring const & tag, FontTypes type): EndTag(tag), font_type_(type) {} /// + EndFontTag(std::string const & tag, FontTypes type): EndTag(from_utf8(tag)), font_type_(type) {} + /// EndFontTag const * asFontTag() const override { return this; } /// FontTypes font_type_;