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.

840 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& name_, const String& value_) noexcept
  24. : name (name_),
  25. value (value_)
  26. {
  27. #if JUCE_DEBUG
  28. // this checks whether the attribute name string contains any illegal characters..
  29. for (String::CharPointerType t (name.getCharPointer()); ! t.isEmpty(); ++t)
  30. jassert (t.isLetterOrDigit() || *t == '_' || *t == '-' || *t == ':');
  31. #endif
  32. }
  33. inline bool XmlElement::XmlAttributeNode::hasName (const String& nameToMatch) const noexcept
  34. {
  35. return name.equalsIgnoreCase (nameToMatch);
  36. }
  37. //==============================================================================
  38. XmlElement::XmlElement (const String& tagName_) noexcept
  39. : tagName (tagName_)
  40. {
  41. // the tag name mustn't be empty, or it'll look like a text element!
  42. jassert (tagName_.containsNonWhitespaceChars())
  43. // The tag can't contain spaces or other characters that would create invalid XML!
  44. jassert (! tagName_.containsAnyOf (" <>/&"));
  45. }
  46. XmlElement::XmlElement (int /*dummy*/) noexcept
  47. {
  48. }
  49. XmlElement::XmlElement (const XmlElement& other)
  50. : tagName (other.tagName)
  51. {
  52. copyChildrenAndAttributesFrom (other);
  53. }
  54. XmlElement& XmlElement::operator= (const XmlElement& other)
  55. {
  56. if (this != &other)
  57. {
  58. removeAllAttributes();
  59. deleteAllChildElements();
  60. tagName = other.tagName;
  61. copyChildrenAndAttributesFrom (other);
  62. }
  63. return *this;
  64. }
  65. #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS
  66. XmlElement::XmlElement (XmlElement&& other) noexcept
  67. : nextListItem (static_cast <LinkedListPointer <XmlElement>&&> (other.nextListItem)),
  68. firstChildElement (static_cast <LinkedListPointer <XmlElement>&&> (other.firstChildElement)),
  69. attributes (static_cast <LinkedListPointer <XmlAttributeNode>&&> (other.attributes)),
  70. tagName (static_cast <String&&> (other.tagName))
  71. {
  72. }
  73. XmlElement& XmlElement::operator= (XmlElement&& other) noexcept
  74. {
  75. jassert (this != &other); // hopefully the compiler should make this situation impossible!
  76. removeAllAttributes();
  77. deleteAllChildElements();
  78. nextListItem = static_cast <LinkedListPointer <XmlElement>&&> (other.nextListItem);
  79. firstChildElement = static_cast <LinkedListPointer <XmlElement>&&> (other.firstChildElement);
  80. attributes = static_cast <LinkedListPointer <XmlAttributeNode>&&> (other.attributes);
  81. tagName = static_cast <String&&> (other.tagName);
  82. return *this;
  83. }
  84. #endif
  85. void XmlElement::copyChildrenAndAttributesFrom (const XmlElement& other)
  86. {
  87. jassert (firstChildElement.get() == nullptr);
  88. firstChildElement.addCopyOfList (other.firstChildElement);
  89. jassert (attributes.get() == nullptr);
  90. attributes.addCopyOfList (other.attributes);
  91. }
  92. XmlElement::~XmlElement() noexcept
  93. {
  94. firstChildElement.deleteAll();
  95. attributes.deleteAll();
  96. }
  97. //==============================================================================
  98. namespace XmlOutputFunctions
  99. {
  100. #if 0 // (These functions are just used to generate the lookup table used below)
  101. bool isLegalXmlCharSlow (const juce_wchar character) noexcept
  102. {
  103. if ((character >= 'a' && character <= 'z')
  104. || (character >= 'A' && character <= 'Z')
  105. || (character >= '0' && character <= '9'))
  106. return true;
  107. const char* t = " .,;:-()_+=?!'#@[]/\\*%~{}$|";
  108. do
  109. {
  110. if (((juce_wchar) (uint8) *t) == character)
  111. return true;
  112. }
  113. while (*++t != 0);
  114. return false;
  115. }
  116. void generateLegalCharLookupTable()
  117. {
  118. uint8 n[32] = { 0 };
  119. for (int i = 0; i < 256; ++i)
  120. if (isLegalXmlCharSlow (i))
  121. n[i >> 3] |= (1 << (i & 7));
  122. String s;
  123. for (int i = 0; i < 32; ++i)
  124. s << (int) n[i] << ", ";
  125. DBG (s);
  126. }
  127. #endif
  128. bool isLegalXmlChar (const uint32 c) noexcept
  129. {
  130. static const unsigned char legalChars[] = { 0, 0, 0, 0, 187, 255, 255, 175, 255, 255, 255, 191, 254, 255, 255, 127 };
  131. return c < sizeof (legalChars) * 8
  132. && (legalChars [c >> 3] & (1 << (c & 7))) != 0;
  133. }
  134. void escapeIllegalXmlChars (OutputStream& outputStream, const String& text, const bool changeNewLines)
  135. {
  136. String::CharPointerType t (text.getCharPointer());
  137. for (;;)
  138. {
  139. const uint32 character = (uint32) t.getAndAdvance();
  140. if (character == 0)
  141. break;
  142. if (isLegalXmlChar (character))
  143. {
  144. outputStream << (char) character;
  145. }
  146. else
  147. {
  148. switch (character)
  149. {
  150. case '&': outputStream << "&amp;"; break;
  151. case '"': outputStream << "&quot;"; break;
  152. case '>': outputStream << "&gt;"; break;
  153. case '<': outputStream << "&lt;"; break;
  154. case '\n':
  155. case '\r':
  156. if (! changeNewLines)
  157. {
  158. outputStream << (char) character;
  159. break;
  160. }
  161. // Note: deliberate fall-through here!
  162. default:
  163. outputStream << "&#" << ((int) character) << ';';
  164. break;
  165. }
  166. }
  167. }
  168. }
  169. void writeSpaces (OutputStream& out, const int numSpaces)
  170. {
  171. out.writeRepeatedByte (' ', numSpaces);
  172. }
  173. }
  174. void XmlElement::writeElementAsText (OutputStream& outputStream,
  175. const int indentationLevel,
  176. const int lineWrapLength) const
  177. {
  178. using namespace XmlOutputFunctions;
  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. if (file.hasWriteAccess())
  287. {
  288. TemporaryFile tempFile (file);
  289. ScopedPointer <FileOutputStream> out (tempFile.getFile().createOutputStream());
  290. if (out != nullptr)
  291. {
  292. writeToStream (*out, dtdToUse, false, true, encodingType, lineWrapLength);
  293. out = nullptr;
  294. return tempFile.overwriteTargetFileWithTemporary();
  295. }
  296. }
  297. return false;
  298. }
  299. //==============================================================================
  300. bool XmlElement::hasTagName (const String& tagNameWanted) const noexcept
  301. {
  302. #if JUCE_DEBUG
  303. // if debugging, check that the case is actually the same, because
  304. // valid xml is case-sensitive, and although this lets it pass, it's
  305. // better not to..
  306. if (tagName.equalsIgnoreCase (tagNameWanted))
  307. {
  308. jassert (tagName == tagNameWanted);
  309. return true;
  310. }
  311. else
  312. {
  313. return false;
  314. }
  315. #else
  316. return tagName.equalsIgnoreCase (tagNameWanted);
  317. #endif
  318. }
  319. XmlElement* XmlElement::getNextElementWithTagName (const String& requiredTagName) const
  320. {
  321. XmlElement* e = nextListItem;
  322. while (e != nullptr && ! e->hasTagName (requiredTagName))
  323. e = e->nextListItem;
  324. return e;
  325. }
  326. //==============================================================================
  327. int XmlElement::getNumAttributes() const noexcept
  328. {
  329. return attributes.size();
  330. }
  331. const String& XmlElement::getAttributeName (const int index) const noexcept
  332. {
  333. const XmlAttributeNode* const att = attributes [index];
  334. return att != nullptr ? att->name : String::empty;
  335. }
  336. const String& XmlElement::getAttributeValue (const int index) const noexcept
  337. {
  338. const XmlAttributeNode* const att = attributes [index];
  339. return att != nullptr ? att->value : String::empty;
  340. }
  341. bool XmlElement::hasAttribute (const String& attributeName) const noexcept
  342. {
  343. for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem)
  344. if (att->hasName (attributeName))
  345. return true;
  346. return false;
  347. }
  348. //==============================================================================
  349. const String& XmlElement::getStringAttribute (const String& attributeName) const noexcept
  350. {
  351. for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem)
  352. if (att->hasName (attributeName))
  353. return att->value;
  354. return String::empty;
  355. }
  356. String XmlElement::getStringAttribute (const String& attributeName, const String& defaultReturnValue) const
  357. {
  358. for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem)
  359. if (att->hasName (attributeName))
  360. return att->value;
  361. return defaultReturnValue;
  362. }
  363. int XmlElement::getIntAttribute (const String& attributeName, const int defaultReturnValue) const
  364. {
  365. for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem)
  366. if (att->hasName (attributeName))
  367. return att->value.getIntValue();
  368. return defaultReturnValue;
  369. }
  370. double XmlElement::getDoubleAttribute (const String& attributeName, const double defaultReturnValue) const
  371. {
  372. for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem)
  373. if (att->hasName (attributeName))
  374. return att->value.getDoubleValue();
  375. return defaultReturnValue;
  376. }
  377. bool XmlElement::getBoolAttribute (const String& attributeName, const bool defaultReturnValue) const
  378. {
  379. for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem)
  380. {
  381. if (att->hasName (attributeName))
  382. {
  383. juce_wchar firstChar = att->value[0];
  384. if (CharacterFunctions::isWhitespace (firstChar))
  385. firstChar = att->value.trimStart() [0];
  386. return firstChar == '1'
  387. || firstChar == 't'
  388. || firstChar == 'y'
  389. || firstChar == 'T'
  390. || firstChar == 'Y';
  391. }
  392. }
  393. return defaultReturnValue;
  394. }
  395. bool XmlElement::compareAttribute (const String& attributeName,
  396. const String& stringToCompareAgainst,
  397. const bool ignoreCase) const noexcept
  398. {
  399. for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem)
  400. if (att->hasName (attributeName))
  401. return ignoreCase ? att->value.equalsIgnoreCase (stringToCompareAgainst)
  402. : att->value == stringToCompareAgainst;
  403. return false;
  404. }
  405. //==============================================================================
  406. void XmlElement::setAttribute (const String& attributeName, const String& value)
  407. {
  408. if (attributes == nullptr)
  409. {
  410. attributes = new XmlAttributeNode (attributeName, value);
  411. }
  412. else
  413. {
  414. XmlAttributeNode* att = attributes;
  415. for (;;)
  416. {
  417. if (att->hasName (attributeName))
  418. {
  419. att->value = value;
  420. break;
  421. }
  422. else if (att->nextListItem == nullptr)
  423. {
  424. att->nextListItem = new XmlAttributeNode (attributeName, value);
  425. break;
  426. }
  427. att = att->nextListItem;
  428. }
  429. }
  430. }
  431. void XmlElement::setAttribute (const String& attributeName, const int number)
  432. {
  433. setAttribute (attributeName, String (number));
  434. }
  435. void XmlElement::setAttribute (const String& attributeName, const double number)
  436. {
  437. setAttribute (attributeName, String (number));
  438. }
  439. void XmlElement::removeAttribute (const String& attributeName) noexcept
  440. {
  441. LinkedListPointer<XmlAttributeNode>* att = &attributes;
  442. while (att->get() != nullptr)
  443. {
  444. if (att->get()->hasName (attributeName))
  445. {
  446. delete att->removeNext();
  447. break;
  448. }
  449. att = &(att->get()->nextListItem);
  450. }
  451. }
  452. void XmlElement::removeAllAttributes() noexcept
  453. {
  454. attributes.deleteAll();
  455. }
  456. //==============================================================================
  457. int XmlElement::getNumChildElements() const noexcept
  458. {
  459. return firstChildElement.size();
  460. }
  461. XmlElement* XmlElement::getChildElement (const int index) const noexcept
  462. {
  463. return firstChildElement [index].get();
  464. }
  465. XmlElement* XmlElement::getChildByName (const String& childName) const noexcept
  466. {
  467. for (XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem)
  468. if (child->hasTagName (childName))
  469. return child;
  470. return nullptr;
  471. }
  472. void XmlElement::addChildElement (XmlElement* const newNode) noexcept
  473. {
  474. if (newNode != nullptr)
  475. firstChildElement.append (newNode);
  476. }
  477. void XmlElement::insertChildElement (XmlElement* const newNode,
  478. int indexToInsertAt) noexcept
  479. {
  480. if (newNode != nullptr)
  481. {
  482. removeChildElement (newNode, false);
  483. firstChildElement.insertAtIndex (indexToInsertAt, newNode);
  484. }
  485. }
  486. XmlElement* XmlElement::createNewChildElement (const String& childTagName)
  487. {
  488. XmlElement* const newElement = new XmlElement (childTagName);
  489. addChildElement (newElement);
  490. return newElement;
  491. }
  492. bool XmlElement::replaceChildElement (XmlElement* const currentChildElement,
  493. XmlElement* const newNode) noexcept
  494. {
  495. if (newNode != nullptr)
  496. {
  497. LinkedListPointer<XmlElement>* const p = firstChildElement.findPointerTo (currentChildElement);
  498. if (p != nullptr)
  499. {
  500. if (currentChildElement != newNode)
  501. delete p->replaceNext (newNode);
  502. return true;
  503. }
  504. }
  505. return false;
  506. }
  507. void XmlElement::removeChildElement (XmlElement* const childToRemove,
  508. const bool shouldDeleteTheChild) noexcept
  509. {
  510. if (childToRemove != nullptr)
  511. {
  512. firstChildElement.remove (childToRemove);
  513. if (shouldDeleteTheChild)
  514. delete childToRemove;
  515. }
  516. }
  517. bool XmlElement::isEquivalentTo (const XmlElement* const other,
  518. const bool ignoreOrderOfAttributes) const noexcept
  519. {
  520. if (this != other)
  521. {
  522. if (other == nullptr || tagName != other->tagName)
  523. return false;
  524. if (ignoreOrderOfAttributes)
  525. {
  526. int totalAtts = 0;
  527. for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem)
  528. {
  529. if (! other->compareAttribute (att->name, att->value))
  530. return false;
  531. ++totalAtts;
  532. }
  533. if (totalAtts != other->getNumAttributes())
  534. return false;
  535. }
  536. else
  537. {
  538. const XmlAttributeNode* thisAtt = attributes;
  539. const XmlAttributeNode* otherAtt = other->attributes;
  540. for (;;)
  541. {
  542. if (thisAtt == nullptr || otherAtt == nullptr)
  543. {
  544. if (thisAtt == otherAtt) // both 0, so it's a match
  545. break;
  546. return false;
  547. }
  548. if (thisAtt->name != otherAtt->name
  549. || thisAtt->value != otherAtt->value)
  550. {
  551. return false;
  552. }
  553. thisAtt = thisAtt->nextListItem;
  554. otherAtt = otherAtt->nextListItem;
  555. }
  556. }
  557. const XmlElement* thisChild = firstChildElement;
  558. const XmlElement* otherChild = other->firstChildElement;
  559. for (;;)
  560. {
  561. if (thisChild == nullptr || otherChild == nullptr)
  562. {
  563. if (thisChild == otherChild) // both 0, so it's a match
  564. break;
  565. return false;
  566. }
  567. if (! thisChild->isEquivalentTo (otherChild, ignoreOrderOfAttributes))
  568. return false;
  569. thisChild = thisChild->nextListItem;
  570. otherChild = otherChild->nextListItem;
  571. }
  572. }
  573. return true;
  574. }
  575. void XmlElement::deleteAllChildElements() noexcept
  576. {
  577. firstChildElement.deleteAll();
  578. }
  579. void XmlElement::deleteAllChildElementsWithTagName (const String& name) noexcept
  580. {
  581. XmlElement* child = firstChildElement;
  582. while (child != nullptr)
  583. {
  584. XmlElement* const nextChild = child->nextListItem;
  585. if (child->hasTagName (name))
  586. removeChildElement (child, true);
  587. child = nextChild;
  588. }
  589. }
  590. bool XmlElement::containsChildElement (const XmlElement* const possibleChild) const noexcept
  591. {
  592. return firstChildElement.contains (possibleChild);
  593. }
  594. XmlElement* XmlElement::findParentElementOf (const XmlElement* const elementToLookFor) noexcept
  595. {
  596. if (this == elementToLookFor || elementToLookFor == nullptr)
  597. return nullptr;
  598. for (XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem)
  599. {
  600. if (elementToLookFor == child)
  601. return this;
  602. XmlElement* const found = child->findParentElementOf (elementToLookFor);
  603. if (found != nullptr)
  604. return found;
  605. }
  606. return nullptr;
  607. }
  608. void XmlElement::getChildElementsAsArray (XmlElement** elems) const noexcept
  609. {
  610. firstChildElement.copyToArray (elems);
  611. }
  612. void XmlElement::reorderChildElements (XmlElement** const elems, const int num) noexcept
  613. {
  614. XmlElement* e = firstChildElement = elems[0];
  615. for (int i = 1; i < num; ++i)
  616. {
  617. e->nextListItem = elems[i];
  618. e = e->nextListItem;
  619. }
  620. e->nextListItem = nullptr;
  621. }
  622. //==============================================================================
  623. bool XmlElement::isTextElement() const noexcept
  624. {
  625. return tagName.isEmpty();
  626. }
  627. static const String juce_xmltextContentAttributeName ("text");
  628. const String& XmlElement::getText() const noexcept
  629. {
  630. jassert (isTextElement()); // you're trying to get the text from an element that
  631. // isn't actually a text element.. If this contains text sub-nodes, you
  632. // probably want to use getAllSubText instead.
  633. return getStringAttribute (juce_xmltextContentAttributeName);
  634. }
  635. void XmlElement::setText (const String& newText)
  636. {
  637. if (isTextElement())
  638. setAttribute (juce_xmltextContentAttributeName, newText);
  639. else
  640. jassertfalse; // you can only change the text in a text element, not a normal one.
  641. }
  642. String XmlElement::getAllSubText() const
  643. {
  644. if (isTextElement())
  645. return getText();
  646. MemoryOutputStream mem (1024);
  647. for (const XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem)
  648. mem << child->getAllSubText();
  649. return mem.toString();
  650. }
  651. String XmlElement::getChildElementAllSubText (const String& childTagName,
  652. const String& defaultReturnValue) const
  653. {
  654. const XmlElement* const child = getChildByName (childTagName);
  655. if (child != nullptr)
  656. return child->getAllSubText();
  657. return defaultReturnValue;
  658. }
  659. XmlElement* XmlElement::createTextElement (const String& text)
  660. {
  661. XmlElement* const e = new XmlElement ((int) 0);
  662. e->setAttribute (juce_xmltextContentAttributeName, text);
  663. return e;
  664. }
  665. void XmlElement::addTextElement (const String& text)
  666. {
  667. addChildElement (createTextElement (text));
  668. }
  669. void XmlElement::deleteAllTextElements() noexcept
  670. {
  671. XmlElement* child = firstChildElement;
  672. while (child != nullptr)
  673. {
  674. XmlElement* const next = child->nextListItem;
  675. if (child->isTextElement())
  676. removeChildElement (child, true);
  677. child = next;
  678. }
  679. }