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.

836 lines
24KB

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