#include <config.h>
+#include "output_docbook.h"
+
#include "Buffer.h"
#include "buffer_funcs.h"
#include "BufferParams.h"
#include "Font.h"
#include "InsetList.h"
-#include "output_docbook.h"
#include "Paragraph.h"
#include "ParagraphList.h"
#include "ParagraphParameters.h"
void openParTag(XMLStream & xs, const Paragraph * par, const Paragraph * prevpar)
{
- Layout const & lay = par->layout();
-
if (par == prevpar)
prevpar = nullptr;
// first paragraph of an author, then merging with the previous tag does not make sense. Say the
// next paragraph is the affiliation, then it should be output in the same <author> tag (different
// layout, same wrapper tag).
+ Layout const & lay = par->layout();
bool openWrapper = lay.docbookwrappertag() != "NONE";
if (prevpar != nullptr) {
Layout const & prevlay = prevpar->layout();
if (!ip)
continue;
if (const auto * bibitem = dynamic_cast<const InsetBibitem*>(ip)) {
- attr = from_utf8("xml:id='") + bibitem->getParam("key") + from_utf8("'");
+ auto id = xml::cleanID(bibitem->getParam("key"));
+ attr = from_utf8("xml:id='") + id + from_utf8("'");
break;
}
}
OutputParams const & runparams,
ParagraphList::const_iterator const & par)
{
+ // If this kind of layout should be ignored, already leave.
+ if (par->layout().docbooktag() == "IGNORE")
+ return;
+
+ // Useful variables.
auto const begin = text.paragraphs().begin();
auto const end = text.paragraphs().end();
auto prevpar = text.paragraphs().getParagraphBefore(par);
}
size_t nInsets = std::distance(par->insetList().begin(), par->insetList().end());
+ auto parSize = (size_t) par->size();
+
+ auto isLyxCodeToIgnore = [](InsetCode x) { return x == TOC_CODE; }; // If this LyX code does not produce any output,
+ // it can be safely ignored in the following checks: if this thing is present in the paragraph, it has no impact
+ // on the definition of the special case (i.e. whether or not a <para> tag should be output).
+
+ // TODO: if a paragraph *only* contains floats, listings, bibliographies, etc., should this be considered as a
+ // special case? If so, the code could be largely simplifies (all the calls to all_of, basically) and optimised
+ // at the compilation stage.
// Plain layouts must be ignored.
special_case |= buf.params().documentClass().isPlainLayout(par->layout()) && !runparams.docbook_force_pars;
// Equations do not deserve their own paragraph (DocBook allows them outside paragraphs).
// Exception: any case that generates an <inlineequation> must still get a paragraph to be valid.
- special_case |= nInsets == (size_t) par->size() && std::all_of(par->insetList().begin(), par->insetList().end(), [](InsetList::Element inset) {
+ special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [](InsetList::Element inset) {
return inset.inset && inset.inset->asInsetMath() && inset.inset->asInsetMath()->getType() != hullSimple;
});
+ // Tables do not deserve their own paragraphs (DocBook allows them outside paragraphs).
+ special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
+ return inset.inset->lyxCode() == TABULAR_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
+ });
// Floats cannot be in paragraphs.
- special_case |= nInsets == (size_t) par->size() && std::all_of(par->insetList().begin(), par->insetList().end(), [](InsetList::Element inset) {
- return inset.inset->lyxCode() == FLOAT_CODE;
+ special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
+ return inset.inset->lyxCode() == FLOAT_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
});
// Bibliographies cannot be in paragraphs. Bibitems should still be handled as paragraphs, though
// (see makeParagraphBibliography).
- special_case |= nInsets == (size_t) par->size() && std::all_of(par->insetList().begin(), par->insetList().end(), [](InsetList::Element inset) {
- return inset.inset->lyxCode() == BIBTEX_CODE;
+ special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
+ return inset.inset->lyxCode() == BIBTEX_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
});
// ERTs are in comments, not paragraphs.
- special_case |= nInsets == (size_t) par->size() && std::all_of(par->insetList().begin(), par->insetList().end(), [](InsetList::Element inset) {
- return inset.inset->lyxCode() == ERT_CODE;
+ special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
+ return inset.inset->lyxCode() == ERT_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
});
// Listings should not get into their own paragraph.
- special_case |= nInsets == (size_t) par->size() && std::all_of(par->insetList().begin(), par->insetList().end(), [](InsetList::Element inset) {
- return inset.inset->lyxCode() == LISTINGS_CODE;
+ special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
+ return inset.inset->lyxCode() == LISTINGS_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
});
// Boxes cannot get into their own paragraph.
- special_case |= nInsets == (size_t) par->size() && std::all_of(par->insetList().begin(), par->insetList().end(), [](InsetList::Element inset) {
- return inset.inset->lyxCode() == BOX_CODE;
+ special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
+ return inset.inset->lyxCode() == BOX_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
+ });
+ // Includes should not have a paragraph.
+ special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
+ return inset.inset->lyxCode() == INCLUDE_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
+ });
+ // Glossaries should not have a paragraph.
+ special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
+ return inset.inset->lyxCode() == NOMENCL_PRINT_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
});
bool const open_par = runparams.docbook_make_pars
++nextpar;
auto pars = par->simpleDocBookOnePar(buf, runparams, text.outerFont(distance(begin, par)), 0, nextpar == end, special_case);
for (docstring const & parXML : pars) {
- if (xml::isNotOnlySpace(parXML)) {
- if (open_par)
- openParTag(xs, &*par, prevpar);
+ if (!xml::isNotOnlySpace(parXML))
+ continue;
- xs << XMLStream::ESCAPE_NONE << parXML;
+ if (open_par)
+ openParTag(xs, &*par, prevpar);
- if (close_par)
- closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr);
- }
+ xs << XMLStream::ESCAPE_NONE << parXML;
+
+ if (close_par)
+ closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr);
}
}
OutputParams const &runparams,
ParagraphList::const_iterator const & par)
{
+ // If this kind of layout should be ignored, already leave.
+ if (par->layout().docbooktag() == "IGNORE")
+ return;
+
+ // Useful variables.
auto const end = text.paragraphs().end();
auto nextpar = par;
++nextpar;
closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
++p;
+ // Insert a new line after each "paragraph" (i.e. line in the listing), except for the last one.
+ // Otherwise, there would one more new line in the output than in the LyX document.
if (p != pars.end())
xs << xml::CR();
}
OutputParams const &runparams,
ParagraphList::const_iterator const & begin)
{
+ // Useful variables.
auto par = begin;
auto const end = text.paragraphs().end();
auto const envend = findEndOfEnvironment(par, end);
+ // If this kind of layout should be ignored, already leave.
+ if (begin->layout().docbooktag() == "IGNORE") {
+ auto nextpar = par;
+ ++nextpar;
+ return nextpar;
+ }
+
// Output the opening tag for this environment.
Layout const & envstyle = par->layout();
openTag(xs, envstyle.docbookwrappertag(), envstyle.docbookwrapperattr(), envstyle.docbookwrappertagtype());
// Handle the content of the list environment, item by item.
while (par != envend) {
- Layout const & style = par->layout();
+ // Skip this paragraph if it is both empty and the last one (otherwise, there may be deeper paragraphs after).
+ auto nextpar = par;
+ ++nextpar;
+ if (par->empty() && nextpar == envend)
+ break;
// Open the item wrapper.
+ Layout const & style = par->layout();
openTag(xs, style.docbookitemwrappertag(), style.docbookitemwrapperattr(), style.docbookitemwrappertagtype());
// Generate the label, if need be. If it is taken from the text, sep != 0 and corresponds to the first
OutputParams const & runparams,
ParagraphList::const_iterator const & par)
{
+ // If this kind of layout should be ignored, already leave.
+ if (par->layout().docbooktag() == "IGNORE")
+ return;
+
+ // Useful variables.
// Unlike XHTML, no need for labels, as they are handled by DocBook tags.
auto const begin = text.paragraphs().begin();
auto const end = text.paragraphs().end();
bool isLayoutSectioning(Layout const & lay)
{
- return lay.category() == from_utf8("Sectioning");
+ if (lay.docbooksection()) // Special case: some DocBook styles must be handled as sections.
+ return true;
+ else if (lay.category() == from_utf8("Sectioning") || lay.docbooktag() == "section") // Generic case.
+ return lay.toclevel != Layout::NOT_IN_TOC;
+ return false;
+}
+
+
+bool isLayoutSectioningOrSimilar(Layout const & lay)
+{
+ return isLayoutSectioning(lay) || lay.docbooktag() == "bridgehead";
}
while (bpit < epit) {
Layout const &style = paragraphs[bpit].layout();
- documentHasSections |= isLayoutSectioning(style);
+ documentHasSections |= isLayoutSectioningOrSimilar(style);
if (documentHasSections)
break;
// return false.
if (!par.isInset(i) || par.getInset(i)->lyxCode() != NOTE_CODE)
return false;
+
+ // An empty paragraph may still require some output.
+ if (par.layout().docbooksection())
+ return false;
+
+ // There should be really no content here.
return true;
}
DocBookInfoTag getParagraphsWithInfo(ParagraphList const ¶graphs,
pit_type bpit, pit_type const epit,
- // Typically, bpit is the beginning of the document and epit the end *or* the first section.
- bool documentHasSections) {
+ // Typically, bpit is the beginning of the document and epit the end of the
+ // document *or* the first section.
+ bool documentHasSections,
+ bool detectUnlayoutedAbstract
+ // Whether paragraphs with no specific layout should be detected as abstracts.
+ // For inner sections, an abstract should only be detected if it has a specific
+ // layout. For others, anything that might look like an abstract should be sought.
+ ) {
set<pit_type> shouldBeInInfo;
set<pit_type> mustBeInInfo;
set<pit_type> abstractWithLayout;
for (; cpit < epit; ++cpit) {
// Skip paragraphs that don't generate anything in DocBook.
Paragraph const & par = paragraphs[cpit];
+ Layout const &style = par.layout();
if (hasOnlyNotes(par))
continue;
- // There should never be any section here. (Just a sanity check: if this fails, this function could end up
- // processing the whole document.)
- if (isLayoutSectioning(par.layout())) {
+ // There should never be any section here, except for the first paragraph (a title can be part of <info>).
+ // (Just a sanity check: if this fails, this function could end up processing the whole document.)
+ if (cpit != bpit && isLayoutSectioningOrSimilar(par.layout())) {
LYXERR0("Assertion failed: section found in potential <info> paragraphs.");
break;
}
// If this is marked as an abstract by the layout, put it in the right set.
- if (par.layout().docbookabstract()) {
+ if (style.docbookabstract()) {
hasAbstractLayout = true;
abstractWithLayout.emplace(cpit);
continue;
// Based on layout information, store this paragraph in one set: should be in <info>, must be,
// or abstract (either because of layout or of position).
- Layout const &style = par.layout();
-
if (style.docbookininfo() == "always")
mustBeInInfo.emplace(cpit);
else if (style.docbookininfo() == "maybe")
shouldBeInInfo.emplace(cpit);
- else if (documentHasSections && !hasAbstractLayout)
+ else if (documentHasSections && !hasAbstractLayout && detectUnlayoutedAbstract &&
+ (style.docbooktag() == "NONE" || style.docbooktag() == "para") &&
+ style.docbookwrappertag() == "NONE")
+ // In this case, it is very likely that style.docbookininfo() == "never"! Be extra careful
+ // about anything that gets caught here.
abstractNoLayout.emplace(cpit);
else // This should definitely not be in <info>.
break;
docstring abstract;
if (hasAbstract) {
// Generate the abstract XML into a string before further checks.
+ // Usually, makeAny only generates one paragraph at a time. However, for the specific case of lists, it might
+ // generate more than one paragraph, as indicated in the return value.
odocstringstream os2;
XMLStream xs2(os2);
- for (auto const & p : info.abstract)
- makeAny(text, buf, xs2, runparams, paragraphs.iterator_at(p));
+
+ set<pit_type> doneParas;
+ for (auto const & p : info.abstract) {
+ if (doneParas.find(p) == doneParas.end()) {
+ auto oldPar = paragraphs.iterator_at(p);
+ auto newPar = makeAny(text, buf, xs2, runparams, oldPar);
+
+ // Insert the indices of all the paragraphs that were just generated (typically, one).
+ // **Make the hypothesis that, when an abstract has a list, all its items are consecutive.**
+ pit_type id = p;
+ while (oldPar != newPar) {
+ doneParas.emplace(id);
+ ++oldPar;
+ ++id;
+ }
+ }
+ }
// Actually output the abstract if there is something to do. Don't count line feeds or spaces in this,
// even though they must be properly output if there is some abstract.
// If there is no title, generate one (required for the document to be valid).
// This code is called for the main document, for table cells, etc., so be precise in this condition.
- if (text.isMainText() && info.shouldBeInInfo.empty()) {
+ if (text.isMainText() && info.shouldBeInInfo.empty() && !runparams.inInclude) {
xs << xml::StartTag("title");
xs << "Untitled Document";
xs << xml::EndTag("title");
ParagraphList const ¶graphs = text.paragraphs();
// First, the <info> tag.
- DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit, false);
+ DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit, false, true);
outputDocBookInfo(text, buf, xs, runparams, paragraphs, info);
// Then, the content. It starts where the <info> ends.
}
// Output the first <info> tag (or just the title).
- DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, eppit, true);
+ DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, eppit, true, true);
outputDocBookInfo(text, buf, xs, runparams, paragraphs, info);
bpit = info.epit;
// Don't output the ID as a DocBook <anchor>.
ourparams.docbook_anchors_to_ignore.emplace(label->screenLabel());
- // Cannot have multiple IDs per tag.
+ // Cannot have multiple IDs per tag. If there is another ID inset in the document, it will
+ // be output as a DocBook anchor.
break;
}
}
}
}
- // Generate this paragraph.
- par = makeAny(text, buf, xs, ourparams, par);
-
- // Some special sections may require abstracts (mostly parts, in books).
+ // Generate the <info> tag if a section was just opened.
+ // Some sections may require abstracts (mostly parts, in books: DocBookForceAbstractTag will not be NONE),
+ // others can still have an abstract (it must be detected so that it can be output at the right place).
// TODO: docbookforceabstracttag is a bit contrived here, but it does the job. Having another field just for this would be cleaner, but that's just for <part> and <partintro>, so it's probably not worth the effort.
- if (isLayoutSectioning(style) && style.docbookforceabstracttag() != "NONE") {
+ if (isLayoutSectioning(style)) {
// This abstract may be found between the next paragraph and the next title.
pit_type cpit = std::distance(text.paragraphs().begin(), par);
- pit_type ppit = std::get<1>(hasDocumentSectioning(paragraphs, cpit, epit));
+ pit_type ppit = std::get<1>(hasDocumentSectioning(paragraphs, cpit + 1L, epit));
// Generate this abstract (this code corresponds to parts of outputDocBookInfo).
- DocBookInfoTag secInfo = getParagraphsWithInfo(paragraphs, cpit, ppit, true);
+ DocBookInfoTag secInfo = getParagraphsWithInfo(paragraphs, cpit, ppit, true,
+ style.docbookforceabstracttag() != "NONE");
+
+ if (!secInfo.mustBeInInfo.empty() || !secInfo.shouldBeInInfo.empty() || !secInfo.abstract.empty()) {
+ // Generate the <info>, if required. If DocBookForceAbstractTag != NONE, this abstract will not be in
+ // <info>, unlike other ("standard") abstracts.
+ bool hasStandardAbstract = !secInfo.abstract.empty() && style.docbookforceabstracttag() == "NONE";
+ bool needInfo = !secInfo.mustBeInInfo.empty() || hasStandardAbstract;
+
+ if (needInfo) {
+ xs.startDivision(false);
+ xs << xml::StartTag("info");
+ xs << xml::CR();
+ }
- if (!secInfo.abstract.empty()) {
- xs << xml::StartTag(style.docbookforceabstracttag());
- xs << xml::CR();
- for (auto const &p : secInfo.abstract)
- makeAny(text, buf, xs, runparams, paragraphs.iterator_at(p));
- xs << xml::EndTag(style.docbookforceabstracttag());
- xs << xml::CR();
- }
+ // Output the elements that should go in <info>, before and after the abstract.
+ for (auto pit : secInfo.shouldBeInInfo) // Typically, the title: these elements are so important and ubiquitous
+ // that mandating a wrapper like <info> would repel users. Thus, generate them first.
+ makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(pit));
+ for (auto pit : secInfo.mustBeInInfo)
+ makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(pit));
+
+ // Deal with the abstract in <info> if it is standard (i.e. its tag is <abstract>).
+ if (!secInfo.abstract.empty() && hasStandardAbstract) {
+ if (!secInfo.abstractLayout) {
+ xs << xml::StartTag("abstract");
+ xs << xml::CR();
+ }
+
+ for (auto const &p : secInfo.abstract)
+ makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(p));
- // Skip all the text that just has been generated.
- par = paragraphs.iterator_at(ppit);
+ if (!secInfo.abstractLayout) {
+ xs << xml::EndTag("abstract");
+ xs << xml::CR();
+ }
+ }
+
+ // End the <info> tag if it was started.
+ if (needInfo) {
+ if (!xs.isLastTagCR())
+ xs << xml::CR();
+
+ xs << xml::EndTag("info");
+ xs << xml::CR();
+ xs.endDivision();
+ }
+
+ // Deal with the abstract outside <info> if it is not standard (i.e. its tag is layout-defined).
+ if (!secInfo.abstract.empty() && !hasStandardAbstract) {
+ // Assert: style.docbookforceabstracttag() != NONE.
+ xs << xml::StartTag(style.docbookforceabstracttag());
+ xs << xml::CR();
+ for (auto const &p : secInfo.abstract)
+ makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(p));
+ xs << xml::EndTag(style.docbookforceabstracttag());
+ xs << xml::CR();
+ }
+
+ // Skip all the text that has just been generated.
+ par = paragraphs.iterator_at(secInfo.epit);
+ } else {
+ // No <info> tag to generate, proceed as for normal paragraphs.
+ par = makeAny(text, buf, xs, ourparams, par);
+ }
+ } else {
+ // Generate this paragraph, as it has nothing special.
+ par = makeAny(text, buf, xs, ourparams, par);
}
}