xref: /trunk/main/xmlreader/source/xmlreader.cxx (revision 9d37da743abb7db0a497688391f6e55a19f27c44)
1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 #include "sal/config.h"
25 
26 #include <climits>
27 #include <cstddef>
28 
29 #include "com/sun/star/container/NoSuchElementException.hpp"
30 #include "com/sun/star/uno/Reference.hxx"
31 #include "com/sun/star/uno/RuntimeException.hpp"
32 #include "com/sun/star/uno/XInterface.hpp"
33 #include "osl/diagnose.h"
34 #include "osl/file.h"
35 #include "rtl/string.h"
36 #include "rtl/ustring.h"
37 #include "rtl/ustring.hxx"
38 #include "sal/types.h"
39 #include "xmlreader/pad.hxx"
40 #include "xmlreader/span.hxx"
41 #include "xmlreader/xmlreader.hxx"
42 
43 namespace xmlreader {
44 
45 namespace {
46 
47 namespace css = com::sun::star;
48 
49 bool isSpace(char c) {
50     switch (c) {
51     case '\x09':
52     case '\x0A':
53     case '\x0D':
54     case ' ':
55         return true;
56     default:
57         return false;
58     }
59 }
60 
61 }
62 
63 XmlReader::XmlReader(rtl::OUString const & fileUrl):
64     fileUrl_(fileUrl)
65 {
66     switch (osl_openFile(fileUrl_.pData, &fileHandle_, osl_File_OpenFlag_Read))
67     {
68     case osl_File_E_None:
69         break;
70     case osl_File_E_NOENT:
71         throw css::container::NoSuchElementException(
72             fileUrl_, css::uno::Reference< css::uno::XInterface >());
73     default:
74         throw css::uno::RuntimeException(
75             (rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("cannot open ")) +
76              fileUrl_),
77             css::uno::Reference< css::uno::XInterface >());
78     }
79     oslFileError e = osl_getFileSize(fileHandle_, &fileSize_);
80     if (e == osl_File_E_None) {
81         e = osl_mapFile(
82             fileHandle_, &fileAddress_, fileSize_, 0,
83             osl_File_MapFlag_WillNeed);
84     }
85     if (e != osl_File_E_None) {
86         e = osl_closeFile(fileHandle_);
87         if (e != osl_File_E_None) {
88             OSL_TRACE("osl_closeFile failed with %ld", static_cast< long >(e));
89         }
90         throw css::uno::RuntimeException(
91             (rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("cannot mmap ")) +
92              fileUrl_),
93             css::uno::Reference< css::uno::XInterface >());
94     }
95     namespaceIris_.push_back(
96         Span(
97             RTL_CONSTASCII_STRINGPARAM(
98                 "http://www.w3.org/XML/1998/namespace")));
99     namespaces_.push_back(
100         NamespaceData(Span(RTL_CONSTASCII_STRINGPARAM("xml")), NAMESPACE_XML));
101     pos_ = static_cast< char * >(fileAddress_);
102     end_ = pos_ + fileSize_;
103     state_ = STATE_CONTENT;
104 }
105 
106 XmlReader::~XmlReader() {
107     oslFileError e = osl_unmapFile(fileAddress_, fileSize_);
108     if (e != osl_File_E_None) {
109         OSL_TRACE("osl_unmapFile failed with %ld", static_cast< long >(e));
110     }
111     e = osl_closeFile(fileHandle_);
112     if (e != osl_File_E_None) {
113         OSL_TRACE("osl_closeFile failed with %ld", static_cast< long >(e));
114     }
115 }
116 
117 int XmlReader::registerNamespaceIri(Span const & iri) {
118     int id = toNamespaceId(namespaceIris_.size());
119     namespaceIris_.push_back(iri);
120     if (iri.equals(
121             Span(
122                 RTL_CONSTASCII_STRINGPARAM(
123                     "http://www.w3.org/2001/XMLSchema-instance"))))
124     {
125         // Old user layer .xcu files used the xsi namespace prefix without
126         // declaring a corresponding namespace binding, see issue 77174; reading
127         // those files during migration would fail without this hack that can be
128         // removed once migration is no longer relevant (see
129         // configmgr::Components::parseModificationLayer):
130         namespaces_.push_back(
131             NamespaceData(Span(RTL_CONSTASCII_STRINGPARAM("xsi")), id));
132     }
133     return id;
134 }
135 
136 XmlReader::Result XmlReader::nextItem(Text reportText, Span * data, int * nsId)
137 {
138     switch (state_) {
139     case STATE_CONTENT:
140         switch (reportText) {
141         case TEXT_NONE:
142             return handleSkippedText(data, nsId);
143         case TEXT_RAW:
144             return handleRawText(data);
145         case TEXT_NORMALIZED:
146             return handleNormalizedText(data);
147         }
148     case STATE_START_TAG:
149         return handleStartTag(nsId, data);
150     case STATE_END_TAG:
151         return handleEndTag();
152     case STATE_EMPTY_ELEMENT_TAG:
153         handleElementEnd();
154         return RESULT_END;
155     default: // STATE_DONE
156         return RESULT_DONE;
157     }
158 }
159 
160 bool XmlReader::nextAttribute(int * nsId, Span * localName) {
161     OSL_ASSERT(nsId != 0 && localName != 0);
162     if (firstAttribute_) {
163         currentAttribute_ = attributes_.begin();
164         firstAttribute_ = false;
165     } else {
166         ++currentAttribute_;
167     }
168     if (currentAttribute_ == attributes_.end()) {
169         return false;
170     }
171     if (currentAttribute_->nameColon == 0) {
172         *nsId = NAMESPACE_NONE;
173         *localName = Span(
174             currentAttribute_->nameBegin,
175             currentAttribute_->nameEnd - currentAttribute_->nameBegin);
176     } else {
177         *nsId = getNamespaceId(
178             Span(
179                 currentAttribute_->nameBegin,
180                 currentAttribute_->nameColon - currentAttribute_->nameBegin));
181         *localName = Span(
182             currentAttribute_->nameColon + 1,
183             currentAttribute_->nameEnd - (currentAttribute_->nameColon + 1));
184     }
185     return true;
186 }
187 
188 Span XmlReader::getAttributeValue(bool fullyNormalize) {
189     return handleAttributeValue(
190         currentAttribute_->valueBegin, currentAttribute_->valueEnd,
191         fullyNormalize);
192 }
193 
194 int XmlReader::getNamespaceId(Span const & prefix) const {
195     for (NamespaceList::const_reverse_iterator i(namespaces_.rbegin());
196          i != namespaces_.rend(); ++i)
197     {
198         if (prefix.equals(i->prefix)) {
199             return i->nsId;
200         }
201     }
202     return NAMESPACE_UNKNOWN;
203 }
204 
205 rtl::OUString XmlReader::getUrl() const {
206     return fileUrl_;
207 }
208 
209 void XmlReader::normalizeLineEnds(Span const & text) {
210     char const * p = text.begin;
211     sal_Int32 n = text.length;
212     for (;;) {
213         sal_Int32 i = rtl_str_indexOfChar_WithLength(p, n, '\x0D');
214         if (i < 0) {
215             break;
216         }
217         pad_.add(p, i);
218         p += i + 1;
219         n -= i + 1;
220         if (n == 0 || *p != '\x0A') {
221             pad_.add(RTL_CONSTASCII_STRINGPARAM("\x0A"));
222         }
223     }
224     pad_.add(p, n);
225 }
226 
227 void XmlReader::skipSpace() {
228     while (isSpace(peek())) {
229         ++pos_;
230     }
231 }
232 
233 bool XmlReader::skipComment() {
234     if (rtl_str_shortenedCompare_WithLength(
235             pos_, end_ - pos_, RTL_CONSTASCII_STRINGPARAM("--"),
236             RTL_CONSTASCII_LENGTH("--")) !=
237         0)
238     {
239         return false;
240     }
241     pos_ += RTL_CONSTASCII_LENGTH("--");
242     sal_Int32 i = rtl_str_indexOfStr_WithLength(
243         pos_, end_ - pos_, RTL_CONSTASCII_STRINGPARAM("--"));
244     if (i < 0) {
245         throw css::uno::RuntimeException(
246             (rtl::OUString(
247                 RTL_CONSTASCII_USTRINGPARAM(
248                     "premature end (within comment) of ")) +
249              fileUrl_),
250             css::uno::Reference< css::uno::XInterface >());
251     }
252     pos_ += i + RTL_CONSTASCII_LENGTH("--");
253     if (read() != '>') {
254         throw css::uno::RuntimeException(
255             (rtl::OUString(
256                 RTL_CONSTASCII_USTRINGPARAM(
257                     "illegal \"--\" within comment in ")) +
258              fileUrl_),
259             css::uno::Reference< css::uno::XInterface >());
260     }
261     return true;
262 }
263 
264 void XmlReader::skipProcessingInstruction() {
265     sal_Int32 i = rtl_str_indexOfStr_WithLength(
266         pos_, end_ - pos_, RTL_CONSTASCII_STRINGPARAM("?>"));
267     if (i < 0) {
268         throw css::uno::RuntimeException(
269             (rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("bad '<?' in ")) +
270              fileUrl_),
271             css::uno::Reference< css::uno::XInterface >());
272     }
273     pos_ += i + RTL_CONSTASCII_LENGTH("?>");
274 }
275 
276 void XmlReader::skipDocumentTypeDeclaration() {
277     // Neither is it checked that the doctypedecl is at the correct position in
278     // the document, nor that it is well-formed:
279     for (;;) {
280         char c = read();
281         switch (c) {
282         case '\0': // i.e., EOF
283             throw css::uno::RuntimeException(
284                 (rtl::OUString(
285                     RTL_CONSTASCII_USTRINGPARAM(
286                         "premature end (within DTD) of ")) +
287                  fileUrl_),
288                 css::uno::Reference< css::uno::XInterface >());
289         case '"':
290         case '\'':
291             {
292                 sal_Int32 i = rtl_str_indexOfChar_WithLength(
293                     pos_, end_ - pos_, c);
294                 if (i < 0) {
295                     throw css::uno::RuntimeException(
296                         (rtl::OUString(
297                             RTL_CONSTASCII_USTRINGPARAM(
298                                 "premature end (within DTD) of ")) +
299                          fileUrl_),
300                         css::uno::Reference< css::uno::XInterface >());
301                 }
302                 pos_ += i + 1;
303             }
304             break;
305         case '>':
306             return;
307         case '[':
308             for (;;) {
309                 c = read();
310                 switch (c) {
311                 case '\0': // i.e., EOF
312                     throw css::uno::RuntimeException(
313                         (rtl::OUString(
314                             RTL_CONSTASCII_USTRINGPARAM(
315                                 "premature end (within DTD) of ")) +
316                          fileUrl_),
317                         css::uno::Reference< css::uno::XInterface >());
318                 case '"':
319                 case '\'':
320                     {
321                         sal_Int32 i = rtl_str_indexOfChar_WithLength(
322                             pos_, end_ - pos_, c);
323                         if (i < 0) {
324                             throw css::uno::RuntimeException(
325                             (rtl::OUString(
326                                 RTL_CONSTASCII_USTRINGPARAM(
327                                     "premature end (within DTD) of ")) +
328                              fileUrl_),
329                             css::uno::Reference< css::uno::XInterface >());
330                         }
331                         pos_ += i + 1;
332                     }
333                     break;
334                 case '<':
335                     switch (read()) {
336                     case '\0': // i.e., EOF
337                         throw css::uno::RuntimeException(
338                             (rtl::OUString(
339                                 RTL_CONSTASCII_USTRINGPARAM(
340                                     "premature end (within DTD) of ")) +
341                              fileUrl_),
342                             css::uno::Reference< css::uno::XInterface >());
343                     case '!':
344                         skipComment();
345                         break;
346                     case '?':
347                         skipProcessingInstruction();
348                         break;
349                     default:
350                         break;
351                     }
352                     break;
353                 case ']':
354                     skipSpace();
355                     if (read() != '>') {
356                         throw css::uno::RuntimeException(
357                             (rtl::OUString(
358                                 RTL_CONSTASCII_USTRINGPARAM(
359                                     "missing \">\" of DTD in ")) +
360                              fileUrl_),
361                             css::uno::Reference< css::uno::XInterface >());
362                     }
363                     return;
364                 default:
365                     break;
366                 }
367             }
368         default:
369             break;
370         }
371     }
372 }
373 
374 Span XmlReader::scanCdataSection() {
375     if (rtl_str_shortenedCompare_WithLength(
376             pos_, end_ - pos_, RTL_CONSTASCII_STRINGPARAM("[CDATA["),
377             RTL_CONSTASCII_LENGTH("[CDATA[")) !=
378         0)
379     {
380         return Span();
381     }
382     pos_ += RTL_CONSTASCII_LENGTH("[CDATA[");
383     char const * begin = pos_;
384     sal_Int32 i = rtl_str_indexOfStr_WithLength(
385         pos_, end_ - pos_, RTL_CONSTASCII_STRINGPARAM("]]>"));
386     if (i < 0) {
387         throw css::uno::RuntimeException(
388             (rtl::OUString(
389                 RTL_CONSTASCII_USTRINGPARAM(
390                     "premature end (within CDATA section) of ")) +
391              fileUrl_),
392             css::uno::Reference< css::uno::XInterface >());
393     }
394     pos_ += i + RTL_CONSTASCII_LENGTH("]]>");
395     return Span(begin, i);
396 }
397 
398 bool XmlReader::scanName(char const ** nameColon) {
399     OSL_ASSERT(nameColon != 0 && *nameColon == 0);
400     for (char const * begin = pos_;; ++pos_) {
401         switch (peek()) {
402         case '\0': // i.e., EOF
403         case '\x09':
404         case '\x0A':
405         case '\x0D':
406         case ' ':
407         case '/':
408         case '=':
409         case '>':
410             return pos_ != begin;
411         case ':':
412             *nameColon = pos_;
413             break;
414         default:
415             break;
416         }
417     }
418 }
419 
420 int XmlReader::scanNamespaceIri(char const * begin, char const * end) {
421     OSL_ASSERT(begin != 0 && begin <= end);
422     Span iri(handleAttributeValue(begin, end, false));
423     for (NamespaceIris::size_type i = 0; i < namespaceIris_.size(); ++i) {
424         if (namespaceIris_[i].equals(iri)) {
425             return toNamespaceId(i);
426         }
427     }
428     return XmlReader::NAMESPACE_UNKNOWN;
429 }
430 
431 char const * XmlReader::handleReference(char const * position, char const * end)
432 {
433     OSL_ASSERT(position != 0 && *position == '&' && position < end);
434     ++position;
435     if (*position == '#') {
436         ++position;
437         sal_Int32 val = 0;
438         char const * p;
439         if (*position == 'x') {
440             ++position;
441             p = position;
442             for (;; ++position) {
443                 char c = *position;
444                 if (c >= '0' && c <= '9') {
445                     val = 16 * val + (c - '0');
446                 } else if (c >= 'A' && c <= 'F') {
447                     val = 16 * val + (c - 'A') + 10;
448                 } else if (c >= 'a' && c <= 'f') {
449                     val = 16 * val + (c - 'a') + 10;
450                 } else {
451                     break;
452                 }
453                 if (val > 0x10FFFF) { // avoid overflow
454                     throw css::uno::RuntimeException(
455                         (rtl::OUString(
456                             RTL_CONSTASCII_USTRINGPARAM(
457                                 "'&#x...' too large in ")) +
458                          fileUrl_),
459                         css::uno::Reference< css::uno::XInterface >());
460                 }
461             }
462         } else {
463             p = position;
464             for (;; ++position) {
465                 char c = *position;
466                 if (c >= '0' && c <= '9') {
467                     val = 10 * val + (c - '0');
468                 } else {
469                     break;
470                 }
471                 if (val > 0x10FFFF) { // avoid overflow
472                     throw css::uno::RuntimeException(
473                         (rtl::OUString(
474                             RTL_CONSTASCII_USTRINGPARAM(
475                                 "'&#...' too large in ")) +
476                          fileUrl_),
477                         css::uno::Reference< css::uno::XInterface >());
478                 }
479             }
480         }
481         if (position == p || *position++ != ';') {
482             throw css::uno::RuntimeException(
483                 (rtl::OUString(
484                     RTL_CONSTASCII_USTRINGPARAM("'&#...' missing ';' in ")) +
485                  fileUrl_),
486                 css::uno::Reference< css::uno::XInterface >());
487         }
488         OSL_ASSERT(val >= 0 && val <= 0x10FFFF);
489         if ((val < 0x20 && val != 0x9 && val != 0xA && val != 0xD) ||
490             (val >= 0xD800 && val <= 0xDFFF) || val == 0xFFFE || val == 0xFFFF)
491         {
492             throw css::uno::RuntimeException(
493                 (rtl::OUString(
494                     RTL_CONSTASCII_USTRINGPARAM(
495                         "character reference denoting invalid character in ")) +
496                  fileUrl_),
497                 css::uno::Reference< css::uno::XInterface >());
498         }
499         char buf[4];
500         sal_Int32 len;
501         if (val < 0x80) {
502             buf[0] = static_cast< char >(val);
503             len = 1;
504         } else if (val < 0x800) {
505             buf[0] = static_cast< char >((val >> 6) | 0xC0);
506             buf[1] = static_cast< char >((val & 0x3F) | 0x80);
507             len = 2;
508         } else if (val < 0x10000) {
509             buf[0] = static_cast< char >((val >> 12) | 0xE0);
510             buf[1] = static_cast< char >(((val >> 6) & 0x3F) | 0x80);
511             buf[2] = static_cast< char >((val & 0x3F) | 0x80);
512             len = 3;
513         } else {
514             buf[0] = static_cast< char >((val >> 18) | 0xF0);
515             buf[1] = static_cast< char >(((val >> 12) & 0x3F) | 0x80);
516             buf[2] = static_cast< char >(((val >> 6) & 0x3F) | 0x80);
517             buf[3] = static_cast< char >((val & 0x3F) | 0x80);
518             len = 4;
519         }
520         pad_.addEphemeral(buf, len);
521         return position;
522     } else {
523         struct EntityRef {
524             char const * inBegin;
525             sal_Int32 inLength;
526             char const * outBegin;
527             sal_Int32 outLength;
528         };
529         static EntityRef const refs[] = {
530             { RTL_CONSTASCII_STRINGPARAM("amp;"),
531               RTL_CONSTASCII_STRINGPARAM("&") },
532             { RTL_CONSTASCII_STRINGPARAM("lt;"),
533               RTL_CONSTASCII_STRINGPARAM("<") },
534             { RTL_CONSTASCII_STRINGPARAM("gt;"),
535               RTL_CONSTASCII_STRINGPARAM(">") },
536             { RTL_CONSTASCII_STRINGPARAM("apos;"),
537               RTL_CONSTASCII_STRINGPARAM("'") },
538             { RTL_CONSTASCII_STRINGPARAM("quot;"),
539               RTL_CONSTASCII_STRINGPARAM("\"") } };
540         for (std::size_t i = 0; i < sizeof refs / sizeof refs[0]; ++i) {
541             if (rtl_str_shortenedCompare_WithLength(
542                     position, end - position, refs[i].inBegin, refs[i].inLength,
543                     refs[i].inLength) ==
544                 0)
545             {
546                 position += refs[i].inLength;
547                 pad_.add(refs[i].outBegin, refs[i].outLength);
548                 return position;
549             }
550         }
551         throw css::uno::RuntimeException(
552             (rtl::OUString(
553                 RTL_CONSTASCII_USTRINGPARAM("unknown entity reference in ")) +
554              fileUrl_),
555             css::uno::Reference< css::uno::XInterface >());
556     }
557 }
558 
559 Span XmlReader::handleAttributeValue(
560     char const * begin, char const * end, bool fullyNormalize)
561 {
562     pad_.clear();
563     if (fullyNormalize) {
564         while (begin != end && isSpace(*begin)) {
565             ++begin;
566         }
567         while (end != begin && isSpace(end[-1])) {
568             --end;
569         }
570         char const * p = begin;
571         enum Space { SPACE_NONE, SPACE_SPAN, SPACE_BREAK };
572             // a single true space character can go into the current span,
573             // everything else breaks the span
574         Space space = SPACE_NONE;
575         while (p != end) {
576             switch (*p) {
577             case '\x09':
578             case '\x0A':
579             case '\x0D':
580                 switch (space) {
581                 case SPACE_NONE:
582                     pad_.add(begin, p - begin);
583                     pad_.add(RTL_CONSTASCII_STRINGPARAM(" "));
584                     space = SPACE_BREAK;
585                     break;
586                 case SPACE_SPAN:
587                     pad_.add(begin, p - begin);
588                     space = SPACE_BREAK;
589                     break;
590                 case SPACE_BREAK:
591                     break;
592                 }
593                 begin = ++p;
594                 break;
595             case ' ':
596                 switch (space) {
597                 case SPACE_NONE:
598                     ++p;
599                     space = SPACE_SPAN;
600                     break;
601                 case SPACE_SPAN:
602                     pad_.add(begin, p - begin);
603                     begin = ++p;
604                     space = SPACE_BREAK;
605                     break;
606                 case SPACE_BREAK:
607                     begin = ++p;
608                     break;
609                 }
610                 break;
611             case '&':
612                 pad_.add(begin, p - begin);
613                 p = handleReference(p, end);
614                 begin = p;
615                 space = SPACE_NONE;
616                 break;
617             default:
618                 ++p;
619                 space = SPACE_NONE;
620                 break;
621             }
622         }
623         pad_.add(begin, p - begin);
624     } else {
625         char const * p = begin;
626         while (p != end) {
627             switch (*p) {
628             case '\x09':
629             case '\x0A':
630                 pad_.add(begin, p - begin);
631                 begin = ++p;
632                 pad_.add(RTL_CONSTASCII_STRINGPARAM(" "));
633                 break;
634             case '\x0D':
635                 pad_.add(begin, p - begin);
636                 ++p;
637                 if (peek() == '\x0A') {
638                     ++p;
639                 }
640                 begin = p;
641                 pad_.add(RTL_CONSTASCII_STRINGPARAM(" "));
642                 break;
643             case '&':
644                 pad_.add(begin, p - begin);
645                 p = handleReference(p, end);
646                 begin = p;
647                 break;
648             default:
649                 ++p;
650                 break;
651             }
652         }
653         pad_.add(begin, p - begin);
654     }
655     return pad_.get();
656 }
657 
658 XmlReader::Result XmlReader::handleStartTag(int * nsId, Span * localName) {
659     OSL_ASSERT(nsId != 0 && localName);
660     char const * nameBegin = pos_;
661     char const * nameColon = 0;
662     if (!scanName(&nameColon)) {
663         throw css::uno::RuntimeException(
664             (rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("bad tag name in ")) +
665              fileUrl_),
666             css::uno::Reference< css::uno::XInterface >());
667     }
668     char const * nameEnd = pos_;
669     NamespaceList::size_type inheritedNamespaces = namespaces_.size();
670     bool hasDefaultNs = false;
671     int defaultNsId = NAMESPACE_NONE;
672     attributes_.clear();
673     for (;;) {
674         char const * p = pos_;
675         skipSpace();
676         if (peek() == '/' || peek() == '>') {
677             break;
678         }
679         if (pos_ == p) {
680             throw css::uno::RuntimeException(
681                 (rtl::OUString(
682                     RTL_CONSTASCII_USTRINGPARAM(
683                         "missing whitespace before attribute in ")) +
684                  fileUrl_),
685                 css::uno::Reference< css::uno::XInterface >());
686         }
687         char const * attrNameBegin = pos_;
688         char const * attrNameColon = 0;
689         if (!scanName(&attrNameColon)) {
690             throw css::uno::RuntimeException(
691                 (rtl::OUString(
692                     RTL_CONSTASCII_USTRINGPARAM("bad attribute name in ")) +
693                  fileUrl_),
694                 css::uno::Reference< css::uno::XInterface >());
695         }
696         char const * attrNameEnd = pos_;
697         skipSpace();
698         if (read() != '=') {
699             throw css::uno::RuntimeException(
700                 (rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("missing '=' in ")) +
701                  fileUrl_),
702                 css::uno::Reference< css::uno::XInterface >());
703         }
704         skipSpace();
705         char del = read();
706         if (del != '\'' && del != '"') {
707             throw css::uno::RuntimeException(
708                 (rtl::OUString(
709                     RTL_CONSTASCII_USTRINGPARAM("bad attribute value in ")) +
710                  fileUrl_),
711                 css::uno::Reference< css::uno::XInterface >());
712         }
713         char const * valueBegin = pos_;
714         sal_Int32 i = rtl_str_indexOfChar_WithLength(pos_, end_ - pos_, del);
715         if (i < 0) {
716             throw css::uno::RuntimeException(
717                 (rtl::OUString(
718                     RTL_CONSTASCII_USTRINGPARAM(
719                         "unterminated attribute value in ")) +
720                  fileUrl_),
721                 css::uno::Reference< css::uno::XInterface >());
722         }
723         char const * valueEnd = pos_ + i;
724         pos_ += i + 1;
725         if (attrNameColon == 0 &&
726             Span(attrNameBegin, attrNameEnd - attrNameBegin).equals(
727                 RTL_CONSTASCII_STRINGPARAM("xmlns")))
728         {
729             hasDefaultNs = true;
730             defaultNsId = scanNamespaceIri(valueBegin, valueEnd);
731         } else if (attrNameColon != 0 &&
732                    Span(attrNameBegin, attrNameColon - attrNameBegin).equals(
733                        RTL_CONSTASCII_STRINGPARAM("xmlns")))
734         {
735             namespaces_.push_back(
736                 NamespaceData(
737                     Span(attrNameColon + 1, attrNameEnd - (attrNameColon + 1)),
738                     scanNamespaceIri(valueBegin, valueEnd)));
739         } else {
740             attributes_.push_back(
741                 AttributeData(
742                     attrNameBegin, attrNameEnd, attrNameColon, valueBegin,
743                     valueEnd));
744         }
745     }
746     if (!hasDefaultNs && !elements_.empty()) {
747         defaultNsId = elements_.top().defaultNamespaceId;
748     }
749     firstAttribute_ = true;
750     if (peek() == '/') {
751         state_ = STATE_EMPTY_ELEMENT_TAG;
752         ++pos_;
753     } else {
754         state_ = STATE_CONTENT;
755     }
756     if (peek() != '>') {
757         throw css::uno::RuntimeException(
758             (rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("missing '>' in ")) +
759              fileUrl_),
760             css::uno::Reference< css::uno::XInterface >());
761     }
762     ++pos_;
763     elements_.push(
764         ElementData(
765             Span(nameBegin, nameEnd - nameBegin), inheritedNamespaces,
766             defaultNsId));
767     if (nameColon == 0) {
768         *nsId = defaultNsId;
769         *localName = Span(nameBegin, nameEnd - nameBegin);
770     } else {
771         *nsId = getNamespaceId(Span(nameBegin, nameColon - nameBegin));
772         *localName = Span(nameColon + 1, nameEnd - (nameColon + 1));
773     }
774     return RESULT_BEGIN;
775 }
776 
777 XmlReader::Result XmlReader::handleEndTag() {
778     if (elements_.empty()) {
779         throw css::uno::RuntimeException(
780             (rtl::OUString(
781                 RTL_CONSTASCII_USTRINGPARAM("spurious end tag in ")) +
782              fileUrl_),
783             css::uno::Reference< css::uno::XInterface >());
784     }
785     char const * nameBegin = pos_;
786     char const * nameColon = 0;
787     if (!scanName(&nameColon) ||
788         !elements_.top().name.equals(nameBegin, pos_ - nameBegin))
789     {
790         throw css::uno::RuntimeException(
791             (rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("tag mismatch in ")) +
792              fileUrl_),
793             css::uno::Reference< css::uno::XInterface >());
794     }
795     handleElementEnd();
796     skipSpace();
797     if (peek() != '>') {
798         throw css::uno::RuntimeException(
799             (rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("missing '>' in ")) +
800              fileUrl_),
801             css::uno::Reference< css::uno::XInterface >());
802     }
803     ++pos_;
804     return RESULT_END;
805 }
806 
807 void XmlReader::handleElementEnd() {
808     OSL_ASSERT(!elements_.empty());
809     namespaces_.resize(elements_.top().inheritedNamespaces);
810     elements_.pop();
811     state_ = elements_.empty() ? STATE_DONE : STATE_CONTENT;
812 }
813 
814 XmlReader::Result XmlReader::handleSkippedText(Span * data, int * nsId) {
815     for (;;) {
816         sal_Int32 i = rtl_str_indexOfChar_WithLength(pos_, end_ - pos_, '<');
817         if (i < 0) {
818             throw css::uno::RuntimeException(
819                 (rtl::OUString(
820                     RTL_CONSTASCII_USTRINGPARAM("premature end of ")) +
821                  fileUrl_),
822                 css::uno::Reference< css::uno::XInterface >());
823         }
824         pos_ += i + 1;
825         switch (peek()) {
826         case '!':
827             ++pos_;
828             if (!skipComment() && !scanCdataSection().is()) {
829                 skipDocumentTypeDeclaration();
830             }
831             break;
832         case '/':
833             ++pos_;
834             return handleEndTag();
835         case '?':
836             ++pos_;
837             skipProcessingInstruction();
838             break;
839         default:
840             return handleStartTag(nsId, data);
841         }
842     }
843 }
844 
845 XmlReader::Result XmlReader::handleRawText(Span * text) {
846     pad_.clear();
847     for (char const * begin = pos_;;) {
848         switch (peek()) {
849         case '\0': // i.e., EOF
850             throw css::uno::RuntimeException(
851                 (rtl::OUString(
852                     RTL_CONSTASCII_USTRINGPARAM("premature end of ")) +
853                  fileUrl_),
854                 css::uno::Reference< css::uno::XInterface >());
855         case '\x0D':
856             pad_.add(begin, pos_ - begin);
857             ++pos_;
858             if (peek() != '\x0A') {
859                 pad_.add(RTL_CONSTASCII_STRINGPARAM("\x0A"));
860             }
861             begin = pos_;
862             break;
863         case '&':
864             pad_.add(begin, pos_ - begin);
865             pos_ = handleReference(pos_, end_);
866             begin = pos_;
867             break;
868         case '<':
869             pad_.add(begin, pos_ - begin);
870             ++pos_;
871             switch (peek()) {
872             case '!':
873                 ++pos_;
874                 if (!skipComment()) {
875                     Span cdata(scanCdataSection());
876                     if (cdata.is()) {
877                         normalizeLineEnds(cdata);
878                     } else {
879                         skipDocumentTypeDeclaration();
880                     }
881                 }
882                 begin = pos_;
883                 break;
884             case '/':
885                 *text = pad_.get();
886                 ++pos_;
887                 state_ = STATE_END_TAG;
888                 return RESULT_TEXT;
889             case '?':
890                 ++pos_;
891                 skipProcessingInstruction();
892                 begin = pos_;
893                 break;
894             default:
895                 *text = pad_.get();
896                 state_ = STATE_START_TAG;
897                 return RESULT_TEXT;
898             }
899             break;
900         default:
901             ++pos_;
902             break;
903         }
904     }
905 }
906 
907 XmlReader::Result XmlReader::handleNormalizedText(Span * text) {
908     pad_.clear();
909     char const * flowBegin = pos_;
910     char const * flowEnd = pos_;
911     enum Space { SPACE_START, SPACE_NONE, SPACE_SPAN, SPACE_BREAK };
912         // a single true space character can go into the current flow,
913         // everything else breaks the flow
914     Space space = SPACE_START;
915     for (;;) {
916         switch (peek()) {
917         case '\0': // i.e., EOF
918             throw css::uno::RuntimeException(
919                 (rtl::OUString(
920                     RTL_CONSTASCII_USTRINGPARAM("premature end of ")) +
921                  fileUrl_),
922                 css::uno::Reference< css::uno::XInterface >());
923         case '\x09':
924         case '\x0A':
925         case '\x0D':
926             switch (space) {
927             case SPACE_START:
928             case SPACE_BREAK:
929                 break;
930             case SPACE_NONE:
931             case SPACE_SPAN:
932                 space = SPACE_BREAK;
933                 break;
934             }
935             ++pos_;
936             break;
937         case ' ':
938             switch (space) {
939             case SPACE_START:
940             case SPACE_BREAK:
941                 break;
942             case SPACE_NONE:
943                 space = SPACE_SPAN;
944                 break;
945             case SPACE_SPAN:
946                 space = SPACE_BREAK;
947                 break;
948             }
949             ++pos_;
950             break;
951         case '&':
952             switch (space) {
953             case SPACE_START:
954                 break;
955             case SPACE_NONE:
956             case SPACE_SPAN:
957                 pad_.add(flowBegin, pos_ - flowBegin);
958                 break;
959             case SPACE_BREAK:
960                 pad_.add(flowBegin, flowEnd - flowBegin);
961                 pad_.add(RTL_CONSTASCII_STRINGPARAM(" "));
962                 break;
963             }
964             pos_ = handleReference(pos_, end_);
965             flowBegin = pos_;
966             flowEnd = pos_;
967             space = SPACE_NONE;
968             break;
969         case '<':
970             ++pos_;
971             switch (peek()) {
972             case '!':
973                 ++pos_;
974                 if (skipComment()) {
975                     space = SPACE_BREAK;
976                 } else {
977                     Span cdata(scanCdataSection());
978                     if (cdata.is()) {
979                         // CDATA is not normalized (similar to character
980                         // references; it keeps the code simple), but it might
981                         // arguably be better to normalize it:
982                         switch (space) {
983                         case SPACE_START:
984                             break;
985                         case SPACE_NONE:
986                         case SPACE_SPAN:
987                             pad_.add(flowBegin, pos_ - flowBegin);
988                             break;
989                         case SPACE_BREAK:
990                             pad_.add(flowBegin, flowEnd - flowBegin);
991                             pad_.add(RTL_CONSTASCII_STRINGPARAM(" "));
992                             break;
993                         }
994                         normalizeLineEnds(cdata);
995                         flowBegin = pos_;
996                         flowEnd = pos_;
997                         space = SPACE_NONE;
998                     } else {
999                         skipDocumentTypeDeclaration();
1000                     }
1001                 }
1002                 break;
1003             case '/':
1004                 ++pos_;
1005                 pad_.add(flowBegin, flowEnd - flowBegin);
1006                 *text = pad_.get();
1007                 state_ = STATE_END_TAG;
1008                 return RESULT_TEXT;
1009             case '?':
1010                 ++pos_;
1011                 skipProcessingInstruction();
1012                 space = SPACE_BREAK;
1013                 break;
1014             default:
1015                 pad_.add(flowBegin, flowEnd - flowBegin);
1016                 *text = pad_.get();
1017                 state_ = STATE_START_TAG;
1018                 return RESULT_TEXT;
1019             }
1020             break;
1021         default:
1022             switch (space) {
1023             case SPACE_START:
1024                 flowBegin = pos_;
1025                 break;
1026             case SPACE_NONE:
1027             case SPACE_SPAN:
1028                 break;
1029             case SPACE_BREAK:
1030                 pad_.add(flowBegin, flowEnd - flowBegin);
1031                 pad_.add(RTL_CONSTASCII_STRINGPARAM(" "));
1032                 flowBegin = pos_;
1033                 break;
1034             }
1035             flowEnd = ++pos_;
1036             space = SPACE_NONE;
1037             break;
1038         }
1039     }
1040 }
1041 
1042 int XmlReader::toNamespaceId(NamespaceIris::size_type pos) {
1043     OSL_ASSERT(pos <= INT_MAX);
1044     return static_cast< int >(pos);
1045 }
1046 
1047 }
1048