#include <config.h>
+#include "InsetFloat.h"
+
#include "InsetBox.h"
#include "InsetCaption.h"
-#include "InsetFloat.h"
#include "InsetGraphics.h"
#include "InsetLabel.h"
}
+CtObject InsetFloat::getCtObject(OutputParams const &) const
+{
+ return CtObject::OmitObject;
+}
+
+
LyXAlignment InsetFloat::contentAlignment() const
{
LyXAlignment align = LYX_ALIGN_NONE;
if (runparams.inDeletedInset) {
// This has to be done manually since we need it inside the float
- OutputParams::CtObject ctobject = runparams.ctObject;
- runparams.ctObject = OutputParams::CT_DISPLAYOBJECT;
+ CtObject ctobject = runparams.ctObject;
+ runparams.ctObject = CtObject::DisplayObject;
Changes::latexMarkChange(os, buffer().params(), Change(Change::UNCHANGED),
Change(Change::DELETED), runparams);
runparams.ctObject = ctobject;
}
-const InsetLabel* findLabelInParagraph(const Paragraph &par)
+/// Takes an unstructured subfigure container (typically, an InsetBox) and find the elements within:
+/// actual content (image or table), maybe a caption, maybe a label.
+std::tuple<InsetCode, const Inset *, const InsetCaption *, const InsetLabel *> docbookParseHopelessSubfigure(const InsetText * subfigure)
{
- 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<const InsetBox *>(inset)) {
- continue;
- }
-
- // Maybe an inset is directly a label, in which case no more work is needed.
- if (inset && dynamic_cast<const InsetLabel *>(inset))
- return dynamic_cast<const InsetLabel *>(inset);
-
- // More likely, the label is hidden in an inset of a paragraph (only if a subtype of InsetText).
- if (!dynamic_cast<const InsetText *>(inset))
- continue;
+ InsetCode type = NO_CODE;
+ const Inset * content = nullptr;
+ const InsetCaption * caption = nullptr;
+ const InsetLabel * label = nullptr;
- auto insetAsText = dynamic_cast<const InsetText *>(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<const InsetLabel *>(insetIn)) {
- return dynamic_cast<const InsetLabel *>(insetIn);
+ for (const auto & it : subfigure->paragraphs()) {
+ for (pos_type posIn = 0; posIn < it.size(); ++posIn) {
+ const Inset * inset = it.getInset(posIn);
+ if (inset) {
+ switch (inset->lyxCode()) {
+ case GRAPHICS_CODE:
+ case TABULAR_CODE:
+ if (!content) {
+ content = inset;
+ type = inset->lyxCode();
+ }
+ break;
+ case CAPTION_CODE:
+ if (!caption) {
+ caption = dynamic_cast<const InsetCaption *>(inset);
+
+ // A label often hides in a caption. Make a simplified version of the main loop.
+ if (!label) {
+ for (const auto &cit : caption->paragraphs()) {
+ for (pos_type cposIn = 0; cposIn < cit.size(); ++cposIn) {
+ const Inset *cinset = cit.getInset(posIn);
+ if (cinset && cinset->lyxCode() == LABEL_CODE) {
+ label = dynamic_cast<const InsetLabel *>(cinset);
+ break;
+ }
+ }
+
+ if (label)
+ break;
+ }
+ }
+ }
+ break;
+ case LABEL_CODE:
+ if (!label)
+ label = dynamic_cast<const InsetLabel *>(inset);
+ break;
+ default:
+ break;
}
}
}
- // 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<const InsetBox *>(inset))
- continue;
-
- if (inset && dynamic_cast<const InsetCaption *>(inset))
- return dynamic_cast<const InsetCaption *>(inset);
+ if (content && caption && label)
+ break;
}
- return nullptr;
+ return std::make_tuple(type, content, caption, label);
}
void docbookSubfigures(XMLStream & xs, OutputParams const & runparams, const InsetCaption * caption,
- const InsetLabel * label, std::vector<const InsetCollapsible *> & subfigures)
+ const InsetLabel * label, std::vector<const InsetCollapsible *> const & subfigures)
{
// Ensure there is no label output, it is supposed to be handled as xml:id.
OutputParams rpNoLabel = runparams;
// Deal with each subfigure individually. This should also deal with their caption and their label.
// This should be a recursive call to InsetFloat.
- // An item in subfigure should either be an InsetBox containing an InsetFloat or directly an InsetFloat.
- for (const InsetCollapsible *subfigure: subfigures) {
- // If there is no InsetFloat in the paragraphs, output a warning.
- bool foundInsetFloat = false;
+ // An item in subfigure should either be an InsetBox containing an InsetFloat, or an InsetBox directly containing
+ // an image or a table, or directly an InsetFloat.
+ for (const InsetCollapsible * subfigure: subfigures) {
+ if (subfigure == nullptr)
+ continue;
// The collapsible may already be a float (InsetFloat).
- if (subfigure && dynamic_cast<const InsetFloat *>(subfigure))
- foundInsetFloat = true;
+ if (dynamic_cast<const InsetFloat *>(subfigure)) {
+ subfigure->docbook(xs, runparams);
+ continue;
+ }
- // Subfigures are in boxes.
- if (!foundInsetFloat) {
+ // Subfigures are in boxes, then in InsetFloat.
+ {
+ bool foundInsetFloat = false;
for (const auto &it : subfigure->paragraphs()) {
for (pos_type posIn = 0; posIn < it.size(); ++posIn) {
const Inset *inset = it.getInset(posIn);
- if (inset && dynamic_cast<const InsetFloat *>(inset)) {
+ if (inset && inset->lyxCode() == FLOAT_CODE) {
foundInsetFloat = true;
+ inset->docbook(xs, runparams);
break;
}
}
if (foundInsetFloat)
break;
}
+ if (foundInsetFloat)
+ continue;
}
- 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.";
+ // Subfigures are in boxes, then directly an image or a table. In that case, generate the whole content of the
+ // InsetBox, but not the box container.
+ // Impose some model on the subfigure: at most a caption, at most a label, exactly one figure or one table.
+ {
+ InsetCode stype = NO_CODE;
+ const Inset * scontent = nullptr;
+ const InsetCaption * scaption = nullptr;
+ const InsetLabel * slabel = nullptr;
+
+ std::tie(stype, scontent, scaption, slabel) = docbookParseHopelessSubfigure(subfigure);
+
+ // If there is something, generate it. This is very much like docbookNoSubfigures, but many things
+ // must be coded differently because there is no float.
+ // TODO: some code is identical to Floating, like Floating::docbookTag or Floating::docbookCaption. How to reuse that code?
+ if (scontent) {
+ // Floating::docbookCaption()
+ string docbook_caption = "caption"; // This is already correct for tables.
+ if (stype == GRAPHICS_CODE)
+ docbook_caption = "title";
+
+ // Floating::docbookTag() with hasTitle = true, as we are in formalgroup.
+ string stag = "float";
+ if (stype == GRAPHICS_CODE)
+ stag = "figure";
+ else if (stype == TABULAR_CODE)
+ stag = "table";
+
+ // Ensure there is no label output, it is supposed to be handled as xml:id.
+ if (slabel)
+ rpNoLabel.docbook_anchors_to_ignore.emplace(slabel->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;
+ if (stype == TABULAR_CODE)
+ rpNoTitle.docbook_in_table = true;
+
+ // Organisation: <float> <title if any/> <contents without title/> </float>.
+ docstring sattr = docstring();
+ if (slabel)
+ sattr += "xml:id=\"" + xml::cleanID(slabel->screenLabel()) + "\"";
+ // No layout way of adding attributes, unlike the normal code path.
+
+ xs << xml::StartTag(stag, sattr);
+ xs << xml::CR();
+ xs << xml::StartTag(docbook_caption);
+ if (scaption)
+ scaption->getCaptionAsDocBook(xs, rpNoLabel);
+ else // Mandatory in formalgroup.
+ xs << "No caption detected";
+ xs << xml::EndTag(docbook_caption);
+ xs << xml::CR();
+ scontent->docbook(xs, rpNoTitle);
+ xs << xml::EndTag(stag);
+ xs << xml::CR();
+
+ // This subfigure could be generated.
+ continue;
+ }
+ }
+
+ // If there is no InsetFloat in the inset, output a warning.
+ 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.
+ // Recurse to generate as much content as possible (avoid any loss).
subfigure->docbook(xs, runparams);
}
}
-void docbookNoSubfigures(XMLStream & xs, OutputParams const & runparams, const InsetCaption * caption,
- const InsetLabel * label, Floating const & ftype, const InsetFloat * thisFloat)
+void docbookGenerateFillerMedia(XMLStream & xs)
{
- string const &titleTag = ftype.docbookCaption();
+ xs << xml::StartTag("mediaobject");
+ xs << xml::CR();
+ xs << xml::StartTag("textobject");
+ xs << xml::CR();
+ xs << xml::StartTag("phrase");
+ xs << "This figure is empty.";
+ xs << xml::EndTag("phrase");
+ xs << xml::CR();
+ xs << xml::EndTag("textobject");
+ xs << xml::CR();
+ xs << xml::EndTag("mediaobject");
+ xs << xml::CR();
+}
+
+
+void docbookGenerateFillerTable(XMLStream & xs, BufferParams::TableOutput format)
+{
+ switch (format) {
+ case BufferParams::HTMLTable:
+ xs << xml::StartTag("tr");
+ xs << xml::CR();
+ xs << xml::StartTag("td");
+ xs << "This table is empty.";
+ xs << xml::EndTag("td");
+ xs << xml::CR();
+ xs << xml::EndTag("tr");
+ xs << xml::CR();
+ break;
+ case BufferParams::CALSTable:
+ // CALS tables allow for <mediaobject>, use that instead.
+ docbookGenerateFillerMedia(xs);
+ break;
+ }
+}
+
+void docbookNoSubfigures(XMLStream & xs, OutputParams const & runparams, const InsetCaption * caption,
+ const InsetLabel * label, Floating const & ftype, const InsetFloat * thisFloat) {
// Ensure there is no label output, it is supposed to be handled as xml:id.
OutputParams rpNoLabel = runparams;
if (label)
if (ftype.docbookFloatType() == "table")
rpNoTitle.docbook_in_table = true;
- // Organisation: <float> <title if any/> <contents without title/> </float>.
+ // Generate the contents of the float (to check for emptiness).
+ odocstringstream osFloatContent;
+ bool hasFloat = false;
+
+ if (thisFloat) {
+ XMLStream xsFloatContent(osFloatContent);
+ thisFloat->InsetText::docbook(xsFloatContent, rpNoTitle);
+ hasFloat = !osFloatContent.str().empty();
+ }
+
+ // Do the same for the caption.
+ odocstringstream osCaptionContent;
+ bool hasCaption = false;
+
+ if (caption != nullptr) {
+ XMLStream xsCaptionContent(osCaptionContent);
+ caption->getCaptionAsDocBook(xsCaptionContent, rpNoLabel);
+ hasCaption = !osCaptionContent.str().empty();
+ }
+
+ // Organisation: <float with xml:id if any> <title if any/> <contents without title nor xml:id/> </float>.
+ // Titles and xml:id are generated with specific insets and must be dealt with using OutputParams.
+
+ // - Generate the attributes for the float tag.
docstring attr = docstring();
if (label)
attr += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\"";
attr += from_utf8(ftype.docbookAttr());
}
- xs << xml::StartTag(ftype.docbookTag(caption != nullptr), attr);
+ // - Open the float tag.
+ xs << xml::StartTag(ftype.docbookTag(hasCaption), attr);
xs << xml::CR();
- if (caption) {
+
+ // - Generate the caption.
+ if (hasCaption) {
+ string const &titleTag = ftype.docbookCaption();
xs << xml::StartTag(titleTag);
- caption->getCaptionAsDocBook(xs, rpNoLabel);
+ xs << XMLStream::ESCAPE_NONE << osCaptionContent.str();
xs << xml::EndTag(titleTag);
xs << xml::CR();
}
- thisFloat->InsetText::docbook(xs, rpNoTitle);
- xs << xml::EndTag(ftype.docbookTag(caption != nullptr));
+
+ // - Output the actual content of the float or some dummy content (to ensure that the output
+ // document is valid).
+ if (hasFloat)
+ xs << XMLStream::ESCAPE_NONE << osFloatContent.str();
+ else if (ftype.docbookFloatType() == "table")
+ docbookGenerateFillerTable(xs, thisFloat->buffer().params().docbook_table_output);
+ else
+ docbookGenerateFillerMedia(xs);
+
+ // - Close the float.
+ xs << xml::EndTag(ftype.docbookTag(hasCaption));
xs << xml::CR();
}
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<const InsetCollapsible *> subfigures;
+ const InsetCaption* caption = getCaptionInset();
+ const InsetLabel* label = getLabelInset();
+ // Determine whether the float has subfigures.
+ std::vector<const InsetCollapsible *> subfigures;
auto end = paragraphs().end();
for (auto it = paragraphs().begin(); it != end; ++it) {
std::vector<const InsetCollapsible *> foundSubfigures = findSubfiguresInParagraph(*it);
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);
+ FloatList const & floats = buffer().params().documentClass().floats();
+ Floating const & ftype = floats.getType(params_.type);
// Switch on subfigures.
if (!subfigures.empty())
void InsetFloat::setNewLabel()
{
- docstring lab = _("float: ");
+ docstring lab = _("Float: ");
if (params_.subfloat)
- lab = _("subfloat: ");
+ lab = _("Subfloat: ");
lab += floatName(params_.type);