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))
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);
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 namespace LegalCharLookupTable
175 enum { v = ((c >=
'a' && c <=
'z')
176 || (c >=
'A' && c <=
'Z')
177 || (c >=
'0' && c <=
'9')
178 || c ==
' ' || c ==
'.' || c ==
',' || c ==
';' 179 || c ==
':' || c ==
'-' || c ==
'(' || c ==
')' 180 || c ==
'_' || c ==
'+' || c ==
'=' || c ==
'?' 181 || c ==
'!' || c ==
'$' || c ==
'#' || c ==
'@' 182 || c ==
'[' || c ==
']' || c ==
'/' || c ==
'|' 183 || c ==
'*' || c ==
'%' || c ==
'~' || c ==
'{' 184 || c ==
'}' || c ==
'\'' || c ==
'\\')
185 ? (1 << (c & 7)) : 0 };
188 template <
int tableIndex>
197 static bool isLegal (uint32 c) noexcept
204 return c <
sizeof (legalChars) * 8
205 && (legalChars[c >> 3] & (1 << (c & 7))) != 0;
209 static void escapeIllegalXmlChars (
OutputStream& outputStream,
const String& text,
bool changeNewLines)
220 if (LegalCharLookupTable::isLegal (character))
222 outputStream << (char) character;
228 case '&': outputStream <<
"&";
break;
229 case '"': outputStream <<
""";
break;
230 case '>': outputStream <<
">";
break;
231 case '<': outputStream <<
"<";
break;
235 if (! changeNewLines)
237 outputStream << (char) character;
242 outputStream <<
"&#" << ((int) character) <<
';';
249 static void writeSpaces (
OutputStream& out,
const size_t numSpaces)
255 void XmlElement::writeElementAsText (
OutputStream& outputStream,
256 int indentationLevel,
258 const char* newLineChars)
const 260 if (indentationLevel >= 0)
261 XmlOutputFunctions::writeSpaces (outputStream, (
size_t) indentationLevel);
266 outputStream << tagName;
269 auto attIndent = (size_t) (indentationLevel + tagName.length() + 1);
272 for (
auto* att = attributes.get(); att !=
nullptr; att = att->nextListItem)
274 if (lineLen > lineWrapLength && indentationLevel >= 0)
276 outputStream << newLineChars;
277 XmlOutputFunctions::writeSpaces (outputStream, attIndent);
283 outputStream << att->name;
284 outputStream.
write (
"=\"", 2);
285 XmlOutputFunctions::escapeIllegalXmlChars (outputStream, att->value,
true);
287 lineLen += (int) (outputStream.
getPosition() - startPos);
291 if (
auto* child = firstChildElement.get())
294 bool lastWasTextNode =
false;
296 for (; child !=
nullptr; child = child->nextListItem)
298 if (child->isTextElement())
300 XmlOutputFunctions::escapeIllegalXmlChars (outputStream, child->getText(),
false);
301 lastWasTextNode =
true;
305 if (indentationLevel >= 0 && ! lastWasTextNode)
306 outputStream << newLineChars;
308 child->writeElementAsText (outputStream,
309 lastWasTextNode ? 0 : (indentationLevel + (indentationLevel >= 0 ? 2 : 0)), lineWrapLength,
311 lastWasTextNode =
false;
315 if (indentationLevel >= 0 && ! lastWasTextNode)
317 outputStream << newLineChars;
318 XmlOutputFunctions::writeSpaces (outputStream, (
size_t) indentationLevel);
321 outputStream.
write (
"</", 2);
322 outputStream << tagName;
327 outputStream.
write (
"/>", 2);
332 XmlOutputFunctions::escapeIllegalXmlChars (outputStream,
getText(),
false);
373 output <<
"<?xml version=\"1.0\" encoding=\"";
391 output << options.
dtd;
399 writeElementAsText (output, options.
newLineChars ==
nullptr ? -1 : 0,
414 if (! out.openedOk())
420 if (out.getStatus().failed())
427 String XmlElement::createDocument (
StringRef dtdToUse,
bool allOnOneLine,
bool includeXmlHeader,
428 StringRef encodingType,
int lineWrapLength)
const 431 options.
dtd = dtdToUse;
443 bool allOnOneLine,
bool includeXmlHeader,
444 StringRef encodingType,
int lineWrapLength)
const 447 options.
dtd = dtdToUse;
458 bool XmlElement::writeToFile (
const File& file,
StringRef dtdToUse,
459 StringRef encodingType,
int lineWrapLength)
const 462 options.
dtd = dtdToUse;
466 return writeTo (file, options);
472 const bool matches = tagName.equalsIgnoreCase (possibleTagName);
476 jassert ((! matches) || tagName == possibleTagName);
498 auto* e = nextListItem.get();
500 while (e !=
nullptr && ! e->hasTagName (requiredTagName))
515 return attributes.size();
518 static const String& getEmptyStringRef() noexcept
526 if (
auto* att = attributes[index].
get())
527 return att->name.toString();
529 return getEmptyStringRef();
534 if (
auto* att = attributes[index].
get())
537 return getEmptyStringRef();
540 XmlElement::XmlAttributeNode* XmlElement::getAttribute (
StringRef attributeName)
const noexcept
542 for (
auto* att = attributes.get(); att !=
nullptr; att = att->nextListItem)
543 if (att->name == attributeName)
551 return getAttribute (attributeName) !=
nullptr;
557 if (
auto* att = getAttribute (attributeName))
560 return getEmptyStringRef();
565 if (
auto* att = getAttribute (attributeName))
568 return defaultReturnValue;
573 if (
auto* att = getAttribute (attributeName))
574 return att->value.getIntValue();
576 return defaultReturnValue;
581 if (
auto* att = getAttribute (attributeName))
582 return att->value.getDoubleValue();
584 return defaultReturnValue;
589 if (
auto* att = getAttribute (attributeName))
591 auto firstChar = *(att->value.getCharPointer().findEndOfWhitespace());
593 return firstChar ==
'1' 600 return defaultReturnValue;
605 const bool ignoreCase)
const noexcept
607 if (
auto* att = getAttribute (attributeName))
608 return ignoreCase ? att->value.equalsIgnoreCase (stringToCompareAgainst)
609 : att->value == stringToCompareAgainst;
617 if (attributes ==
nullptr)
619 attributes =
new XmlAttributeNode (attributeName, value);
623 for (
auto* att = attributes.get(); ; att = att->nextListItem)
625 if (att->name == attributeName)
631 if (att->nextListItem ==
nullptr)
633 att->nextListItem =
new XmlAttributeNode (attributeName, value);
652 for (
auto* att = &attributes; att->get() !=
nullptr; att = &(att->get()->nextListItem))
654 if (att->get()->name == attributeName)
656 delete att->removeNext();
664 attributes.deleteAll();
670 return firstChildElement.size();
675 return firstChildElement[index].get();
680 jassert (! childName.isEmpty());
682 for (
auto* child = firstChildElement.get(); child !=
nullptr; child = child->nextListItem)
683 if (child->hasTagName (childName))
691 jassert (! attributeName.
isEmpty());
693 for (
auto* child = firstChildElement.get(); child !=
nullptr; child = child->nextListItem)
694 if (child->compareAttribute (attributeName, attributeValue))
702 if (newNode !=
nullptr)
705 jassert (newNode->nextListItem ==
nullptr);
707 firstChildElement.append (newNode);
713 if (newNode !=
nullptr)
716 jassert (newNode->nextListItem ==
nullptr);
718 firstChildElement.insertAtIndex (indexToInsertAt, newNode);
724 if (newNode !=
nullptr)
727 jassert (newNode->nextListItem ==
nullptr);
729 firstChildElement.insertNext (newNode);
735 auto newElement =
new XmlElement (childTagName);
743 if (newNode !=
nullptr)
745 if (
auto* p = firstChildElement.findPointerTo (currentChildElement))
747 if (currentChildElement != newNode)
748 delete p->replaceNext (newNode);
758 const bool shouldDeleteTheChild) noexcept
760 if (childToRemove !=
nullptr)
764 firstChildElement.remove (childToRemove);
766 if (shouldDeleteTheChild)
767 delete childToRemove;
772 const bool ignoreOrderOfAttributes)
const noexcept
776 if (other ==
nullptr || tagName != other->tagName)
779 if (ignoreOrderOfAttributes)
783 for (
auto* att = attributes.get(); att !=
nullptr; att = att->nextListItem)
796 auto* thisAtt = attributes.get();
797 auto* otherAtt = other->attributes.
get();
801 if (thisAtt ==
nullptr || otherAtt ==
nullptr)
803 if (thisAtt == otherAtt)
809 if (thisAtt->name != otherAtt->name
810 || thisAtt->value != otherAtt->value)
815 thisAtt = thisAtt->nextListItem;
816 otherAtt = otherAtt->nextListItem;
820 auto* thisChild = firstChildElement.get();
821 auto* otherChild = other->firstChildElement.get();
825 if (thisChild ==
nullptr || otherChild ==
nullptr)
827 if (thisChild == otherChild)
833 if (! thisChild->isEquivalentTo (otherChild, ignoreOrderOfAttributes))
836 thisChild = thisChild->nextListItem;
837 otherChild = otherChild->nextListItem;
846 firstChildElement.deleteAll();
851 for (
auto* child = firstChildElement.get(); child !=
nullptr;)
853 auto* nextChild = child->nextListItem.get();
855 if (child->hasTagName (name))
864 return firstChildElement.contains (possibleChild);
869 if (
this == elementToLookFor || elementToLookFor ==
nullptr)
872 for (
auto* child = firstChildElement.get(); child !=
nullptr; child = child->nextListItem)
874 if (elementToLookFor == child)
877 if (
auto* found = child->findParentElementOf (elementToLookFor))
884 void XmlElement::getChildElementsAsArray (
XmlElement** elems)
const noexcept
886 firstChildElement.copyToArray (elems);
889 void XmlElement::reorderChildElements (
XmlElement** elems,
int num) noexcept
892 firstChildElement = e;
894 for (
int i = 1; i < num; ++i)
896 e->nextListItem = elems[i];
900 e->nextListItem =
nullptr;
906 return tagName.isEmpty();
909 static const String juce_xmltextContentAttributeName (
"text");
923 setAttribute (juce_xmltextContentAttributeName, newText);
934 return firstChildElement.get()->getAllSubText();
938 for (
auto* child = firstChildElement.get(); child !=
nullptr; child = child->nextListItem)
939 mem << child->getAllSubText();
947 return child->getAllSubText();
949 return defaultReturnValue;
955 e->setAttribute (juce_xmltextContentAttributeName, text);
961 if (text.isEmpty() || ! isValidXmlNameStartCharacter (text.text.getAndAdvance()))
969 if (! isValidXmlNameBodyCharacter (text.text.getAndAdvance()))
981 for (
auto* child = firstChildElement.get(); child !=
nullptr;)
983 auto* next = child->nextListItem.get();
985 if (child->isTextElement())
996 class XmlElementTests :
public UnitTest 1000 :
UnitTest (
"XmlElement", UnitTestCategories::xml)
1003 void runTest()
override 1006 beginTest (
"Float formatting");
1008 auto element = std::make_unique<XmlElement> (
"test");
1011 std::map<double, String> tests;
1014 tests[1.01] =
"1.01";
1015 tests[0.76378] =
"0.76378";
1016 tests[-10] =
"-10.0";
1017 tests[10.01] =
"10.01";
1018 tests[0.0123] =
"0.0123";
1019 tests[-3.7e-27] =
"-3.7e-27";
1020 tests[1e+40] =
"1.0e40";
1021 tests[-12345678901234567.0] =
"-1.234567890123457e16";
1022 tests[192000] =
"192000.0";
1023 tests[1234567] =
"1.234567e6";
1024 tests[0.00006] =
"0.00006";
1025 tests[0.000006] =
"6.0e-6";
1027 for (
auto& test : tests)
1029 element->setAttribute (number, test.first);
1030 expectEquals (element->getStringAttribute (number), test.second);
1036 static XmlElementTests xmlElementTests;
const String & getText() const noexcept
Returns the text for a text element.
Manages a temporary file, which will be deleted when this object is deleted.
bool hasTagName(StringRef possibleTagName) const noexcept
Tests whether this element has a particular tag name.
void deleteAllChildElementsWithTagName(StringRef tagName) noexcept
Deletes all the child elements with a given tag name.
virtual bool write(const void *dataToWrite, size_t numberOfBytes)=0
Writes a block of data to the stream.
void deleteAll()
Iterates the list, calling the delete operator on all of its elements and leaving this pointer empty...
TextFormat singleLine() const
returns a copy of this format with newLineChars set to nullptr.
bool overwriteTargetFileWithTemporary() const
Tries to move the temporary file to overwrite the target file that was specified in the constructor...
bool isNotEmpty() const noexcept
Returns true if the string contains at least one character.
void removeAttribute(const Identifier &attributeName) noexcept
Removes a named attribute from the element.
String customEncoding
If not empty and addDefaultHeader is true, this will be set as the encoding.
const File & getFile() const noexcept
Returns the temporary file.
virtual bool writeByte(char byte)
Writes a single byte to the stream.
XmlElement & operator=(const XmlElement &)
Creates a (deep) copy of another element.
~XmlElement() noexcept
Deleting an XmlElement will also delete all of its child elements.
Represents a string identifier, designed for accessing properties by name.
XmlElement * getChildElement(int index) const noexcept
Returns the sub-element at a certain index.
A simple class for holding temporary references to a string literal or String.
static XmlElement * createTextElement(const String &text)
Creates a text element that can be added to a parent element.
bool hasTagNameIgnoringNamespace(StringRef possibleTagName) const
Tests whether this element has a particular tag name, ignoring any XML namespace prefix.
static StringPool & getGlobalPool() noexcept
Returns a shared global pool which is used for things like Identifiers, XML parsing.
const String & getAttributeValue(int attributeIndex) const noexcept
Returns the value of one of the elements attributes.
XmlElement * getChildByName(StringRef tagNameToLookFor) const noexcept
Returns the first sub-element with a given tag-name.
Used to build a tree of elements representing an XML document.
void writeTo(OutputStream &output, const TextFormat &format={}) const
Writes the document to a stream as UTF-8.
CharPointerType getCharPointer() const noexcept
Returns the character pointer currently being used to store this string.
void addChildElement(XmlElement *newChildElement) noexcept
Appends an element to this element's list of children.
int getIntAttribute(StringRef attributeName, int defaultReturnValue=0) const
Returns the value of a named attribute as an integer.
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.
int getNumChildElements() const noexcept
Returns the number of sub-elements in this element.
String dtd
If supplied, this DTD will be added to the document.
This is a base class for classes that perform a unit test.
XmlElement * createNewChildElement(StringRef tagName)
Creates a new element with the given name and returns it, after adding it as a child element...
String fromLastOccurrenceOf(StringRef substringToFind, bool includeSubStringInResult, bool ignoreCase) const
Returns a section of the string starting from the last occurrence of a given substring.
XmlElement(const String &tagName)
Creates an XmlElement with this tag name.
String getAllSubText() const
Returns all the text from this element's child nodes.
ObjectType * get() const noexcept
Returns the item which this pointer points to.
bool isEmpty() const noexcept
Returns true if the string is empty.
void setAttribute(const Identifier &attributeName, const String &newValue)
Adds a named attribute to the element.
A StringPool holds a set of shared strings, which reduces storage overheads and improves comparison s...
void setTagName(StringRef newTagName)
Changes this elements tag name.
juce_wchar getAndAdvance() noexcept
Returns the character that this pointer is currently pointing to, and then advances the pointer to po...
double getDoubleAttribute(StringRef attributeName, double defaultReturnValue=0.0) const
Returns the value of a named attribute as floating-point.
void addCopyOfList(const LinkedListPointer &other)
Creates copies of all the items in another list and adds them to this one.
int getNumAttributes() const noexcept
Returns the number of XML attributes this element contains.
void addTextElement(const String &text)
Appends a section of text to this element.
void removeAllAttributes() noexcept
Removes all attributes from this element.
bool compareAttribute(StringRef attributeName, StringRef stringToCompareAgainst, bool ignoreCase=false) const noexcept
Compares the value of a named attribute with a value passed-in.
Represents a local file or directory.
The base class for streams that write data to some kind of destination.
CharPointer_UTF8 CharPointerType
This is the character encoding type used internally to store the string.
static bool isValidXmlName(StringRef possibleName) noexcept
Checks if a given string is a valid XML name.
String getTagNameWithoutNamespace() const
Returns the part of the tag-name that follows any namespace declaration.
void deleteAllChildElements() noexcept
Deletes all the child elements in the element.
String upToFirstOccurrenceOf(StringRef substringToEndWith, bool includeSubStringInResult, bool ignoreCase) const
Returns the start of this string, up to the first occurrence of a substring.
int lineWrapLength
A maximum line length before wrapping is done.
XmlElement * getNextElementWithTagName(StringRef requiredTagName) const
Returns the next of this element's siblings which has the specified tag name.
bool containsChildElement(const XmlElement *possibleChild) const noexcept
Returns true if the given element is a child of this one.
XmlElement * getChildByAttribute(StringRef attributeName, StringRef attributeValue) const noexcept
Returns the first sub-element which has an attribute that matches the given value.
An output stream that writes into a local file.
const String & getStringAttribute(StringRef attributeName) const noexcept
Returns the value of a named attribute.
virtual bool writeRepeatedByte(uint8 byte, size_t numTimesToRepeat)
Writes a byte to the output stream a given number of times.
XmlElement * findParentElementOf(const XmlElement *childToSearchFor) noexcept
Recursively searches all sub-elements of this one, looking for an element which is the direct parent ...
void deleteAllTextElements() noexcept
Removes all the text elements from this element.
void setText(const String &newText)
Sets the text in a text element.
const char * newLineChars
Allows the newline characters to be set.
String getPooledString(const String &original)
Returns a pointer to a shared copy of the string that is passed in.
bool isEquivalentTo(const XmlElement *other, bool ignoreOrderOfAttributes) const noexcept
Compares two XmlElements to see if they contain the same text and attributes.
String toUTF8() const
Returns a String created from the (UTF8) data that has been written to the stream.
bool isTextElement() const noexcept
Returns true if this element is a section of text.
Writes data to an internal memory buffer, which grows as required.
void removeChildElement(XmlElement *childToRemove, bool shouldDeleteTheChild) noexcept
Removes a child element.
TextFormat()
Default constructor.
virtual int64 getPosition()=0
Returns the stream's current position.
bool getBoolAttribute(StringRef attributeName, bool defaultReturnValue=false) const
Returns the value of a named attribute as a boolean.
String customHeader
If supplied, this header will be used (and customEncoding & addDefaultHeader will be ignored)...
String getNamespace() const
Returns the namespace portion of the tag-name, or an empty string if none is specified.
void insertChildElement(XmlElement *newChildElement, int indexToInsertAt) noexcept
Inserts an element into this element's list of children.
bool replaceChildElement(XmlElement *currentChildElement, XmlElement *newChildNode) noexcept
Replaces one of this element's children with another node.
bool addDefaultHeader
If true, a default header will be generated; otherwise just bare XML will be emitted.
String getChildElementAllSubText(StringRef childTagName, const String &defaultReturnValue) const
Returns all the sub-text of a named child element.
A struct containing options for formatting the text when representing an XML element as a string...
TextFormat withoutHeader() const
returns a copy of this format with the addDefaultHeader flag set to false.
void prependChildElement(XmlElement *newChildElement) noexcept
Inserts an element at the beginning of this element's list of children.
Wraps a pointer to a null-terminated UTF-8 character string, and provides various methods to operate ...
String toString(const TextFormat &format={}) const
Returns a text version of this XML element.