]> git.lyx.org Git - lyx.git/blob - src/insets/InsetFloat.cpp
Use correct bounding box when exporting from command line
[lyx.git] / src / insets / InsetFloat.cpp
1 /**
2  * \file InsetFloat.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Jürgen Vigna
7  * \author Lars Gullik Bjønnes
8  * \author Jürgen Spitzmüller
9  *
10  * Full author contact details are available in file CREDITS.
11  */
12
13 #include <config.h>
14
15 #include "InsetBox.h"
16 #include "InsetCaption.h"
17 #include "InsetFloat.h"
18 #include "InsetGraphics.h"
19 #include "InsetLabel.h"
20
21 #include "Buffer.h"
22 #include "BufferParams.h"
23 #include "BufferView.h"
24 #include "Counters.h"
25 #include "Cursor.h"
26 #include "DispatchResult.h"
27 #include "Floating.h"
28 #include "FloatList.h"
29 #include "FuncRequest.h"
30 #include "FuncStatus.h"
31 #include "LaTeXFeatures.h"
32 #include "Lexer.h"
33 #include "xml.h"
34 #include "output_docbook.h"
35 #include "output_xhtml.h"
36 #include "ParIterator.h"
37 #include "TexRow.h"
38 #include "texstream.h"
39 #include "TextClass.h"
40 #include "InsetList.h"
41
42 #include "support/debug.h"
43 #include "support/docstream.h"
44 #include "support/gettext.h"
45 #include "support/lstrings.h"
46
47 #include "frontends/Application.h"
48
49 using namespace std;
50 using namespace lyx::support;
51
52
53 namespace lyx {
54
55 // With this inset it will be possible to support the latex package
56 // float.sty, and I am sure that with this and some additional support
57 // classes we can support similar functionality in other formats
58 // (read DocBook).
59 // By using float.sty we will have the same handling for all floats, both
60 // for those already in existence (table and figure) and all user created
61 // ones¹. So suddenly we give the users the possibility of creating new
62 // kinds of floats on the fly. (and with a uniform look)
63 //
64 // API to float.sty:
65 //   \newfloat{type}{placement}{ext}[within]
66 //     type      - The "type" of the new class of floats, like program or
67 //                 algorithm. After the appropriate \newfloat, commands
68 //                 such as \begin{program} or \end{algorithm*} will be
69 //                 available.
70 //     placement - The default placement for the given class of floats.
71 //                 They are like in standard LaTeX: t, b, p and h for top,
72 //                 bottom, page, and here, respectively. On top of that
73 //                 there is a new type, H, which does not really correspond
74 //                 to a float, since it means: put it "here" and nowhere else.
75 //                 Note, however that the H specifier is special and, because
76 //                 of implementation details cannot be used in the second
77 //                 argument of \newfloat.
78 //     ext       - The file name extension of an auxiliary file for the list
79 //                 of figures (or whatever). LaTeX writes the captions to
80 //                 this file.
81 //     within    - This (optional) argument determines whether floats of this
82 //                 class will be numbered within some sectional unit of the
83 //                 document. For example, if within is equal to chapter, the
84 //                 floats will be numbered within chapters.
85 //   \floatstyle{style}
86 //     style -  plain, boxed, ruled
87 //   \floatname{float}{floatname}
88 //     float     -
89 //     floatname -
90 //   \floatplacement{float}{placement}
91 //     float     -
92 //     placement -
93 //   \restylefloat{float}
94 //     float -
95 //   \listof{type}{title}
96 //     title -
97
98 // ¹ the algorithm float is defined using the float.sty package. Like this
99 //   \floatstyle{ruled}
100 //   \newfloat{algorithm}{htbp}{loa}[<sect>]
101 //   \floatname{algorithm}{Algorithm}
102 //
103 // The intention is that floats should be definable from two places:
104 //          - layout files
105 //          - the "gui" (i.e. by the user)
106 //
107 // From layout files.
108 // This should only be done for floats defined in a documentclass and that
109 // does not need any additional packages. The two most known floats in this
110 // category is "table" and "figure". Floats defined in layout files are only
111 // stored in lyx files if the user modifies them.
112 //
113 // By the user.
114 // There should be a gui dialog (and also a collection of lyxfuncs) where
115 // the user can modify existing floats and/or create new ones.
116 //
117 // The individual floats will also have some settable
118 // variables: wide and placement.
119 //
120 // Lgb
121
122 //FIXME: why do we set in stone the type here?
123 InsetFloat::InsetFloat(Buffer * buf, string const & params_str)
124         : InsetCaptionable(buf)
125 {
126         string2params(params_str, params_);
127         setCaptionType(params_.type);
128 }
129
130
131 // Enforce equality of float type and caption type.
132 void InsetFloat::setCaptionType(std::string const & type)
133 {
134         InsetCaptionable::setCaptionType(type);
135         params_.type = captionType();
136         // check if the float type exists
137         if (buffer().params().documentClass().floats().typeExist(params_.type))
138                 setNewLabel();
139         else
140                 setLabel(bformat(_("ERROR: Unknown float type: %1$s"), from_utf8(params_.type)));
141 }
142
143
144 docstring InsetFloat::layoutName() const
145 {
146         return "Float:" + from_utf8(params_.type);
147 }
148
149
150 docstring InsetFloat::toolTip(BufferView const & bv, int x, int y) const
151 {
152         if (isOpen(bv))
153                 return InsetCaptionable::toolTip(bv, x, y);
154
155         OutputParams rp(&buffer().params().encoding());
156         return getCaptionText(rp);
157 }
158
159
160 void InsetFloat::doDispatch(Cursor & cur, FuncRequest & cmd)
161 {
162         switch (cmd.action()) {
163
164         case LFUN_INSET_MODIFY: {
165                 if (!buffer().params().documentClass().floats().typeExist(cmd.getArg(0))) {
166                         // not for us: pass further.
167                         cur.undispatched();
168                         break;
169                 }
170                 InsetFloatParams params;
171                 string2params(to_utf8(cmd.argument()), params);
172                 cur.recordUndoInset(this);
173
174                 // placement, wide and sideways are not used for subfloats
175                 if (!params_.subfloat) {
176                         params_.placement = params.placement;
177                         params_.wide      = params.wide;
178                         params_.sideways  = params.sideways;
179                 }
180                 params_.alignment  = params.alignment;
181                 setNewLabel();
182                 if (params_.type != params.type)
183                         setCaptionType(params.type);
184                 // what we really want here is a TOC update, but that means
185                 // a full buffer update
186                 cur.forceBufferUpdate();
187                 break;
188         }
189
190         case LFUN_INSET_DIALOG_UPDATE: {
191                 cur.bv().updateDialog("float", params2string(params()));
192                 break;
193         }
194
195         default:
196                 InsetCaptionable::doDispatch(cur, cmd);
197                 break;
198         }
199 }
200
201
202 bool InsetFloat::getStatus(Cursor & cur, FuncRequest const & cmd,
203                 FuncStatus & flag) const
204 {
205         switch (cmd.action()) {
206
207         case LFUN_INSET_MODIFY:
208                 if (!buffer().params().documentClass().floats().typeExist(cmd.getArg(0)))
209                         return Inset::getStatus(cur, cmd, flag);
210         // fall through
211         case LFUN_INSET_DIALOG_UPDATE:
212                 flag.setEnabled(true);
213                 return true;
214
215         case LFUN_INSET_SETTINGS:
216                 if (InsetCaptionable::getStatus(cur, cmd, flag)) {
217                         flag.setEnabled(flag.enabled() && !params_.subfloat);
218                         return true;
219                 } else
220                         return false;
221
222         case LFUN_NEWLINE_INSERT:
223                 if (params_.subfloat) {
224                         flag.setEnabled(false);
225                         return true;
226                 }
227                 // no subfloat:
228                 // fall through
229
230         default:
231                 return InsetCaptionable::getStatus(cur, cmd, flag);
232         }
233 }
234
235
236 bool InsetFloat::hasSubCaptions(ParIterator const & it) const
237 {
238         return (it.innerInsetOfType(FLOAT_CODE) || it.innerInsetOfType(WRAP_CODE));
239 }
240
241
242 string InsetFloat::getAlignment() const
243 {
244         string alignment;
245         string const buf_alignment = buffer().params().float_alignment;
246         if (params_.alignment == "document"
247             && !buf_alignment.empty()) {
248                 alignment = buf_alignment;
249         } else if (!params_.alignment.empty()
250                    && params_.alignment != "class"
251                    && params_.alignment != "document") {
252                 alignment = params_.alignment;
253         }
254         return alignment;
255 }
256
257
258 LyXAlignment InsetFloat::contentAlignment() const
259 {
260         LyXAlignment align = LYX_ALIGN_NONE;
261         string alignment = getAlignment();
262         if (alignment == "left")
263                 align = LYX_ALIGN_LEFT;
264         else if (alignment == "center")
265                 align = LYX_ALIGN_CENTER;
266         else if (alignment == "right")
267                 align = LYX_ALIGN_RIGHT;
268
269         return align;
270 }
271
272
273 void InsetFloatParams::write(ostream & os) const
274 {
275         if (type.empty()) {
276                 // Better this than creating a parse error. This in fact happens in the
277                 // parameters dialog via InsetFloatParams::params2string.
278                 os << "senseless" << '\n';
279         } else
280                 os << type << '\n';
281
282         if (!placement.empty())
283                 os << "placement " << placement << "\n";
284         if (!alignment.empty())
285                 os << "alignment " << alignment << "\n";
286
287         if (wide)
288                 os << "wide true\n";
289         else
290                 os << "wide false\n";
291
292         if (sideways)
293                 os << "sideways true\n";
294         else
295                 os << "sideways false\n";
296 }
297
298
299 void InsetFloatParams::read(Lexer & lex)
300 {
301         lex.setContext("InsetFloatParams::read");
302         lex >> type;
303         if (lex.checkFor("placement"))
304                 lex >> placement;
305         if (lex.checkFor("alignment"))
306                 lex >> alignment;
307         lex >> "wide" >> wide;
308         lex >> "sideways" >> sideways;
309 }
310
311
312 void InsetFloat::write(ostream & os) const
313 {
314         os << "Float ";
315         params_.write(os);
316         InsetCaptionable::write(os);
317 }
318
319
320 void InsetFloat::read(Lexer & lex)
321 {
322         params_.read(lex);
323         InsetCaptionable::read(lex);
324         setCaptionType(params_.type);
325 }
326
327
328 void InsetFloat::validate(LaTeXFeatures & features) const
329 {
330         if (support::contains(params_.placement, 'H'))
331                 features.require("float");
332
333         if (params_.sideways)
334                 features.require("rotfloat");
335
336         if (features.inFloat())
337                 features.require("subfig");
338
339         if (features.inDeletedInset()) {
340                 features.require("tikz");
341                 features.require("ct-tikz-object-sout");
342         }
343
344         features.useFloat(params_.type, features.inFloat());
345         features.inFloat(true);
346         InsetCaptionable::validate(features);
347         features.inFloat(false);
348 }
349
350
351 docstring InsetFloat::xhtml(XMLStream & xs, OutputParams const & rp) const
352 {
353         FloatList const & floats = buffer().params().documentClass().floats();
354         Floating const & ftype = floats.getType(params_.type);
355         string const & htmltype = ftype.htmlTag();
356         string const & attr = ftype.htmlAttrib();
357
358         odocstringstream ods;
359         XMLStream newxs(ods);
360         newxs << xml::StartTag(htmltype, attr);
361         InsetText::XHTMLOptions const opts =
362                 InsetText::WriteLabel | InsetText::WriteInnerTag;
363         docstring deferred = InsetText::insetAsXHTML(newxs, rp, opts);
364         newxs << xml::EndTag(htmltype);
365
366         if (rp.inFloat == OutputParams::NONFLOAT) {
367                 // In this case, this float needs to be deferred, but we'll put it
368                 // before anything the text itself deferred.
369                 deferred = ods.str() + '\n' + deferred;
370         } else {
371                 // In this case, the whole thing is already being deferred, so
372                 // we can write to the stream.
373                 // Note that things will already have been escaped, so we do not
374                 // want to escape them again.
375                 xs << XMLStream::ESCAPE_NONE << ods.str();
376         }
377         return deferred;
378 }
379
380
381 void InsetFloat::latex(otexstream & os, OutputParams const & runparams_in) const
382 {
383         if (runparams_in.inFloat != OutputParams::NONFLOAT) {
384                 if (!paragraphs().empty() && !runparams_in.nice)
385                         // improve TexRow precision in non-nice mode
386                         os << safebreakln;
387
388                 if (runparams_in.moving_arg)
389                         os << "\\protect";
390                 os << "\\subfloat";
391
392                 OutputParams rp = runparams_in;
393                 rp.moving_arg = true;
394                 rp.inFloat = OutputParams::SUBFLOAT;
395                 os << getCaption(rp);
396                 os << '{';
397                 // The main argument is the contents of the float. This is not a moving argument.
398                 rp.moving_arg = false;
399                 InsetText::latex(os, rp);
400                 os << "}";
401
402                 return;
403         }
404         OutputParams runparams(runparams_in);
405         runparams.inFloat = OutputParams::MAINFLOAT;
406
407         FloatList const & floats = buffer().params().documentClass().floats();
408         string tmptype = params_.type;
409         if (params_.sideways && floats.allowsSideways(params_.type))
410                 tmptype = "sideways" + params_.type;
411         if (params_.wide && floats.allowsWide(params_.type)
412                 && (!params_.sideways ||
413                      params_.type == "figure" ||
414                      params_.type == "table"))
415                 tmptype += "*";
416         // Figure out the float placement to use.
417         // From lowest to highest:
418         // - float default placement
419         // - document wide default placement
420         // - specific float placement
421         string tmpplacement;
422         string const buf_placement = buffer().params().float_placement;
423         string const def_placement = floats.defaultPlacement(params_.type);
424         if (params_.placement == "document"
425             && !buf_placement.empty()
426             && buf_placement != def_placement) {
427                 tmpplacement = buf_placement;
428         } else if (!params_.placement.empty()
429                    && params_.placement != "document"
430                    && params_.placement != def_placement) {
431                 tmpplacement = params_.placement;
432         }
433
434         // Check if placement is allowed by this float
435         string const allowed_placement =
436                 floats.allowedPlacement(params_.type);
437         string placement;
438         string::const_iterator lit = tmpplacement.begin();
439         string::const_iterator end = tmpplacement.end();
440         for (; lit != end; ++lit) {
441                 if (contains(allowed_placement, *lit))
442                         placement += *lit;
443         }
444
445         // Force \begin{<floatname>} to appear in a new line.
446         os << breakln << "\\begin{" << from_ascii(tmptype) << '}';
447         if (runparams.lastid != -1)
448                 os.texrow().start(runparams.lastid, runparams.lastpos);
449         // We only output placement if different from the def_placement.
450         // sidewaysfloats always use their own page,
451         // therefore don't output the p option that is always set
452         if (!placement.empty()
453             && (!params_.sideways || from_ascii(placement) != "p"))
454                 os << '[' << from_ascii(placement) << ']';
455         os << '\n';
456
457         if (runparams.inDeletedInset) {
458                 // This has to be done manually since we need it inside the float
459                 OutputParams::CtObject ctobject = runparams.ctObject;
460                 runparams.ctObject = OutputParams::CT_DISPLAYOBJECT;
461                 Changes::latexMarkChange(os, buffer().params(), Change(Change::UNCHANGED),
462                                          Change(Change::DELETED), runparams);
463                 runparams.ctObject = ctobject;
464         }
465
466         string alignment = getAlignment();
467         if (alignment == "left")
468                 os << "\\raggedright" << breakln;
469         else if (alignment == "center")
470                 os << "\\centering" << breakln;
471         else if (alignment == "right")
472                 os << "\\raggedleft" << breakln;
473
474         InsetText::latex(os, runparams);
475
476         if (runparams.inDeletedInset)
477                 os << "}";
478
479         // Force \end{<floatname>} to appear in a new line.
480         os << breakln << "\\end{" << from_ascii(tmptype) << "}\n";
481 }
482
483
484 int InsetFloat::plaintext(odocstringstream & os, OutputParams const & runparams, size_t max_length) const
485 {
486         os << '[' << buffer().B_("float") << ' '
487                 << floatName(params_.type) << ":\n";
488         InsetText::plaintext(os, runparams, max_length);
489         os << "\n]";
490
491         return PLAINTEXT_NEWLINE + 1; // one char on a separate line
492 }
493
494
495 std::vector<const InsetCollapsible *> findSubfiguresInParagraph(const Paragraph &par)
496 {
497
498         // Don't make the hypothesis that all subfigures are in the same paragraph.
499         // Similarly, there may be several subfigures in the same paragraph (most likely case, based on the documentation).
500         // Any box is considered as a subfigure, even though the most likely case is \minipage.
501         // Boxes are not required to make subfigures. The common root between InsetBox and InsetFLoat is InsetCollapsible.
502         std::vector<const InsetCollapsible *> subfigures;
503         for (pos_type pos = 0; pos < par.size(); ++pos) {
504                 const Inset *inset = par.getInset(pos);
505                 if (!inset)
506                         continue;
507                 if (const auto box = dynamic_cast<const InsetBox *>(inset))
508                         subfigures.push_back(box);
509                 else if (const auto fl = dynamic_cast<const InsetFloat *>(inset))
510                         subfigures.push_back(fl);
511         }
512         return subfigures;
513 }
514
515
516 const InsetLabel* findLabelInParagraph(const Paragraph &par)
517 {
518         for (pos_type pos = 0; pos < par.size(); ++pos) {
519                 // If this inset is a subfigure, skip it.
520                 const Inset *inset = par.getInset(pos);
521                 if (dynamic_cast<const InsetBox *>(inset)) {
522                         continue;
523                 }
524
525                 // Maybe an inset is directly a label, in which case no more work is needed.
526                 if (inset && dynamic_cast<const InsetLabel *>(inset))
527                         return dynamic_cast<const InsetLabel *>(inset);
528
529                 // More likely, the label is hidden in an inset of a paragraph (only if a subtype of InsetText).
530                 if (!dynamic_cast<const InsetText *>(inset))
531                         continue;
532
533                 auto insetAsText = dynamic_cast<const InsetText *>(inset);
534                 auto itIn = insetAsText->paragraphs().begin();
535                 auto endIn = insetAsText->paragraphs().end();
536                 for (; itIn != endIn; ++itIn) {
537                         for (pos_type posIn = 0; posIn < itIn->size(); ++posIn) {
538                                 const Inset *insetIn = itIn->getInset(posIn);
539                                 if (insetIn && dynamic_cast<const InsetLabel *>(insetIn)) {
540                                         return dynamic_cast<const InsetLabel *>(insetIn);
541                                 }
542                         }
543                 }
544
545                 // Obviously, this solution does not scale with more levels of paragraphs-insets, but this should be enough.
546         }
547
548         return nullptr;
549 }
550
551
552 const InsetCaption* findCaptionInParagraph(const Paragraph &par)
553 {
554         // Don't dive too deep, otherwise, this could be a subfigure caption.
555         for (pos_type pos = 0; pos < par.size(); ++pos) {
556                 // If this inset is a subfigure, skip it.
557                 const Inset *inset = par.getInset(pos);
558                 if (dynamic_cast<const InsetBox *>(inset))
559                         continue;
560
561                 if (inset && dynamic_cast<const InsetCaption *>(inset))
562                         return dynamic_cast<const InsetCaption *>(inset);
563         }
564
565         return nullptr;
566 }
567
568
569 /// Takes an unstructured subfigure container (typically, an InsetBox) and find the elements within:
570 /// actual content (image or table), maybe a caption, maybe a label.
571 std::tuple<InsetCode, const Inset *, const InsetCaption *, const InsetLabel *> docbookParseHopelessSubfigure(const InsetText * subfigure)
572 {
573         InsetCode type = NO_CODE;
574         const Inset * content = nullptr;
575         const InsetCaption * caption = nullptr;
576         const InsetLabel * label = nullptr;
577
578         for (const auto & it : subfigure->paragraphs()) {
579                 for (pos_type posIn = 0; posIn < it.size(); ++posIn) {
580                         const Inset * inset = it.getInset(posIn);
581                         if (inset) {
582                                 switch (inset->lyxCode()) {
583                                         case GRAPHICS_CODE:
584                                         case TABULAR_CODE:
585                                                 if (!content) {
586                                                         content = inset;
587                                                         type = inset->lyxCode();
588                                                 }
589                                                 break;
590                                         case CAPTION_CODE:
591                                                 if (!caption) {
592                                                         caption = dynamic_cast<const InsetCaption *>(inset);
593
594                                                         // A label often hides in a caption. Make a simplified version of the main loop.
595                                                         if (!label) {
596                                                                 for (const auto &cit : caption->paragraphs()) {
597                                                                         for (pos_type cposIn = 0; cposIn < cit.size(); ++cposIn) {
598                                                                                 const Inset *cinset = cit.getInset(posIn);
599                                                                                 if (cinset && cinset->lyxCode() == LABEL_CODE) {
600                                                                                         label = dynamic_cast<const InsetLabel *>(cinset);
601                                                                                         break;
602                                                                                 }
603                                                                         }
604
605                                                                         if (label)
606                                                                                 break;
607                                                                 }
608                                                         }
609                                                 }
610                                                 break;
611                                         case LABEL_CODE:
612                                                 if (!label)
613                                                         label = dynamic_cast<const InsetLabel *>(inset);
614                                                 break;
615                                         default:
616                                                 break;
617                                 }
618                         }
619                 }
620
621                 if (content && caption && label)
622                         break;
623         }
624
625         return std::make_tuple(type, content, caption, label);
626 }
627
628
629 void docbookSubfigures(XMLStream & xs, OutputParams const & runparams, const InsetCaption * caption,
630                                            const InsetLabel * label, std::vector<const InsetCollapsible *> & subfigures)
631 {
632         // Ensure there is no label output, it is supposed to be handled as xml:id.
633         OutputParams rpNoLabel = runparams;
634         if (label)
635                 rpNoLabel.docbook_anchors_to_ignore.emplace(label->screenLabel());
636
637         // First, open the formal group.
638         docstring attr = docstring();
639         if (label)
640                 attr += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\"";
641
642         xs.startDivision(false);
643         xs << xml::StartTag("formalgroup", attr);
644         xs << xml::CR();
645
646         xs << xml::StartTag("title"); // Don't take attr here, the ID should only go in one place, not two.
647         if (caption) {
648                 caption->getCaptionAsDocBook(xs, rpNoLabel);
649         } else {
650                 xs << "No caption";
651                 // No caption has been detected, but this tag is required for the document to be valid DocBook.
652         }
653         xs << xml::EndTag("title");
654         xs << xml::CR();
655
656         // Deal with each subfigure individually. This should also deal with their caption and their label.
657         // This should be a recursive call to InsetFloat.
658         // An item in subfigure should either be an InsetBox containing an InsetFloat, or an InsetBox directly containing
659         // an image or a table, or directly an InsetFloat.
660         for (const InsetCollapsible * subfigure: subfigures) {
661                 if (subfigure == nullptr)
662                         continue;
663
664                 // The collapsible may already be a float (InsetFloat).
665                 if (dynamic_cast<const InsetFloat *>(subfigure)) {
666                         subfigure->docbook(xs, runparams);
667                         continue;
668                 }
669
670                 // Subfigures are in boxes, then in InsetFloat.
671                 {
672                         bool foundInsetFloat = false;
673                         for (const auto &it : subfigure->paragraphs()) {
674                                 for (pos_type posIn = 0; posIn < it.size(); ++posIn) {
675                                         const Inset *inset = it.getInset(posIn);
676                                         if (inset && inset->lyxCode() == FLOAT_CODE) {
677                                                 foundInsetFloat = true;
678                                                 inset->docbook(xs, runparams);
679                                                 break;
680                                         }
681                                 }
682
683                                 if (foundInsetFloat)
684                                         break;
685                         }
686                         if (foundInsetFloat)
687                                 continue;
688                 }
689
690                 // Subfigures are in boxes, then directly an image or a table. In that case, generate the whole content of the
691                 // InsetBox, but not the box container.
692                 // Impose some model on the subfigure: at most a caption, at most a label, exactly one figure or one table.
693                 {
694                         InsetCode stype = NO_CODE;
695                         const Inset * scontent = nullptr;
696                         const InsetCaption * scaption = nullptr;
697                         const InsetLabel * slabel = nullptr;
698
699                         std::tie(stype, scontent, scaption, slabel) = docbookParseHopelessSubfigure(subfigure);
700
701                         // If there is something, generate it. This is very much like docbookNoSubfigures, but many things
702                         // must be coded differently because there is no float.
703                         // TODO: some code is identical to Floating, like Floating::docbookTag or Floating::docbookCaption. How to reuse that code?
704                         if (scontent) {
705                                 // Floating::docbookCaption()
706                                 string docbook_caption = "caption"; // This is already correct for tables.
707                                 if (stype == GRAPHICS_CODE)
708                                         docbook_caption = "title";
709
710                                 // Floating::docbookTag() with hasTitle = true, as we are in formalgroup.
711                                 string stag = "float";
712                                 if (stype == GRAPHICS_CODE)
713                                         stag = "figure";
714                                 else if (stype == TABULAR_CODE)
715                                         stag = "table";
716
717                                 // Ensure there is no label output, it is supposed to be handled as xml:id.
718                                 if (slabel)
719                                         rpNoLabel.docbook_anchors_to_ignore.emplace(slabel->screenLabel());
720
721                                 // Ensure the float does not output its caption, as it is handled here (DocBook mandates a specific place for
722                                 // captions, they cannot appear at the end of the float, albeit LyX is happy with that).
723                                 OutputParams rpNoTitle = runparams;
724                                 rpNoTitle.docbook_in_float = true;
725                                 if (stype == TABULAR_CODE)
726                                         rpNoTitle.docbook_in_table = true;
727
728                                 // Organisation: <float> <title if any/> <contents without title/> </float>.
729                                 docstring sattr = docstring();
730                                 if (slabel)
731                                         sattr += "xml:id=\"" + xml::cleanID(slabel->screenLabel()) + "\"";
732                                 // No layout way of adding attributes, unlike the normal code path.
733
734                                 xs << xml::StartTag(stag, sattr);
735                                 xs << xml::CR();
736                                 xs << xml::StartTag(docbook_caption);
737                                 if (scaption)
738                                         scaption->getCaptionAsDocBook(xs, rpNoLabel);
739                                 else // Mandatory in formalgroup.
740                                         xs << "No caption detected";
741                                 xs << xml::EndTag(docbook_caption);
742                                 xs << xml::CR();
743                                 scontent->docbook(xs, rpNoTitle);
744                                 xs << xml::EndTag(stag);
745                                 xs << xml::CR();
746
747                                 // This subfigure could be generated.
748                                 continue;
749                         }
750                 }
751
752                 // If there is no InsetFloat in the inset, output a warning.
753                 xs << XMLStream::ESCAPE_NONE << "Error: no float found in the box. "
754                                                         "To use subfigures in DocBook, elements must be wrapped in a float "
755                                     "inset and have a title/caption.";
756                 // 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.
757
758                 // Recurse to generate as much content as possible (avoid any loss).
759                 subfigure->docbook(xs, runparams);
760         }
761
762         // Every subfigure is done: close the formal group.
763         xs << xml::EndTag("formalgroup");
764         xs << xml::CR();
765         xs.endDivision();
766 }
767
768
769 void docbookNoSubfigures(XMLStream & xs, OutputParams const & runparams, const InsetCaption * caption,
770                          const InsetLabel * label, Floating const & ftype, const InsetFloat * thisFloat)
771 {
772         string const &titleTag = ftype.docbookCaption();
773
774         // Ensure there is no label output, it is supposed to be handled as xml:id.
775         OutputParams rpNoLabel = runparams;
776         if (label)
777                 rpNoLabel.docbook_anchors_to_ignore.emplace(label->screenLabel());
778
779         // Ensure the float does not output its caption, as it is handled here (DocBook mandates a specific place for
780         // captions, they cannot appear at the end of the float, albeit LyX is happy with that).
781         OutputParams rpNoTitle = runparams;
782         rpNoTitle.docbook_in_float = true;
783         if (ftype.docbookFloatType() == "table")
784                 rpNoTitle.docbook_in_table = true;
785
786         // Organisation: <float> <title if any/> <contents without title/> </float>.
787         docstring attr = docstring();
788         if (label)
789                 attr += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\"";
790         if (!ftype.docbookAttr().empty()) {
791                 if (!attr.empty())
792                         attr += " ";
793                 attr += from_utf8(ftype.docbookAttr());
794         }
795
796         xs << xml::StartTag(ftype.docbookTag(caption != nullptr), attr);
797         xs << xml::CR();
798         if (caption) {
799                 xs << xml::StartTag(titleTag);
800                 caption->getCaptionAsDocBook(xs, rpNoLabel);
801                 xs << xml::EndTag(titleTag);
802                 xs << xml::CR();
803         }
804         thisFloat->InsetText::docbook(xs, rpNoTitle);
805         xs << xml::EndTag(ftype.docbookTag(caption != nullptr));
806         xs << xml::CR();
807 }
808
809
810 void InsetFloat::docbook(XMLStream & xs, OutputParams const & runparams) const
811 {
812         // Determine whether the float has a title or not. For this, iterate through the paragraphs and look
813         // for an InsetCaption. Do the same for labels and subfigures.
814         // The caption and the label for each subfigure is handled by recursive calls.
815         const InsetCaption* caption = nullptr;
816         const InsetLabel* label = nullptr;
817         std::vector<const InsetCollapsible *> subfigures;
818
819         auto end = paragraphs().end();
820         for (auto it = paragraphs().begin(); it != end; ++it) {
821                 std::vector<const InsetCollapsible *> foundSubfigures = findSubfiguresInParagraph(*it);
822                 if (!foundSubfigures.empty()) {
823                         subfigures.reserve(subfigures.size() + foundSubfigures.size());
824                         subfigures.insert(subfigures.end(), foundSubfigures.begin(), foundSubfigures.end());
825                 }
826
827                 if (!caption)
828                         caption = findCaptionInParagraph(*it);
829                 if (!label)
830                         label = findLabelInParagraph(*it);
831         }
832
833         // Gather a few things from global environment that are shared between all following cases.
834         FloatList const &floats = buffer().params().documentClass().floats();
835         Floating const &ftype = floats.getType(params_.type);
836
837         // Switch on subfigures.
838         if (!subfigures.empty())
839                 docbookSubfigures(xs, runparams, caption, label, subfigures);
840         else
841                 docbookNoSubfigures(xs, runparams, caption, label, ftype, this);
842 }
843
844
845 bool InsetFloat::insetAllowed(InsetCode code) const
846 {
847         // The case that code == FLOAT_CODE is handled in Text3.cpp,
848         // because we need to know what type of float is meant.
849         switch(code) {
850         case WRAP_CODE:
851         case FOOT_CODE:
852         case MARGIN_CODE:
853                 return false;
854         default:
855                 return InsetCaptionable::insetAllowed(code);
856         }
857 }
858
859
860 void InsetFloat::setWide(bool w, bool update_label)
861 {
862         if (!buffer().params().documentClass().floats().allowsWide(params_.type))
863                 params_.wide = false;
864         else
865             params_.wide = w;
866         if (update_label)
867                 setNewLabel();
868 }
869
870
871 void InsetFloat::setSideways(bool s, bool update_label)
872 {
873         if (!buffer().params().documentClass().floats().allowsSideways(params_.type))
874                 params_.sideways = false;
875         else
876                 params_.sideways = s;
877         if (update_label)
878                 setNewLabel();
879 }
880
881
882 void InsetFloat::setSubfloat(bool s, bool update_label)
883 {
884         params_.subfloat = s;
885         if (update_label)
886                 setNewLabel();
887 }
888
889
890 void InsetFloat::setNewLabel()
891 {
892         docstring lab = _("float: ");
893
894         if (params_.subfloat)
895                 lab = _("subfloat: ");
896
897         lab += floatName(params_.type);
898
899         FloatList const & floats = buffer().params().documentClass().floats();
900
901         if (params_.wide && floats.allowsWide(params_.type))
902                 lab += '*';
903
904         if (params_.sideways && floats.allowsSideways(params_.type))
905                 lab += _(" (sideways)");
906
907         setLabel(lab);
908 }
909
910
911 bool InsetFloat::allowsCaptionVariation(std::string const & newtype) const
912 {
913         return !params_.subfloat && newtype != "Unnumbered";
914 }
915
916
917 TexString InsetFloat::getCaption(OutputParams const & runparams) const
918 {
919         InsetCaption const * ins = getCaptionInset();
920         if (ins == 0)
921                 return TexString();
922
923         otexstringstream os;
924         ins->getArgs(os, runparams);
925
926         if (!runparams.nice)
927                 // increase TexRow precision in non-nice mode
928                 os << safebreakln;
929         os << '[';
930         otexstringstream os2;
931         ins->getArgument(os2, runparams);
932         TexString ts = os2.release();
933         docstring & arg = ts.str;
934         // Protect ']'
935         if (arg.find(']') != docstring::npos)
936                 arg = '{' + arg + '}';
937         os << move(ts);
938         os << ']';
939         if (!runparams.nice)
940                 os << safebreakln;
941         return os.release();
942 }
943
944
945 void InsetFloat::string2params(string const & in, InsetFloatParams & params)
946 {
947         params = InsetFloatParams();
948         if (in.empty())
949                 return;
950
951         istringstream data(in);
952         Lexer lex;
953         lex.setStream(data);
954         lex.setContext("InsetFloat::string2params");
955         params.read(lex);
956 }
957
958
959 string InsetFloat::params2string(InsetFloatParams const & params)
960 {
961         ostringstream data;
962         params.write(data);
963         return data.str();
964 }
965
966
967 } // namespace lyx