OpenShot Audio Library | OpenShotAudio 0.3.2
Loading...
Searching...
No Matches
juce_XmlDocument.cpp
1/*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2017 - ROLI Ltd.
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 The code included in this file is provided under the terms of the ISC license
11 http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12 To use, copy, modify, and/or distribute this software for any purpose with or
13 without fee is hereby granted provided that the above copyright notice and
14 this permission notice appear in all copies.
15
16 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18 DISCLAIMED.
19
20 ==============================================================================
21*/
22
23namespace juce
24{
25
26XmlDocument::XmlDocument (const String& text) : originalText (text) {}
27XmlDocument::XmlDocument (const File& file) : inputSource (new FileInputSource (file)) {}
28
30
31std::unique_ptr<XmlElement> XmlDocument::parse (const File& file)
32{
33 return XmlDocument (file).getDocumentElement();
34}
35
36std::unique_ptr<XmlElement> XmlDocument::parse (const String& textToParse)
37{
39}
40
41std::unique_ptr<XmlElement> parseXML (const String& textToParse)
42{
44}
45
46std::unique_ptr<XmlElement> parseXML (const File& file)
47{
48 return XmlDocument (file).getDocumentElement();
49}
50
51std::unique_ptr<XmlElement> parseXMLIfTagMatches (const String& textToParse, StringRef requiredTag)
52{
53 return XmlDocument (textToParse).getDocumentElementIfTagMatches (requiredTag);
54}
55
56std::unique_ptr<XmlElement> parseXMLIfTagMatches (const File& file, StringRef requiredTag)
57{
58 return XmlDocument (file).getDocumentElementIfTagMatches (requiredTag);
59}
60
62{
63 inputSource.reset (newSource);
64}
65
67{
68 ignoreEmptyTextElements = shouldBeIgnored;
69}
70
71namespace XmlIdentifierChars
72{
73 static bool isIdentifierCharSlow (juce_wchar c) noexcept
74 {
76 || c == '_' || c == '-' || c == ':' || c == '.';
77 }
78
79 static bool isIdentifierChar (juce_wchar c) noexcept
80 {
81 static const uint32 legalChars[] = { 0, 0x7ff6000, 0x87fffffe, 0x7fffffe, 0 };
82
83 return ((int) c < (int) numElementsInArray (legalChars) * 32) ? ((legalChars [c >> 5] & (uint32) (1 << (c & 31))) != 0)
84 : isIdentifierCharSlow (c);
85 }
86
87 /*static void generateIdentifierCharConstants()
88 {
89 uint32 n[8] = { 0 };
90 for (int i = 0; i < 256; ++i)
91 if (isIdentifierCharSlow (i))
92 n[i >> 5] |= (1 << (i & 31));
93
94 String s;
95 for (int i = 0; i < 8; ++i)
96 s << "0x" << String::toHexString ((int) n[i]) << ", ";
97
98 DBG (s);
99 }*/
100
101 static String::CharPointerType findEndOfToken (String::CharPointerType p) noexcept
102 {
103 while (isIdentifierChar (*p))
104 ++p;
105
106 return p;
107 }
108}
109
110std::unique_ptr<XmlElement> XmlDocument::getDocumentElement (const bool onlyReadOuterDocumentElement)
111{
112 if (originalText.isEmpty() && inputSource != nullptr)
113 {
114 std::unique_ptr<InputStream> in (inputSource->createInputStream());
115
116 if (in != nullptr)
117 {
120
121 #if JUCE_STRING_UTF_TYPE == 8
122 if (data.getDataSize() > 2)
123 {
124 data.writeByte (0);
125 auto* text = static_cast<const char*> (data.getData());
126
129 {
130 originalText = data.toString();
131 }
132 else
133 {
135 text += 3;
136
137 // parse the input buffer directly to avoid copying it all to a string..
138 return parseDocumentElement (String::CharPointerType (text), onlyReadOuterDocumentElement);
139 }
140 }
141 #else
142 originalText = data.toString();
143 #endif
144 }
145 }
146
147 return parseDocumentElement (originalText.getCharPointer(), onlyReadOuterDocumentElement);
148}
149
151{
152 if (auto xml = getDocumentElement (true))
153 if (xml->hasTagName (requiredTag))
154 return getDocumentElement (false);
155
156 return {};
157}
158
160{
161 return lastError;
162}
163
164void XmlDocument::setLastError (const String& desc, const bool carryOn)
165{
166 lastError = desc;
167 errorOccurred = ! carryOn;
168}
169
170String XmlDocument::getFileContents (const String& filename) const
171{
172 if (inputSource != nullptr)
173 {
174 std::unique_ptr<InputStream> in (inputSource->createInputStreamFor (filename.trim().unquoted()));
175
176 if (in != nullptr)
177 return in->readEntireStreamAsString();
178 }
179
180 return {};
181}
182
183juce_wchar XmlDocument::readNextChar() noexcept
184{
185 auto c = input.getAndAdvance();
186
187 if (c == 0)
188 {
189 outOfData = true;
190 --input;
191 }
192
193 return c;
194}
195
196std::unique_ptr<XmlElement> XmlDocument::parseDocumentElement (String::CharPointerType textToParse,
197 bool onlyReadOuterDocumentElement)
198{
199 input = textToParse;
200 errorOccurred = false;
201 outOfData = false;
202 needToLoadDTD = true;
203
204 if (textToParse.isEmpty())
205 {
206 lastError = "not enough input";
207 }
208 else if (! parseHeader())
209 {
210 lastError = "malformed header";
211 }
212 else if (! parseDTD())
213 {
214 lastError = "malformed DTD";
215 }
216 else
217 {
218 lastError.clear();
219 std::unique_ptr<XmlElement> result (readNextElement (! onlyReadOuterDocumentElement));
220
221 if (! errorOccurred)
222 return result;
223 }
224
225 return {};
226}
227
228bool XmlDocument::parseHeader()
229{
230 skipNextWhiteSpace();
231
232 if (CharacterFunctions::compareUpTo (input, CharPointer_ASCII ("<?xml"), 5) == 0)
233 {
234 auto headerEnd = CharacterFunctions::find (input, CharPointer_ASCII ("?>"));
235
236 if (headerEnd.isEmpty())
237 return false;
238
239 #if JUCE_DEBUG
240 auto encoding = String (input, headerEnd)
241 .fromFirstOccurrenceOf ("encoding", false, true)
242 .fromFirstOccurrenceOf ("=", false, false)
243 .fromFirstOccurrenceOf ("\"", false, false)
244 .upToFirstOccurrenceOf ("\"", false, false)
245 .trim();
246
247 /* If you load an XML document with a non-UTF encoding type, it may have been
248 loaded wrongly.. Since all the files are read via the normal juce file streams,
249 they're treated as UTF-8, so by the time it gets to the parser, the encoding will
250 have been lost. Best plan is to stick to utf-8 or if you have specific files to
251 read, use your own code to convert them to a unicode String, and pass that to the
252 XML parser.
253 */
254 jassert (encoding.isEmpty() || encoding.startsWithIgnoreCase ("utf-"));
255 #endif
256
257 input = headerEnd + 2;
258 skipNextWhiteSpace();
259 }
260
261 return true;
262}
263
264bool XmlDocument::parseDTD()
265{
266 if (CharacterFunctions::compareUpTo (input, CharPointer_ASCII ("<!DOCTYPE"), 9) == 0)
267 {
268 input += 9;
269 auto dtdStart = input;
270
271 for (int n = 1; n > 0;)
272 {
273 auto c = readNextChar();
274
275 if (outOfData)
276 return false;
277
278 if (c == '<')
279 ++n;
280 else if (c == '>')
281 --n;
282 }
283
284 dtdText = String (dtdStart, input - 1).trim();
285 }
286
287 return true;
288}
289
290void XmlDocument::skipNextWhiteSpace()
291{
292 for (;;)
293 {
294 input = input.findEndOfWhitespace();
295
296 if (input.isEmpty())
297 {
298 outOfData = true;
299 break;
300 }
301
302 if (*input == '<')
303 {
304 if (input[1] == '!'
305 && input[2] == '-'
306 && input[3] == '-')
307 {
308 input += 4;
309 auto closeComment = input.indexOf (CharPointer_ASCII ("-->"));
310
311 if (closeComment < 0)
312 {
313 outOfData = true;
314 break;
315 }
316
317 input += closeComment + 3;
318 continue;
319 }
320
321 if (input[1] == '?')
322 {
323 input += 2;
324 auto closeBracket = input.indexOf (CharPointer_ASCII ("?>"));
325
326 if (closeBracket < 0)
327 {
328 outOfData = true;
329 break;
330 }
331
332 input += closeBracket + 2;
333 continue;
334 }
335 }
336
337 break;
338 }
339}
340
341void XmlDocument::readQuotedString (String& result)
342{
343 auto quote = readNextChar();
344
345 while (! outOfData)
346 {
347 auto c = readNextChar();
348
349 if (c == quote)
350 break;
351
352 --input;
353
354 if (c == '&')
355 {
356 readEntity (result);
357 }
358 else
359 {
360 auto start = input;
361
362 for (;;)
363 {
364 auto character = *input;
365
366 if (character == quote)
367 {
368 result.appendCharPointer (start, input);
369 ++input;
370 return;
371 }
372
373 if (character == '&')
374 {
375 result.appendCharPointer (start, input);
376 break;
377 }
378
379 if (character == 0)
380 {
381 setLastError ("unmatched quotes", false);
382 outOfData = true;
383 break;
384 }
385
386 ++input;
387 }
388 }
389 }
390}
391
392XmlElement* XmlDocument::readNextElement (const bool alsoParseSubElements)
393{
394 XmlElement* node = nullptr;
395 skipNextWhiteSpace();
396
397 if (outOfData)
398 return nullptr;
399
400 if (*input == '<')
401 {
402 ++input;
403 auto endOfToken = XmlIdentifierChars::findEndOfToken (input);
404
405 if (endOfToken == input)
406 {
407 // no tag name - but allow for a gap after the '<' before giving an error
408 skipNextWhiteSpace();
409 endOfToken = XmlIdentifierChars::findEndOfToken (input);
410
411 if (endOfToken == input)
412 {
413 setLastError ("tag name missing", false);
414 return node;
415 }
416 }
417
418 node = new XmlElement (input, endOfToken);
419 input = endOfToken;
420 LinkedListPointer<XmlElement::XmlAttributeNode>::Appender attributeAppender (node->attributes);
421
422 // look for attributes
423 for (;;)
424 {
425 skipNextWhiteSpace();
426 auto c = *input;
427
428 // empty tag..
429 if (c == '/' && input[1] == '>')
430 {
431 input += 2;
432 break;
433 }
434
435 // parse the guts of the element..
436 if (c == '>')
437 {
438 ++input;
439
440 if (alsoParseSubElements)
441 readChildElements (*node);
442
443 break;
444 }
445
446 // get an attribute..
447 if (XmlIdentifierChars::isIdentifierChar (c))
448 {
449 auto attNameEnd = XmlIdentifierChars::findEndOfToken (input);
450
451 if (attNameEnd != input)
452 {
453 auto attNameStart = input;
454 input = attNameEnd;
455 skipNextWhiteSpace();
456
457 if (readNextChar() == '=')
458 {
459 skipNextWhiteSpace();
460 auto nextChar = *input;
461
462 if (nextChar == '"' || nextChar == '\'')
463 {
464 auto* newAtt = new XmlElement::XmlAttributeNode (attNameStart, attNameEnd);
465 readQuotedString (newAtt->value);
466 attributeAppender.append (newAtt);
467 continue;
468 }
469 }
470 else
471 {
472 setLastError ("expected '=' after attribute '"
473 + String (attNameStart, attNameEnd) + "'", false);
474 return node;
475 }
476 }
477 }
478 else
479 {
480 if (! outOfData)
481 setLastError ("illegal character found in " + node->getTagName() + ": '" + c + "'", false);
482 }
483
484 break;
485 }
486 }
487
488 return node;
489}
490
491void XmlDocument::readChildElements (XmlElement& parent)
492{
493 LinkedListPointer<XmlElement>::Appender childAppender (parent.firstChildElement);
494
495 for (;;)
496 {
497 auto preWhitespaceInput = input;
498 skipNextWhiteSpace();
499
500 if (outOfData)
501 {
502 setLastError ("unmatched tags", false);
503 break;
504 }
505
506 if (*input == '<')
507 {
508 auto c1 = input[1];
509
510 if (c1 == '/')
511 {
512 // our close tag..
513 auto closeTag = input.indexOf ((juce_wchar) '>');
514
515 if (closeTag >= 0)
516 input += closeTag + 1;
517
518 break;
519 }
520
521 if (c1 == '!' && CharacterFunctions::compareUpTo (input + 2, CharPointer_ASCII ("[CDATA["), 7) == 0)
522 {
523 input += 9;
524 auto inputStart = input;
525
526 for (;;)
527 {
528 auto c0 = *input;
529
530 if (c0 == 0)
531 {
532 setLastError ("unterminated CDATA section", false);
533 outOfData = true;
534 break;
535 }
536
537 if (c0 == ']' && input[1] == ']' && input[2] == '>')
538 {
539 childAppender.append (XmlElement::createTextElement (String (inputStart, input)));
540 input += 3;
541 break;
542 }
543
544 ++input;
545 }
546 }
547 else
548 {
549 // this is some other element, so parse and add it..
550 if (auto* n = readNextElement (true))
551 childAppender.append (n);
552 else
553 break;
554 }
555 }
556 else // must be a character block
557 {
558 input = preWhitespaceInput; // roll back to include the leading whitespace
559 MemoryOutputStream textElementContent;
560 bool contentShouldBeUsed = ! ignoreEmptyTextElements;
561
562 for (;;)
563 {
564 auto c = *input;
565
566 if (c == '<')
567 {
568 if (input[1] == '!' && input[2] == '-' && input[3] == '-')
569 {
570 input += 4;
571 auto closeComment = input.indexOf (CharPointer_ASCII ("-->"));
572
573 if (closeComment < 0)
574 {
575 setLastError ("unterminated comment", false);
576 outOfData = true;
577 return;
578 }
579
580 input += closeComment + 3;
581 continue;
582 }
583
584 break;
585 }
586
587 if (c == 0)
588 {
589 setLastError ("unmatched tags", false);
590 outOfData = true;
591 return;
592 }
593
594 if (c == '&')
595 {
596 String entity;
597 readEntity (entity);
598
599 if (entity.startsWithChar ('<') && entity [1] != 0)
600 {
601 auto oldInput = input;
602 auto oldOutOfData = outOfData;
603
604 input = entity.getCharPointer();
605 outOfData = false;
606
607 while (auto* n = readNextElement (true))
608 childAppender.append (n);
609
610 input = oldInput;
611 outOfData = oldOutOfData;
612 }
613 else
614 {
615 textElementContent << entity;
616 contentShouldBeUsed = contentShouldBeUsed || entity.containsNonWhitespaceChars();
617 }
618 }
619 else
620 {
621 for (;; ++input)
622 {
623 auto nextChar = *input;
624
625 if (nextChar == '\r')
626 {
627 nextChar = '\n';
628
629 if (input[1] == '\n')
630 continue;
631 }
632
633 if (nextChar == '<' || nextChar == '&')
634 break;
635
636 if (nextChar == 0)
637 {
638 setLastError ("unmatched tags", false);
639 outOfData = true;
640 return;
641 }
642
643 textElementContent.appendUTF8Char (nextChar);
644 contentShouldBeUsed = contentShouldBeUsed || ! CharacterFunctions::isWhitespace (nextChar);
645 }
646 }
647 }
648
649 if (contentShouldBeUsed)
650 childAppender.append (XmlElement::createTextElement (textElementContent.toUTF8()));
651 }
652 }
653}
654
655void XmlDocument::readEntity (String& result)
656{
657 // skip over the ampersand
658 ++input;
659
660 if (input.compareIgnoreCaseUpTo (CharPointer_ASCII ("amp;"), 4) == 0)
661 {
662 input += 4;
663 result += '&';
664 }
665 else if (input.compareIgnoreCaseUpTo (CharPointer_ASCII ("quot;"), 5) == 0)
666 {
667 input += 5;
668 result += '"';
669 }
670 else if (input.compareIgnoreCaseUpTo (CharPointer_ASCII ("apos;"), 5) == 0)
671 {
672 input += 5;
673 result += '\'';
674 }
675 else if (input.compareIgnoreCaseUpTo (CharPointer_ASCII ("lt;"), 3) == 0)
676 {
677 input += 3;
678 result += '<';
679 }
680 else if (input.compareIgnoreCaseUpTo (CharPointer_ASCII ("gt;"), 3) == 0)
681 {
682 input += 3;
683 result += '>';
684 }
685 else if (*input == '#')
686 {
687 int charCode = 0;
688 ++input;
689
690 if (*input == 'x' || *input == 'X')
691 {
692 ++input;
693 int numChars = 0;
694
695 while (input[0] != ';')
696 {
697 auto hexValue = CharacterFunctions::getHexDigitValue (input[0]);
698
699 if (hexValue < 0 || ++numChars > 8)
700 {
701 setLastError ("illegal escape sequence", true);
702 break;
703 }
704
705 charCode = (charCode << 4) | hexValue;
706 ++input;
707 }
708
709 ++input;
710 }
711 else if (input[0] >= '0' && input[0] <= '9')
712 {
713 int numChars = 0;
714
715 while (input[0] != ';')
716 {
717 if (++numChars > 12)
718 {
719 setLastError ("illegal escape sequence", true);
720 break;
721 }
722
723 charCode = charCode * 10 + ((int) input[0] - '0');
724 ++input;
725 }
726
727 ++input;
728 }
729 else
730 {
731 setLastError ("illegal escape sequence", true);
732 result += '&';
733 return;
734 }
735
736 result << (juce_wchar) charCode;
737 }
738 else
739 {
740 auto entityNameStart = input;
741 auto closingSemiColon = input.indexOf ((juce_wchar) ';');
742
743 if (closingSemiColon < 0)
744 {
745 outOfData = true;
746 result += '&';
747 }
748 else
749 {
750 input += closingSemiColon + 1;
751 result += expandExternalEntity (String (entityNameStart, (size_t) closingSemiColon));
752 }
753 }
754}
755
756String XmlDocument::expandEntity (const String& ent)
757{
758 if (ent.equalsIgnoreCase ("amp")) return String::charToString ('&');
759 if (ent.equalsIgnoreCase ("quot")) return String::charToString ('"');
760 if (ent.equalsIgnoreCase ("apos")) return String::charToString ('\'');
761 if (ent.equalsIgnoreCase ("lt")) return String::charToString ('<');
762 if (ent.equalsIgnoreCase ("gt")) return String::charToString ('>');
763
764 if (ent[0] == '#')
765 {
766 auto char1 = ent[1];
767
768 if (char1 == 'x' || char1 == 'X')
769 return String::charToString (static_cast<juce_wchar> (ent.substring (2).getHexValue32()));
770
771 if (char1 >= '0' && char1 <= '9')
772 return String::charToString (static_cast<juce_wchar> (ent.substring (1).getIntValue()));
773
774 setLastError ("illegal escape sequence", false);
775 return String::charToString ('&');
776 }
777
778 return expandExternalEntity (ent);
779}
780
781String XmlDocument::expandExternalEntity (const String& entity)
782{
783 if (needToLoadDTD)
784 {
785 if (dtdText.isNotEmpty())
786 {
787 dtdText = dtdText.trimCharactersAtEnd (">");
788 tokenisedDTD.addTokens (dtdText, true);
789
790 if (tokenisedDTD[tokenisedDTD.size() - 2].equalsIgnoreCase ("system")
791 && tokenisedDTD[tokenisedDTD.size() - 1].isQuotedString())
792 {
793 auto fn = tokenisedDTD[tokenisedDTD.size() - 1];
794
795 tokenisedDTD.clear();
796 tokenisedDTD.addTokens (getFileContents (fn), true);
797 }
798 else
799 {
800 tokenisedDTD.clear();
801 auto openBracket = dtdText.indexOfChar ('[');
802
803 if (openBracket > 0)
804 {
805 auto closeBracket = dtdText.lastIndexOfChar (']');
806
807 if (closeBracket > openBracket)
808 tokenisedDTD.addTokens (dtdText.substring (openBracket + 1,
809 closeBracket), true);
810 }
811 }
812
813 for (int i = tokenisedDTD.size(); --i >= 0;)
814 {
815 if (tokenisedDTD[i].startsWithChar ('%')
816 && tokenisedDTD[i].endsWithChar (';'))
817 {
818 auto parsed = getParameterEntity (tokenisedDTD[i].substring (1, tokenisedDTD[i].length() - 1));
819 StringArray newToks;
820 newToks.addTokens (parsed, true);
821
822 tokenisedDTD.remove (i);
823
824 for (int j = newToks.size(); --j >= 0;)
825 tokenisedDTD.insert (i, newToks[j]);
826 }
827 }
828 }
829
830 needToLoadDTD = false;
831 }
832
833 for (int i = 0; i < tokenisedDTD.size(); ++i)
834 {
835 if (tokenisedDTD[i] == entity)
836 {
837 if (tokenisedDTD[i - 1].equalsIgnoreCase ("<!entity"))
838 {
839 auto ent = tokenisedDTD [i + 1].trimCharactersAtEnd (">").trim().unquoted();
840
841 // check for sub-entities..
842 auto ampersand = ent.indexOfChar ('&');
843
844 while (ampersand >= 0)
845 {
846 auto semiColon = ent.indexOf (i + 1, ";");
847
848 if (semiColon < 0)
849 {
850 setLastError ("entity without terminating semi-colon", false);
851 break;
852 }
853
854 auto resolved = expandEntity (ent.substring (i + 1, semiColon));
855
856 ent = ent.substring (0, ampersand)
857 + resolved
858 + ent.substring (semiColon + 1);
859
860 ampersand = ent.indexOfChar (semiColon + 1, '&');
861 }
862
863 return ent;
864 }
865 }
866 }
867
868 setLastError ("unknown entity", true);
869 return entity;
870}
871
872String XmlDocument::getParameterEntity (const String& entity)
873{
874 for (int i = 0; i < tokenisedDTD.size(); ++i)
875 {
876 if (tokenisedDTD[i] == entity
877 && tokenisedDTD [i - 1] == "%"
878 && tokenisedDTD [i - 2].equalsIgnoreCase ("<!entity"))
879 {
880 auto ent = tokenisedDTD [i + 1].trimCharactersAtEnd (">");
881
882 if (ent.equalsIgnoreCase ("system"))
883 return getFileContents (tokenisedDTD [i + 2].trimCharactersAtEnd (">"));
884
885 return ent.trim().unquoted();
886 }
887 }
888
889 return entity;
890}
891
892}
int indexOf(ParameterType elementToLookFor) const
Definition juce_Array.h:382
static bool isByteOrderMarkBigEndian(const void *possibleByteOrder) noexcept
static bool isByteOrderMarkLittleEndian(const void *possibleByteOrder) noexcept
static bool isByteOrderMark(const void *possibleByteOrder) noexcept
static int getHexDigitValue(juce_wchar digit) noexcept
static bool isWhitespace(char character) noexcept
static bool isLetterOrDigit(char character) noexcept
static CharPointerType1 find(CharPointerType1 textToSearch, const CharPointerType2 substringToLookFor) noexcept
static int compareUpTo(CharPointerType1 s1, CharPointerType2 s2, int maxChars) noexcept
const void * getData() const noexcept
size_t getDataSize() const noexcept
int64 writeFromInputStream(InputStream &, int64 maxNumBytesToWrite) override
virtual bool writeByte(char byte)
void insert(int index, String stringToAdd)
int size() const noexcept
void remove(int index)
int addTokens(StringRef stringToTokenise, bool preserveQuotedStrings)
CharPointerType getCharPointer() const noexcept
int indexOfChar(juce_wchar characterToLookFor) const noexcept
String trim() const
bool isEmpty() const noexcept
void clear() noexcept
int lastIndexOfChar(juce_wchar character) const noexcept
String trimCharactersAtEnd(StringRef charactersToTrim) const
static String charToString(juce_wchar character)
String substring(int startIndex, int endIndex) const
bool isNotEmpty() const noexcept
const String & getLastParseError() const noexcept
std::unique_ptr< XmlElement > getDocumentElementIfTagMatches(StringRef requiredTag)
std::unique_ptr< XmlElement > getDocumentElement(bool onlyReadOuterDocumentElement=false)
XmlDocument(const String &documentText)
static std::unique_ptr< XmlElement > parse(const File &file)
void setInputSource(InputSource *newSource) noexcept
void setEmptyTextElementsIgnored(bool shouldBeIgnored) noexcept
static XmlElement * createTextElement(const String &text)