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