juce_XmlElement.cpp 28 KB

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