26 inline static bool isValidXmlNameStartCharacter (juce_wchar character) noexcept
28 return character ==
':'
30 || (character >=
'a' && character <=
'z')
31 || (character >=
'A' && character <=
'Z')
32 || (character >= 0xc0 && character <= 0xd6)
33 || (character >= 0xd8 && character <= 0xf6)
34 || (character >= 0xf8 && character <= 0x2ff)
35 || (character >= 0x370 && character <= 0x37d)
36 || (character >= 0x37f && character <= 0x1fff)
37 || (character >= 0x200c && character <= 0x200d)
38 || (character >= 0x2070 && character <= 0x218f)
39 || (character >= 0x2c00 && character <= 0x2fef)
40 || (character >= 0x3001 && character <= 0xd7ff)
41 || (character >= 0xf900 && character <= 0xfdcf)
42 || (character >= 0xfdf0 && character <= 0xfffd)
43 || (character >= 0x10000 && character <= 0xeffff);
46 inline static bool isValidXmlNameBodyCharacter (juce_wchar character) noexcept
48 return isValidXmlNameStartCharacter (character)
52 || (character >=
'0' && character <=
'9')
53 || (character >= 0x300 && character <= 0x036f)
54 || (character >= 0x203f && character <= 0x2040);
57 XmlElement::XmlAttributeNode::XmlAttributeNode (
const XmlAttributeNode& other) noexcept
63 XmlElement::XmlAttributeNode::XmlAttributeNode (
const Identifier& n,
const String& v) noexcept
66 jassert (isValidXmlName (name));
70 : name (nameStart, nameEnd)
72 jassert (isValidXmlName (name));
77 : tagName (
StringPool::getGlobalPool().getPooledString (tag))
83 : tagName (
StringPool::getGlobalPool().getPooledString (tag))
89 : tagName (
StringPool::getGlobalPool().getPooledString (tag))
95 : tagName (tag.toString())
101 : tagName (
StringPool::getGlobalPool().getPooledString (tagNameStart, tagNameEnd))
111 : tagName (other.tagName)
113 copyChildrenAndAttributesFrom (other);
122 tagName = other.tagName;
123 copyChildrenAndAttributesFrom (other);
130 : nextListItem (std::move (other.nextListItem)),
131 firstChildElement (std::move (other.firstChildElement)),
132 attributes (std::move (other.attributes)),
133 tagName (std::move (other.tagName))
139 jassert (
this != &other);
141 removeAllAttributes();
142 deleteAllChildElements();
144 nextListItem = std::move (other.nextListItem);
145 firstChildElement = std::move (other.firstChildElement);
146 attributes = std::move (other.attributes);
147 tagName = std::move (other.tagName);
152 void XmlElement::copyChildrenAndAttributesFrom (
const XmlElement& other)
154 jassert (firstChildElement.get() ==
nullptr);
155 firstChildElement.addCopyOfList (other.firstChildElement);
157 jassert (attributes.
get() ==
nullptr);
163 firstChildElement.deleteAll();
168 namespace XmlOutputFunctions
170 #if 0 // (These functions are just used to generate the lookup table used below)
171 bool isLegalXmlCharSlow (
const juce_wchar character) noexcept
173 if ((character >=
'a' && character <=
'z')
174 || (character >=
'A' && character <=
'Z')
175 || (character >=
'0' && character <=
'9'))
178 const char* t =
" .,;:-()_+=?!'#@[]/\\*%~{}$|";
182 if (((juce_wchar) (uint8) *t) == character)
190 void generateLegalCharLookupTable()
193 for (
int i = 0; i < 256; ++i)
194 if (isLegalXmlCharSlow (i))
195 n[i >> 3] |= (1 << (i & 7));
198 for (
int i = 0; i < 32; ++i)
199 s << (
int) n[i] <<
", ";
205 static bool isLegalXmlChar (
const uint32 c) noexcept
207 static const unsigned char legalChars[] = { 0, 0, 0, 0, 187, 255, 255, 175, 255,
208 255, 255, 191, 254, 255, 255, 127 };
209 return c <
sizeof (legalChars) * 8
210 && (legalChars [c >> 3] & (1 << (c & 7))) != 0;
213 static void escapeIllegalXmlChars (OutputStream& outputStream,
const String& text,
const bool changeNewLines)
215 auto t = text.getCharPointer();
219 auto character = (uint32) t.getAndAdvance();
224 if (isLegalXmlChar (character))
226 outputStream << (char) character;
232 case '&': outputStream <<
"&";
break;
233 case '"': outputStream <<
""";
break;
234 case '>': outputStream <<
">";
break;
235 case '<': outputStream <<
"<";
break;
239 if (! changeNewLines)
241 outputStream << (char) character;
246 outputStream <<
"&#" << ((int) character) <<
';';
253 static void writeSpaces (OutputStream& out,
const size_t numSpaces)
255 out.writeRepeatedByte (
' ', numSpaces);
259 void XmlElement::writeElementAsText (OutputStream& outputStream,
260 const int indentationLevel,
261 const int lineWrapLength)
const
263 using namespace XmlOutputFunctions;
265 if (indentationLevel >= 0)
266 writeSpaces (outputStream, (
size_t) indentationLevel);
270 outputStream.writeByte (
'<');
271 outputStream << tagName;
274 auto attIndent = (size_t) (indentationLevel + tagName.length() + 1);
277 for (
auto* att = attributes.get(); att !=
nullptr; att = att->nextListItem)
279 if (lineLen > lineWrapLength && indentationLevel >= 0)
281 outputStream << newLine;
282 writeSpaces (outputStream, attIndent);
286 auto startPos = outputStream.getPosition();
287 outputStream.writeByte (
' ');
288 outputStream << att->name;
289 outputStream.write (
"=\"", 2);
290 escapeIllegalXmlChars (outputStream, att->value,
true);
291 outputStream.writeByte (
'"');
292 lineLen += (int) (outputStream.getPosition() - startPos);
296 if (
auto* child = firstChildElement.get())
298 outputStream.writeByte (
'>');
299 bool lastWasTextNode =
false;
301 for (; child !=
nullptr; child = child->nextListItem)
303 if (child->isTextElement())
305 escapeIllegalXmlChars (outputStream, child->getText(),
false);
306 lastWasTextNode =
true;
310 if (indentationLevel >= 0 && ! lastWasTextNode)
311 outputStream << newLine;
313 child->writeElementAsText (outputStream,
314 lastWasTextNode ? 0 : (indentationLevel + (indentationLevel >= 0 ? 2 : 0)), lineWrapLength);
315 lastWasTextNode =
false;
319 if (indentationLevel >= 0 && ! lastWasTextNode)
321 outputStream << newLine;
322 writeSpaces (outputStream, (
size_t) indentationLevel);
325 outputStream.write (
"</", 2);
326 outputStream << tagName;
327 outputStream.writeByte (
'>');
331 outputStream.write (
"/>", 2);
336 escapeIllegalXmlChars (outputStream,
getText(),
false);
341 const bool allOnOneLine,
342 const bool includeXmlHeader,
344 const int lineWrapLength)
const
347 writeToStream (mem, dtdToUse, allOnOneLine, includeXmlHeader, encodingType, lineWrapLength);
353 bool allOnOneLine,
bool includeXmlHeader,
354 StringRef encodingType,
int lineWrapLength)
const
356 using namespace XmlOutputFunctions;
358 if (includeXmlHeader)
360 output <<
"<?xml version=\"1.0\" encoding=\"" << encodingType <<
"\"?>";
365 output << newLine << newLine;
378 writeElementAsText (output, allOnOneLine ? -1 : 0, lineWrapLength);
385 StringRef encodingType,
int lineWrapLength)
const
395 writeToStream (out, dtdToUse,
false,
true, encodingType, lineWrapLength);
408 const bool matches = tagName.equalsIgnoreCase (possibleTagName);
412 jassert ((! matches) || tagName == possibleTagName);
434 auto* e = nextListItem.get();
436 while (e !=
nullptr && ! e->hasTagName (requiredTagName))
451 return attributes.
size();
454 static const String& getEmptyStringRef() noexcept
462 if (
auto* att = attributes[index].get())
463 return att->name.toString();
465 return getEmptyStringRef();
470 if (
auto* att = attributes[index].get())
473 return getEmptyStringRef();
476 XmlElement::XmlAttributeNode* XmlElement::getAttribute (
StringRef attributeName)
const noexcept
478 for (
auto* att = attributes.
get(); att !=
nullptr; att = att->nextListItem)
479 if (att->name == attributeName)
487 return getAttribute (attributeName) !=
nullptr;
493 if (
auto* att = getAttribute (attributeName))
496 return getEmptyStringRef();
501 if (
auto* att = getAttribute (attributeName))
504 return defaultReturnValue;
509 if (
auto* att = getAttribute (attributeName))
510 return att->value.getIntValue();
512 return defaultReturnValue;
517 if (
auto* att = getAttribute (attributeName))
518 return att->value.getDoubleValue();
520 return defaultReturnValue;
525 if (
auto* att = getAttribute (attributeName))
527 auto firstChar = *(att->value.getCharPointer().findEndOfWhitespace());
529 return firstChar ==
'1'
536 return defaultReturnValue;
541 const bool ignoreCase)
const noexcept
543 if (
auto* att = getAttribute (attributeName))
544 return ignoreCase ? att->value.equalsIgnoreCase (stringToCompareAgainst)
545 : att->value == stringToCompareAgainst;
553 if (attributes ==
nullptr)
555 attributes =
new XmlAttributeNode (attributeName, value);
559 for (
auto* att = attributes.
get(); ; att = att->nextListItem)
561 if (att->name == attributeName)
567 if (att->nextListItem ==
nullptr)
569 att->nextListItem =
new XmlAttributeNode (attributeName, value);
588 for (
auto* att = &attributes; att->
get() !=
nullptr; att = &(att->get()->nextListItem))
590 if (att->get()->name == attributeName)
592 delete att->removeNext();
606 return firstChildElement.size();
611 return firstChildElement [index].get();
616 jassert (! childName.isEmpty());
618 for (
auto* child = firstChildElement.get(); child !=
nullptr; child = child->nextListItem)
619 if (child->hasTagName (childName))
627 jassert (! attributeName.isEmpty());
629 for (
auto* child = firstChildElement.get(); child !=
nullptr; child = child->nextListItem)
630 if (child->compareAttribute (attributeName, attributeValue))
638 if (newNode !=
nullptr)
641 jassert (newNode->nextListItem ==
nullptr);
643 firstChildElement.append (newNode);
649 if (newNode !=
nullptr)
652 jassert (newNode->nextListItem ==
nullptr);
654 firstChildElement.insertAtIndex (indexToInsertAt, newNode);
660 if (newNode !=
nullptr)
663 jassert (newNode->nextListItem ==
nullptr);
665 firstChildElement.insertNext (newNode);
671 auto newElement =
new XmlElement (childTagName);
679 if (newNode !=
nullptr)
681 if (
auto* p = firstChildElement.findPointerTo (currentChildElement))
683 if (currentChildElement != newNode)
684 delete p->replaceNext (newNode);
694 const bool shouldDeleteTheChild) noexcept
696 if (childToRemove !=
nullptr)
700 firstChildElement.remove (childToRemove);
702 if (shouldDeleteTheChild)
703 delete childToRemove;
708 const bool ignoreOrderOfAttributes)
const noexcept
712 if (other ==
nullptr || tagName != other->tagName)
715 if (ignoreOrderOfAttributes)
719 for (
auto* att = attributes.
get(); att !=
nullptr; att = att->nextListItem)
732 auto* thisAtt = attributes.
get();
733 auto* otherAtt = other->attributes.
get();
737 if (thisAtt ==
nullptr || otherAtt ==
nullptr)
739 if (thisAtt == otherAtt)
745 if (thisAtt->name != otherAtt->name
746 || thisAtt->value != otherAtt->value)
751 thisAtt = thisAtt->nextListItem;
752 otherAtt = otherAtt->nextListItem;
756 auto* thisChild = firstChildElement.get();
757 auto* otherChild = other->firstChildElement.get();
761 if (thisChild ==
nullptr || otherChild ==
nullptr)
763 if (thisChild == otherChild)
769 if (! thisChild->isEquivalentTo (otherChild, ignoreOrderOfAttributes))
772 thisChild = thisChild->nextListItem;
773 otherChild = otherChild->nextListItem;
782 firstChildElement.deleteAll();
787 for (
auto* child = firstChildElement.get(); child !=
nullptr;)
789 auto* nextChild = child->nextListItem.get();
791 if (child->hasTagName (name))
800 return firstChildElement.contains (possibleChild);
805 if (
this == elementToLookFor || elementToLookFor ==
nullptr)
808 for (
auto* child = firstChildElement.get(); child !=
nullptr; child = child->nextListItem)
810 if (elementToLookFor == child)
813 if (
auto* found = child->findParentElementOf (elementToLookFor))
820 void XmlElement::getChildElementsAsArray (
XmlElement** elems)
const noexcept
822 firstChildElement.copyToArray (elems);
825 void XmlElement::reorderChildElements (
XmlElement** elems,
int num) noexcept
828 firstChildElement = e;
830 for (
int i = 1; i < num; ++i)
832 e->nextListItem = elems[i];
836 e->nextListItem =
nullptr;
842 return tagName.isEmpty();
845 static const String juce_xmltextContentAttributeName (
"text");
859 setAttribute (juce_xmltextContentAttributeName, newText);
870 return firstChildElement.get()->getAllSubText();
874 for (
auto* child = firstChildElement.get(); child !=
nullptr; child = child->nextListItem)
875 mem << child->getAllSubText();
883 return child->getAllSubText();
885 return defaultReturnValue;
891 e->setAttribute (juce_xmltextContentAttributeName, text);
897 if (text.isEmpty() || ! isValidXmlNameStartCharacter (text.text.getAndAdvance()))
905 if (! isValidXmlNameBodyCharacter (text.text.getAndAdvance()))
917 for (
auto* child = firstChildElement.get(); child !=
nullptr;)
919 auto* next = child->nextListItem.get();
921 if (child->isTextElement())
931 class XmlElementTests :
public UnitTest
934 XmlElementTests() :
UnitTest (
"XmlElement",
"XML") {}
936 void runTest()
override
939 beginTest (
"Float formatting");
941 auto element = std::make_unique<XmlElement> (
"test");
942 Identifier number (
"number");
944 std::map<double, String> tests;
947 tests[1.01] =
"1.01";
948 tests[0.76378] =
"0.76378";
949 tests[-10] =
"-10.0";
950 tests[10.01] =
"10.01";
951 tests[0.0123] =
"0.0123";
952 tests[-3.7e-27] =
"-3.7e-27";
953 tests[1e+40] =
"1.0e40";
954 tests[-12345678901234567.0] =
"-1.234567890123457e16";
955 tests[192000] =
"192000.0";
956 tests[1234567] =
"1.234567e6";
957 tests[0.00006] =
"0.00006";
958 tests[0.000006] =
"6.0e-6";
960 for (
auto& test : tests)
962 element->setAttribute (number, test.first);
963 expectEquals (element->getStringAttribute (number), test.second);
969 static XmlElementTests xmlElementTests;