1//
2// XMLStreamParser.cpp
3//
4// Library: XML
5// Package: XML
6// Module: XMLStreamParser
7//
8// Copyright (c) 2015, Applied Informatics Software Engineering GmbH.
9// and Contributors.
10//
11// Based on libstudxml (http://www.codesynthesis.com/projects/libstudxml/).
12// Copyright (c) 2009-2013 Code Synthesis Tools CC.
13//
14// SPDX-License-Identifier: BSL-1.0
15//
16
17
18#include "Poco/XML/XMLStreamParser.h"
19#include <new>
20#include <cstring>
21#include <istream>
22#include <ostream>
23#include <sstream>
24
25
26namespace Poco {
27namespace XML {
28
29
30struct StreamExceptionController
31{
32 StreamExceptionController(std::istream& is):
33 _istr(is),
34 _oldState(_istr.exceptions())
35 {
36 _istr.exceptions(_oldState & ~std::istream::failbit);
37 }
38
39 ~StreamExceptionController()
40 {
41 std::istream::iostate s = _istr.rdstate();
42 s &= ~std::istream::failbit;
43
44 // If our error state (sans failbit) intersects with the
45 // exception state then that means we have an active
46 // exception and changing error/exception state will
47 // cause another to be thrown.
48 if (!(_oldState & s))
49 {
50 // Clear failbit if it was caused by eof.
51 //
52 if (_istr.fail() && _istr.eof())
53 _istr.clear(s);
54
55 _istr.exceptions(_oldState);
56 }
57 }
58
59private:
60 StreamExceptionController(const StreamExceptionController&);
61 StreamExceptionController& operator = (const StreamExceptionController&);
62
63private:
64 std::istream& _istr;
65 std::istream::iostate _oldState;
66};
67
68
69static const char* parserEventStrings[] =
70{
71 "start element",
72 "end element",
73 "start attribute",
74 "end attribute",
75 "characters",
76 "start namespace declaration",
77 "end namespace declaration",
78 "end of file"
79};
80
81
82std::ostream& operator << (std::ostream& os, XMLStreamParser::EventType e)
83{
84 return os << parserEventStrings[e];
85}
86
87
88XMLStreamParser::XMLStreamParser(std::istream& is, const std::string& iname, FeatureType f):
89 _size(0),
90 _inputName(iname),
91 _feature(f)
92{
93 _data.is = &is;
94 init();
95}
96
97
98XMLStreamParser::XMLStreamParser(const void* data, std::size_t size, const std::string& iname, FeatureType f):
99 _size(size),
100 _inputName(iname),
101 _feature(f)
102{
103 poco_assert(data != 0 && size != 0);
104
105 _data.buf = data;
106 init();
107}
108
109
110XMLStreamParser::~XMLStreamParser()
111{
112 if (_parser) XML_ParserFree(_parser);
113}
114
115
116void XMLStreamParser::init()
117{
118 _depth = 0;
119 _parserState = state_next;
120 _currentEvent = EV_EOF;
121 _queue = EV_EOF;
122
123 _qualifiedName = &_qname;
124 _pvalue = &_value;
125
126 _line = 0;
127 _column = 0;
128
129 _currentAttributeIndex = 0;
130 _startNamespaceIndex = 0;
131 _endNamespaceIndex = 0;
132
133 if ((_feature & RECEIVE_ATTRIBUTE_MAP) != 0 && (_feature & RECEIVE_ATTRIBUTES_EVENT) != 0)
134 _feature &= ~RECEIVE_ATTRIBUTE_MAP;
135
136 // Allocate the XMLStreamParser. Make sure nothing else can throw after
137 // this call since otherwise we will leak it.
138 //
139 _parser = XML_ParserCreateNS(0, XML_Char(' '));
140
141 if (_parser == 0)
142 throw std::bad_alloc();
143
144 // Get prefixes in addition to namespaces and local names.
145 //
146 XML_SetReturnNSTriplet(_parser, true);
147
148 // Set handlers.
149 //
150 XML_SetUserData(_parser, this);
151
152 if ((_feature & RECEIVE_ELEMENTS) != 0)
153 {
154 XML_SetStartElementHandler(_parser, &handleStartElement);
155 XML_SetEndElementHandler(_parser, &handleEndElement);
156 }
157
158 if ((_feature & RECEIVE_CHARACTERS) != 0)
159 XML_SetCharacterDataHandler(_parser, &handleCharacters);
160
161 if ((_feature & RECEIVE_NAMESPACE_DECLS) != 0)
162 XML_SetNamespaceDeclHandler(_parser, &handleStartNamespaceDecl, &handleEndNamespaceDecl);
163}
164
165
166void XMLStreamParser::handleError()
167{
168 XML_Error e(XML_GetErrorCode(_parser));
169
170 if (e == XML_ERROR_ABORTED)
171 {
172 // For now we only abort the XMLStreamParser in the handleCharacters() and
173 // handleStartElement() handlers.
174 //
175 switch (content())
176 {
177 case Content::Empty:
178 throw XMLStreamParserException(*this, "characters in empty content");
179 case Content::Simple:
180 throw XMLStreamParserException(*this, "element in simple content");
181 case Content::Complex:
182 throw XMLStreamParserException(*this, "characters in complex content");
183 default:
184 poco_assert(false);
185 }
186 }
187 else
188 throw XMLStreamParserException(_inputName, XML_GetCurrentLineNumber(_parser), XML_GetCurrentColumnNumber(_parser), XML_ErrorString(e));
189}
190
191
192XMLStreamParser::EventType XMLStreamParser::next()
193{
194 if (_parserState == state_next)
195 return nextImpl(false);
196 else
197 {
198 // If we previously peeked at start/end_element, then adjust
199 // state accordingly.
200 //
201 switch (_currentEvent)
202 {
203 case EV_END_ELEMENT:
204 {
205 if (!_elementState.empty() && _elementState.back().depth == _depth)
206 popElement();
207
208 _depth--;
209 break;
210 }
211 case EV_START_ELEMENT:
212 {
213 _depth++;
214 break;
215 }
216 default:
217 break;
218 }
219
220 _parserState = state_next;
221 return _currentEvent;
222 }
223}
224
225
226const std::string& XMLStreamParser::attribute(const QName& qn) const
227{
228 if (const ElementEntry* e = getElement())
229 {
230 AttributeMapType::const_iterator i(e->attributeMap.find(qn));
231
232 if (i != e->attributeMap.end())
233 {
234 if (!i->second.handled)
235 {
236 i->second.handled = true;
237 e->attributesUnhandled--;
238 }
239 return i->second.value;
240 }
241 }
242
243 throw XMLStreamParserException(*this, "attribute '" + qn.toString() + "' expected");
244}
245
246
247std::string XMLStreamParser::attribute(const QName& qn, const std::string& dv) const
248{
249 if (const ElementEntry* e = getElement())
250 {
251 AttributeMapType::const_iterator i(e->attributeMap.find(qn));
252
253 if (i != e->attributeMap.end())
254 {
255 if (!i->second.handled)
256 {
257 i->second.handled = true;
258 e->attributesUnhandled--;
259 }
260 return i->second.value;
261 }
262 }
263
264 return dv;
265}
266
267
268bool XMLStreamParser::attributePresent(const QName& qn) const
269{
270 if (const ElementEntry* e = getElement())
271 {
272 AttributeMapType::const_iterator i(e->attributeMap.find(qn));
273
274 if (i != e->attributeMap.end())
275 {
276 if (!i->second.handled)
277 {
278 i->second.handled = true;
279 e->attributesUnhandled--;
280 }
281 return true;
282 }
283 }
284
285 return false;
286}
287
288
289void XMLStreamParser::nextExpect(EventType e)
290{
291 if (next() != e)
292 throw XMLStreamParserException(*this, std::string(parserEventStrings[e]) + " expected");
293}
294
295
296void XMLStreamParser::nextExpect(EventType e, const std::string& ns, const std::string& n)
297{
298 if (next() != e || namespaceURI() != ns || localName() != n)
299 throw XMLStreamParserException(*this, std::string(parserEventStrings[e]) + " '" + QName(ns, n).toString() + "' expected");
300}
301
302
303std::string XMLStreamParser::element()
304{
305 content(Content::Simple);
306 std::string r;
307
308 // The content of the element can be empty in which case there
309 // will be no characters event.
310 //
311 EventType e(next());
312 if (e == EV_CHARACTERS)
313 {
314 r.swap(value());
315 e = next();
316 }
317
318 // We cannot really get anything other than end_element since
319 // the simple content validation won't allow it.
320 //
321 poco_assert(e == EV_END_ELEMENT);
322
323 return r;
324}
325
326
327std::string XMLStreamParser::element(const QName& qn, const std::string& dv)
328{
329 if (peek() == EV_START_ELEMENT && getQName() == qn)
330 {
331 next();
332 return element();
333 }
334
335 return dv;
336}
337
338
339const XMLStreamParser::ElementEntry* XMLStreamParser::getElementImpl() const
340{
341 // The handleStartElement() Expat handler may have already provisioned
342 // an entry in the element stack. In this case, we need to get the
343 // one before it, if any.
344 //
345 const ElementEntry* r(0);
346 ElementState::size_type n(_elementState.size() - 1);
347
348 if (_elementState[n].depth == _depth)
349 r = &_elementState[n];
350 else if (n != 0 && _elementState[n].depth > _depth)
351 {
352 n--;
353 if (_elementState[n].depth == _depth)
354 r = &_elementState[n];
355 }
356
357 return r;
358}
359
360
361void XMLStreamParser::popElement()
362{
363 // Make sure there are no unhandled attributes left.
364 //
365 const ElementEntry& e(_elementState.back());
366 if (e.attributesUnhandled != 0)
367 {
368 // Find the first unhandled attribute and report it.
369 //
370 for (AttributeMapType::const_iterator i(e.attributeMap.begin()); i != e.attributeMap.end(); ++i)
371 {
372 if (!i->second.handled)
373 throw XMLStreamParserException(*this, "unexpected attribute '" + i->first.toString() + "'");
374 }
375 poco_assert(false);
376 }
377
378 _elementState.pop_back();
379}
380
381
382XMLStreamParser::EventType XMLStreamParser::nextImpl(bool peek)
383{
384 EventType e(nextBody());
385
386 // Content-specific processing. Note that we handle characters in the
387 // handleCharacters() Expat handler for two reasons. Firstly, it is faster
388 // to ignore the whitespaces at the source. Secondly, this allows us
389 // to distinguish between element and attribute characters. We can
390 // move this processing to the handler because the characters event
391 // is never queued.
392 //
393 switch (e)
394 {
395 case EV_END_ELEMENT:
396 {
397 // If this is a peek, then avoid popping the stack just yet.
398 // This way, the attribute map will still be valid until we
399 // call next().
400 //
401 if (!peek)
402 {
403 if (!_elementState.empty() && _elementState.back().depth == _depth)
404 popElement();
405
406 _depth--;
407 }
408 break;
409 }
410 case EV_START_ELEMENT:
411 {
412 if (const ElementEntry* pEntry = getElement())
413 {
414 switch (pEntry->content)
415 {
416 case Content::Empty:
417 throw XMLStreamParserException(*this, "element in empty content");
418 case Content::Simple:
419 throw XMLStreamParserException(*this, "element in simple content");
420 default:
421 break;
422 }
423 }
424
425 // If this is a peek, then delay adjusting the depth.
426 //
427 if (!peek)
428 _depth++;
429
430 break;
431 }
432 default:
433 break;
434 }
435
436 return e;
437}
438
439
440XMLStreamParser::EventType XMLStreamParser::nextBody()
441{
442 // See if we have any start namespace declarations we need to return.
443 //
444 if (_startNamespaceIndex < _startNamespace.size())
445 {
446 // Based on the previous event determine what's the next one must be.
447 //
448 switch (_currentEvent)
449 {
450 case EV_START_NAMESPACE_DECL:
451 {
452 if (++_startNamespaceIndex == _startNamespace.size())
453 {
454 _startNamespaceIndex = 0;
455 _startNamespace.clear();
456 _qualifiedName = &_qname;
457 break; // No more declarations.
458 }
459 // Fall through.
460 }
461 case EV_START_ELEMENT:
462 {
463 _currentEvent = EV_START_NAMESPACE_DECL;
464 _qualifiedName = &_startNamespace[_startNamespaceIndex];
465 return _currentEvent;
466 }
467 default:
468 {
469 poco_assert(false);
470 return _currentEvent = EV_EOF;
471 }
472 }
473 }
474
475 // See if we have any attributes we need to return as events.
476 //
477 if (_currentAttributeIndex < _attributes.size())
478 {
479 // Based on the previous event determine what's the next one must be.
480 //
481 switch (_currentEvent)
482 {
483 case EV_START_ATTRIBUTE:
484 {
485 _currentEvent = EV_CHARACTERS;
486 _pvalue = &_attributes[_currentAttributeIndex].value;
487 return _currentEvent;
488 }
489 case EV_CHARACTERS:
490 {
491 _currentEvent = EV_END_ATTRIBUTE; // Name is already set.
492 return _currentEvent;
493 }
494 case EV_END_ATTRIBUTE:
495 {
496 if (++_currentAttributeIndex == _attributes.size())
497 {
498 _currentAttributeIndex = 0;
499 _attributes.clear();
500 _qualifiedName = &_qname;
501 _pvalue = &_value;
502 break; // No more attributes.
503 }
504 // Fall through.
505 }
506 case EV_START_ELEMENT:
507 case EV_START_NAMESPACE_DECL:
508 {
509 _currentEvent = EV_START_ATTRIBUTE;
510 _qualifiedName = &_attributes[_currentAttributeIndex].qname;
511 return _currentEvent;
512 }
513 default:
514 {
515 poco_assert(false);
516 return _currentEvent = EV_EOF;
517 }
518 }
519 }
520
521 // See if we have any end namespace declarations we need to return.
522 //
523 if (_endNamespaceIndex < _endNamespace.size())
524 {
525 // Based on the previous event determine what's the next one must be.
526 //
527 switch (_currentEvent)
528 {
529 case EV_END_NAMESPACE_DECL:
530 {
531 if (++_endNamespaceIndex == _endNamespace.size())
532 {
533 _endNamespaceIndex = 0;
534 _endNamespace.clear();
535 _qualifiedName = &_qname;
536 break; // No more declarations.
537 }
538 // Fall through.
539 }
540 // The end namespace declaration comes before the end element
541 // which means it can follow pretty much any other event.
542 //
543 default:
544 {
545 _currentEvent = EV_END_NAMESPACE_DECL;
546 _qualifiedName = &_endNamespace[_endNamespaceIndex];
547 return _currentEvent;
548 }
549 }
550 }
551
552 // Check the queue.
553 //
554 if (_queue != EV_EOF)
555 {
556 _currentEvent = _queue;
557 _queue = EV_EOF;
558
559 _line = XML_GetCurrentLineNumber(_parser);
560 _column = XML_GetCurrentColumnNumber(_parser);
561
562 return _currentEvent;
563 }
564
565 // Reset the character accumulation flag.
566 //
567 _accumulateContent = false;
568
569 XML_ParsingStatus ps;
570 XML_GetParsingStatus(_parser, &ps);
571
572 switch (ps.parsing)
573 {
574 case XML_INITIALIZED:
575 {
576 // As if we finished the previous chunk.
577 break;
578 }
579 case XML_PARSING:
580 {
581 poco_assert(false);
582 return _currentEvent = EV_EOF;
583 }
584 case XML_FINISHED:
585 {
586 return _currentEvent = EV_EOF;
587 }
588 case XML_SUSPENDED:
589 {
590 switch (XML_ResumeParser(_parser))
591 {
592 case XML_STATUS_SUSPENDED:
593 {
594 // If the XMLStreamParser is again in the suspended state, then
595 // that means we have the next event.
596 //
597 return _currentEvent;
598 }
599 case XML_STATUS_OK:
600 {
601 // Otherwise, we need to get and parse the next chunk of data
602 // unless this was the last chunk, in which case this is eof.
603 //
604 if (ps.finalBuffer)
605 return _currentEvent = EV_EOF;
606
607 break;
608 }
609 case XML_STATUS_ERROR:
610 handleError();
611 }
612 break;
613 }
614 }
615
616 // Get and parse the next chunk of data until we get the next event
617 // or reach eof.
618 //
619 if (!_accumulateContent)
620 _currentEvent = EV_EOF;
621
622 XML_Status s;
623 do
624 {
625 if (_size != 0)
626 {
627 s = XML_Parse(_parser, static_cast<const char*>(_data.buf), static_cast<int>(_size), true);
628
629 if (s == XML_STATUS_ERROR)
630 handleError();
631
632 break;
633 }
634 else
635 {
636 const size_t cap(4096);
637
638 char* b(static_cast<char*>(XML_GetBuffer(_parser, cap)));
639 if (b == 0)
640 throw std::bad_alloc();
641
642 // Temporarily unset the exception failbit. Also clear the fail bit
643 // when we reset the old state if it was caused by eof.
644 //
645 std::istream& is(*_data.is);
646 {
647 StreamExceptionController sec(is);
648 is.read(b, static_cast<std::streamsize>(cap));
649 }
650
651 // If the caller hasn't configured the stream to use exceptions,
652 // then use the parsing exception to report an error.
653 //
654 if (is.bad() || (is.fail() && !is.eof()))
655 throw XMLStreamParserException(*this, "io failure");
656
657 bool eof(is.eof());
658
659 s = XML_ParseBuffer(_parser, static_cast<int>(is.gcount()), eof);
660
661 if (s == XML_STATUS_ERROR)
662 handleError();
663
664 if (eof)
665 break;
666 }
667 } while (s != XML_STATUS_SUSPENDED);
668
669 return _currentEvent;
670}
671
672
673static void splitName(const XML_Char* s, QName& qn)
674{
675 std::string& ns(qn.namespaceURI());
676 std::string& name(qn.localName());
677 std::string& prefix(qn.prefix());
678
679 const char* p(strchr(s, ' '));
680
681 if (p == 0)
682 {
683 ns.clear();
684 name = s;
685 prefix.clear();
686 }
687 else
688 {
689 ns.assign(s, 0, p - s);
690
691 s = p + 1;
692 p = strchr(s, ' ');
693
694 if (p == 0)
695 {
696 name = s;
697 prefix.clear();
698 }
699 else
700 {
701 name.assign(s, 0, p - s);
702 prefix = p + 1;
703 }
704 }
705}
706
707
708void XMLCALL XMLStreamParser::handleStartElement(void* v, const XML_Char* name, const XML_Char** atts)
709{
710 XMLStreamParser& p(*static_cast<XMLStreamParser*>(v));
711
712 XML_ParsingStatus ps;
713 XML_GetParsingStatus(p._parser, &ps);
714
715 // Expat has a (mis)-feature of a possibily calling handlers even
716 // after the non-resumable XML_StopParser call.
717 //
718 if (ps.parsing == XML_FINISHED)
719 return;
720
721 // Cannot be a followup event.
722 //
723 poco_assert(ps.parsing == XML_PARSING);
724
725 // When accumulating characters in simple content, we expect to
726 // see more characters or end element. Seeing start element is
727 // possible but means violation of the content model.
728 //
729 if (p._accumulateContent)
730 {
731 // It would have been easier to throw the exception directly,
732 // however, the Expat code is most likely not exception safe.
733 //
734 p._line = XML_GetCurrentLineNumber(p._parser);
735 p._column = XML_GetCurrentColumnNumber(p._parser);
736 XML_StopParser(p._parser, false);
737 return;
738 }
739
740 p._currentEvent = EV_START_ELEMENT;
741 splitName(name, p._qname);
742
743 p._line = XML_GetCurrentLineNumber(p._parser);
744 p._column = XML_GetCurrentColumnNumber(p._parser);
745
746 // Handle attributes.
747 //
748 if (*atts != 0)
749 {
750 bool am((p._feature & RECEIVE_ATTRIBUTE_MAP) != 0);
751 bool ae((p._feature & RECEIVE_ATTRIBUTES_EVENT) != 0);
752
753 // Provision an entry for this element.
754 //
755 ElementEntry* pe(0);
756 if (am)
757 {
758 p._elementState.push_back(ElementEntry(p._depth + 1));
759 pe = &p._elementState.back();
760 }
761
762 if (am || ae)
763 {
764 for (; *atts != 0; atts += 2)
765 {
766 if (am)
767 {
768 QName qn;
769 splitName(*atts, qn);
770 AttributeMapType::value_type v(qn, AttributeValueType());
771 v.second.value = *(atts + 1);
772 v.second.handled = false;
773 pe->attributeMap.insert(v);
774 }
775 else
776 {
777 p._attributes.push_back(AttributeType());
778 splitName(*atts, p._attributes.back().qname);
779 p._attributes.back().value = *(atts + 1);
780 }
781 }
782
783 if (am)
784 pe->attributesUnhandled = pe->attributeMap.size();
785 }
786 }
787
788 XML_StopParser(p._parser, true);
789}
790
791
792void XMLCALL XMLStreamParser::handleEndElement(void* v, const XML_Char* name)
793{
794 XMLStreamParser& p(*static_cast<XMLStreamParser*>(v));
795
796 XML_ParsingStatus ps;
797 XML_GetParsingStatus(p._parser, &ps);
798
799 // Expat has a (mis)-feature of a possibily calling handlers even
800 // after the non-resumable XML_StopParser call.
801 //
802 if (ps.parsing == XML_FINISHED)
803 return;
804
805 // This can be a followup event for empty elements (<foo/>). In this
806 // case the element name is already set.
807 //
808 if (ps.parsing != XML_PARSING)
809 p._queue = EV_END_ELEMENT;
810 else
811 {
812 splitName(name, p._qname);
813
814 // If we are accumulating characters, then queue this event.
815 //
816 if (p._accumulateContent)
817 p._queue = EV_END_ELEMENT;
818 else
819 {
820 p._currentEvent = EV_END_ELEMENT;
821
822 p._line = XML_GetCurrentLineNumber(p._parser);
823 p._column = XML_GetCurrentColumnNumber(p._parser);
824 }
825
826 XML_StopParser(p._parser, true);
827 }
828}
829
830
831void XMLCALL XMLStreamParser::handleCharacters(void* v, const XML_Char* s, int n)
832{
833 XMLStreamParser& p(*static_cast<XMLStreamParser*>(v));
834
835 XML_ParsingStatus ps;
836 XML_GetParsingStatus(p._parser, &ps);
837
838 // Expat has a (mis)-feature of a possibily calling handlers even
839 // after the non-resumable XML_StopParser call.
840 //
841 if (ps.parsing == XML_FINISHED)
842 return;
843
844 Content cont(p.content());
845
846 // If this is empty or complex content, see if these are whitespaces.
847 //
848 switch (cont)
849 {
850 case Content::Empty:
851 case Content::Complex:
852 {
853 for (int i(0); i != n; ++i)
854 {
855 char c(s[i]);
856 if (c == 0x20 || c == 0x0A || c == 0x0D || c == 0x09)
857 continue;
858
859 // It would have been easier to throw the exception directly,
860 // however, the Expat code is most likely not exception safe.
861 //
862 p._line = XML_GetCurrentLineNumber(p._parser);
863 p._column = XML_GetCurrentColumnNumber(p._parser);
864 XML_StopParser(p._parser, false);
865 break;
866 }
867 return;
868 }
869 default:
870 break;
871 }
872
873 // Append the characters if we are accumulating. This can also be a
874 // followup event for another character event. In this case also
875 // append the data.
876 //
877 if (p._accumulateContent || ps.parsing != XML_PARSING)
878 {
879 poco_assert(p._currentEvent == EV_CHARACTERS);
880 p._value.append(s, n);
881 }
882 else
883 {
884 p._currentEvent = EV_CHARACTERS;
885 p._value.assign(s, n);
886
887 p._line = XML_GetCurrentLineNumber(p._parser);
888 p._column = XML_GetCurrentColumnNumber(p._parser);
889
890 // In simple content we need to accumulate all the characters
891 // into a single event. To do this we will let the XMLStreamParser run
892 // until we reach the end of the element.
893 //
894 if (cont == Content::Simple)
895 p._accumulateContent = true;
896 else
897 XML_StopParser(p._parser, true);
898 }
899}
900
901
902void XMLCALL XMLStreamParser::handleStartNamespaceDecl(void* v, const XML_Char* prefix, const XML_Char* ns)
903{
904 XMLStreamParser& p(*static_cast<XMLStreamParser*>(v));
905
906 XML_ParsingStatus ps;
907 XML_GetParsingStatus(p._parser, &ps);
908
909 // Expat has a (mis)-feature of a possibily calling handlers even
910 // after the non-resumable XML_StopParser call.
911 //
912 if (ps.parsing == XML_FINISHED)
913 return;
914
915 p._startNamespace.push_back(QName());
916 p._startNamespace.back().prefix() = (prefix != 0 ? prefix : "");
917 p._startNamespace.back().namespaceURI() = (ns != 0 ? ns : "");
918}
919
920
921void XMLCALL XMLStreamParser::handleEndNamespaceDecl(void* v, const XML_Char* prefix)
922{
923 XMLStreamParser& p(*static_cast<XMLStreamParser*>(v));
924
925 XML_ParsingStatus ps;
926 XML_GetParsingStatus(p._parser, &ps);
927
928 // Expat has a (mis)-feature of a possibily calling handlers even
929 // after the non-resumable XML_StopParser call.
930 //
931 if (ps.parsing == XML_FINISHED)
932 return;
933
934 p._endNamespace.push_back(QName());
935 p._endNamespace.back().prefix() = (prefix != 0 ? prefix : "");
936}
937
938
939} } // namespace Poco::XML
940