Cleanup
[carla.git] / source / modules / water / xml / XmlElement.cpp
blob9782156dbd797a54423de64f829300039e8d47f8
1 /*
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
21 OF THIS SOFTWARE.
23 ==============================================================================
26 #include "XmlElement.h"
27 #include "../streams/MemoryOutputStream.h"
28 #include "../streams/OutputStream.h"
29 #include "../text/NewLine.h"
31 namespace water {
33 inline bool isValidXmlNameStartCharacter (const water_uchar character) noexcept
35 return character == ':'
36 || 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)
56 || character == '-'
57 || character == '.'
58 || character == 0xb7
59 || (character >= '0' && character <= '9')
60 || (character >= 0x300 && character <= 0x036f)
61 || (character >= 0x203f && character <= 0x2040);
64 XmlElement::XmlAttributeNode::XmlAttributeNode (const XmlAttributeNode& other) noexcept
65 : name (other.name),
66 value (other.value)
70 XmlElement::XmlAttributeNode::XmlAttributeNode (const Identifier& n, const String& v) noexcept
71 : name (n), value (v)
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)
84 : tagName (tag)
86 wassert (isValidXmlName (tagName));
89 XmlElement::XmlElement (const char* tag)
90 : tagName (tag)
92 wassert (isValidXmlName (tagName));
95 XmlElement::XmlElement (StringRef tag)
96 : tagName (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)
125 if (this != &other)
127 removeAllAttributes();
128 deleteAllChildElements();
129 tagName = other.tagName;
130 copyChildrenAndAttributesFrom (other);
133 return *this;
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'))
160 return true;
162 const char* t = " .,;:-()_+=?!'#@[]/\\*%~{}$|";
166 if (((water_uchar) (uint8) *t) == character)
167 return true;
169 while (*++t != 0);
171 return false;
174 void generateLegalCharLookupTable()
176 uint8 n[32] = { 0 };
177 for (int i = 0; i < 256; ++i)
178 if (isLegalXmlCharSlow (i))
179 n[i >> 3] |= (1 << (i & 7));
181 String s;
182 for (int i = 0; i < 32; ++i)
183 s << (int) n[i] << ", ";
185 DBG (s);
187 #endif
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());
201 for (;;)
203 const uint32 character = (uint32) t.getAndAdvance();
205 if (character == 0)
206 break;
208 if (isLegalXmlChar (character))
210 outputStream << (char) character;
212 else
214 switch (character)
216 case '&': outputStream << "&amp;"; break;
217 case '"': outputStream << "&quot;"; break;
218 case '>': outputStream << "&gt;"; break;
219 case '<': outputStream << "&lt;"; break;
221 case '\n':
222 case '\r':
223 if (! changeNewLines)
225 outputStream << (char) character;
226 break;
228 // fall-through
229 default:
230 outputStream << "&#" << ((int) character) << ';';
231 break;
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;
249 NewLine newLine;
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);
261 int lineLen = 0;
263 for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem)
265 if (lineLen > lineWrapLength && indentationLevel >= 0)
267 outputStream << newLine;
268 writeSpaces (outputStream, attIndent);
269 lineLen = 0;
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;
295 else
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 ('>');
316 else
318 outputStream.write ("/>", 2);
321 else
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);
336 return mem.toUTF8();
339 void XmlElement::writeToStream (OutputStream& output,
340 StringRef dtdToUse,
341 const bool allOnOneLine,
342 const bool includeXmlHeader,
343 StringRef encodingType,
344 const int lineWrapLength) const
346 using namespace XmlOutputFunctions;
348 NewLine newLine;
350 if (includeXmlHeader)
352 output << "<?xml version=\"1.0\" encoding=\"" << encodingType << "\"?>";
354 if (allOnOneLine)
355 output.writeByte (' ');
356 else
357 output << newLine << newLine;
360 if (dtdToUse.isNotEmpty())
362 output << dtdToUse;
364 if (allOnOneLine)
365 output.writeByte (' ');
366 else
367 output << newLine;
370 writeElementAsText (output, allOnOneLine ? -1 : 0, lineWrapLength);
372 if (! allOnOneLine)
373 output << newLine;
376 #if 0
377 bool XmlElement::writeToFile (const File& file,
378 StringRef dtdToUse,
379 StringRef encodingType,
380 const int lineWrapLength) const
382 TemporaryFile tempFile (file);
385 FileOutputStream out (tempFile.getFile());
387 if (! out.openedOk())
388 return false;
390 writeToStream (out, dtdToUse, false, true, encodingType, lineWrapLength);
392 out.flush(); // (called explicitly to force an fsync on posix)
394 if (out.getStatus().failed())
395 return false;
398 return tempFile.overwriteTargetFileWithTemporary();
400 #endif
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);
411 return matches;
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))
434 e = e->nextListItem;
436 return e;
439 //==============================================================================
440 int XmlElement::getNumAttributes() const noexcept
442 return attributes.size();
445 static const String& getEmptyStringRef() noexcept
447 static String empty;
448 return empty;
451 static const std::string& getEmptyStdStringRef() noexcept
453 static std::string empty;
454 return 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])
468 return att->value;
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)
477 return att;
479 return nullptr;
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))
491 return att->value;
493 return getEmptyStringRef();
496 String XmlElement::getStringAttribute (StringRef attributeName, const String& defaultReturnValue) const
498 if (const XmlAttributeNode* att = getAttribute (attributeName))
499 return att->value;
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'
527 || firstChar == 't'
528 || firstChar == 'y'
529 || firstChar == 'T'
530 || firstChar == 'Y';
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;
544 return false;
547 //==============================================================================
548 void XmlElement::setAttribute (const Identifier& attributeName, const String& value)
550 if (attributes == nullptr)
552 attributes = new XmlAttributeNode (attributeName, value);
554 else
556 for (XmlAttributeNode* att = attributes; ; att = att->nextListItem)
558 if (att->name == attributeName)
560 att->value = value;
561 break;
564 if (att->nextListItem == nullptr)
566 att->nextListItem = new XmlAttributeNode (attributeName, value);
567 break;
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();
592 break;
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))
619 return child;
621 return nullptr;
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))
630 return child;
632 return nullptr;
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);
672 return 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);
685 return true;
689 return false;
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
707 if (this != other)
709 if (other == nullptr || tagName != other->tagName)
710 return false;
712 if (ignoreOrderOfAttributes)
714 int totalAtts = 0;
716 for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem)
718 if (! other->compareAttribute (att->name, att->value))
719 return false;
721 ++totalAtts;
724 if (totalAtts != other->getNumAttributes())
725 return false;
727 else
729 const XmlAttributeNode* thisAtt = attributes;
730 const XmlAttributeNode* otherAtt = other->attributes;
732 for (;;)
734 if (thisAtt == nullptr || otherAtt == nullptr)
736 if (thisAtt == otherAtt) // both nullptr, so it's a match
737 break;
739 return false;
742 if (thisAtt->name != otherAtt->name
743 || thisAtt->value != otherAtt->value)
745 return false;
748 thisAtt = thisAtt->nextListItem;
749 otherAtt = otherAtt->nextListItem;
753 const XmlElement* thisChild = firstChildElement;
754 const XmlElement* otherChild = other->firstChildElement;
756 for (;;)
758 if (thisChild == nullptr || otherChild == nullptr)
760 if (thisChild == otherChild) // both 0, so it's a match
761 break;
763 return false;
766 if (! thisChild->isEquivalentTo (otherChild, ignoreOrderOfAttributes))
767 return false;
769 thisChild = thisChild->nextListItem;
770 otherChild = otherChild->nextListItem;
774 return true;
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);
791 child = nextChild;
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)
803 return nullptr;
805 for (XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem)
807 if (elementToLookFor == child)
808 return this;
810 if (XmlElement* const found = child->findParentElementOf (elementToLookFor))
811 return found;
814 return nullptr;
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];
829 e = e->nextListItem;
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
860 if (isTextElement())
861 return getText();
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();
871 return mem.toUTF8();
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);
886 return e;
889 bool XmlElement::isValidXmlName (StringRef text) noexcept
891 if (text.isEmpty() || ! isValidXmlNameStartCharacter (text.text.getAndAdvance()))
892 return false;
894 for (;;)
896 if (text.isEmpty())
897 return true;
899 if (! isValidXmlNameBodyCharacter (text.text.getAndAdvance()))
900 return false;
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);
918 child = next;