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.

933 lines
28KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. The code included in this file is provided under the terms of the ISC license
  8. http://www.isc.org/downloads/software-support-policy/isc-license. Permission
  9. To use, copy, modify, and/or distribute this software for any purpose with or
  10. without fee is hereby granted provided that the above copyright notice and
  11. this permission notice appear in all copies.
  12. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  13. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  14. DISCLAIMED.
  15. ==============================================================================
  16. */
  17. namespace juce
  18. {
  19. inline static bool isValidXmlNameStartCharacter (juce_wchar character) noexcept
  20. {
  21. return character == ':'
  22. || character == '_'
  23. || (character >= 'a' && character <= 'z')
  24. || (character >= 'A' && character <= 'Z')
  25. || (character >= 0xc0 && character <= 0xd6)
  26. || (character >= 0xd8 && character <= 0xf6)
  27. || (character >= 0xf8 && character <= 0x2ff)
  28. || (character >= 0x370 && character <= 0x37d)
  29. || (character >= 0x37f && character <= 0x1fff)
  30. || (character >= 0x200c && character <= 0x200d)
  31. || (character >= 0x2070 && character <= 0x218f)
  32. || (character >= 0x2c00 && character <= 0x2fef)
  33. || (character >= 0x3001 && character <= 0xd7ff)
  34. || (character >= 0xf900 && character <= 0xfdcf)
  35. || (character >= 0xfdf0 && character <= 0xfffd)
  36. || (character >= 0x10000 && character <= 0xeffff);
  37. }
  38. inline static bool isValidXmlNameBodyCharacter (juce_wchar character) noexcept
  39. {
  40. return isValidXmlNameStartCharacter (character)
  41. || character == '-'
  42. || character == '.'
  43. || character == 0xb7
  44. || (character >= '0' && character <= '9')
  45. || (character >= 0x300 && character <= 0x036f)
  46. || (character >= 0x203f && character <= 0x2040);
  47. }
  48. XmlElement::XmlAttributeNode::XmlAttributeNode (const XmlAttributeNode& other) noexcept
  49. : name (other.name),
  50. value (other.value)
  51. {
  52. }
  53. XmlElement::XmlAttributeNode::XmlAttributeNode (const Identifier& n, const String& v) noexcept
  54. : name (n), value (v)
  55. {
  56. jassert (isValidXmlName (name));
  57. }
  58. XmlElement::XmlAttributeNode::XmlAttributeNode (String::CharPointerType nameStart, String::CharPointerType nameEnd)
  59. : name (nameStart, nameEnd)
  60. {
  61. jassert (isValidXmlName (name));
  62. }
  63. //==============================================================================
  64. XmlElement::XmlElement (const String& tag)
  65. : tagName (StringPool::getGlobalPool().getPooledString (tag))
  66. {
  67. jassert (isValidXmlName (tagName));
  68. }
  69. XmlElement::XmlElement (const char* tag)
  70. : tagName (StringPool::getGlobalPool().getPooledString (tag))
  71. {
  72. jassert (isValidXmlName (tagName));
  73. }
  74. XmlElement::XmlElement (StringRef tag)
  75. : tagName (StringPool::getGlobalPool().getPooledString (tag))
  76. {
  77. jassert (isValidXmlName (tagName));
  78. }
  79. XmlElement::XmlElement (const Identifier& tag)
  80. : tagName (tag.toString())
  81. {
  82. jassert (isValidXmlName (tagName));
  83. }
  84. XmlElement::XmlElement (String::CharPointerType tagNameStart, String::CharPointerType tagNameEnd)
  85. : tagName (StringPool::getGlobalPool().getPooledString (tagNameStart, tagNameEnd))
  86. {
  87. jassert (isValidXmlName (tagName));
  88. }
  89. XmlElement::XmlElement (int /*dummy*/) noexcept
  90. {
  91. }
  92. XmlElement::XmlElement (const XmlElement& other)
  93. : tagName (other.tagName)
  94. {
  95. copyChildrenAndAttributesFrom (other);
  96. }
  97. XmlElement& XmlElement::operator= (const XmlElement& other)
  98. {
  99. if (this != &other)
  100. {
  101. removeAllAttributes();
  102. deleteAllChildElements();
  103. tagName = other.tagName;
  104. copyChildrenAndAttributesFrom (other);
  105. }
  106. return *this;
  107. }
  108. XmlElement::XmlElement (XmlElement&& other) noexcept
  109. : nextListItem (static_cast<LinkedListPointer<XmlElement>&&> (other.nextListItem)),
  110. firstChildElement (static_cast<LinkedListPointer<XmlElement>&&> (other.firstChildElement)),
  111. attributes (static_cast<LinkedListPointer<XmlAttributeNode>&&> (other.attributes)),
  112. tagName (static_cast<String&&> (other.tagName))
  113. {
  114. }
  115. XmlElement& XmlElement::operator= (XmlElement&& other) noexcept
  116. {
  117. jassert (this != &other); // hopefully the compiler should make this situation impossible!
  118. removeAllAttributes();
  119. deleteAllChildElements();
  120. nextListItem = static_cast<LinkedListPointer<XmlElement>&&> (other.nextListItem);
  121. firstChildElement = static_cast<LinkedListPointer<XmlElement>&&> (other.firstChildElement);
  122. attributes = static_cast<LinkedListPointer<XmlAttributeNode>&&> (other.attributes);
  123. tagName = static_cast<String&&> (other.tagName);
  124. return *this;
  125. }
  126. void XmlElement::copyChildrenAndAttributesFrom (const XmlElement& other)
  127. {
  128. jassert (firstChildElement.get() == nullptr);
  129. firstChildElement.addCopyOfList (other.firstChildElement);
  130. jassert (attributes.get() == nullptr);
  131. attributes.addCopyOfList (other.attributes);
  132. }
  133. XmlElement::~XmlElement() noexcept
  134. {
  135. firstChildElement.deleteAll();
  136. attributes.deleteAll();
  137. }
  138. //==============================================================================
  139. namespace XmlOutputFunctions
  140. {
  141. #if 0 // (These functions are just used to generate the lookup table used below)
  142. bool isLegalXmlCharSlow (const juce_wchar character) noexcept
  143. {
  144. if ((character >= 'a' && character <= 'z')
  145. || (character >= 'A' && character <= 'Z')
  146. || (character >= '0' && character <= '9'))
  147. return true;
  148. const char* t = " .,;:-()_+=?!'#@[]/\\*%~{}$|";
  149. do
  150. {
  151. if (((juce_wchar) (uint8) *t) == character)
  152. return true;
  153. }
  154. while (*++t != 0);
  155. return false;
  156. }
  157. void generateLegalCharLookupTable()
  158. {
  159. uint8 n[32] = { 0 };
  160. for (int i = 0; i < 256; ++i)
  161. if (isLegalXmlCharSlow (i))
  162. n[i >> 3] |= (1 << (i & 7));
  163. String s;
  164. for (int i = 0; i < 32; ++i)
  165. s << (int) n[i] << ", ";
  166. DBG (s);
  167. }
  168. #endif
  169. static bool isLegalXmlChar (const uint32 c) noexcept
  170. {
  171. static const unsigned char legalChars[] = { 0, 0, 0, 0, 187, 255, 255, 175, 255,
  172. 255, 255, 191, 254, 255, 255, 127 };
  173. return c < sizeof (legalChars) * 8
  174. && (legalChars [c >> 3] & (1 << (c & 7))) != 0;
  175. }
  176. static void escapeIllegalXmlChars (OutputStream& outputStream, const String& text, const bool changeNewLines)
  177. {
  178. String::CharPointerType t (text.getCharPointer());
  179. for (;;)
  180. {
  181. const uint32 character = (uint32) t.getAndAdvance();
  182. if (character == 0)
  183. break;
  184. if (isLegalXmlChar (character))
  185. {
  186. outputStream << (char) character;
  187. }
  188. else
  189. {
  190. switch (character)
  191. {
  192. case '&': outputStream << "&amp;"; break;
  193. case '"': outputStream << "&quot;"; break;
  194. case '>': outputStream << "&gt;"; break;
  195. case '<': outputStream << "&lt;"; break;
  196. case '\n':
  197. case '\r':
  198. if (! changeNewLines)
  199. {
  200. outputStream << (char) character;
  201. break;
  202. }
  203. // fall-through
  204. default:
  205. outputStream << "&#" << ((int) character) << ';';
  206. break;
  207. }
  208. }
  209. }
  210. }
  211. static void writeSpaces (OutputStream& out, const size_t numSpaces)
  212. {
  213. out.writeRepeatedByte (' ', numSpaces);
  214. }
  215. }
  216. void XmlElement::writeElementAsText (OutputStream& outputStream,
  217. const int indentationLevel,
  218. const int lineWrapLength) const
  219. {
  220. using namespace XmlOutputFunctions;
  221. if (indentationLevel >= 0)
  222. writeSpaces (outputStream, (size_t) indentationLevel);
  223. if (! isTextElement())
  224. {
  225. outputStream.writeByte ('<');
  226. outputStream << tagName;
  227. {
  228. const size_t attIndent = (size_t) (indentationLevel + tagName.length() + 1);
  229. int lineLen = 0;
  230. for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem)
  231. {
  232. if (lineLen > lineWrapLength && indentationLevel >= 0)
  233. {
  234. outputStream << newLine;
  235. writeSpaces (outputStream, attIndent);
  236. lineLen = 0;
  237. }
  238. const int64 startPos = outputStream.getPosition();
  239. outputStream.writeByte (' ');
  240. outputStream << att->name;
  241. outputStream.write ("=\"", 2);
  242. escapeIllegalXmlChars (outputStream, att->value, true);
  243. outputStream.writeByte ('"');
  244. lineLen += (int) (outputStream.getPosition() - startPos);
  245. }
  246. }
  247. if (firstChildElement != nullptr)
  248. {
  249. outputStream.writeByte ('>');
  250. bool lastWasTextNode = false;
  251. for (XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem)
  252. {
  253. if (child->isTextElement())
  254. {
  255. escapeIllegalXmlChars (outputStream, child->getText(), false);
  256. lastWasTextNode = true;
  257. }
  258. else
  259. {
  260. if (indentationLevel >= 0 && ! lastWasTextNode)
  261. outputStream << newLine;
  262. child->writeElementAsText (outputStream,
  263. lastWasTextNode ? 0 : (indentationLevel + (indentationLevel >= 0 ? 2 : 0)), lineWrapLength);
  264. lastWasTextNode = false;
  265. }
  266. }
  267. if (indentationLevel >= 0 && ! lastWasTextNode)
  268. {
  269. outputStream << newLine;
  270. writeSpaces (outputStream, (size_t) indentationLevel);
  271. }
  272. outputStream.write ("</", 2);
  273. outputStream << tagName;
  274. outputStream.writeByte ('>');
  275. }
  276. else
  277. {
  278. outputStream.write ("/>", 2);
  279. }
  280. }
  281. else
  282. {
  283. escapeIllegalXmlChars (outputStream, getText(), false);
  284. }
  285. }
  286. String XmlElement::createDocument (StringRef dtdToUse,
  287. const bool allOnOneLine,
  288. const bool includeXmlHeader,
  289. StringRef encodingType,
  290. const int lineWrapLength) const
  291. {
  292. MemoryOutputStream mem (2048);
  293. writeToStream (mem, dtdToUse, allOnOneLine, includeXmlHeader, encodingType, lineWrapLength);
  294. return mem.toUTF8();
  295. }
  296. void XmlElement::writeToStream (OutputStream& output,
  297. StringRef dtdToUse,
  298. const bool allOnOneLine,
  299. const bool includeXmlHeader,
  300. StringRef encodingType,
  301. const int lineWrapLength) const
  302. {
  303. using namespace XmlOutputFunctions;
  304. if (includeXmlHeader)
  305. {
  306. output << "<?xml version=\"1.0\" encoding=\"" << encodingType << "\"?>";
  307. if (allOnOneLine)
  308. output.writeByte (' ');
  309. else
  310. output << newLine << newLine;
  311. }
  312. if (dtdToUse.isNotEmpty())
  313. {
  314. output << dtdToUse;
  315. if (allOnOneLine)
  316. output.writeByte (' ');
  317. else
  318. output << newLine;
  319. }
  320. writeElementAsText (output, allOnOneLine ? -1 : 0, lineWrapLength);
  321. if (! allOnOneLine)
  322. output << newLine;
  323. }
  324. bool XmlElement::writeToFile (const File& file,
  325. StringRef dtdToUse,
  326. StringRef encodingType,
  327. const int lineWrapLength) const
  328. {
  329. TemporaryFile tempFile (file);
  330. {
  331. FileOutputStream out (tempFile.getFile());
  332. if (! out.openedOk())
  333. return false;
  334. writeToStream (out, dtdToUse, false, true, encodingType, lineWrapLength);
  335. out.flush(); // (called explicitly to force an fsync on posix)
  336. if (out.getStatus().failed())
  337. return false;
  338. }
  339. return tempFile.overwriteTargetFileWithTemporary();
  340. }
  341. //==============================================================================
  342. bool XmlElement::hasTagName (StringRef possibleTagName) const noexcept
  343. {
  344. const bool matches = tagName.equalsIgnoreCase (possibleTagName);
  345. // XML tags should be case-sensitive, so although this method allows a
  346. // case-insensitive match to pass, you should try to avoid this.
  347. jassert ((! matches) || tagName == possibleTagName);
  348. return matches;
  349. }
  350. String XmlElement::getNamespace() const
  351. {
  352. return tagName.upToFirstOccurrenceOf (":", false, false);
  353. }
  354. String XmlElement::getTagNameWithoutNamespace() const
  355. {
  356. return tagName.fromLastOccurrenceOf (":", false, false);
  357. }
  358. bool XmlElement::hasTagNameIgnoringNamespace (StringRef possibleTagName) const
  359. {
  360. return hasTagName (possibleTagName) || getTagNameWithoutNamespace() == possibleTagName;
  361. }
  362. XmlElement* XmlElement::getNextElementWithTagName (StringRef requiredTagName) const
  363. {
  364. XmlElement* e = nextListItem;
  365. while (e != nullptr && ! e->hasTagName (requiredTagName))
  366. e = e->nextListItem;
  367. return e;
  368. }
  369. //==============================================================================
  370. int XmlElement::getNumAttributes() const noexcept
  371. {
  372. return attributes.size();
  373. }
  374. static const String& getEmptyStringRef() noexcept
  375. {
  376. #if JUCE_ALLOW_STATIC_NULL_VARIABLES
  377. return String::empty;
  378. #else
  379. static String empty;
  380. return empty;
  381. #endif
  382. }
  383. const String& XmlElement::getAttributeName (const int index) const noexcept
  384. {
  385. if (const XmlAttributeNode* const att = attributes [index])
  386. return att->name.toString();
  387. return getEmptyStringRef();
  388. }
  389. const String& XmlElement::getAttributeValue (const int index) const noexcept
  390. {
  391. if (const XmlAttributeNode* const att = attributes [index])
  392. return att->value;
  393. return getEmptyStringRef();
  394. }
  395. XmlElement::XmlAttributeNode* XmlElement::getAttribute (StringRef attributeName) const noexcept
  396. {
  397. for (XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem)
  398. if (att->name == attributeName)
  399. return att;
  400. return nullptr;
  401. }
  402. bool XmlElement::hasAttribute (StringRef attributeName) const noexcept
  403. {
  404. return getAttribute (attributeName) != nullptr;
  405. }
  406. //==============================================================================
  407. const String& XmlElement::getStringAttribute (StringRef attributeName) const noexcept
  408. {
  409. if (const XmlAttributeNode* att = getAttribute (attributeName))
  410. return att->value;
  411. return getEmptyStringRef();
  412. }
  413. String XmlElement::getStringAttribute (StringRef attributeName, const String& defaultReturnValue) const
  414. {
  415. if (const XmlAttributeNode* att = getAttribute (attributeName))
  416. return att->value;
  417. return defaultReturnValue;
  418. }
  419. int XmlElement::getIntAttribute (StringRef attributeName, const int defaultReturnValue) const
  420. {
  421. if (const XmlAttributeNode* att = getAttribute (attributeName))
  422. return att->value.getIntValue();
  423. return defaultReturnValue;
  424. }
  425. double XmlElement::getDoubleAttribute (StringRef attributeName, const double defaultReturnValue) const
  426. {
  427. if (const XmlAttributeNode* att = getAttribute (attributeName))
  428. return att->value.getDoubleValue();
  429. return defaultReturnValue;
  430. }
  431. bool XmlElement::getBoolAttribute (StringRef attributeName, const bool defaultReturnValue) const
  432. {
  433. if (const XmlAttributeNode* att = getAttribute (attributeName))
  434. {
  435. const juce_wchar firstChar = *(att->value.getCharPointer().findEndOfWhitespace());
  436. return firstChar == '1'
  437. || firstChar == 't'
  438. || firstChar == 'y'
  439. || firstChar == 'T'
  440. || firstChar == 'Y';
  441. }
  442. return defaultReturnValue;
  443. }
  444. bool XmlElement::compareAttribute (StringRef attributeName,
  445. StringRef stringToCompareAgainst,
  446. const bool ignoreCase) const noexcept
  447. {
  448. if (const XmlAttributeNode* att = getAttribute (attributeName))
  449. return ignoreCase ? att->value.equalsIgnoreCase (stringToCompareAgainst)
  450. : att->value == stringToCompareAgainst;
  451. return false;
  452. }
  453. //==============================================================================
  454. void XmlElement::setAttribute (const Identifier& attributeName, const String& value)
  455. {
  456. if (attributes == nullptr)
  457. {
  458. attributes = new XmlAttributeNode (attributeName, value);
  459. }
  460. else
  461. {
  462. for (XmlAttributeNode* att = attributes; ; att = att->nextListItem)
  463. {
  464. if (att->name == attributeName)
  465. {
  466. att->value = value;
  467. break;
  468. }
  469. if (att->nextListItem == nullptr)
  470. {
  471. att->nextListItem = new XmlAttributeNode (attributeName, value);
  472. break;
  473. }
  474. }
  475. }
  476. }
  477. void XmlElement::setAttribute (const Identifier& attributeName, const int number)
  478. {
  479. setAttribute (attributeName, String (number));
  480. }
  481. void XmlElement::setAttribute (const Identifier& attributeName, const double number)
  482. {
  483. setAttribute (attributeName, String (number, 20));
  484. }
  485. void XmlElement::removeAttribute (const Identifier& attributeName) noexcept
  486. {
  487. for (LinkedListPointer<XmlAttributeNode>* att = &attributes;
  488. att->get() != nullptr;
  489. att = &(att->get()->nextListItem))
  490. {
  491. if (att->get()->name == attributeName)
  492. {
  493. delete att->removeNext();
  494. break;
  495. }
  496. }
  497. }
  498. void XmlElement::removeAllAttributes() noexcept
  499. {
  500. attributes.deleteAll();
  501. }
  502. //==============================================================================
  503. int XmlElement::getNumChildElements() const noexcept
  504. {
  505. return firstChildElement.size();
  506. }
  507. XmlElement* XmlElement::getChildElement (const int index) const noexcept
  508. {
  509. return firstChildElement [index].get();
  510. }
  511. XmlElement* XmlElement::getChildByName (StringRef childName) const noexcept
  512. {
  513. jassert (! childName.isEmpty());
  514. for (XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem)
  515. if (child->hasTagName (childName))
  516. return child;
  517. return nullptr;
  518. }
  519. XmlElement* XmlElement::getChildByAttribute (StringRef attributeName, StringRef attributeValue) const noexcept
  520. {
  521. jassert (! attributeName.isEmpty());
  522. for (XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem)
  523. if (child->compareAttribute (attributeName, attributeValue))
  524. return child;
  525. return nullptr;
  526. }
  527. void XmlElement::addChildElement (XmlElement* const newNode) noexcept
  528. {
  529. if (newNode != nullptr)
  530. {
  531. // The element being added must not be a child of another node!
  532. jassert (newNode->nextListItem == nullptr);
  533. firstChildElement.append (newNode);
  534. }
  535. }
  536. void XmlElement::insertChildElement (XmlElement* const newNode, int indexToInsertAt) noexcept
  537. {
  538. if (newNode != nullptr)
  539. {
  540. // The element being added must not be a child of another node!
  541. jassert (newNode->nextListItem == nullptr);
  542. firstChildElement.insertAtIndex (indexToInsertAt, newNode);
  543. }
  544. }
  545. void XmlElement::prependChildElement (XmlElement* newNode) noexcept
  546. {
  547. if (newNode != nullptr)
  548. {
  549. // The element being added must not be a child of another node!
  550. jassert (newNode->nextListItem == nullptr);
  551. firstChildElement.insertNext (newNode);
  552. }
  553. }
  554. XmlElement* XmlElement::createNewChildElement (StringRef childTagName)
  555. {
  556. XmlElement* const newElement = new XmlElement (childTagName);
  557. addChildElement (newElement);
  558. return newElement;
  559. }
  560. bool XmlElement::replaceChildElement (XmlElement* const currentChildElement,
  561. XmlElement* const newNode) noexcept
  562. {
  563. if (newNode != nullptr)
  564. {
  565. if (LinkedListPointer<XmlElement>* const p = firstChildElement.findPointerTo (currentChildElement))
  566. {
  567. if (currentChildElement != newNode)
  568. delete p->replaceNext (newNode);
  569. return true;
  570. }
  571. }
  572. return false;
  573. }
  574. void XmlElement::removeChildElement (XmlElement* const childToRemove,
  575. const bool shouldDeleteTheChild) noexcept
  576. {
  577. if (childToRemove != nullptr)
  578. {
  579. firstChildElement.remove (childToRemove);
  580. if (shouldDeleteTheChild)
  581. delete childToRemove;
  582. }
  583. }
  584. bool XmlElement::isEquivalentTo (const XmlElement* const other,
  585. const bool ignoreOrderOfAttributes) const noexcept
  586. {
  587. if (this != other)
  588. {
  589. if (other == nullptr || tagName != other->tagName)
  590. return false;
  591. if (ignoreOrderOfAttributes)
  592. {
  593. int totalAtts = 0;
  594. for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem)
  595. {
  596. if (! other->compareAttribute (att->name, att->value))
  597. return false;
  598. ++totalAtts;
  599. }
  600. if (totalAtts != other->getNumAttributes())
  601. return false;
  602. }
  603. else
  604. {
  605. const XmlAttributeNode* thisAtt = attributes;
  606. const XmlAttributeNode* otherAtt = other->attributes;
  607. for (;;)
  608. {
  609. if (thisAtt == nullptr || otherAtt == nullptr)
  610. {
  611. if (thisAtt == otherAtt) // both nullptr, so it's a match
  612. break;
  613. return false;
  614. }
  615. if (thisAtt->name != otherAtt->name
  616. || thisAtt->value != otherAtt->value)
  617. {
  618. return false;
  619. }
  620. thisAtt = thisAtt->nextListItem;
  621. otherAtt = otherAtt->nextListItem;
  622. }
  623. }
  624. const XmlElement* thisChild = firstChildElement;
  625. const XmlElement* otherChild = other->firstChildElement;
  626. for (;;)
  627. {
  628. if (thisChild == nullptr || otherChild == nullptr)
  629. {
  630. if (thisChild == otherChild) // both 0, so it's a match
  631. break;
  632. return false;
  633. }
  634. if (! thisChild->isEquivalentTo (otherChild, ignoreOrderOfAttributes))
  635. return false;
  636. thisChild = thisChild->nextListItem;
  637. otherChild = otherChild->nextListItem;
  638. }
  639. }
  640. return true;
  641. }
  642. void XmlElement::deleteAllChildElements() noexcept
  643. {
  644. firstChildElement.deleteAll();
  645. }
  646. void XmlElement::deleteAllChildElementsWithTagName (StringRef name) noexcept
  647. {
  648. for (XmlElement* child = firstChildElement; child != nullptr;)
  649. {
  650. XmlElement* const nextChild = child->nextListItem;
  651. if (child->hasTagName (name))
  652. removeChildElement (child, true);
  653. child = nextChild;
  654. }
  655. }
  656. bool XmlElement::containsChildElement (const XmlElement* const possibleChild) const noexcept
  657. {
  658. return firstChildElement.contains (possibleChild);
  659. }
  660. XmlElement* XmlElement::findParentElementOf (const XmlElement* const elementToLookFor) noexcept
  661. {
  662. if (this == elementToLookFor || elementToLookFor == nullptr)
  663. return nullptr;
  664. for (XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem)
  665. {
  666. if (elementToLookFor == child)
  667. return this;
  668. if (XmlElement* const found = child->findParentElementOf (elementToLookFor))
  669. return found;
  670. }
  671. return nullptr;
  672. }
  673. void XmlElement::getChildElementsAsArray (XmlElement** elems) const noexcept
  674. {
  675. firstChildElement.copyToArray (elems);
  676. }
  677. void XmlElement::reorderChildElements (XmlElement** const elems, const int num) noexcept
  678. {
  679. XmlElement* e = firstChildElement = elems[0];
  680. for (int i = 1; i < num; ++i)
  681. {
  682. e->nextListItem = elems[i];
  683. e = e->nextListItem;
  684. }
  685. e->nextListItem = nullptr;
  686. }
  687. //==============================================================================
  688. bool XmlElement::isTextElement() const noexcept
  689. {
  690. return tagName.isEmpty();
  691. }
  692. static const String juce_xmltextContentAttributeName ("text");
  693. const String& XmlElement::getText() const noexcept
  694. {
  695. jassert (isTextElement()); // you're trying to get the text from an element that
  696. // isn't actually a text element.. If this contains text sub-nodes, you
  697. // probably want to use getAllSubText instead.
  698. return getStringAttribute (juce_xmltextContentAttributeName);
  699. }
  700. void XmlElement::setText (const String& newText)
  701. {
  702. if (isTextElement())
  703. setAttribute (juce_xmltextContentAttributeName, newText);
  704. else
  705. jassertfalse; // you can only change the text in a text element, not a normal one.
  706. }
  707. String XmlElement::getAllSubText() const
  708. {
  709. if (isTextElement())
  710. return getText();
  711. if (getNumChildElements() == 1)
  712. return firstChildElement.get()->getAllSubText();
  713. MemoryOutputStream mem (1024);
  714. for (const XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem)
  715. mem << child->getAllSubText();
  716. return mem.toUTF8();
  717. }
  718. String XmlElement::getChildElementAllSubText (StringRef childTagName, const String& defaultReturnValue) const
  719. {
  720. if (const XmlElement* const child = getChildByName (childTagName))
  721. return child->getAllSubText();
  722. return defaultReturnValue;
  723. }
  724. XmlElement* XmlElement::createTextElement (const String& text)
  725. {
  726. XmlElement* const e = new XmlElement ((int) 0);
  727. e->setAttribute (juce_xmltextContentAttributeName, text);
  728. return e;
  729. }
  730. bool XmlElement::isValidXmlName (StringRef text) noexcept
  731. {
  732. if (text.isEmpty() || ! isValidXmlNameStartCharacter (text.text.getAndAdvance()))
  733. return false;
  734. for (;;)
  735. {
  736. if (text.isEmpty())
  737. return true;
  738. if (! isValidXmlNameBodyCharacter (text.text.getAndAdvance()))
  739. return false;
  740. }
  741. }
  742. void XmlElement::addTextElement (const String& text)
  743. {
  744. addChildElement (createTextElement (text));
  745. }
  746. void XmlElement::deleteAllTextElements() noexcept
  747. {
  748. for (XmlElement* child = firstChildElement; child != nullptr;)
  749. {
  750. XmlElement* const next = child->nextListItem;
  751. if (child->isTextElement())
  752. removeChildElement (child, true);
  753. child = next;
  754. }
  755. }
  756. } // namespace juce