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.

838 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. static 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. static 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. static 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. if (indentationLevel >= 0)
  180. writeSpaces (outputStream, indentationLevel);
  181. if (! isTextElement())
  182. {
  183. outputStream.writeByte ('<');
  184. outputStream << tagName;
  185. {
  186. const int attIndent = indentationLevel + tagName.length() + 1;
  187. int lineLen = 0;
  188. for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem)
  189. {
  190. if (lineLen > lineWrapLength && indentationLevel >= 0)
  191. {
  192. outputStream << newLine;
  193. writeSpaces (outputStream, attIndent);
  194. lineLen = 0;
  195. }
  196. const int64 startPos = outputStream.getPosition();
  197. outputStream.writeByte (' ');
  198. outputStream << att->name;
  199. outputStream.write ("=\"", 2);
  200. escapeIllegalXmlChars (outputStream, att->value, true);
  201. outputStream.writeByte ('"');
  202. lineLen += (int) (outputStream.getPosition() - startPos);
  203. }
  204. }
  205. if (firstChildElement != nullptr)
  206. {
  207. outputStream.writeByte ('>');
  208. bool lastWasTextNode = false;
  209. for (XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem)
  210. {
  211. if (child->isTextElement())
  212. {
  213. escapeIllegalXmlChars (outputStream, child->getText(), false);
  214. lastWasTextNode = true;
  215. }
  216. else
  217. {
  218. if (indentationLevel >= 0 && ! lastWasTextNode)
  219. outputStream << newLine;
  220. child->writeElementAsText (outputStream,
  221. lastWasTextNode ? 0 : (indentationLevel + (indentationLevel >= 0 ? 2 : 0)), lineWrapLength);
  222. lastWasTextNode = false;
  223. }
  224. }
  225. if (indentationLevel >= 0 && ! lastWasTextNode)
  226. {
  227. outputStream << newLine;
  228. writeSpaces (outputStream, indentationLevel);
  229. }
  230. outputStream.write ("</", 2);
  231. outputStream << tagName;
  232. outputStream.writeByte ('>');
  233. }
  234. else
  235. {
  236. outputStream.write ("/>", 2);
  237. }
  238. }
  239. else
  240. {
  241. escapeIllegalXmlChars (outputStream, getText(), false);
  242. }
  243. }
  244. String XmlElement::createDocument (const String& dtdToUse,
  245. const bool allOnOneLine,
  246. const bool includeXmlHeader,
  247. const String& encodingType,
  248. const int lineWrapLength) const
  249. {
  250. MemoryOutputStream mem (2048);
  251. writeToStream (mem, dtdToUse, allOnOneLine, includeXmlHeader, encodingType, lineWrapLength);
  252. return mem.toUTF8();
  253. }
  254. void XmlElement::writeToStream (OutputStream& output,
  255. const String& dtdToUse,
  256. const bool allOnOneLine,
  257. const bool includeXmlHeader,
  258. const String& encodingType,
  259. const int lineWrapLength) const
  260. {
  261. using namespace XmlOutputFunctions;
  262. if (includeXmlHeader)
  263. {
  264. output << "<?xml version=\"1.0\" encoding=\"" << encodingType << "\"?>";
  265. if (allOnOneLine)
  266. output.writeByte (' ');
  267. else
  268. output << newLine << newLine;
  269. }
  270. if (dtdToUse.isNotEmpty())
  271. {
  272. output << dtdToUse;
  273. if (allOnOneLine)
  274. output.writeByte (' ');
  275. else
  276. output << newLine;
  277. }
  278. writeElementAsText (output, allOnOneLine ? -1 : 0, lineWrapLength);
  279. if (! allOnOneLine)
  280. output << newLine;
  281. }
  282. bool XmlElement::writeToFile (const File& file,
  283. const String& dtdToUse,
  284. const String& encodingType,
  285. const int lineWrapLength) const
  286. {
  287. TemporaryFile tempFile (file);
  288. {
  289. FileOutputStream out (tempFile.getFile());
  290. if (! out.openedOk())
  291. return false;
  292. writeToStream (out, dtdToUse, false, true, encodingType, lineWrapLength);
  293. }
  294. return tempFile.overwriteTargetFileWithTemporary();
  295. }
  296. //==============================================================================
  297. bool XmlElement::hasTagName (const String& tagNameWanted) const noexcept
  298. {
  299. #if JUCE_DEBUG
  300. // if debugging, check that the case is actually the same, because
  301. // valid xml is case-sensitive, and although this lets it pass, it's
  302. // better not to..
  303. if (tagName.equalsIgnoreCase (tagNameWanted))
  304. {
  305. jassert (tagName == tagNameWanted);
  306. return true;
  307. }
  308. else
  309. {
  310. return false;
  311. }
  312. #else
  313. return tagName.equalsIgnoreCase (tagNameWanted);
  314. #endif
  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. XmlAttributeNode* att = attributes;
  412. for (;;)
  413. {
  414. if (att->hasName (attributeName))
  415. {
  416. att->value = value;
  417. break;
  418. }
  419. else if (att->nextListItem == nullptr)
  420. {
  421. att->nextListItem = new XmlAttributeNode (attributeName, value);
  422. break;
  423. }
  424. att = att->nextListItem;
  425. }
  426. }
  427. }
  428. void XmlElement::setAttribute (const String& attributeName, const int number)
  429. {
  430. setAttribute (attributeName, String (number));
  431. }
  432. void XmlElement::setAttribute (const String& attributeName, const double number)
  433. {
  434. setAttribute (attributeName, String (number));
  435. }
  436. void XmlElement::removeAttribute (const String& attributeName) noexcept
  437. {
  438. LinkedListPointer<XmlAttributeNode>* att = &attributes;
  439. while (att->get() != nullptr)
  440. {
  441. if (att->get()->hasName (attributeName))
  442. {
  443. delete att->removeNext();
  444. break;
  445. }
  446. att = &(att->get()->nextListItem);
  447. }
  448. }
  449. void XmlElement::removeAllAttributes() noexcept
  450. {
  451. attributes.deleteAll();
  452. }
  453. //==============================================================================
  454. int XmlElement::getNumChildElements() const noexcept
  455. {
  456. return firstChildElement.size();
  457. }
  458. XmlElement* XmlElement::getChildElement (const int index) const noexcept
  459. {
  460. return firstChildElement [index].get();
  461. }
  462. XmlElement* XmlElement::getChildByName (const String& childName) const noexcept
  463. {
  464. for (XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem)
  465. if (child->hasTagName (childName))
  466. return child;
  467. return nullptr;
  468. }
  469. void XmlElement::addChildElement (XmlElement* const newNode) noexcept
  470. {
  471. if (newNode != nullptr)
  472. firstChildElement.append (newNode);
  473. }
  474. void XmlElement::insertChildElement (XmlElement* const newNode,
  475. int indexToInsertAt) noexcept
  476. {
  477. if (newNode != nullptr)
  478. {
  479. removeChildElement (newNode, false);
  480. firstChildElement.insertAtIndex (indexToInsertAt, newNode);
  481. }
  482. }
  483. XmlElement* XmlElement::createNewChildElement (const String& childTagName)
  484. {
  485. XmlElement* const newElement = new XmlElement (childTagName);
  486. addChildElement (newElement);
  487. return newElement;
  488. }
  489. bool XmlElement::replaceChildElement (XmlElement* const currentChildElement,
  490. XmlElement* const newNode) noexcept
  491. {
  492. if (newNode != nullptr)
  493. {
  494. LinkedListPointer<XmlElement>* const p = firstChildElement.findPointerTo (currentChildElement);
  495. if (p != nullptr)
  496. {
  497. if (currentChildElement != newNode)
  498. delete p->replaceNext (newNode);
  499. return true;
  500. }
  501. }
  502. return false;
  503. }
  504. void XmlElement::removeChildElement (XmlElement* const childToRemove,
  505. const bool shouldDeleteTheChild) noexcept
  506. {
  507. if (childToRemove != nullptr)
  508. {
  509. firstChildElement.remove (childToRemove);
  510. if (shouldDeleteTheChild)
  511. delete childToRemove;
  512. }
  513. }
  514. bool XmlElement::isEquivalentTo (const XmlElement* const other,
  515. const bool ignoreOrderOfAttributes) const noexcept
  516. {
  517. if (this != other)
  518. {
  519. if (other == nullptr || tagName != other->tagName)
  520. return false;
  521. if (ignoreOrderOfAttributes)
  522. {
  523. int totalAtts = 0;
  524. for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem)
  525. {
  526. if (! other->compareAttribute (att->name, att->value))
  527. return false;
  528. ++totalAtts;
  529. }
  530. if (totalAtts != other->getNumAttributes())
  531. return false;
  532. }
  533. else
  534. {
  535. const XmlAttributeNode* thisAtt = attributes;
  536. const XmlAttributeNode* otherAtt = other->attributes;
  537. for (;;)
  538. {
  539. if (thisAtt == nullptr || otherAtt == nullptr)
  540. {
  541. if (thisAtt == otherAtt) // both 0, so it's a match
  542. break;
  543. return false;
  544. }
  545. if (thisAtt->name != otherAtt->name
  546. || thisAtt->value != otherAtt->value)
  547. {
  548. return false;
  549. }
  550. thisAtt = thisAtt->nextListItem;
  551. otherAtt = otherAtt->nextListItem;
  552. }
  553. }
  554. const XmlElement* thisChild = firstChildElement;
  555. const XmlElement* otherChild = other->firstChildElement;
  556. for (;;)
  557. {
  558. if (thisChild == nullptr || otherChild == nullptr)
  559. {
  560. if (thisChild == otherChild) // both 0, so it's a match
  561. break;
  562. return false;
  563. }
  564. if (! thisChild->isEquivalentTo (otherChild, ignoreOrderOfAttributes))
  565. return false;
  566. thisChild = thisChild->nextListItem;
  567. otherChild = otherChild->nextListItem;
  568. }
  569. }
  570. return true;
  571. }
  572. void XmlElement::deleteAllChildElements() noexcept
  573. {
  574. firstChildElement.deleteAll();
  575. }
  576. void XmlElement::deleteAllChildElementsWithTagName (const String& name) noexcept
  577. {
  578. XmlElement* child = firstChildElement;
  579. while (child != nullptr)
  580. {
  581. XmlElement* const nextChild = child->nextListItem;
  582. if (child->hasTagName (name))
  583. removeChildElement (child, true);
  584. child = nextChild;
  585. }
  586. }
  587. bool XmlElement::containsChildElement (const XmlElement* const possibleChild) const noexcept
  588. {
  589. return firstChildElement.contains (possibleChild);
  590. }
  591. XmlElement* XmlElement::findParentElementOf (const XmlElement* const elementToLookFor) noexcept
  592. {
  593. if (this == elementToLookFor || elementToLookFor == nullptr)
  594. return nullptr;
  595. for (XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem)
  596. {
  597. if (elementToLookFor == child)
  598. return this;
  599. XmlElement* const found = child->findParentElementOf (elementToLookFor);
  600. if (found != nullptr)
  601. return found;
  602. }
  603. return nullptr;
  604. }
  605. void XmlElement::getChildElementsAsArray (XmlElement** elems) const noexcept
  606. {
  607. firstChildElement.copyToArray (elems);
  608. }
  609. void XmlElement::reorderChildElements (XmlElement** const elems, const int num) noexcept
  610. {
  611. XmlElement* e = firstChildElement = elems[0];
  612. for (int i = 1; i < num; ++i)
  613. {
  614. e->nextListItem = elems[i];
  615. e = e->nextListItem;
  616. }
  617. e->nextListItem = nullptr;
  618. }
  619. //==============================================================================
  620. bool XmlElement::isTextElement() const noexcept
  621. {
  622. return tagName.isEmpty();
  623. }
  624. static const String juce_xmltextContentAttributeName ("text");
  625. const String& XmlElement::getText() const noexcept
  626. {
  627. jassert (isTextElement()); // you're trying to get the text from an element that
  628. // isn't actually a text element.. If this contains text sub-nodes, you
  629. // probably want to use getAllSubText instead.
  630. return getStringAttribute (juce_xmltextContentAttributeName);
  631. }
  632. void XmlElement::setText (const String& newText)
  633. {
  634. if (isTextElement())
  635. setAttribute (juce_xmltextContentAttributeName, newText);
  636. else
  637. jassertfalse; // you can only change the text in a text element, not a normal one.
  638. }
  639. String XmlElement::getAllSubText() const
  640. {
  641. if (isTextElement())
  642. return getText();
  643. MemoryOutputStream mem (1024);
  644. for (const XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem)
  645. mem << child->getAllSubText();
  646. return mem.toString();
  647. }
  648. String XmlElement::getChildElementAllSubText (const String& childTagName,
  649. const String& defaultReturnValue) const
  650. {
  651. const XmlElement* const child = getChildByName (childTagName);
  652. if (child != nullptr)
  653. return child->getAllSubText();
  654. return defaultReturnValue;
  655. }
  656. XmlElement* XmlElement::createTextElement (const String& text)
  657. {
  658. XmlElement* const e = new XmlElement ((int) 0);
  659. e->setAttribute (juce_xmltextContentAttributeName, text);
  660. return e;
  661. }
  662. void XmlElement::addTextElement (const String& text)
  663. {
  664. addChildElement (createTextElement (text));
  665. }
  666. void XmlElement::deleteAllTextElements() noexcept
  667. {
  668. XmlElement* child = firstChildElement;
  669. while (child != nullptr)
  670. {
  671. XmlElement* const next = child->nextListItem;
  672. if (child->isTextElement())
  673. removeChildElement (child, true);
  674. child = next;
  675. }
  676. }