]> git.lyx.org Git - features.git/blob - src/xml.cpp
Try to be more pedantic wrt blank lines in lyx2lyx output
[features.git] / src / xml.cpp
1 /**
2  * \file xml.cpp
3  * This file is part of LyX, the document processor.
4  * License details can be found in the file COPYING.
5  *
6  * \author José Matos
7  * \author John Levon
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "xml.h"
15
16 #include "Buffer.h"
17 #include "BufferParams.h"
18 #include "Counters.h"
19 #include "Layout.h"
20 #include "Paragraph.h"
21 #include "Text.h"
22 #include "TextClass.h"
23
24 #include "support/convert.h"
25 #include "support/debug.h"
26 #include "support/docstream.h"
27 #include "support/lassert.h"
28 #include "support/lstrings.h"
29 #include "support/textutils.h"
30
31 #include <atomic>
32 #include <map>
33 #include <functional>
34 #include <QThreadStorage>
35
36 using namespace std;
37 using namespace lyx::support;
38
39 namespace lyx {
40 namespace xml {
41
42
43 docstring escapeChar(char_type c, XMLStream::EscapeSettings e)
44 {
45         docstring str;
46         switch (e) { // For HTML: always ESCAPE_NONE. For XML: it depends, hence the parameter.
47         case XMLStream::ESCAPE_NONE:
48         case XMLStream::ESCAPE_COMMENTS:
49                 str += c;
50                 break;
51         case XMLStream::ESCAPE_ALL:
52                 if (c == '<') {
53                         str += "&lt;";
54                         break;
55                 } else if (c == '>') {
56                         str += "&gt;";
57                         break;
58                 }
59                 // fall through
60         case XMLStream::ESCAPE_AND:
61                 if (c == '&')
62                         str += "&amp;";
63                 else
64                         str     +=c ;
65                 break;
66         }
67         return str;
68 }
69
70
71 docstring escapeChar(char c, XMLStream::EscapeSettings e)
72 {
73         LATTEST(static_cast<unsigned char>(c) < 0x80);
74         return escapeChar(static_cast<char_type>(c), e);
75 }
76
77
78 docstring escapeString(docstring const & raw, XMLStream::EscapeSettings e)
79 {
80         docstring bin;
81         bin.reserve(raw.size() * 2); // crude approximation is sufficient
82         for (size_t i = 0; i != raw.size(); ++i) {
83                 char_type c = raw[i];
84                 if (e == XMLStream::ESCAPE_COMMENTS && c == '-' && i > 0 && raw[i - 1] == '-')
85                         bin += "&#45;";
86                 else
87                         bin += xml::escapeChar(c, e);
88         }
89
90         return bin;
91 }
92
93
94 docstring cleanAttr(docstring const & str)
95 {
96         docstring newname;
97         docstring::const_iterator it = str.begin();
98         docstring::const_iterator en = str.end();
99         for (; it != en; ++it) {
100                 char_type const c = *it;
101                 newname += isAlnumASCII(c) ? c : char_type('_');
102         }
103         return newname;
104 }
105
106
107 docstring StartTag::writeTag() const
108 {
109         docstring output = '<' + tag_;
110         if (!attr_.empty()) {
111                 docstring attributes = xml::trimLeft(xml::escapeString(attr_, XMLStream::ESCAPE_NONE));
112                 if (!attributes.empty())
113                         output += ' ' + attributes;
114         }
115         output += ">";
116         return output;
117 }
118
119
120 docstring StartTag::writeEndTag() const
121 {
122         return from_utf8("</") + tag_ + from_utf8(">");
123 }
124
125
126 bool StartTag::operator==(FontTag const &rhs) const
127 {
128         return rhs == *this;
129 }
130
131
132 docstring EndTag::writeEndTag() const
133 {
134         return from_utf8("</") + tag_ + from_utf8(">");
135 }
136
137
138 docstring CompTag::writeTag() const
139 {
140         docstring output = '<' + tag_;
141         if (!attr_.empty()) {
142                 // Erase the beginning of the attributes if it contains space characters: this function deals with that
143                 // automatically.
144                 docstring attributes = escapeString(attr_, XMLStream::ESCAPE_NONE);
145                 attributes.erase(attributes.begin(), std::find_if(attributes.begin(), attributes.end(),
146                                                           [](char_type c) {return !isSpace(c);}));
147                 if (!attributes.empty()) {
148                         output += ' ' + attributes;
149                 }
150         }
151         output += " />";
152         return output;
153 }
154
155
156 bool FontTag::operator==(StartTag const & tag) const
157 {
158         FontTag const * const ftag = tag.asFontTag();
159         if (!ftag)
160                 return false;
161         return (font_type_ == ftag->font_type_);
162 }
163
164 } // namespace xml
165
166
167 void XMLStream::writeError(std::string const &s)
168 {
169         LYXERR(Debug::OUTFILE, s);
170         *this << ESCAPE_NONE << from_utf8("<!-- Output Error: " + s + " -->");
171         *this << xml::CR();
172 }
173
174
175 void XMLStream::writeError(docstring const &s)
176 {
177         LYXERR(Debug::OUTFILE, s);
178         *this << ESCAPE_NONE << from_utf8("<!-- Output Error: ");
179         *this << s;
180         *this << ESCAPE_NONE << from_utf8(" -->");
181         *this << xml::CR();
182 }
183
184
185 XMLStream::TagPtr XMLStream::getLastStackTag()
186 {
187         return tag_stack_.back();
188 }
189
190
191 bool XMLStream::closeFontTags()
192 {
193         if (isTagPending(xml::parsep_tag))
194                 // we haven't had any content
195                 return true;
196
197         // this may be a useless check, since we ought at least to have
198         // the parsep_tag. but it can't hurt too much to be careful.
199         if (tag_stack_.empty())
200                 return true;
201
202         // first, we close any open font tags we can close
203         TagPtr *curtag = &tag_stack_.back();
204         while ((*curtag)->asFontTag()) {
205                 if (**curtag != xml::parsep_tag)
206                         os_ << (*curtag)->writeEndTag();
207                 tag_stack_.pop_back();
208                 if (tag_stack_.empty())
209                         return true;
210                 curtag = &tag_stack_.back();
211         }
212
213         if (**curtag == xml::parsep_tag)
214                 return true;
215
216         // so we've hit a non-font tag.
217         writeError("Tags still open in closeFontTags(). Probably not a problem,\n"
218                                            "but you might want to check these tags:");
219         TagDeque::const_reverse_iterator it = tag_stack_.rbegin();
220         TagDeque::const_reverse_iterator const en = tag_stack_.rend();
221         for (; it != en; ++it) {
222                 if (**it == xml::parsep_tag)
223                         break;
224                 writeError((*it)->tag_);
225         }
226         return false;
227 }
228
229
230 void XMLStream::startDivision(bool keep_empty)
231 {
232         pending_tags_.push_back(makeTagPtr(xml::StartTag(xml::parsep_tag)));
233         if (keep_empty)
234                 clearTagDeque();
235 }
236
237
238 void XMLStream::endDivision()
239 {
240         if (isTagPending(xml::parsep_tag)) {
241                 // this case is normal. it just means we didn't have content,
242                 // so the parsep_tag never got moved onto the tag stack.
243                 while (!pending_tags_.empty()) {
244                         // clear all pending tags up to and including the parsep tag.
245                         // note that we work from the back, because we want to get rid
246                         // of everything that hasn't been used.
247                         TagPtr const cur_tag = pending_tags_.back();
248                         pending_tags_.pop_back();
249                         if (*cur_tag == xml::parsep_tag)
250                                 break;
251                 }
252
253 #ifdef  XHTML_DEBUG
254                 dumpTagStack("EndDivision");
255 #endif
256
257                 return;
258         }
259
260         if (!isTagOpen(xml::parsep_tag)) {
261                 writeError("No division separation tag found in endDivision().");
262                 return;
263         }
264
265         // this case is also normal, if the parsep tag is the last one
266         // on the stack. otherwise, it's an error.
267         while (!tag_stack_.empty()) {
268                 TagPtr const cur_tag = tag_stack_.back();
269                 tag_stack_.pop_back();
270                 if (*cur_tag == xml::parsep_tag)
271                         break;
272                 writeError("Tag `" + cur_tag->tag_ + "' still open at end of paragraph. Closing.");
273                 os_ << cur_tag->writeEndTag();
274         }
275
276 #ifdef  XHTML_DEBUG
277         dumpTagStack("EndDivision");
278 #endif
279 }
280
281
282 void XMLStream::clearTagDeque()
283 {
284         while (!pending_tags_.empty()) {
285                 TagPtr const & tag = pending_tags_.front();
286                 if (*tag != xml::parsep_tag)
287                         // tabs?
288                         os_ << tag->writeTag();
289                 tag_stack_.push_back(tag);
290                 pending_tags_.pop_front();
291         }
292 }
293
294
295 XMLStream &XMLStream::operator<<(docstring const &d)
296 {
297         is_last_tag_cr_ = false;
298         clearTagDeque();
299         os_ << xml::escapeString(d, escape_);
300         escape_ = ESCAPE_ALL;
301         return *this;
302 }
303
304
305 XMLStream &XMLStream::operator<<(const char *s)
306 {
307         is_last_tag_cr_ = false;
308         clearTagDeque();
309         docstring const d = from_ascii(s);
310         os_ << xml::escapeString(d, escape_);
311         escape_ = ESCAPE_ALL;
312         return *this;
313 }
314
315
316 XMLStream &XMLStream::operator<<(char_type c)
317 {
318         is_last_tag_cr_ = false;
319         clearTagDeque();
320         os_ << xml::escapeChar(c, escape_);
321         escape_ = ESCAPE_ALL;
322         return *this;
323 }
324
325
326 XMLStream &XMLStream::operator<<(char c)
327 {
328         is_last_tag_cr_ = false;
329         clearTagDeque();
330         os_ << xml::escapeChar(c, escape_);
331         escape_ = ESCAPE_ALL;
332         return *this;
333 }
334
335
336 XMLStream &XMLStream::operator<<(int i)
337 {
338         is_last_tag_cr_ = false;
339         clearTagDeque();
340         os_ << i;
341         escape_ = ESCAPE_ALL;
342         return *this;
343 }
344
345
346 XMLStream &XMLStream::operator<<(EscapeSettings e)
347 {
348         // Don't update is_last_tag_cr_ here, as this does not output anything.
349         escape_ = e;
350         return *this;
351 }
352
353
354 XMLStream &XMLStream::operator<<(xml::StartTag const &tag)
355 {
356         is_last_tag_cr_ = false;
357         if (tag.tag_.empty())
358                 return *this;
359         pending_tags_.push_back(makeTagPtr(tag));
360         if (tag.keepempty_)
361                 clearTagDeque();
362         return *this;
363 }
364
365
366 XMLStream &XMLStream::operator<<(xml::ParTag const &tag)
367 {
368         is_last_tag_cr_ = false;
369         if (tag.tag_.empty())
370                 return *this;
371         pending_tags_.push_back(makeTagPtr(tag));
372         return *this;
373 }
374
375
376 XMLStream &XMLStream::operator<<(xml::CompTag const &tag)
377 {
378         is_last_tag_cr_ = false;
379         if (tag.tag_.empty())
380                 return *this;
381         clearTagDeque();
382         os_ << tag.writeTag();
383         return *this;
384 }
385
386
387 XMLStream &XMLStream::operator<<(xml::FontTag const &tag)
388 {
389         is_last_tag_cr_ = false;
390         if (tag.tag_.empty())
391                 return *this;
392         pending_tags_.push_back(makeTagPtr(tag));
393         return *this;
394 }
395
396
397 XMLStream &XMLStream::operator<<(xml::CR const &)
398 {
399         is_last_tag_cr_ = true;
400         clearTagDeque();
401         os_ << from_ascii("\n");
402         return *this;
403 }
404
405
406 bool XMLStream::isTagOpen(xml::StartTag const &stag, int maxdepth) const
407 {
408         auto sit = tag_stack_.begin();
409         auto sen = tag_stack_.cend();
410         for (; sit != sen && maxdepth != 0; ++sit) {
411                 if (**sit == stag)
412                         return true;
413                 maxdepth -= 1;
414         }
415         return false;
416 }
417
418
419 bool XMLStream::isTagOpen(xml::EndTag const &etag, int maxdepth) const
420 {
421         auto sit = tag_stack_.begin();
422         auto sen = tag_stack_.cend();
423         for (; sit != sen && maxdepth != 0; ++sit) {
424                 if (etag == **sit)
425                         return true;
426                 maxdepth -= 1;
427         }
428         return false;
429 }
430
431
432 bool XMLStream::isTagPending(xml::StartTag const &stag, int maxdepth) const
433 {
434         auto sit = pending_tags_.begin();
435         auto sen = pending_tags_.cend();
436         for (; sit != sen && maxdepth != 0; ++sit) {
437                 if (**sit == stag)
438                         return true;
439                 maxdepth -= 1;
440         }
441         return false;
442 }
443
444
445 // this is complicated, because we want to make sure that
446 // everything is properly nested. the code ought to make
447 // sure of that, but we won't assert (yet) if we run into
448 // a problem. we'll just output error messages and try our
449 // best to make things work.
450 XMLStream &XMLStream::operator<<(xml::EndTag const &etag)
451 {
452         is_last_tag_cr_ = false;
453
454         if (etag.tag_.empty())
455                 return *this;
456
457         // if this tag is pending, we can simply discard it.
458         if (!pending_tags_.empty()) {
459                 if (etag == *pending_tags_.back()) {
460                         // we have <tag></tag>, so we discard it and remove it
461                         // from the pending_tags_.
462                         pending_tags_.pop_back();
463                         return *this;
464                 }
465
466                 // there is a pending tag that isn't the one we are trying
467                 // to close.
468
469                 // is this tag itself pending?
470                 // non-const iterators because we may call erase().
471                 TagDeque::iterator dit = pending_tags_.begin();
472                 TagDeque::iterator const den = pending_tags_.end();
473                 for (; dit != den; ++dit) {
474                         if (etag == **dit) {
475                                 // it was pending, so we just erase it
476                                 writeError("Tried to close pending tag `" + to_utf8(etag.tag_)
477                                                    + "' when other tags were pending. Last pending tag is `"
478                                                    + to_utf8(pending_tags_.back()->writeTag())
479                                                    + "'. Tag discarded.");
480                                 if (!pending_tags_.empty())
481                                         pending_tags_.erase(dit);
482                                 return *this;
483                         }
484                 }
485                 // so etag isn't itself pending. is it even open?
486                 if (!isTagOpen(etag)) {
487                         writeError("Tried to close `" + to_utf8(etag.tag_)
488                                            + "' when tag was not open. Tag discarded.");
489                         return *this;
490                 }
491                 // ok, so etag is open.
492                 // our strategy will be as below: we will do what we need to
493                 // do to close this tag.
494                 string estr = "Closing tag `" + to_utf8(etag.tag_)
495                                           + "' when other tags are pending. Discarded pending tags:\n";
496                 for (dit = pending_tags_.begin(); dit != den; ++dit)
497                         estr += to_utf8(xml::escapeString((*dit)->writeTag(), XMLStream::ESCAPE_ALL)) + "\n";
498                 writeError(estr);
499                 // clear the pending tags...
500                 pending_tags_.clear();
501                 // ...and then just fall through.
502         }
503
504         // make sure there are tags to be closed
505         if (tag_stack_.empty()) {
506                 writeError("Tried to close `" + etag.tag_
507                                    + "' when no tags were open!");
508                 return *this;
509         }
510
511         // is the tag we are closing the last one we opened?
512         if (etag == *tag_stack_.back()) {
513                 // output it...
514                 os_ << etag.writeEndTag();
515                 // ...and forget about it
516                 tag_stack_.pop_back();
517                 return *this;
518         }
519
520         // we are trying to close a tag other than the one last opened.
521         // let's first see if this particular tag is still open somehow.
522         if (!isTagOpen(etag)) {
523                 writeError("Tried to close `" + etag.tag_
524                                    + "' when tag was not open. Tag discarded.");
525                 return *this;
526         }
527
528         // so the tag was opened, but other tags have been opened since
529         // and not yet closed.
530         // if it's a font tag, though...
531         if (etag.asFontTag()) {
532                 // it won't be a problem if the other tags open since this one
533                 // are also font tags.
534                 TagDeque::const_reverse_iterator rit = tag_stack_.rbegin();
535                 TagDeque::const_reverse_iterator ren = tag_stack_.rend();
536                 for (; rit != ren; ++rit) {
537                         if (etag == **rit)
538                                 break;
539                         if (!(*rit)->asFontTag()) {
540                                 // we'll just leave it and, presumably, have to close it later.
541                                 writeError("Unable to close font tag `" + etag.tag_
542                                                    + "' due to open non-font tag `" + (*rit)->tag_ + "'.");
543                                 return *this;
544                         }
545                 }
546
547                 // so we have e.g.:
548                 //    <em>this is <strong>bold
549                 // and are being asked to closed em. we want:
550                 //    <em>this is <strong>bold</strong></em><strong>
551                 // first, we close the intervening tags...
552                 TagPtr *curtag = &tag_stack_.back();
553                 // ...remembering them in a stack.
554                 TagDeque fontstack;
555                 while (etag != **curtag) {
556                         os_ << (*curtag)->writeEndTag();
557                         fontstack.push_back(*curtag);
558                         tag_stack_.pop_back();
559                         curtag = &tag_stack_.back();
560                 }
561                 os_ << etag.writeEndTag();
562                 tag_stack_.pop_back();
563
564                 // ...and restore the other tags.
565                 rit = fontstack.rbegin();
566                 ren = fontstack.rend();
567                 for (; rit != ren; ++rit)
568                         pending_tags_.push_back(*rit);
569                 return *this;
570         }
571
572         // it wasn't a font tag.
573         // so other tags were opened before this one and not properly closed.
574         // so we'll close them, too. that may cause other issues later, but it
575         // at least guarantees proper nesting.
576         writeError("Closing tag `" + etag.tag_
577                            + "' when other tags are open, namely:");
578         TagPtr *curtag = &tag_stack_.back();
579         while (etag != **curtag) {
580                 writeError((*curtag)->tag_);
581                 if (**curtag != xml::parsep_tag)
582                         os_ << (*curtag)->writeEndTag();
583                 tag_stack_.pop_back();
584                 curtag = &tag_stack_.back();
585         }
586         // curtag is now the one we actually want.
587         os_ << (*curtag)->writeEndTag();
588         tag_stack_.pop_back();
589
590         return *this;
591 }
592
593
594 docstring xml::uniqueID(docstring const & label)
595 {
596         // thread-safe
597         static atomic_uint seed(1000);
598         return label + convert<docstring>(++seed);
599 }
600
601
602 bool xml::isNotOnlySpace(docstring const & str)
603 {
604         for (auto const & c: str) {
605                 if (c != ' ' && c != '\t' && c != '\n' && c != '\v' && c != '\f' && c != '\r')
606                 return true;
607         }
608         return false;
609 }
610
611
612 docstring xml::trimLeft(docstring const & str)
613 {
614         size_t i = 0;
615         for (auto const & c: str) {
616                 if (c != ' ' && c != '\t' && c != '\n' && c != '\v' && c != '\f' && c != '\r')
617                         return str.substr(i, docstring::npos);
618                 i++;
619         }
620         return str;
621 }
622
623
624 docstring xml::cleanID(docstring const & orig)
625 {
626         // The standard xml:id only allows letters, digits, '-' and '.' in a name.
627         // This routine replaces illegal characters by '-' or '.' and adds a number for uniqueness if need be.
628
629         // Use a cache of already mangled names: the alterations may merge several IDs as one. This ensures that the IDs
630         // are not mixed up in the document.
631         // This code could be improved: it uses Qt outside the GUI part. Any TLS implementation could do the trick.
632         typedef map<docstring, docstring> MangledMap;
633         static QThreadStorage<MangledMap> tMangledNames;
634         static QThreadStorage<int> tMangleID;
635
636         // If the name is already known, just return it.
637         MangledMap & mangledNames = tMangledNames.localData();
638         auto const known = mangledNames.find(orig);
639         if (known != mangledNames.end())
640                 return known->second;
641
642         // Start creating the mangled name by iterating over the characters.
643         docstring content;
644         auto it = orig.cbegin();
645         auto end = orig.cend();
646
647         // Make sure it starts with a letter.
648         if (!isAlphaASCII(*it))
649                 content += "x";
650
651         // Parse the ID character by character and change what needs to.
652         bool mangle = false; // Indicates whether the ID had to be changed, i.e. if ID no more ensured to be unique.
653         for (; it != end; ++it) {
654                 char_type c = *it;
655                 if (isAlphaASCII(c) || isDigitASCII(c) || c == '-' || c == '.' || c == '_') {
656                         content += c;
657                 } else if (c == ':' || c == ',' || c == ';' || c == '!') {
658                         mangle = true;
659                         content += ".";
660                 } else { // Other invalid characters, such as ' '.
661                         mangle = true;
662                         content += "-";
663                 }
664         }
665
666         // If there had to be a change, check if ID unicity is still guaranteed.
667         // This avoids having a clash if satisfying XML requirements for ID makes two IDs identical, like "a:b" and "a!b",
668         // as both of them would be transformed as "a.b". With this procedure, one will become "a.b" and the other "a.b-1".
669         if (mangle && mangledNames.find(content) != mangledNames.end()) {
670                 int & mangleID = tMangleID.localData();
671                 if (mangleID > 0)
672                         content += "-" + convert<docstring>(mangleID);
673                 mangleID += 1;
674         }
675
676         // Save the new ID to avoid recomputing it afterwards and to ensure stability over the document.
677         mangledNames[orig] = content;
678         return content;
679 }
680
681
682 void xml::openTag(odocstream & os, string const & name, string const & attribute)
683 {
684     // FIXME UNICODE
685     // This should be fixed in layout files later.
686     string param = subst(attribute, "<", "\"");
687     param = subst(param, ">", "\"");
688
689     // Note: we ignore the name if it is empty or if it is a comment "<!-- -->" or
690     // if the name is *dummy*.
691     // We ignore dummy because dummy is not a valid DocBook element and it is
692     // the internal name given to single paragraphs in the latex output.
693     // This allow us to simplify the code a lot and is a reasonable compromise.
694     if (!name.empty() && name != "!-- --" && name != "dummy") {
695         os << '<' << from_ascii(name);
696         if (!param.empty())
697             os << ' ' << from_ascii(param);
698         os << '>';
699     }
700 }
701
702
703 void xml::closeTag(odocstream & os, string const & name)
704 {
705     if (!name.empty() && name != "!-- --" && name != "dummy")
706         os << "</" << from_ascii(name) << '>';
707 }
708
709
710 void xml::openTag(Buffer const & buf, odocstream & os,
711                    OutputParams const & runparams, Paragraph const & par)
712 {
713     Layout const & style = par.layout();
714     string const & name = style.latexname();
715     string param = style.latexparam();
716     Counters & counters = buf.params().documentClass().counters();
717
718     string id = par.getID(buf, runparams);
719
720     string attribute;
721     if (!id.empty()) {
722         if (param.find('#') != string::npos) {
723             string::size_type pos = param.find("id=<");
724             string::size_type end = param.find(">");
725             if( pos != string::npos && end != string::npos)
726                 param.erase(pos, end-pos + 1);
727         }
728         attribute = id + ' ' + param;
729     } else {
730         if (param.find('#') != string::npos) {
731             // FIXME UNICODE
732             if (!style.counter.empty())
733                 // This uses InternalUpdate at the moment becuase xml output
734                 // does not do anything with tracked counters, and it would need
735                 // to track layouts if it did want to use them.
736                 counters.step(style.counter, InternalUpdate);
737             else
738                 counters.step(from_ascii(name), InternalUpdate);
739             int i = counters.value(from_ascii(name));
740             attribute = subst(param, "#", convert<string>(i));
741         } else {
742             attribute = param;
743         }
744     }
745     openTag(os, name, attribute);
746 }
747
748
749 void xml::closeTag(odocstream & os, Paragraph const & par)
750 {
751     Layout const & style = par.layout();
752     closeTag(os, style.latexname());
753 }
754
755
756 void openInlineTag(XMLStream & xs, const docstring & tag, const docstring & attr)
757 {
758         xs << xml::StartTag(tag, attr);
759 }
760
761
762 void closeInlineTag(XMLStream & xs, const docstring & tag)
763 {
764         xs << xml::EndTag(tag);
765 }
766
767
768 void openParTag(XMLStream & xs, const docstring & tag, const docstring & attr)
769 {
770         if (!xs.isLastTagCR())
771                 xs << xml::CR();
772         xs << xml::StartTag(tag, attr);
773 }
774
775
776 void closeParTag(XMLStream & xs, const docstring & tag)
777 {
778         xs << xml::EndTag(tag);
779         xs << xml::CR();
780 }
781
782
783 void openBlockTag(XMLStream & xs, const docstring & tag, const docstring & attr)
784 {
785         if (!xs.isLastTagCR())
786                 xs << xml::CR();
787         xs << xml::StartTag(tag, attr);
788         xs << xml::CR();
789 }
790
791
792 void closeBlockTag(XMLStream & xs, const docstring & tag)
793 {
794         if (!xs.isLastTagCR())
795                 xs << xml::CR();
796         xs << xml::EndTag(tag);
797         xs << xml::CR();
798 }
799
800
801 void xml::openTag(XMLStream & xs, const docstring & tag, const docstring & attr, const std::string & tagtype)
802 {
803         if (tag.empty() || tag == "NONE") // Common check to be performed elsewhere, if it was not here.
804                 return;
805
806         if (tag == "para" || tagtype == "paragraph") // Special case for <para>: always considered as a paragraph.
807                 openParTag(xs, tag, attr);
808         else if (tagtype == "block")
809                 openBlockTag(xs, tag, attr);
810         else if (tagtype == "inline")
811                 openInlineTag(xs, tag, attr);
812         else if (tagtype == "none")
813                 xs << xml::StartTag(tag, attr);
814         else
815                 xs.writeError("Unrecognised tag type '" + tagtype + "' for '" + to_utf8(tag) + " " + to_utf8(attr) + "'");
816 }
817
818
819 void xml::openTag(XMLStream & xs, const std::string & tag, const std::string & attr, const std::string & tagtype)
820 {
821         xml::openTag(xs, from_utf8(tag), from_utf8(attr), tagtype);
822 }
823
824
825 void xml::openTag(XMLStream & xs, const docstring & tag, const std::string & attr, const std::string & tagtype)
826 {
827         xml::openTag(xs, tag, from_utf8(attr), tagtype);
828 }
829
830
831 void xml::openTag(XMLStream & xs, const std::string & tag, const docstring & attr, const std::string & tagtype)
832 {
833         xml::openTag(xs, from_utf8(tag), attr, tagtype);
834 }
835
836
837 void xml::closeTag(XMLStream & xs, const docstring & tag, const std::string & tagtype)
838 {
839         if (tag.empty() || tag == "NONE" || tag == "IGNORE")
840                 return;
841
842         if (tag == "para" || tagtype == "paragraph") // Special case for <para>: always considered as a paragraph.
843                 closeParTag(xs, tag);
844         else if (tagtype == "block")
845                 closeBlockTag(xs, tag);
846         else if (tagtype == "inline")
847                 closeInlineTag(xs, tag);
848         else if (tagtype == "none")
849                 xs << xml::EndTag(tag);
850         else
851                 xs.writeError("Unrecognised tag type '" + tagtype + "' for '" + to_utf8(tag) + "'");
852 }
853
854
855 void xml::closeTag(XMLStream & xs, const std::string & tag, const std::string & tagtype)
856 {
857         xml::closeTag(xs, from_utf8(tag), tagtype);
858 }
859
860
861 void xml::compTag(XMLStream & xs, const docstring & tag, const docstring & attr, const std::string & tagtype)
862 {
863         if (tag.empty() || tag == from_ascii("NONE"))
864                 return;
865
866         // Special case for <para>: always considered as a paragraph.
867         if (tag == from_ascii("para") || tagtype == "paragraph" || tagtype == "block") {
868                 if (!xs.isLastTagCR())
869                         xs << xml::CR();
870                 xs << xml::CompTag(tag, attr);
871                 xs << xml::CR();
872         } else if (tagtype == "inline") {
873                 xs << xml::CompTag(tag, attr);
874         } else {
875                 xs.writeError("Unrecognised tag type '" + tagtype + "' for '" + to_utf8(tag) + "'");
876         }
877 }
878
879
880 void xml::compTag(XMLStream & xs, const std::string & tag, const std::string & attr, const std::string & tagtype)
881 {
882         xml::compTag(xs, from_utf8(tag), from_utf8(attr), tagtype);
883 }
884
885
886 void xml::compTag(XMLStream & xs, const docstring & tag, const std::string & attr, const std::string & tagtype)
887 {
888         xml::compTag(xs, tag, from_utf8(attr), tagtype);
889 }
890
891
892 void xml::compTag(XMLStream & xs, const std::string & tag, const docstring & attr, const std::string & tagtype)
893 {
894         xml::compTag(xs, from_utf8(tag), attr, tagtype);
895 }
896
897
898 } // namespace lyx