26inline 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);
46inline 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);
57XmlElement::XmlAttributeNode::XmlAttributeNode (
const XmlAttributeNode& other) noexcept
63XmlElement::XmlAttributeNode::XmlAttributeNode (
const Identifier& n,
const String& v) noexcept
66 jassert (isValidXmlName (name));
70 : name (nameStart, nameEnd)
72 jassert (isValidXmlName (name));
76XmlElement::XmlElement (
const String& tag)
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);
152void XmlElement::copyChildrenAndAttributesFrom (
const XmlElement& other)
154 jassert (firstChildElement.get() ==
nullptr);
155 firstChildElement.addCopyOfList (other.firstChildElement);
157 jassert (attributes.
get() ==
nullptr);
163 firstChildElement.deleteAll();
168namespace XmlOutputFunctions
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);
259void 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();
454static 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();
476XmlElement::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)
698 jassert (containsChildElement (childToRemove));
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))
792 removeChildElement (child,
true);
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))
820void XmlElement::getChildElementsAsArray (
XmlElement** elems)
const noexcept
822 firstChildElement.copyToArray (elems);
825void 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();
845static 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())
931class 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);
969static XmlElementTests xmlElementTests;
Wraps a pointer to a null-terminated UTF-8 character string, and provides various methods to operate ...
An output stream that writes into a local file.
const Result & getStatus() const noexcept
Returns the status of the file stream.
bool openedOk() const noexcept
Returns true if the stream opened without problems.
void flush() override
If the stream is using a buffer, this will ensure it gets written out to the destination.
Represents a local file or directory.
Represents a string identifier, designed for accessing properties by name.
void deleteAll()
Iterates the list, calling the delete operator on all of its elements and leaving this pointer empty.
ObjectType * get() const noexcept
Returns the item which this pointer points to.
void addCopyOfList(const LinkedListPointer &other)
Creates copies of all the items in another list and adds them to this one.
int size() const noexcept
Returns the number of items in the list.
Writes data to an internal memory buffer, which grows as required.
String toUTF8() const
Returns a String created from the (UTF8) data that has been written to the stream.
The base class for streams that write data to some kind of destination.
virtual bool writeByte(char byte)
Writes a single byte to the stream.
bool failed() const noexcept
Returns true if this result indicates a failure.
A StringPool holds a set of shared strings, which reduces storage overheads and improves comparison s...
String getPooledString(const String &original)
Returns a pointer to a shared copy of the string that is passed in.
static StringPool & getGlobalPool() noexcept
Returns a shared global pool which is used for things like Identifiers, XML parsing.
A simple class for holding temporary references to a string literal or String.
bool isNotEmpty() const noexcept
Returns true if the string is not empty.
String upToFirstOccurrenceOf(StringRef substringToEndWith, bool includeSubStringInResult, bool ignoreCase) const
Returns the start of this string, up to the first occurrence of a substring.
String fromLastOccurrenceOf(StringRef substringToFind, bool includeSubStringInResult, bool ignoreCase) const
Returns a section of the string starting from the last occurrence of a given substring.
CharPointer_UTF8 CharPointerType
This is the character encoding type used internally to store the string.
Manages a temporary file, which will be deleted when this object is deleted.
bool overwriteTargetFileWithTemporary() const
Tries to move the temporary file to overwrite the target file that was specified in the constructor.
const File & getFile() const noexcept
Returns the temporary file.
This is a base class for classes that perform a unit test.
Used to build a tree of elements representing an XML document.
XmlElement * getNextElementWithTagName(StringRef requiredTagName) const
Returns the next of this element's siblings which has the specified tag name.
void setTagName(StringRef newTagName)
Changes this elements tag name.
void writeToStream(OutputStream &output, StringRef dtdToUse, bool allOnOneLine=false, bool includeXmlHeader=true, StringRef encodingType="UTF-8", int lineWrapLength=60) const
Writes the document to a stream as UTF-8.
void addTextElement(const String &text)
Appends a section of text to this element.
int getNumChildElements() const noexcept
Returns the number of sub-elements in this element.
XmlElement(const String &tagName)
Creates an XmlElement with this tag name.
void insertChildElement(XmlElement *newChildElement, int indexToInsertAt) noexcept
Inserts an element into this element's list of children.
XmlElement * getChildByName(StringRef tagNameToLookFor) const noexcept
Returns the first sub-element with a given tag-name.
bool isTextElement() const noexcept
Returns true if this element is a section of text.
void deleteAllChildElementsWithTagName(StringRef tagName) noexcept
Deletes all the child elements with a given tag name.
void addChildElement(XmlElement *newChildElement) noexcept
Appends an element to this element's list of children.
String getTagNameWithoutNamespace() const
Returns the part of the tag-name that follows any namespace declaration.
double getDoubleAttribute(StringRef attributeName, double defaultReturnValue=0.0) const
Returns the value of a named attribute as floating-point.
const String & getAttributeValue(int attributeIndex) const noexcept
Returns the value of one of the elements attributes.
void prependChildElement(XmlElement *newChildElement) noexcept
Inserts an element at the beginning of this element's list of children.
XmlElement * createNewChildElement(StringRef tagName)
Creates a new element with the given name and returns it, after adding it as a child element.
String getChildElementAllSubText(StringRef childTagName, const String &defaultReturnValue) const
Returns all the sub-text of a named child element.
const String & getText() const noexcept
Returns the text for a text element.
void deleteAllTextElements() noexcept
Removes all the text elements from this element.
~XmlElement() noexcept
Deleting an XmlElement will also delete all of its child elements.
String getAllSubText() const
Returns all the text from this element's child nodes.
bool compareAttribute(StringRef attributeName, StringRef stringToCompareAgainst, bool ignoreCase=false) const noexcept
Compares the value of a named attribute with a value passed-in.
void setText(const String &newText)
Sets the text in a text element.
XmlElement * findParentElementOf(const XmlElement *childToSearchFor) noexcept
Recursively searches all sub-elements of this one, looking for an element which is the direct parent ...
bool isEquivalentTo(const XmlElement *other, bool ignoreOrderOfAttributes) const noexcept
Compares two XmlElements to see if they contain the same text and attributes.
bool getBoolAttribute(StringRef attributeName, bool defaultReturnValue=false) const
Returns the value of a named attribute as a boolean.
void removeAllAttributes() noexcept
Removes all attributes from this element.
static XmlElement * createTextElement(const String &text)
Creates a text element that can be added to a parent element.
String createDocument(StringRef dtdToUse, bool allOnOneLine=false, bool includeXmlHeader=true, StringRef encodingType="UTF-8", int lineWrapLength=60) const
Returns an XML text document that represents this element.
const String & getAttributeName(int attributeIndex) const noexcept
Returns the name of one of the elements attributes.
bool hasAttribute(StringRef attributeName) const noexcept
Checks whether the element contains an attribute with a certain name.
bool containsChildElement(const XmlElement *possibleChild) const noexcept
Returns true if the given element is a child of this one.
bool replaceChildElement(XmlElement *currentChildElement, XmlElement *newChildNode) noexcept
Replaces one of this element's children with another node.
XmlElement * getChildElement(int index) const noexcept
Returns the sub-element at a certain index.
XmlElement & operator=(const XmlElement &)
Creates a (deep) copy of another element.
bool writeToFile(const File &destinationFile, StringRef dtdToUse, StringRef encodingType="UTF-8", int lineWrapLength=60) const
Writes the element to a file as an XML document.
void deleteAllChildElements() noexcept
Deletes all the child elements in the element.
bool hasTagName(StringRef possibleTagName) const noexcept
Tests whether this element has a particular tag name.
void removeAttribute(const Identifier &attributeName) noexcept
Removes a named attribute from the element.
XmlElement * getChildByAttribute(StringRef attributeName, StringRef attributeValue) const noexcept
Returns the first sub-element which has an attribute that matches the given value.
int getNumAttributes() const noexcept
Returns the number of XML attributes this element contains.
void removeChildElement(XmlElement *childToRemove, bool shouldDeleteTheChild) noexcept
Removes a child element.
static bool isValidXmlName(StringRef possibleName) noexcept
Checks if a given string is a valid XML name.
int getIntAttribute(StringRef attributeName, int defaultReturnValue=0) const
Returns the value of a named attribute as an integer.
const String & getStringAttribute(StringRef attributeName) const noexcept
Returns the value of a named attribute.
bool hasTagNameIgnoringNamespace(StringRef possibleTagName) const
Tests whether this element has a particular tag name, ignoring any XML namespace prefix.
String getNamespace() const
Returns the namespace portion of the tag-name, or an empty string if none is specified.
void setAttribute(const Identifier &attributeName, const String &newValue)
Adds a named attribute to the element.