The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

827 lines
25KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. XmlElement::XmlAttributeNode::XmlAttributeNode (const XmlAttributeNode& other) noexcept
  19. : name (other.name),
  20. value (other.value)
  21. {
  22. }
  23. XmlElement::XmlAttributeNode::XmlAttributeNode (const String& n, const String& v) noexcept
  24. : name (n), value (v)
  25. {
  26. #if JUCE_DEBUG
  27. // this checks whether the attribute name string contains any illegal characters..
  28. for (String::CharPointerType t (name.getCharPointer()); ! t.isEmpty(); ++t)
  29. jassert (t.isLetterOrDigit() || *t == '_' || *t == '-' || *t == ':');
  30. #endif
  31. }
  32. inline bool XmlElement::XmlAttributeNode::hasName (const String& nameToMatch) const noexcept
  33. {
  34. return name.equalsIgnoreCase (nameToMatch);
  35. }
  36. //==============================================================================
  37. XmlElement::XmlElement (const String& tag) noexcept
  38. : tagName (tag)
  39. {
  40. // the tag name mustn't be empty, or it'll look like a text element!
  41. jassert (tag.containsNonWhitespaceChars())
  42. // The tag can't contain spaces or other characters that would create invalid XML!
  43. jassert (! tag.containsAnyOf (" <>/&"));
  44. }
  45. XmlElement::XmlElement (int /*dummy*/) noexcept
  46. {
  47. }
  48. XmlElement::XmlElement (const XmlElement& other)
  49. : tagName (other.tagName)
  50. {
  51. copyChildrenAndAttributesFrom (other);
  52. }
  53. XmlElement& XmlElement::operator= (const XmlElement& other)
  54. {
  55. if (this != &other)
  56. {
  57. removeAllAttributes();
  58. deleteAllChildElements();
  59. tagName = other.tagName;
  60. copyChildrenAndAttributesFrom (other);
  61. }
  62. return *this;
  63. }
  64. #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS
  65. XmlElement::XmlElement (XmlElement&& other) noexcept
  66. : nextListItem (static_cast <LinkedListPointer <XmlElement>&&> (other.nextListItem)),
  67. firstChildElement (static_cast <LinkedListPointer <XmlElement>&&> (other.firstChildElement)),
  68. attributes (static_cast <LinkedListPointer <XmlAttributeNode>&&> (other.attributes)),
  69. tagName (static_cast <String&&> (other.tagName))
  70. {
  71. }
  72. XmlElement& XmlElement::operator= (XmlElement&& other) noexcept
  73. {
  74. jassert (this != &other); // hopefully the compiler should make this situation impossible!
  75. removeAllAttributes();
  76. deleteAllChildElements();
  77. nextListItem = static_cast <LinkedListPointer <XmlElement>&&> (other.nextListItem);
  78. firstChildElement = static_cast <LinkedListPointer <XmlElement>&&> (other.firstChildElement);
  79. attributes = static_cast <LinkedListPointer <XmlAttributeNode>&&> (other.attributes);
  80. tagName = static_cast <String&&> (other.tagName);
  81. return *this;
  82. }
  83. #endif
  84. void XmlElement::copyChildrenAndAttributesFrom (const XmlElement& other)
  85. {
  86. jassert (firstChildElement.get() == nullptr);
  87. firstChildElement.addCopyOfList (other.firstChildElement);
  88. jassert (attributes.get() == nullptr);
  89. attributes.addCopyOfList (other.attributes);
  90. }
  91. XmlElement::~XmlElement() noexcept
  92. {
  93. firstChildElement.deleteAll();
  94. attributes.deleteAll();
  95. }
  96. //==============================================================================
  97. namespace XmlOutputFunctions
  98. {
  99. #if 0 // (These functions are just used to generate the lookup table used below)
  100. bool isLegalXmlCharSlow (const juce_wchar character) noexcept
  101. {
  102. if ((character >= 'a' && character <= 'z')
  103. || (character >= 'A' && character <= 'Z')
  104. || (character >= '0' && character <= '9'))
  105. return true;
  106. const char* t = " .,;:-()_+=?!'#@[]/\\*%~{}$|";
  107. do
  108. {
  109. if (((juce_wchar) (uint8) *t) == character)
  110. return true;
  111. }
  112. while (*++t != 0);
  113. return false;
  114. }
  115. void generateLegalCharLookupTable()
  116. {
  117. uint8 n[32] = { 0 };
  118. for (int i = 0; i < 256; ++i)
  119. if (isLegalXmlCharSlow (i))
  120. n[i >> 3] |= (1 << (i & 7));
  121. String s;
  122. for (int i = 0; i < 32; ++i)
  123. s << (int) n[i] << ", ";
  124. DBG (s);
  125. }
  126. #endif
  127. static bool isLegalXmlChar (const uint32 c) noexcept
  128. {
  129. static const unsigned char legalChars[] = { 0, 0, 0, 0, 187, 255, 255, 175, 255, 255, 255, 191, 254, 255, 255, 127 };
  130. return c < sizeof (legalChars) * 8
  131. && (legalChars [c >> 3] & (1 << (c & 7))) != 0;
  132. }
  133. static void escapeIllegalXmlChars (OutputStream& outputStream, const String& text, const bool changeNewLines)
  134. {
  135. String::CharPointerType t (text.getCharPointer());
  136. for (;;)
  137. {
  138. const uint32 character = (uint32) t.getAndAdvance();
  139. if (character == 0)
  140. break;
  141. if (isLegalXmlChar (character))
  142. {
  143. outputStream << (char) character;
  144. }
  145. else
  146. {
  147. switch (character)
  148. {
  149. case '&': outputStream << "&amp;"; break;
  150. case '"': outputStream << "&quot;"; break;
  151. case '>': outputStream << "&gt;"; break;
  152. case '<': outputStream << "&lt;"; break;
  153. case '\n':
  154. case '\r':
  155. if (! changeNewLines)
  156. {
  157. outputStream << (char) character;
  158. break;
  159. }
  160. // Note: deliberate fall-through here!
  161. default:
  162. outputStream << "&#" << ((int) character) << ';';
  163. break;
  164. }
  165. }
  166. }
  167. }
  168. static void writeSpaces (OutputStream& out, const int numSpaces)
  169. {
  170. out.writeRepeatedByte (' ', numSpaces);
  171. }
  172. }
  173. void XmlElement::writeElementAsText (OutputStream& outputStream,
  174. const int indentationLevel,
  175. const int lineWrapLength) const
  176. {
  177. using namespace XmlOutputFunctions;
  178. if (indentationLevel >= 0)
  179. writeSpaces (outputStream, indentationLevel);
  180. if (! isTextElement())
  181. {
  182. outputStream.writeByte ('<');
  183. outputStream << tagName;
  184. {
  185. const int attIndent = indentationLevel + tagName.length() + 1;
  186. int lineLen = 0;
  187. for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem)
  188. {
  189. if (lineLen > lineWrapLength && indentationLevel >= 0)
  190. {
  191. outputStream << newLine;
  192. writeSpaces (outputStream, attIndent);
  193. lineLen = 0;
  194. }
  195. const int64 startPos = outputStream.getPosition();
  196. outputStream.writeByte (' ');
  197. outputStream << att->name;
  198. outputStream.write ("=\"", 2);
  199. escapeIllegalXmlChars (outputStream, att->value, true);
  200. outputStream.writeByte ('"');
  201. lineLen += (int) (outputStream.getPosition() - startPos);
  202. }
  203. }
  204. if (firstChildElement != nullptr)
  205. {
  206. outputStream.writeByte ('>');
  207. bool lastWasTextNode = false;
  208. for (XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem)
  209. {
  210. if (child->isTextElement())
  211. {
  212. escapeIllegalXmlChars (outputStream, child->getText(), false);
  213. lastWasTextNode = true;
  214. }
  215. else
  216. {
  217. if (indentationLevel >= 0 && ! lastWasTextNode)
  218. outputStream << newLine;
  219. child->writeElementAsText (outputStream,
  220. lastWasTextNode ? 0 : (indentationLevel + (indentationLevel >= 0 ? 2 : 0)), lineWrapLength);
  221. lastWasTextNode = false;
  222. }
  223. }
  224. if (indentationLevel >= 0 && ! lastWasTextNode)
  225. {
  226. outputStream << newLine;
  227. writeSpaces (outputStream, indentationLevel);
  228. }
  229. outputStream.write ("</", 2);
  230. outputStream << tagName;
  231. outputStream.writeByte ('>');
  232. }
  233. else
  234. {
  235. outputStream.write ("/>", 2);
  236. }
  237. }
  238. else
  239. {
  240. escapeIllegalXmlChars (outputStream, getText(), false);
  241. }
  242. }
  243. String XmlElement::createDocument (const String& dtdToUse,
  244. const bool allOnOneLine,
  245. const bool includeXmlHeader,
  246. const String& encodingType,
  247. const int lineWrapLength) const
  248. {
  249. MemoryOutputStream mem (2048);
  250. writeToStream (mem, dtdToUse, allOnOneLine, includeXmlHeader, encodingType, lineWrapLength);
  251. return mem.toUTF8();
  252. }
  253. void XmlElement::writeToStream (OutputStream& output,
  254. const String& dtdToUse,
  255. const bool allOnOneLine,
  256. const bool includeXmlHeader,
  257. const String& encodingType,
  258. const int lineWrapLength) const
  259. {
  260. using namespace XmlOutputFunctions;
  261. if (includeXmlHeader)
  262. {
  263. output << "<?xml version=\"1.0\" encoding=\"" << encodingType << "\"?>";
  264. if (allOnOneLine)
  265. output.writeByte (' ');
  266. else
  267. output << newLine << newLine;
  268. }
  269. if (dtdToUse.isNotEmpty())
  270. {
  271. output << dtdToUse;
  272. if (allOnOneLine)
  273. output.writeByte (' ');
  274. else
  275. output << newLine;
  276. }
  277. writeElementAsText (output, allOnOneLine ? -1 : 0, lineWrapLength);
  278. if (! allOnOneLine)
  279. output << newLine;
  280. }
  281. bool XmlElement::writeToFile (const File& file,
  282. const String& dtdToUse,
  283. const String& encodingType,
  284. const int lineWrapLength) const
  285. {
  286. TemporaryFile tempFile (file);
  287. {
  288. FileOutputStream out (tempFile.getFile());
  289. if (! out.openedOk())
  290. return false;
  291. writeToStream (out, dtdToUse, false, true, encodingType, lineWrapLength);
  292. }
  293. return tempFile.overwriteTargetFileWithTemporary();
  294. }
  295. //==============================================================================
  296. bool XmlElement::hasTagName (const String& possibleTagName) const noexcept
  297. {
  298. const bool matches = tagName.equalsIgnoreCase (possibleTagName);
  299. // XML tags should be case-sensitive, so although this method allows a
  300. // case-insensitive match to pass, you should try to avoid this.
  301. jassert ((! matches) || tagName == possibleTagName);
  302. return matches;
  303. }
  304. String XmlElement::getNamespace() const
  305. {
  306. return tagName.upToFirstOccurrenceOf (":", false, false);
  307. }
  308. String XmlElement::getTagNameWithoutNamespace() const
  309. {
  310. return tagName.fromLastOccurrenceOf (":", false, false);
  311. }
  312. bool XmlElement::hasTagNameIgnoringNamespace (const String& possibleTagName) const
  313. {
  314. return hasTagName (possibleTagName) || getTagNameWithoutNamespace() == possibleTagName;
  315. }
  316. XmlElement* XmlElement::getNextElementWithTagName (const String& requiredTagName) const
  317. {
  318. XmlElement* e = nextListItem;
  319. while (e != nullptr && ! e->hasTagName (requiredTagName))
  320. e = e->nextListItem;
  321. return e;
  322. }
  323. //==============================================================================
  324. int XmlElement::getNumAttributes() const noexcept
  325. {
  326. return attributes.size();
  327. }
  328. const String& XmlElement::getAttributeName (const int index) const noexcept
  329. {
  330. const XmlAttributeNode* const att = attributes [index];
  331. return att != nullptr ? att->name : String::empty;
  332. }
  333. const String& XmlElement::getAttributeValue (const int index) const noexcept
  334. {
  335. const XmlAttributeNode* const att = attributes [index];
  336. return att != nullptr ? att->value : String::empty;
  337. }
  338. bool XmlElement::hasAttribute (const String& attributeName) const noexcept
  339. {
  340. for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem)
  341. if (att->hasName (attributeName))
  342. return true;
  343. return false;
  344. }
  345. //==============================================================================
  346. const String& XmlElement::getStringAttribute (const String& attributeName) const noexcept
  347. {
  348. for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem)
  349. if (att->hasName (attributeName))
  350. return att->value;
  351. return String::empty;
  352. }
  353. String XmlElement::getStringAttribute (const String& attributeName, const String& defaultReturnValue) const
  354. {
  355. for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem)
  356. if (att->hasName (attributeName))
  357. return att->value;
  358. return defaultReturnValue;
  359. }
  360. int XmlElement::getIntAttribute (const String& attributeName, const int defaultReturnValue) const
  361. {
  362. for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem)
  363. if (att->hasName (attributeName))
  364. return att->value.getIntValue();
  365. return defaultReturnValue;
  366. }
  367. double XmlElement::getDoubleAttribute (const String& attributeName, const double defaultReturnValue) const
  368. {
  369. for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem)
  370. if (att->hasName (attributeName))
  371. return att->value.getDoubleValue();
  372. return defaultReturnValue;
  373. }
  374. bool XmlElement::getBoolAttribute (const String& attributeName, const bool defaultReturnValue) const
  375. {
  376. for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem)
  377. {
  378. if (att->hasName (attributeName))
  379. {
  380. juce_wchar firstChar = att->value[0];
  381. if (CharacterFunctions::isWhitespace (firstChar))
  382. firstChar = att->value.trimStart() [0];
  383. return firstChar == '1'
  384. || firstChar == 't'
  385. || firstChar == 'y'
  386. || firstChar == 'T'
  387. || firstChar == 'Y';
  388. }
  389. }
  390. return defaultReturnValue;
  391. }
  392. bool XmlElement::compareAttribute (const String& attributeName,
  393. const String& stringToCompareAgainst,
  394. const bool ignoreCase) const noexcept
  395. {
  396. for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem)
  397. if (att->hasName (attributeName))
  398. return ignoreCase ? att->value.equalsIgnoreCase (stringToCompareAgainst)
  399. : att->value == stringToCompareAgainst;
  400. return false;
  401. }
  402. //==============================================================================
  403. void XmlElement::setAttribute (const String& attributeName, const String& value)
  404. {
  405. if (attributes == nullptr)
  406. {
  407. attributes = new XmlAttributeNode (attributeName, value);
  408. }
  409. else
  410. {
  411. for (XmlAttributeNode* att = attributes; ; att = att->nextListItem)
  412. {
  413. if (att->hasName (attributeName))
  414. {
  415. att->value = value;
  416. break;
  417. }
  418. if (att->nextListItem == nullptr)
  419. {
  420. att->nextListItem = new XmlAttributeNode (attributeName, value);
  421. break;
  422. }
  423. }
  424. }
  425. }
  426. void XmlElement::setAttribute (const String& attributeName, const int number)
  427. {
  428. setAttribute (attributeName, String (number));
  429. }
  430. void XmlElement::setAttribute (const String& attributeName, const double number)
  431. {
  432. setAttribute (attributeName, String (number));
  433. }
  434. void XmlElement::removeAttribute (const String& attributeName) noexcept
  435. {
  436. for (LinkedListPointer<XmlAttributeNode>* att = &attributes;
  437. att->get() != nullptr;
  438. att = &(att->get()->nextListItem))
  439. {
  440. if (att->get()->hasName (attributeName))
  441. {
  442. delete att->removeNext();
  443. break;
  444. }
  445. }
  446. }
  447. void XmlElement::removeAllAttributes() noexcept
  448. {
  449. attributes.deleteAll();
  450. }
  451. //==============================================================================
  452. int XmlElement::getNumChildElements() const noexcept
  453. {
  454. return firstChildElement.size();
  455. }
  456. XmlElement* XmlElement::getChildElement (const int index) const noexcept
  457. {
  458. return firstChildElement [index].get();
  459. }
  460. XmlElement* XmlElement::getChildByName (const String& childName) const noexcept
  461. {
  462. for (XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem)
  463. if (child->hasTagName (childName))
  464. return child;
  465. return nullptr;
  466. }
  467. void XmlElement::addChildElement (XmlElement* const newNode) noexcept
  468. {
  469. if (newNode != nullptr)
  470. firstChildElement.append (newNode);
  471. }
  472. void XmlElement::insertChildElement (XmlElement* const newNode,
  473. int indexToInsertAt) noexcept
  474. {
  475. if (newNode != nullptr)
  476. {
  477. removeChildElement (newNode, false);
  478. firstChildElement.insertAtIndex (indexToInsertAt, newNode);
  479. }
  480. }
  481. XmlElement* XmlElement::createNewChildElement (const String& childTagName)
  482. {
  483. XmlElement* const newElement = new XmlElement (childTagName);
  484. addChildElement (newElement);
  485. return newElement;
  486. }
  487. bool XmlElement::replaceChildElement (XmlElement* const currentChildElement,
  488. XmlElement* const newNode) noexcept
  489. {
  490. if (newNode != nullptr)
  491. {
  492. if (LinkedListPointer<XmlElement>* const p = firstChildElement.findPointerTo (currentChildElement))
  493. {
  494. if (currentChildElement != newNode)
  495. delete p->replaceNext (newNode);
  496. return true;
  497. }
  498. }
  499. return false;
  500. }
  501. void XmlElement::removeChildElement (XmlElement* const childToRemove,
  502. const bool shouldDeleteTheChild) noexcept
  503. {
  504. if (childToRemove != nullptr)
  505. {
  506. firstChildElement.remove (childToRemove);
  507. if (shouldDeleteTheChild)
  508. delete childToRemove;
  509. }
  510. }
  511. bool XmlElement::isEquivalentTo (const XmlElement* const other,
  512. const bool ignoreOrderOfAttributes) const noexcept
  513. {
  514. if (this != other)
  515. {
  516. if (other == nullptr || tagName != other->tagName)
  517. return false;
  518. if (ignoreOrderOfAttributes)
  519. {
  520. int totalAtts = 0;
  521. for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem)
  522. {
  523. if (! other->compareAttribute (att->name, att->value))
  524. return false;
  525. ++totalAtts;
  526. }
  527. if (totalAtts != other->getNumAttributes())
  528. return false;
  529. }
  530. else
  531. {
  532. const XmlAttributeNode* thisAtt = attributes;
  533. const XmlAttributeNode* otherAtt = other->attributes;
  534. for (;;)
  535. {
  536. if (thisAtt == nullptr || otherAtt == nullptr)
  537. {
  538. if (thisAtt == otherAtt) // both 0, so it's a match
  539. break;
  540. return false;
  541. }
  542. if (thisAtt->name != otherAtt->name
  543. || thisAtt->value != otherAtt->value)
  544. {
  545. return false;
  546. }
  547. thisAtt = thisAtt->nextListItem;
  548. otherAtt = otherAtt->nextListItem;
  549. }
  550. }
  551. const XmlElement* thisChild = firstChildElement;
  552. const XmlElement* otherChild = other->firstChildElement;
  553. for (;;)
  554. {
  555. if (thisChild == nullptr || otherChild == nullptr)
  556. {
  557. if (thisChild == otherChild) // both 0, so it's a match
  558. break;
  559. return false;
  560. }
  561. if (! thisChild->isEquivalentTo (otherChild, ignoreOrderOfAttributes))
  562. return false;
  563. thisChild = thisChild->nextListItem;
  564. otherChild = otherChild->nextListItem;
  565. }
  566. }
  567. return true;
  568. }
  569. void XmlElement::deleteAllChildElements() noexcept
  570. {
  571. firstChildElement.deleteAll();
  572. }
  573. void XmlElement::deleteAllChildElementsWithTagName (const String& name) noexcept
  574. {
  575. for (XmlElement* child = firstChildElement; child != nullptr;)
  576. {
  577. XmlElement* const nextChild = child->nextListItem;
  578. if (child->hasTagName (name))
  579. removeChildElement (child, true);
  580. child = nextChild;
  581. }
  582. }
  583. bool XmlElement::containsChildElement (const XmlElement* const possibleChild) const noexcept
  584. {
  585. return firstChildElement.contains (possibleChild);
  586. }
  587. XmlElement* XmlElement::findParentElementOf (const XmlElement* const elementToLookFor) noexcept
  588. {
  589. if (this == elementToLookFor || elementToLookFor == nullptr)
  590. return nullptr;
  591. for (XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem)
  592. {
  593. if (elementToLookFor == child)
  594. return this;
  595. if (XmlElement* const found = child->findParentElementOf (elementToLookFor))
  596. return found;
  597. }
  598. return nullptr;
  599. }
  600. void XmlElement::getChildElementsAsArray (XmlElement** elems) const noexcept
  601. {
  602. firstChildElement.copyToArray (elems);
  603. }
  604. void XmlElement::reorderChildElements (XmlElement** const elems, const int num) noexcept
  605. {
  606. XmlElement* e = firstChildElement = elems[0];
  607. for (int i = 1; i < num; ++i)
  608. {
  609. e->nextListItem = elems[i];
  610. e = e->nextListItem;
  611. }
  612. e->nextListItem = nullptr;
  613. }
  614. //==============================================================================
  615. bool XmlElement::isTextElement() const noexcept
  616. {
  617. return tagName.isEmpty();
  618. }
  619. static const String juce_xmltextContentAttributeName ("text");
  620. const String& XmlElement::getText() const noexcept
  621. {
  622. jassert (isTextElement()); // you're trying to get the text from an element that
  623. // isn't actually a text element.. If this contains text sub-nodes, you
  624. // probably want to use getAllSubText instead.
  625. return getStringAttribute (juce_xmltextContentAttributeName);
  626. }
  627. void XmlElement::setText (const String& newText)
  628. {
  629. if (isTextElement())
  630. setAttribute (juce_xmltextContentAttributeName, newText);
  631. else
  632. jassertfalse; // you can only change the text in a text element, not a normal one.
  633. }
  634. String XmlElement::getAllSubText() const
  635. {
  636. if (isTextElement())
  637. return getText();
  638. MemoryOutputStream mem (1024);
  639. for (const XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem)
  640. mem << child->getAllSubText();
  641. return mem.toString();
  642. }
  643. String XmlElement::getChildElementAllSubText (const String& childTagName,
  644. const String& defaultReturnValue) const
  645. {
  646. if (const XmlElement* const child = getChildByName (childTagName))
  647. return child->getAllSubText();
  648. return defaultReturnValue;
  649. }
  650. XmlElement* XmlElement::createTextElement (const String& text)
  651. {
  652. XmlElement* const e = new XmlElement ((int) 0);
  653. e->setAttribute (juce_xmltextContentAttributeName, text);
  654. return e;
  655. }
  656. void XmlElement::addTextElement (const String& text)
  657. {
  658. addChildElement (createTextElement (text));
  659. }
  660. void XmlElement::deleteAllTextElements() noexcept
  661. {
  662. for (XmlElement* child = firstChildElement; child != nullptr;)
  663. {
  664. XmlElement* const next = child->nextListItem;
  665. if (child->isTextElement())
  666. removeChildElement (child, true);
  667. child = next;
  668. }
  669. }