2 ==============================================================================
4 This file is part of the Water library.
5 Copyright (c) 2016 ROLI Ltd.
6 Copyright (C) 2017-2022 Filipe Coelho <falktx@falktx.com>
8 Permission is granted to use this software under the terms of the ISC license
9 http://www.isc.org/downloads/software-support-policy/isc-license/
11 Permission to use, copy, modify, and/or distribute this software for any
12 purpose with or without fee is hereby granted, provided that the above
13 copyright notice and this permission notice appear in all copies.
15 THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD
16 TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
17 FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT,
18 OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
19 USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
20 TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
23 ==============================================================================
26 #include "XmlElement.h"
27 #include "../streams/MemoryOutputStream.h"
28 #include "../streams/OutputStream.h"
29 #include "../text/NewLine.h"
33 inline bool isValidXmlNameStartCharacter (const water_uchar character
) noexcept
35 return character
== ':'
37 || (character
>= 'a' && character
<= 'z')
38 || (character
>= 'A' && character
<= 'Z')
39 || (character
>= 0xc0 && character
<= 0xd6)
40 || (character
>= 0xd8 && character
<= 0xf6)
41 || (character
>= 0xf8 && character
<= 0x2ff)
42 || (character
>= 0x370 && character
<= 0x37d)
43 || (character
>= 0x37f && character
<= 0x1fff)
44 || (character
>= 0x200c && character
<= 0x200d)
45 || (character
>= 0x2070 && character
<= 0x218f)
46 || (character
>= 0x2c00 && character
<= 0x2fef)
47 || (character
>= 0x3001 && character
<= 0xd7ff)
48 || (character
>= 0xf900 && character
<= 0xfdcf)
49 || (character
>= 0xfdf0 && character
<= 0xfffd)
50 || (character
>= 0x10000 && character
<= 0xeffff);
53 inline bool isValidXmlNameBodyCharacter (const water_uchar character
) noexcept
55 return isValidXmlNameStartCharacter (character
)
59 || (character
>= '0' && character
<= '9')
60 || (character
>= 0x300 && character
<= 0x036f)
61 || (character
>= 0x203f && character
<= 0x2040);
64 XmlElement::XmlAttributeNode::XmlAttributeNode (const XmlAttributeNode
& other
) noexcept
70 XmlElement::XmlAttributeNode::XmlAttributeNode (const Identifier
& n
, const String
& v
) noexcept
73 wassert (isValidXmlName (name
));
76 XmlElement::XmlAttributeNode::XmlAttributeNode (CharPointer_UTF8 nameStart
, CharPointer_UTF8 nameEnd
)
77 : name (nameStart
, nameEnd
)
79 wassert (isValidXmlName (name
));
82 //==============================================================================
83 XmlElement::XmlElement (const String
& tag
)
86 wassert (isValidXmlName (tagName
));
89 XmlElement::XmlElement (const char* tag
)
92 wassert (isValidXmlName (tagName
));
95 XmlElement::XmlElement (StringRef tag
)
98 wassert (isValidXmlName (tagName
));
101 XmlElement::XmlElement (const Identifier
& tag
)
102 : tagName (tag
.toString())
104 wassert (isValidXmlName (tagName
));
107 XmlElement::XmlElement (CharPointer_UTF8 tagNameStart
, CharPointer_UTF8 tagNameEnd
)
108 : tagName (StartEndString (tagNameStart
, tagNameEnd
))
110 wassert (isValidXmlName (tagName
));
113 XmlElement::XmlElement (int /*dummy*/) noexcept
117 XmlElement::XmlElement (const XmlElement
& other
)
118 : tagName (other
.tagName
)
120 copyChildrenAndAttributesFrom (other
);
123 XmlElement
& XmlElement::operator= (const XmlElement
& other
)
127 removeAllAttributes();
128 deleteAllChildElements();
129 tagName
= other
.tagName
;
130 copyChildrenAndAttributesFrom (other
);
136 void XmlElement::copyChildrenAndAttributesFrom (const XmlElement
& other
)
138 wassert (firstChildElement
.get() == nullptr);
139 firstChildElement
.addCopyOfList (other
.firstChildElement
);
141 wassert (attributes
.get() == nullptr);
142 attributes
.addCopyOfList (other
.attributes
);
145 XmlElement::~XmlElement() noexcept
147 firstChildElement
.deleteAll();
148 attributes
.deleteAll();
151 //==============================================================================
152 namespace XmlOutputFunctions
154 #if 0 // (These functions are just used to generate the lookup table used below)
155 bool isLegalXmlCharSlow (const water_uchar character
) noexcept
157 if ((character
>= 'a' && character
<= 'z')
158 || (character
>= 'A' && character
<= 'Z')
159 || (character
>= '0' && character
<= '9'))
162 const char* t
= " .,;:-()_+=?!'#@[]/\\*%~{}$|";
166 if (((water_uchar
) (uint8
) *t
) == character
)
174 void generateLegalCharLookupTable()
177 for (int i
= 0; i
< 256; ++i
)
178 if (isLegalXmlCharSlow (i
))
179 n
[i
>> 3] |= (1 << (i
& 7));
182 for (int i
= 0; i
< 32; ++i
)
183 s
<< (int) n
[i
] << ", ";
189 static bool isLegalXmlChar (const uint32 c
) noexcept
191 static const unsigned char legalChars
[] = { 0, 0, 0, 0, 187, 255, 255, 175, 255,
192 255, 255, 191, 254, 255, 255, 127 };
193 return c
< sizeof (legalChars
) * 8
194 && (legalChars
[c
>> 3] & (1 << (c
& 7))) != 0;
197 static void escapeIllegalXmlChars (OutputStream
& outputStream
, const String
& text
, const bool changeNewLines
)
199 CharPointer_UTF8
t (text
.getCharPointer());
203 const uint32 character
= (uint32
) t
.getAndAdvance();
208 if (isLegalXmlChar (character
))
210 outputStream
<< (char) character
;
216 case '&': outputStream
<< "&"; break;
217 case '"': outputStream
<< """; break;
218 case '>': outputStream
<< ">"; break;
219 case '<': outputStream
<< "<"; break;
223 if (! changeNewLines
)
225 outputStream
<< (char) character
;
230 outputStream
<< "&#" << ((int) character
) << ';';
237 static void writeSpaces (OutputStream
& out
, const size_t numSpaces
)
239 out
.writeRepeatedByte (' ', numSpaces
);
243 void XmlElement::writeElementAsText (OutputStream
& outputStream
,
244 const int indentationLevel
,
245 const int lineWrapLength
) const
247 using namespace XmlOutputFunctions
;
251 if (indentationLevel
>= 0)
252 writeSpaces (outputStream
, (size_t) indentationLevel
);
254 if (! isTextElement())
256 outputStream
.writeByte ('<');
257 outputStream
<< tagName
;
260 const size_t attIndent
= (size_t) (indentationLevel
+ tagName
.length() + 1);
263 for (const XmlAttributeNode
* att
= attributes
; att
!= nullptr; att
= att
->nextListItem
)
265 if (lineLen
> lineWrapLength
&& indentationLevel
>= 0)
267 outputStream
<< newLine
;
268 writeSpaces (outputStream
, attIndent
);
272 const int64 startPos
= outputStream
.getPosition();
273 outputStream
.writeByte (' ');
274 outputStream
<< att
->name
;
275 outputStream
.write ("=\"", 2);
276 escapeIllegalXmlChars (outputStream
, att
->value
, true);
277 outputStream
.writeByte ('"');
278 lineLen
+= (int) (outputStream
.getPosition() - startPos
);
282 if (firstChildElement
!= nullptr)
284 outputStream
.writeByte ('>');
286 bool lastWasTextNode
= false;
288 for (XmlElement
* child
= firstChildElement
; child
!= nullptr; child
= child
->nextListItem
)
290 if (child
->isTextElement())
292 escapeIllegalXmlChars (outputStream
, child
->getText(), false);
293 lastWasTextNode
= true;
297 if (indentationLevel
>= 0 && ! lastWasTextNode
)
298 outputStream
<< newLine
;
300 child
->writeElementAsText (outputStream
,
301 lastWasTextNode
? 0 : (indentationLevel
+ (indentationLevel
>= 0 ? 2 : 0)), lineWrapLength
);
302 lastWasTextNode
= false;
306 if (indentationLevel
>= 0 && ! lastWasTextNode
)
308 outputStream
<< newLine
;
309 writeSpaces (outputStream
, (size_t) indentationLevel
);
312 outputStream
.write ("</", 2);
313 outputStream
<< tagName
;
314 outputStream
.writeByte ('>');
318 outputStream
.write ("/>", 2);
323 escapeIllegalXmlChars (outputStream
, getText(), false);
327 String
XmlElement::createDocument (StringRef dtdToUse
,
328 const bool allOnOneLine
,
329 const bool includeXmlHeader
,
330 StringRef encodingType
,
331 const int lineWrapLength
) const
333 MemoryOutputStream
mem (2048);
334 writeToStream (mem
, dtdToUse
, allOnOneLine
, includeXmlHeader
, encodingType
, lineWrapLength
);
339 void XmlElement::writeToStream (OutputStream
& output
,
341 const bool allOnOneLine
,
342 const bool includeXmlHeader
,
343 StringRef encodingType
,
344 const int lineWrapLength
) const
346 using namespace XmlOutputFunctions
;
350 if (includeXmlHeader
)
352 output
<< "<?xml version=\"1.0\" encoding=\"" << encodingType
<< "\"?>";
355 output
.writeByte (' ');
357 output
<< newLine
<< newLine
;
360 if (dtdToUse
.isNotEmpty())
365 output
.writeByte (' ');
370 writeElementAsText (output
, allOnOneLine
? -1 : 0, lineWrapLength
);
377 bool XmlElement::writeToFile (const File
& file
,
379 StringRef encodingType
,
380 const int lineWrapLength
) const
382 TemporaryFile
tempFile (file
);
385 FileOutputStream
out (tempFile
.getFile());
387 if (! out
.openedOk())
390 writeToStream (out
, dtdToUse
, false, true, encodingType
, lineWrapLength
);
392 out
.flush(); // (called explicitly to force an fsync on posix)
394 if (out
.getStatus().failed())
398 return tempFile
.overwriteTargetFileWithTemporary();
402 //==============================================================================
403 bool XmlElement::hasTagName (StringRef possibleTagName
) const noexcept
405 const bool matches
= tagName
.equalsIgnoreCase (possibleTagName
);
407 // XML tags should be case-sensitive, so although this method allows a
408 // case-insensitive match to pass, you should try to avoid this.
409 wassert ((! matches
) || tagName
== possibleTagName
);
414 String
XmlElement::getNamespace() const
416 return tagName
.upToFirstOccurrenceOf (":", false, false);
419 String
XmlElement::getTagNameWithoutNamespace() const
421 return tagName
.fromLastOccurrenceOf (":", false, false);
424 bool XmlElement::hasTagNameIgnoringNamespace (StringRef possibleTagName
) const
426 return hasTagName (possibleTagName
) || getTagNameWithoutNamespace() == possibleTagName
;
429 XmlElement
* XmlElement::getNextElementWithTagName (StringRef requiredTagName
) const
431 XmlElement
* e
= nextListItem
;
433 while (e
!= nullptr && ! e
->hasTagName (requiredTagName
))
439 //==============================================================================
440 int XmlElement::getNumAttributes() const noexcept
442 return attributes
.size();
445 static const String
& getEmptyStringRef() noexcept
451 static const std::string
& getEmptyStdStringRef() noexcept
453 static std::string empty
;
457 const std::string
& XmlElement::getAttributeName (const int index
) const noexcept
459 if (const XmlAttributeNode
* const att
= attributes
[index
])
460 return att
->name
.toString();
462 return getEmptyStdStringRef();
465 const String
& XmlElement::getAttributeValue (const int index
) const noexcept
467 if (const XmlAttributeNode
* const att
= attributes
[index
])
470 return getEmptyStringRef();
473 XmlElement::XmlAttributeNode
* XmlElement::getAttribute (StringRef attributeName
) const noexcept
475 for (XmlAttributeNode
* att
= attributes
; att
!= nullptr; att
= att
->nextListItem
)
476 if (att
->name
== attributeName
)
482 bool XmlElement::hasAttribute (StringRef attributeName
) const noexcept
484 return getAttribute (attributeName
) != nullptr;
487 //==============================================================================
488 const String
& XmlElement::getStringAttribute (StringRef attributeName
) const noexcept
490 if (const XmlAttributeNode
* att
= getAttribute (attributeName
))
493 return getEmptyStringRef();
496 String
XmlElement::getStringAttribute (StringRef attributeName
, const String
& defaultReturnValue
) const
498 if (const XmlAttributeNode
* att
= getAttribute (attributeName
))
501 return defaultReturnValue
;
504 int XmlElement::getIntAttribute (StringRef attributeName
, const int defaultReturnValue
) const
506 if (const XmlAttributeNode
* att
= getAttribute (attributeName
))
507 return att
->value
.getIntValue();
509 return defaultReturnValue
;
512 double XmlElement::getDoubleAttribute (StringRef attributeName
, const double defaultReturnValue
) const
514 if (const XmlAttributeNode
* att
= getAttribute (attributeName
))
515 return att
->value
.getDoubleValue();
517 return defaultReturnValue
;
520 bool XmlElement::getBoolAttribute (StringRef attributeName
, const bool defaultReturnValue
) const
522 if (const XmlAttributeNode
* att
= getAttribute (attributeName
))
524 const water_uchar firstChar
= *(att
->value
.getCharPointer().findEndOfWhitespace());
526 return firstChar
== '1'
533 return defaultReturnValue
;
536 bool XmlElement::compareAttribute (StringRef attributeName
,
537 StringRef stringToCompareAgainst
,
538 const bool ignoreCase
) const noexcept
540 if (const XmlAttributeNode
* att
= getAttribute (attributeName
))
541 return ignoreCase
? att
->value
.equalsIgnoreCase (stringToCompareAgainst
)
542 : att
->value
== stringToCompareAgainst
;
547 //==============================================================================
548 void XmlElement::setAttribute (const Identifier
& attributeName
, const String
& value
)
550 if (attributes
== nullptr)
552 attributes
= new XmlAttributeNode (attributeName
, value
);
556 for (XmlAttributeNode
* att
= attributes
; ; att
= att
->nextListItem
)
558 if (att
->name
== attributeName
)
564 if (att
->nextListItem
== nullptr)
566 att
->nextListItem
= new XmlAttributeNode (attributeName
, value
);
573 void XmlElement::setAttribute (const Identifier
& attributeName
, const int number
)
575 setAttribute (attributeName
, String (number
));
578 void XmlElement::setAttribute (const Identifier
& attributeName
, const double number
)
580 setAttribute (attributeName
, String (number
, 20));
583 void XmlElement::removeAttribute (const Identifier
& attributeName
) noexcept
585 for (LinkedListPointer
<XmlAttributeNode
>* att
= &attributes
;
586 att
->get() != nullptr;
587 att
= &(att
->get()->nextListItem
))
589 if (att
->get()->name
== attributeName
)
591 delete att
->removeNext();
597 void XmlElement::removeAllAttributes() noexcept
599 attributes
.deleteAll();
602 //==============================================================================
603 int XmlElement::getNumChildElements() const noexcept
605 return firstChildElement
.size();
608 XmlElement
* XmlElement::getChildElement (const int index
) const noexcept
610 return firstChildElement
[index
].get();
613 XmlElement
* XmlElement::getChildByName (StringRef childName
) const noexcept
615 wassert (! childName
.isEmpty());
617 for (XmlElement
* child
= firstChildElement
; child
!= nullptr; child
= child
->nextListItem
)
618 if (child
->hasTagName (childName
))
624 XmlElement
* XmlElement::getChildByAttribute (StringRef attributeName
, StringRef attributeValue
) const noexcept
626 wassert (! attributeName
.isEmpty());
628 for (XmlElement
* child
= firstChildElement
; child
!= nullptr; child
= child
->nextListItem
)
629 if (child
->compareAttribute (attributeName
, attributeValue
))
635 void XmlElement::addChildElement (XmlElement
* const newNode
) noexcept
637 if (newNode
!= nullptr)
639 // The element being added must not be a child of another node!
640 wassert (newNode
->nextListItem
== nullptr);
642 firstChildElement
.append (newNode
);
646 void XmlElement::insertChildElement (XmlElement
* const newNode
, int indexToInsertAt
) noexcept
648 if (newNode
!= nullptr)
650 // The element being added must not be a child of another node!
651 wassert (newNode
->nextListItem
== nullptr);
653 firstChildElement
.insertAtIndex (indexToInsertAt
, newNode
);
657 void XmlElement::prependChildElement (XmlElement
* newNode
) noexcept
659 if (newNode
!= nullptr)
661 // The element being added must not be a child of another node!
662 wassert (newNode
->nextListItem
== nullptr);
664 firstChildElement
.insertNext (newNode
);
668 XmlElement
* XmlElement::createNewChildElement (StringRef childTagName
)
670 XmlElement
* const newElement
= new XmlElement (childTagName
);
671 addChildElement (newElement
);
675 bool XmlElement::replaceChildElement (XmlElement
* const currentChildElement
,
676 XmlElement
* const newNode
) noexcept
678 if (newNode
!= nullptr)
680 if (LinkedListPointer
<XmlElement
>* const p
= firstChildElement
.findPointerTo (currentChildElement
))
682 if (currentChildElement
!= newNode
)
683 delete p
->replaceNext (newNode
);
692 void XmlElement::removeChildElement (XmlElement
* const childToRemove
,
693 const bool shouldDeleteTheChild
) noexcept
695 if (childToRemove
!= nullptr)
697 firstChildElement
.remove (childToRemove
);
699 if (shouldDeleteTheChild
)
700 delete childToRemove
;
704 bool XmlElement::isEquivalentTo (const XmlElement
* const other
,
705 const bool ignoreOrderOfAttributes
) const noexcept
709 if (other
== nullptr || tagName
!= other
->tagName
)
712 if (ignoreOrderOfAttributes
)
716 for (const XmlAttributeNode
* att
= attributes
; att
!= nullptr; att
= att
->nextListItem
)
718 if (! other
->compareAttribute (att
->name
, att
->value
))
724 if (totalAtts
!= other
->getNumAttributes())
729 const XmlAttributeNode
* thisAtt
= attributes
;
730 const XmlAttributeNode
* otherAtt
= other
->attributes
;
734 if (thisAtt
== nullptr || otherAtt
== nullptr)
736 if (thisAtt
== otherAtt
) // both nullptr, so it's a match
742 if (thisAtt
->name
!= otherAtt
->name
743 || thisAtt
->value
!= otherAtt
->value
)
748 thisAtt
= thisAtt
->nextListItem
;
749 otherAtt
= otherAtt
->nextListItem
;
753 const XmlElement
* thisChild
= firstChildElement
;
754 const XmlElement
* otherChild
= other
->firstChildElement
;
758 if (thisChild
== nullptr || otherChild
== nullptr)
760 if (thisChild
== otherChild
) // both 0, so it's a match
766 if (! thisChild
->isEquivalentTo (otherChild
, ignoreOrderOfAttributes
))
769 thisChild
= thisChild
->nextListItem
;
770 otherChild
= otherChild
->nextListItem
;
777 void XmlElement::deleteAllChildElements() noexcept
779 firstChildElement
.deleteAll();
782 void XmlElement::deleteAllChildElementsWithTagName (StringRef name
) noexcept
784 for (XmlElement
* child
= firstChildElement
; child
!= nullptr;)
786 XmlElement
* const nextChild
= child
->nextListItem
;
788 if (child
->hasTagName (name
))
789 removeChildElement (child
, true);
795 bool XmlElement::containsChildElement (const XmlElement
* const possibleChild
) const noexcept
797 return firstChildElement
.contains (possibleChild
);
800 XmlElement
* XmlElement::findParentElementOf (const XmlElement
* const elementToLookFor
) noexcept
802 if (this == elementToLookFor
|| elementToLookFor
== nullptr)
805 for (XmlElement
* child
= firstChildElement
; child
!= nullptr; child
= child
->nextListItem
)
807 if (elementToLookFor
== child
)
810 if (XmlElement
* const found
= child
->findParentElementOf (elementToLookFor
))
817 void XmlElement::getChildElementsAsArray (XmlElement
** elems
) const noexcept
819 firstChildElement
.copyToArray (elems
);
822 void XmlElement::reorderChildElements (XmlElement
** const elems
, const int num
) noexcept
824 XmlElement
* e
= firstChildElement
= elems
[0];
826 for (int i
= 1; i
< num
; ++i
)
828 e
->nextListItem
= elems
[i
];
832 e
->nextListItem
= nullptr;
835 //==============================================================================
836 bool XmlElement::isTextElement() const noexcept
838 return tagName
.isEmpty();
841 static const char* const water_xmltextContentAttributeName
= "text";
843 const String
& XmlElement::getText() const noexcept
845 wassert (isTextElement()); // you're trying to get the text from an element that
846 // isn't actually a text element.. If this contains text sub-nodes, you
847 // probably want to use getAllSubText instead.
849 return getStringAttribute (water_xmltextContentAttributeName
);
852 void XmlElement::setText (const String
& newText
)
854 CARLA_SAFE_ASSERT_RETURN(isTextElement(),);
855 setAttribute (water_xmltextContentAttributeName
, newText
);
858 String
XmlElement::getAllSubText() const
863 if (getNumChildElements() == 1)
864 return firstChildElement
.get()->getAllSubText();
866 MemoryOutputStream
mem (1024);
868 for (const XmlElement
* child
= firstChildElement
; child
!= nullptr; child
= child
->nextListItem
)
869 mem
<< child
->getAllSubText();
874 String
XmlElement::getChildElementAllSubText (StringRef childTagName
, const String
& defaultReturnValue
) const
876 if (const XmlElement
* const child
= getChildByName (childTagName
))
877 return child
->getAllSubText();
879 return defaultReturnValue
;
882 XmlElement
* XmlElement::createTextElement (const String
& text
)
884 XmlElement
* const e
= new XmlElement ((int) 0);
885 e
->setAttribute (water_xmltextContentAttributeName
, text
);
889 bool XmlElement::isValidXmlName (StringRef text
) noexcept
891 if (text
.isEmpty() || ! isValidXmlNameStartCharacter (text
.text
.getAndAdvance()))
899 if (! isValidXmlNameBodyCharacter (text
.text
.getAndAdvance()))
904 void XmlElement::addTextElement (const String
& text
)
906 addChildElement (createTextElement (text
));
909 void XmlElement::deleteAllTextElements() noexcept
911 for (XmlElement
* child
= firstChildElement
; child
!= nullptr;)
913 XmlElement
* const next
= child
->nextListItem
;
915 if (child
->isTextElement())
916 removeChildElement (child
, true);