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));
69XmlElement::XmlAttributeNode::XmlAttributeNode (String::CharPointerType nameStart, String::CharPointerType nameEnd)
70 : name (nameStart, nameEnd)
72 jassert (isValidXmlName (name));
95 : tagName (
tag.toString())
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
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>
191 enum { v = (int) Bit<tableIndex * 8 + 0>::v | (
int) Bit<tableIndex * 8 + 1>::v
192 | (int) Bit<tableIndex * 8 + 2>::v | (
int) Bit<tableIndex * 8 + 3>::v
193 | (int) Bit<tableIndex * 8 + 4>::v | (
int) Bit<tableIndex * 8 + 5>::v
194 | (int) Bit<tableIndex * 8 + 6>::v | (
int) Bit<tableIndex * 8 + 7>::v };
197 static bool isLegal (uint32 c)
noexcept
199 static const unsigned char legalChars[] = { Byte< 0>::v, Byte< 1>::v, Byte< 2>::v, Byte< 3>::v,
200 Byte< 4>::v, Byte< 5>::v, Byte< 6>::v, Byte< 7>::v,
201 Byte< 8>::v, Byte< 9>::v, Byte<10>::v, Byte<11>::v,
202 Byte<12>::v, Byte<13>::v, Byte<14>::v, Byte<15>::v };
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)
211 auto t = text.getCharPointer();
215 auto character = (uint32) t.getAndAdvance();
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)
251 out.writeRepeatedByte (
' ', numSpaces);
255void XmlElement::writeElementAsText (OutputStream& outputStream,
256 int indentationLevel,
258 const char* newLineChars)
const
260 if (indentationLevel >= 0)
261 XmlOutputFunctions::writeSpaces (outputStream, (
size_t) indentationLevel);
265 outputStream.writeByte (
'<');
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);
281 auto startPos = outputStream.getPosition();
282 outputStream.writeByte (
' ');
283 outputStream << att->name;
284 outputStream.write (
"=\"", 2);
285 XmlOutputFunctions::escapeIllegalXmlChars (outputStream, att->value,
true);
286 outputStream.writeByte (
'"');
287 lineLen += (int) (outputStream.getPosition() - startPos);
291 if (
auto* child = firstChildElement.get())
293 outputStream.writeByte (
'>');
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;
323 outputStream.writeByte (
'>');
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())
424 return tempFile.overwriteTargetFileWithTemporary();
434 options.lineWrapLength = lineWrapLength;
437 options.newLineChars =
nullptr;
442void XmlElement::writeToStream (OutputStream& output, StringRef dtdToUse,
443 bool allOnOneLine,
bool includeXmlHeader,
444 StringRef encodingType,
int lineWrapLength)
const
447 options.dtd = dtdToUse;
448 options.customEncoding = encodingType;
449 options.addDefaultHeader = includeXmlHeader;
450 options.lineWrapLength = lineWrapLength;
453 options.newLineChars =
nullptr;
458bool XmlElement::writeToFile (
const File& file, StringRef dtdToUse,
459 StringRef encodingType,
int lineWrapLength)
const
462 options.dtd = dtdToUse;
463 options.customEncoding = encodingType;
464 options.lineWrapLength = lineWrapLength;
466 return writeTo (file, options);
498 auto*
e = nextListItem.get();
515 return attributes.
size();
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();
542 for (
auto*
att = attributes.
get();
att !=
nullptr;
att =
att->nextListItem)
560 return getEmptyStringRef();
574 return att->value.getIntValue();
582 return att->value.getDoubleValue();
591 auto firstChar = *(
att->value.getCharPointer().findEndOfWhitespace());
605 const bool ignoreCase)
const noexcept
617 if (attributes ==
nullptr)
623 for (
auto*
att = attributes.
get(); ;
att =
att->nextListItem)
631 if (
att->nextListItem ==
nullptr)
652 for (
auto*
att = &attributes;
att->get() !=
nullptr;
att = &(
att->get()->nextListItem))
656 delete att->removeNext();
670 return firstChildElement.size();
675 return firstChildElement[index].get();
682 for (
auto* child = firstChildElement.get(); child !=
nullptr; child = child->nextListItem)
693 for (
auto* child = firstChildElement.get(); child !=
nullptr; child = child->nextListItem)
705 jassert (
newNode->nextListItem ==
nullptr);
707 firstChildElement.append (
newNode);
716 jassert (
newNode->nextListItem ==
nullptr);
727 jassert (
newNode->nextListItem ==
nullptr);
729 firstChildElement.insertNext (
newNode);
748 delete p->replaceNext (
newNode);
776 if (
other ==
nullptr || tagName !=
other->tagName)
783 for (
auto*
att = attributes.
get();
att !=
nullptr;
att =
att->nextListItem)
785 if (!
other->compareAttribute (
att->name,
att->value))
820 auto*
thisChild = firstChildElement.get();
846 firstChildElement.deleteAll();
851 for (
auto* child = firstChildElement.get(); child !=
nullptr;)
853 auto*
nextChild = child->nextListItem.get();
855 if (child->hasTagName (name))
872 for (
auto* child = firstChildElement.get(); child !=
nullptr; child = child->nextListItem)
884void XmlElement::getChildElementsAsArray (
XmlElement**
elems)
const noexcept
886 firstChildElement.copyToArray (
elems);
889void 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;
909static const String juce_xmltextContentAttributeName (
"text");
934 return firstChildElement.get()->getAllSubText();
938 for (
auto* child = firstChildElement.get(); child !=
nullptr; child = child->nextListItem)
939 mem << child->getAllSubText();
947 return child->getAllSubText();
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())
1000 :
UnitTest (
"XmlElement", UnitTestCategories::xml)
1003 void runTest()
override
1006 beginTest (
"Float formatting");
1008 auto element = std::make_unique<XmlElement> (
"test");
1009 Identifier number (
"number");
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);
1036static XmlElementTests xmlElementTests;
bool isEmpty() const noexcept
ObjectType * get() const noexcept
void addCopyOfList(const LinkedListPointer &other)
int size() const noexcept
virtual bool writeByte(char byte)
static StringPool & getGlobalPool() noexcept
String upToFirstOccurrenceOf(StringRef substringToEndWith, bool includeSubStringInResult, bool ignoreCase) const
String fromLastOccurrenceOf(StringRef substringToFind, bool includeSubStringInResult, bool ignoreCase) const
bool isNotEmpty() const noexcept
XmlElement * getNextElementWithTagName(StringRef requiredTagName) const
void setTagName(StringRef newTagName)
void addTextElement(const String &text)
int getNumChildElements() const noexcept
XmlElement(const String &tagName)
void insertChildElement(XmlElement *newChildElement, int indexToInsertAt) noexcept
XmlElement * getChildByName(StringRef tagNameToLookFor) const noexcept
bool isTextElement() const noexcept
void deleteAllChildElementsWithTagName(StringRef tagName) noexcept
String toString(const TextFormat &format={}) const
void addChildElement(XmlElement *newChildElement) noexcept
String getTagNameWithoutNamespace() const
double getDoubleAttribute(StringRef attributeName, double defaultReturnValue=0.0) const
const String & getAttributeValue(int attributeIndex) const noexcept
void prependChildElement(XmlElement *newChildElement) noexcept
XmlElement * createNewChildElement(StringRef tagName)
String getChildElementAllSubText(StringRef childTagName, const String &defaultReturnValue) const
const String & getText() const noexcept
void deleteAllTextElements() noexcept
String getAllSubText() const
bool compareAttribute(StringRef attributeName, StringRef stringToCompareAgainst, bool ignoreCase=false) const noexcept
void setText(const String &newText)
XmlElement * findParentElementOf(const XmlElement *childToSearchFor) noexcept
bool isEquivalentTo(const XmlElement *other, bool ignoreOrderOfAttributes) const noexcept
bool getBoolAttribute(StringRef attributeName, bool defaultReturnValue=false) const
void removeAllAttributes() noexcept
static XmlElement * createTextElement(const String &text)
const String & getAttributeName(int attributeIndex) const noexcept
bool hasAttribute(StringRef attributeName) const noexcept
bool containsChildElement(const XmlElement *possibleChild) const noexcept
bool replaceChildElement(XmlElement *currentChildElement, XmlElement *newChildNode) noexcept
void writeTo(OutputStream &output, const TextFormat &format={}) const
XmlElement * getChildElement(int index) const noexcept
XmlElement & operator=(const XmlElement &)
void deleteAllChildElements() noexcept
bool hasTagName(StringRef possibleTagName) const noexcept
void removeAttribute(const Identifier &attributeName) noexcept
XmlElement * getChildByAttribute(StringRef attributeName, StringRef attributeValue) const noexcept
int getNumAttributes() const noexcept
void removeChildElement(XmlElement *childToRemove, bool shouldDeleteTheChild) noexcept
static bool isValidXmlName(StringRef possibleName) noexcept
int getIntAttribute(StringRef attributeName, int defaultReturnValue=0) const
const String & getStringAttribute(StringRef attributeName) const noexcept
bool hasTagNameIgnoringNamespace(StringRef possibleTagName) const
String getNamespace() const
void setAttribute(const Identifier &attributeName, const String &newValue)
TextFormat withoutHeader() const
const char * newLineChars
TextFormat singleLine() const