OpenShot Audio Library | OpenShotAudio  0.3.0
juce_XmlElement.cpp
1 /*
2  ==============================================================================
3 
4  This file is part of the JUCE library.
5  Copyright (c) 2017 - ROLI Ltd.
6 
7  JUCE is an open source library subject to commercial or open-source
8  licensing.
9 
10  The code included in this file is provided under the terms of the ISC license
11  http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12  To use, copy, modify, and/or distribute this software for any purpose with or
13  without fee is hereby granted provided that the above copyright notice and
14  this permission notice appear in all copies.
15 
16  JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17  EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18  DISCLAIMED.
19 
20  ==============================================================================
21 */
22 
23 namespace juce
24 {
25 
26 inline static bool isValidXmlNameStartCharacter (juce_wchar character) noexcept
27 {
28  return character == ':'
29  || character == '_'
30  || (character >= 'a' && character <= 'z')
31  || (character >= 'A' && character <= 'Z')
32  || (character >= 0xc0 && character <= 0xd6)
33  || (character >= 0xd8 && character <= 0xf6)
34  || (character >= 0xf8 && character <= 0x2ff)
35  || (character >= 0x370 && character <= 0x37d)
36  || (character >= 0x37f && character <= 0x1fff)
37  || (character >= 0x200c && character <= 0x200d)
38  || (character >= 0x2070 && character <= 0x218f)
39  || (character >= 0x2c00 && character <= 0x2fef)
40  || (character >= 0x3001 && character <= 0xd7ff)
41  || (character >= 0xf900 && character <= 0xfdcf)
42  || (character >= 0xfdf0 && character <= 0xfffd)
43  || (character >= 0x10000 && character <= 0xeffff);
44 }
45 
46 inline static bool isValidXmlNameBodyCharacter (juce_wchar character) noexcept
47 {
48  return isValidXmlNameStartCharacter (character)
49  || character == '-'
50  || character == '.'
51  || character == 0xb7
52  || (character >= '0' && character <= '9')
53  || (character >= 0x300 && character <= 0x036f)
54  || (character >= 0x203f && character <= 0x2040);
55 }
56 
57 XmlElement::XmlAttributeNode::XmlAttributeNode (const XmlAttributeNode& other) noexcept
58  : name (other.name),
59  value (other.value)
60 {
61 }
62 
63 XmlElement::XmlAttributeNode::XmlAttributeNode (const Identifier& n, const String& v) noexcept
64  : name (n), value (v)
65 {
66  jassert (isValidXmlName (name));
67 }
68 
69 XmlElement::XmlAttributeNode::XmlAttributeNode (String::CharPointerType nameStart, String::CharPointerType nameEnd)
70  : name (nameStart, nameEnd)
71 {
72  jassert (isValidXmlName (name));
73 }
74 
75 //==============================================================================
76 XmlElement::XmlElement (const String& tag)
77  : tagName (StringPool::getGlobalPool().getPooledString (tag))
78 {
79  jassert (isValidXmlName (tagName));
80 }
81 
82 XmlElement::XmlElement (const char* tag)
83  : tagName (StringPool::getGlobalPool().getPooledString (tag))
84 {
85  jassert (isValidXmlName (tagName));
86 }
87 
89  : tagName (StringPool::getGlobalPool().getPooledString (tag))
90 {
91  jassert (isValidXmlName (tagName));
92 }
93 
95  : tagName (tag.toString())
96 {
97  jassert (isValidXmlName (tagName));
98 }
99 
100 XmlElement::XmlElement (String::CharPointerType tagNameStart, String::CharPointerType tagNameEnd)
101  : tagName (StringPool::getGlobalPool().getPooledString (tagNameStart, tagNameEnd))
102 {
103  jassert (isValidXmlName (tagName));
104 }
105 
106 XmlElement::XmlElement (int /*dummy*/) noexcept
107 {
108 }
109 
111  : tagName (other.tagName)
112 {
113  copyChildrenAndAttributesFrom (other);
114 }
115 
117 {
118  if (this != &other)
119  {
122  tagName = other.tagName;
123  copyChildrenAndAttributesFrom (other);
124  }
125 
126  return *this;
127 }
128 
130  : nextListItem (std::move (other.nextListItem)),
131  firstChildElement (std::move (other.firstChildElement)),
132  attributes (std::move (other.attributes)),
133  tagName (std::move (other.tagName))
134 {
135 }
136 
138 {
139  jassert (this != &other); // hopefully the compiler should make this situation impossible!
140 
141  removeAllAttributes();
142  deleteAllChildElements();
143 
144  nextListItem = std::move (other.nextListItem);
145  firstChildElement = std::move (other.firstChildElement);
146  attributes = std::move (other.attributes);
147  tagName = std::move (other.tagName);
148 
149  return *this;
150 }
151 
152 void XmlElement::copyChildrenAndAttributesFrom (const XmlElement& other)
153 {
154  jassert (firstChildElement.get() == nullptr);
155  firstChildElement.addCopyOfList (other.firstChildElement);
156 
157  jassert (attributes.get() == nullptr);
158  attributes.addCopyOfList (other.attributes);
159 }
160 
162 {
163  firstChildElement.deleteAll();
164  attributes.deleteAll();
165 }
166 
167 //==============================================================================
168 namespace XmlOutputFunctions
169 {
170  namespace LegalCharLookupTable
171  {
172  template <int c>
173  struct Bit
174  {
175  enum { v = ((c >= 'a' && c <= 'z')
176  || (c >= 'A' && c <= 'Z')
177  || (c >= '0' && c <= '9')
178  || c == ' ' || c == '.' || c == ',' || c == ';'
179  || c == ':' || c == '-' || c == '(' || c == ')'
180  || c == '_' || c == '+' || c == '=' || c == '?'
181  || c == '!' || c == '$' || c == '#' || c == '@'
182  || c == '[' || c == ']' || c == '/' || c == '|'
183  || c == '*' || c == '%' || c == '~' || c == '{'
184  || c == '}' || c == '\'' || c == '\\')
185  ? (1 << (c & 7)) : 0 };
186  };
187 
188  template <int tableIndex>
189  struct Byte
190  {
191  enum { v = (int) Bit<tableIndex * 8 + 0>::v | (int) Bit<tableIndex * 8 + 1>::v
192  | (int) Bit<tableIndex * 8 + 2>::v | (int) Bit<tableIndex * 8 + 3>::v
193  | (int) Bit<tableIndex * 8 + 4>::v | (int) Bit<tableIndex * 8 + 5>::v
194  | (int) Bit<tableIndex * 8 + 6>::v | (int) Bit<tableIndex * 8 + 7>::v };
195  };
196 
197  static bool isLegal (uint32 c) noexcept
198  {
199  static const unsigned char legalChars[] = { Byte< 0>::v, Byte< 1>::v, Byte< 2>::v, Byte< 3>::v,
200  Byte< 4>::v, Byte< 5>::v, Byte< 6>::v, Byte< 7>::v,
201  Byte< 8>::v, Byte< 9>::v, Byte<10>::v, Byte<11>::v,
202  Byte<12>::v, Byte<13>::v, Byte<14>::v, Byte<15>::v };
203 
204  return c < sizeof (legalChars) * 8
205  && (legalChars[c >> 3] & (1 << (c & 7))) != 0;
206  }
207  }
208 
209  static void escapeIllegalXmlChars (OutputStream& outputStream, const String& text, bool changeNewLines)
210  {
211  auto t = text.getCharPointer();
212 
213  for (;;)
214  {
215  auto character = (uint32) t.getAndAdvance();
216 
217  if (character == 0)
218  break;
219 
220  if (LegalCharLookupTable::isLegal (character))
221  {
222  outputStream << (char) character;
223  }
224  else
225  {
226  switch (character)
227  {
228  case '&': outputStream << "&amp;"; break;
229  case '"': outputStream << "&quot;"; break;
230  case '>': outputStream << "&gt;"; break;
231  case '<': outputStream << "&lt;"; break;
232 
233  case '\n':
234  case '\r':
235  if (! changeNewLines)
236  {
237  outputStream << (char) character;
238  break;
239  }
240  // Note: Deliberate fall-through here!
241  default:
242  outputStream << "&#" << ((int) character) << ';';
243  break;
244  }
245  }
246  }
247  }
248 
249  static void writeSpaces (OutputStream& out, const size_t numSpaces)
250  {
251  out.writeRepeatedByte (' ', numSpaces);
252  }
253 }
254 
255 void XmlElement::writeElementAsText (OutputStream& outputStream,
256  int indentationLevel,
257  int lineWrapLength,
258  const char* newLineChars) const
259 {
260  if (indentationLevel >= 0)
261  XmlOutputFunctions::writeSpaces (outputStream, (size_t) indentationLevel);
262 
263  if (! isTextElement())
264  {
265  outputStream.writeByte ('<');
266  outputStream << tagName;
267 
268  {
269  auto attIndent = (size_t) (indentationLevel + tagName.length() + 1);
270  int lineLen = 0;
271 
272  for (auto* att = attributes.get(); att != nullptr; att = att->nextListItem)
273  {
274  if (lineLen > lineWrapLength && indentationLevel >= 0)
275  {
276  outputStream << newLineChars;
277  XmlOutputFunctions::writeSpaces (outputStream, attIndent);
278  lineLen = 0;
279  }
280 
281  auto startPos = outputStream.getPosition();
282  outputStream.writeByte (' ');
283  outputStream << att->name;
284  outputStream.write ("=\"", 2);
285  XmlOutputFunctions::escapeIllegalXmlChars (outputStream, att->value, true);
286  outputStream.writeByte ('"');
287  lineLen += (int) (outputStream.getPosition() - startPos);
288  }
289  }
290 
291  if (auto* child = firstChildElement.get())
292  {
293  outputStream.writeByte ('>');
294  bool lastWasTextNode = false;
295 
296  for (; child != nullptr; child = child->nextListItem)
297  {
298  if (child->isTextElement())
299  {
300  XmlOutputFunctions::escapeIllegalXmlChars (outputStream, child->getText(), false);
301  lastWasTextNode = true;
302  }
303  else
304  {
305  if (indentationLevel >= 0 && ! lastWasTextNode)
306  outputStream << newLineChars;
307 
308  child->writeElementAsText (outputStream,
309  lastWasTextNode ? 0 : (indentationLevel + (indentationLevel >= 0 ? 2 : 0)), lineWrapLength,
310  newLineChars);
311  lastWasTextNode = false;
312  }
313  }
314 
315  if (indentationLevel >= 0 && ! lastWasTextNode)
316  {
317  outputStream << newLineChars;
318  XmlOutputFunctions::writeSpaces (outputStream, (size_t) indentationLevel);
319  }
320 
321  outputStream.write ("</", 2);
322  outputStream << tagName;
323  outputStream.writeByte ('>');
324  }
325  else
326  {
327  outputStream.write ("/>", 2);
328  }
329  }
330  else
331  {
332  XmlOutputFunctions::escapeIllegalXmlChars (outputStream, getText(), false);
333  }
334 }
335 
337 
339 {
340  auto f = *this;
341  f.newLineChars = nullptr;
342  return f;
343 }
344 
346 {
347  auto f = *this;
348  f.addDefaultHeader = false;
349  return f;
350 }
351 
352 String XmlElement::toString (const TextFormat& options) const
353 {
354  MemoryOutputStream mem (2048);
355  writeTo (mem, options);
356  return mem.toUTF8();
357 }
358 
359 void XmlElement::writeTo (OutputStream& output, const TextFormat& options) const
360 {
361  if (options.customHeader.isNotEmpty())
362  {
363  output << options.customHeader;
364 
365  if (options.newLineChars == nullptr)
366  output.writeByte (' ');
367  else
368  output << options.newLineChars
369  << options.newLineChars;
370  }
371  else if (options.addDefaultHeader)
372  {
373  output << "<?xml version=\"1.0\" encoding=\"";
374 
375  if (options.customEncoding.isNotEmpty())
376  output << options.customEncoding;
377  else
378  output << "UTF-8";
379 
380  output << "\"?>";
381 
382  if (options.newLineChars == nullptr)
383  output.writeByte (' ');
384  else
385  output << options.newLineChars
386  << options.newLineChars;
387  }
388 
389  if (options.dtd.isNotEmpty())
390  {
391  output << options.dtd;
392 
393  if (options.newLineChars == nullptr)
394  output.writeByte (' ');
395  else
396  output << options.newLineChars;
397  }
398 
399  writeElementAsText (output, options.newLineChars == nullptr ? -1 : 0,
400  options.lineWrapLength,
401  options.newLineChars);
402 
403  if (options.newLineChars != nullptr)
404  output << options.newLineChars;
405 }
406 
407 bool XmlElement::writeTo (const File& destinationFile, const TextFormat& options) const
408 {
409  TemporaryFile tempFile (destinationFile);
410 
411  {
412  FileOutputStream out (tempFile.getFile());
413 
414  if (! out.openedOk())
415  return false;
416 
417  writeTo (out, options);
418  out.flush(); // (called explicitly to force an fsync on posix)
419 
420  if (out.getStatus().failed())
421  return false;
422  }
423 
424  return tempFile.overwriteTargetFileWithTemporary();
425 }
426 
427 String XmlElement::createDocument (StringRef dtdToUse, bool allOnOneLine, bool includeXmlHeader,
428  StringRef encodingType, int lineWrapLength) const
429 {
430  TextFormat options;
431  options.dtd = dtdToUse;
432  options.customEncoding = encodingType;
433  options.addDefaultHeader = includeXmlHeader;
434  options.lineWrapLength = lineWrapLength;
435 
436  if (allOnOneLine)
437  options.newLineChars = nullptr;
438 
439  return toString (options);
440 }
441 
442 void XmlElement::writeToStream (OutputStream& output, StringRef dtdToUse,
443  bool allOnOneLine, bool includeXmlHeader,
444  StringRef encodingType, int lineWrapLength) const
445 {
446  TextFormat options;
447  options.dtd = dtdToUse;
448  options.customEncoding = encodingType;
449  options.addDefaultHeader = includeXmlHeader;
450  options.lineWrapLength = lineWrapLength;
451 
452  if (allOnOneLine)
453  options.newLineChars = nullptr;
454 
455  writeTo (output, options);
456 }
457 
458 bool XmlElement::writeToFile (const File& file, StringRef dtdToUse,
459  StringRef encodingType, int lineWrapLength) const
460 {
461  TextFormat options;
462  options.dtd = dtdToUse;
463  options.customEncoding = encodingType;
464  options.lineWrapLength = lineWrapLength;
465 
466  return writeTo (file, options);
467 }
468 
469 //==============================================================================
470 bool XmlElement::hasTagName (StringRef possibleTagName) const noexcept
471 {
472  const bool matches = tagName.equalsIgnoreCase (possibleTagName);
473 
474  // XML tags should be case-sensitive, so although this method allows a
475  // case-insensitive match to pass, you should try to avoid this.
476  jassert ((! matches) || tagName == possibleTagName);
477 
478  return matches;
479 }
480 
482 {
483  return tagName.upToFirstOccurrenceOf (":", false, false);
484 }
485 
487 {
488  return tagName.fromLastOccurrenceOf (":", false, false);
489 }
490 
492 {
493  return hasTagName (possibleTagName) || getTagNameWithoutNamespace() == possibleTagName;
494 }
495 
497 {
498  auto* e = nextListItem.get();
499 
500  while (e != nullptr && ! e->hasTagName (requiredTagName))
501  e = e->nextListItem;
502 
503  return e;
504 }
505 
507 {
508  jassert (isValidXmlName (newTagName));
509  tagName = StringPool::getGlobalPool().getPooledString (newTagName);
510 }
511 
512 //==============================================================================
513 int XmlElement::getNumAttributes() const noexcept
514 {
515  return attributes.size();
516 }
517 
518 static const String& getEmptyStringRef() noexcept
519 {
520  static String empty;
521  return empty;
522 }
523 
524 const String& XmlElement::getAttributeName (const int index) const noexcept
525 {
526  if (auto* att = attributes[index].get())
527  return att->name.toString();
528 
529  return getEmptyStringRef();
530 }
531 
532 const String& XmlElement::getAttributeValue (const int index) const noexcept
533 {
534  if (auto* att = attributes[index].get())
535  return att->value;
536 
537  return getEmptyStringRef();
538 }
539 
540 XmlElement::XmlAttributeNode* XmlElement::getAttribute (StringRef attributeName) const noexcept
541 {
542  for (auto* att = attributes.get(); att != nullptr; att = att->nextListItem)
543  if (att->name == attributeName)
544  return att;
545 
546  return nullptr;
547 }
548 
549 bool XmlElement::hasAttribute (StringRef attributeName) const noexcept
550 {
551  return getAttribute (attributeName) != nullptr;
552 }
553 
554 //==============================================================================
555 const String& XmlElement::getStringAttribute (StringRef attributeName) const noexcept
556 {
557  if (auto* att = getAttribute (attributeName))
558  return att->value;
559 
560  return getEmptyStringRef();
561 }
562 
563 String XmlElement::getStringAttribute (StringRef attributeName, const String& defaultReturnValue) const
564 {
565  if (auto* att = getAttribute (attributeName))
566  return att->value;
567 
568  return defaultReturnValue;
569 }
570 
571 int XmlElement::getIntAttribute (StringRef attributeName, const int defaultReturnValue) const
572 {
573  if (auto* att = getAttribute (attributeName))
574  return att->value.getIntValue();
575 
576  return defaultReturnValue;
577 }
578 
579 double XmlElement::getDoubleAttribute (StringRef attributeName, const double defaultReturnValue) const
580 {
581  if (auto* att = getAttribute (attributeName))
582  return att->value.getDoubleValue();
583 
584  return defaultReturnValue;
585 }
586 
587 bool XmlElement::getBoolAttribute (StringRef attributeName, const bool defaultReturnValue) const
588 {
589  if (auto* att = getAttribute (attributeName))
590  {
591  auto firstChar = *(att->value.getCharPointer().findEndOfWhitespace());
592 
593  return firstChar == '1'
594  || firstChar == 't'
595  || firstChar == 'y'
596  || firstChar == 'T'
597  || firstChar == 'Y';
598  }
599 
600  return defaultReturnValue;
601 }
602 
604  StringRef stringToCompareAgainst,
605  const bool ignoreCase) const noexcept
606 {
607  if (auto* att = getAttribute (attributeName))
608  return ignoreCase ? att->value.equalsIgnoreCase (stringToCompareAgainst)
609  : att->value == stringToCompareAgainst;
610 
611  return false;
612 }
613 
614 //==============================================================================
615 void XmlElement::setAttribute (const Identifier& attributeName, const String& value)
616 {
617  if (attributes == nullptr)
618  {
619  attributes = new XmlAttributeNode (attributeName, value);
620  }
621  else
622  {
623  for (auto* att = attributes.get(); ; att = att->nextListItem)
624  {
625  if (att->name == attributeName)
626  {
627  att->value = value;
628  break;
629  }
630 
631  if (att->nextListItem == nullptr)
632  {
633  att->nextListItem = new XmlAttributeNode (attributeName, value);
634  break;
635  }
636  }
637  }
638 }
639 
640 void XmlElement::setAttribute (const Identifier& attributeName, const int number)
641 {
642  setAttribute (attributeName, String (number));
643 }
644 
645 void XmlElement::setAttribute (const Identifier& attributeName, const double number)
646 {
647  setAttribute (attributeName, serialiseDouble (number));
648 }
649 
650 void XmlElement::removeAttribute (const Identifier& attributeName) noexcept
651 {
652  for (auto* att = &attributes; att->get() != nullptr; att = &(att->get()->nextListItem))
653  {
654  if (att->get()->name == attributeName)
655  {
656  delete att->removeNext();
657  break;
658  }
659  }
660 }
661 
663 {
664  attributes.deleteAll();
665 }
666 
667 //==============================================================================
669 {
670  return firstChildElement.size();
671 }
672 
673 XmlElement* XmlElement::getChildElement (const int index) const noexcept
674 {
675  return firstChildElement[index].get();
676 }
677 
679 {
680  jassert (! childName.isEmpty());
681 
682  for (auto* child = firstChildElement.get(); child != nullptr; child = child->nextListItem)
683  if (child->hasTagName (childName))
684  return child;
685 
686  return nullptr;
687 }
688 
689 XmlElement* XmlElement::getChildByAttribute (StringRef attributeName, StringRef attributeValue) const noexcept
690 {
691  jassert (! attributeName.isEmpty());
692 
693  for (auto* child = firstChildElement.get(); child != nullptr; child = child->nextListItem)
694  if (child->compareAttribute (attributeName, attributeValue))
695  return child;
696 
697  return nullptr;
698 }
699 
700 void XmlElement::addChildElement (XmlElement* const newNode) noexcept
701 {
702  if (newNode != nullptr)
703  {
704  // The element being added must not be a child of another node!
705  jassert (newNode->nextListItem == nullptr);
706 
707  firstChildElement.append (newNode);
708  }
709 }
710 
711 void XmlElement::insertChildElement (XmlElement* const newNode, int indexToInsertAt) noexcept
712 {
713  if (newNode != nullptr)
714  {
715  // The element being added must not be a child of another node!
716  jassert (newNode->nextListItem == nullptr);
717 
718  firstChildElement.insertAtIndex (indexToInsertAt, newNode);
719  }
720 }
721 
723 {
724  if (newNode != nullptr)
725  {
726  // The element being added must not be a child of another node!
727  jassert (newNode->nextListItem == nullptr);
728 
729  firstChildElement.insertNext (newNode);
730  }
731 }
732 
734 {
735  auto newElement = new XmlElement (childTagName);
736  addChildElement (newElement);
737  return newElement;
738 }
739 
740 bool XmlElement::replaceChildElement (XmlElement* const currentChildElement,
741  XmlElement* const newNode) noexcept
742 {
743  if (newNode != nullptr)
744  {
745  if (auto* p = firstChildElement.findPointerTo (currentChildElement))
746  {
747  if (currentChildElement != newNode)
748  delete p->replaceNext (newNode);
749 
750  return true;
751  }
752  }
753 
754  return false;
755 }
756 
757 void XmlElement::removeChildElement (XmlElement* const childToRemove,
758  const bool shouldDeleteTheChild) noexcept
759 {
760  if (childToRemove != nullptr)
761  {
762  jassert (containsChildElement (childToRemove));
763 
764  firstChildElement.remove (childToRemove);
765 
766  if (shouldDeleteTheChild)
767  delete childToRemove;
768  }
769 }
770 
771 bool XmlElement::isEquivalentTo (const XmlElement* const other,
772  const bool ignoreOrderOfAttributes) const noexcept
773 {
774  if (this != other)
775  {
776  if (other == nullptr || tagName != other->tagName)
777  return false;
778 
779  if (ignoreOrderOfAttributes)
780  {
781  int totalAtts = 0;
782 
783  for (auto* att = attributes.get(); att != nullptr; att = att->nextListItem)
784  {
785  if (! other->compareAttribute (att->name, att->value))
786  return false;
787 
788  ++totalAtts;
789  }
790 
791  if (totalAtts != other->getNumAttributes())
792  return false;
793  }
794  else
795  {
796  auto* thisAtt = attributes.get();
797  auto* otherAtt = other->attributes.get();
798 
799  for (;;)
800  {
801  if (thisAtt == nullptr || otherAtt == nullptr)
802  {
803  if (thisAtt == otherAtt) // both nullptr, so it's a match
804  break;
805 
806  return false;
807  }
808 
809  if (thisAtt->name != otherAtt->name
810  || thisAtt->value != otherAtt->value)
811  {
812  return false;
813  }
814 
815  thisAtt = thisAtt->nextListItem;
816  otherAtt = otherAtt->nextListItem;
817  }
818  }
819 
820  auto* thisChild = firstChildElement.get();
821  auto* otherChild = other->firstChildElement.get();
822 
823  for (;;)
824  {
825  if (thisChild == nullptr || otherChild == nullptr)
826  {
827  if (thisChild == otherChild) // both 0, so it's a match
828  break;
829 
830  return false;
831  }
832 
833  if (! thisChild->isEquivalentTo (otherChild, ignoreOrderOfAttributes))
834  return false;
835 
836  thisChild = thisChild->nextListItem;
837  otherChild = otherChild->nextListItem;
838  }
839  }
840 
841  return true;
842 }
843 
845 {
846  firstChildElement.deleteAll();
847 }
848 
850 {
851  for (auto* child = firstChildElement.get(); child != nullptr;)
852  {
853  auto* nextChild = child->nextListItem.get();
854 
855  if (child->hasTagName (name))
856  removeChildElement (child, true);
857 
858  child = nextChild;
859  }
860 }
861 
862 bool XmlElement::containsChildElement (const XmlElement* const possibleChild) const noexcept
863 {
864  return firstChildElement.contains (possibleChild);
865 }
866 
867 XmlElement* XmlElement::findParentElementOf (const XmlElement* const elementToLookFor) noexcept
868 {
869  if (this == elementToLookFor || elementToLookFor == nullptr)
870  return nullptr;
871 
872  for (auto* child = firstChildElement.get(); child != nullptr; child = child->nextListItem)
873  {
874  if (elementToLookFor == child)
875  return this;
876 
877  if (auto* found = child->findParentElementOf (elementToLookFor))
878  return found;
879  }
880 
881  return nullptr;
882 }
883 
884 void XmlElement::getChildElementsAsArray (XmlElement** elems) const noexcept
885 {
886  firstChildElement.copyToArray (elems);
887 }
888 
889 void XmlElement::reorderChildElements (XmlElement** elems, int num) noexcept
890 {
891  auto* e = elems[0];
892  firstChildElement = e;
893 
894  for (int i = 1; i < num; ++i)
895  {
896  e->nextListItem = elems[i];
897  e = e->nextListItem;
898  }
899 
900  e->nextListItem = nullptr;
901 }
902 
903 //==============================================================================
904 bool XmlElement::isTextElement() const noexcept
905 {
906  return tagName.isEmpty();
907 }
908 
909 static const String juce_xmltextContentAttributeName ("text");
910 
911 const String& XmlElement::getText() const noexcept
912 {
913  jassert (isTextElement()); // you're trying to get the text from an element that
914  // isn't actually a text element.. If this contains text sub-nodes, you
915  // probably want to use getAllSubText instead.
916 
917  return getStringAttribute (juce_xmltextContentAttributeName);
918 }
919 
920 void XmlElement::setText (const String& newText)
921 {
922  if (isTextElement())
923  setAttribute (juce_xmltextContentAttributeName, newText);
924  else
925  jassertfalse; // you can only change the text in a text element, not a normal one.
926 }
927 
929 {
930  if (isTextElement())
931  return getText();
932 
933  if (getNumChildElements() == 1)
934  return firstChildElement.get()->getAllSubText();
935 
936  MemoryOutputStream mem (1024);
937 
938  for (auto* child = firstChildElement.get(); child != nullptr; child = child->nextListItem)
939  mem << child->getAllSubText();
940 
941  return mem.toUTF8();
942 }
943 
944 String XmlElement::getChildElementAllSubText (StringRef childTagName, const String& defaultReturnValue) const
945 {
946  if (auto* child = getChildByName (childTagName))
947  return child->getAllSubText();
948 
949  return defaultReturnValue;
950 }
951 
953 {
954  auto e = new XmlElement ((int) 0);
955  e->setAttribute (juce_xmltextContentAttributeName, text);
956  return e;
957 }
958 
960 {
961  if (text.isEmpty() || ! isValidXmlNameStartCharacter (text.text.getAndAdvance()))
962  return false;
963 
964  for (;;)
965  {
966  if (text.isEmpty())
967  return true;
968 
969  if (! isValidXmlNameBodyCharacter (text.text.getAndAdvance()))
970  return false;
971  }
972 }
973 
975 {
977 }
978 
980 {
981  for (auto* child = firstChildElement.get(); child != nullptr;)
982  {
983  auto* next = child->nextListItem.get();
984 
985  if (child->isTextElement())
986  removeChildElement (child, true);
987 
988  child = next;
989  }
990 }
991 
992 //==============================================================================
993 //==============================================================================
994 #if JUCE_UNIT_TESTS
995 
996 class XmlElementTests : public UnitTest
997 {
998 public:
999  XmlElementTests()
1000  : UnitTest ("XmlElement", UnitTestCategories::xml)
1001  {}
1002 
1003  void runTest() override
1004  {
1005  {
1006  beginTest ("Float formatting");
1007 
1008  auto element = std::make_unique<XmlElement> ("test");
1009  Identifier number ("number");
1010 
1011  std::map<double, String> tests;
1012  tests[1] = "1.0";
1013  tests[1.1] = "1.1";
1014  tests[1.01] = "1.01";
1015  tests[0.76378] = "0.76378";
1016  tests[-10] = "-10.0";
1017  tests[10.01] = "10.01";
1018  tests[0.0123] = "0.0123";
1019  tests[-3.7e-27] = "-3.7e-27";
1020  tests[1e+40] = "1.0e40";
1021  tests[-12345678901234567.0] = "-1.234567890123457e16";
1022  tests[192000] = "192000.0";
1023  tests[1234567] = "1.234567e6";
1024  tests[0.00006] = "0.00006";
1025  tests[0.000006] = "6.0e-6";
1026 
1027  for (auto& test : tests)
1028  {
1029  element->setAttribute (number, test.first);
1030  expectEquals (element->getStringAttribute (number), test.second);
1031  }
1032  }
1033  }
1034 };
1035 
1036 static XmlElementTests xmlElementTests;
1037 
1038 #endif
1039 
1040 } // namespace juce
const Result & getStatus() const noexcept
bool openedOk() const noexcept
ObjectType * get() const noexcept
void addCopyOfList(const LinkedListPointer &other)
virtual bool writeByte(char byte)
bool failed() const noexcept
Definition: juce_Result.cpp:77
String getPooledString(const String &original)
static StringPool & getGlobalPool() noexcept
String upToFirstOccurrenceOf(StringRef substringToEndWith, bool includeSubStringInResult, bool ignoreCase) const
String fromLastOccurrenceOf(StringRef substringToFind, bool includeSubStringInResult, bool ignoreCase) const
bool isNotEmpty() const noexcept
Definition: juce_String.h:302
bool overwriteTargetFileWithTemporary() const
const File & getFile() const noexcept
XmlElement * getNextElementWithTagName(StringRef requiredTagName) const
void setTagName(StringRef newTagName)
void addTextElement(const String &text)
int getNumChildElements() const noexcept
XmlElement(const String &tagName)
void insertChildElement(XmlElement *newChildElement, int indexToInsertAt) noexcept
XmlElement * getChildByName(StringRef tagNameToLookFor) const noexcept
bool isTextElement() const noexcept
void deleteAllChildElementsWithTagName(StringRef tagName) noexcept
String toString(const TextFormat &format={}) const
void addChildElement(XmlElement *newChildElement) noexcept
String getTagNameWithoutNamespace() const
double getDoubleAttribute(StringRef attributeName, double defaultReturnValue=0.0) const
const String & getAttributeValue(int attributeIndex) const noexcept
void prependChildElement(XmlElement *newChildElement) noexcept
XmlElement * createNewChildElement(StringRef tagName)
String getChildElementAllSubText(StringRef childTagName, const String &defaultReturnValue) const
const String & getText() const noexcept
void deleteAllTextElements() noexcept
String getAllSubText() const
bool compareAttribute(StringRef attributeName, StringRef stringToCompareAgainst, bool ignoreCase=false) const noexcept
void setText(const String &newText)
XmlElement * findParentElementOf(const XmlElement *childToSearchFor) noexcept
bool isEquivalentTo(const XmlElement *other, bool ignoreOrderOfAttributes) const noexcept
bool getBoolAttribute(StringRef attributeName, bool defaultReturnValue=false) const
void removeAllAttributes() noexcept
static XmlElement * createTextElement(const String &text)
const String & getAttributeName(int attributeIndex) const noexcept
bool hasAttribute(StringRef attributeName) const noexcept
bool containsChildElement(const XmlElement *possibleChild) const noexcept
bool replaceChildElement(XmlElement *currentChildElement, XmlElement *newChildNode) noexcept
void writeTo(OutputStream &output, const TextFormat &format={}) const
XmlElement * getChildElement(int index) const noexcept
XmlElement & operator=(const XmlElement &)
void deleteAllChildElements() noexcept
bool hasTagName(StringRef possibleTagName) const noexcept
void removeAttribute(const Identifier &attributeName) noexcept
XmlElement * getChildByAttribute(StringRef attributeName, StringRef attributeValue) const noexcept
int getNumAttributes() const noexcept
void removeChildElement(XmlElement *childToRemove, bool shouldDeleteTheChild) noexcept
static bool isValidXmlName(StringRef possibleName) noexcept
int getIntAttribute(StringRef attributeName, int defaultReturnValue=0) const
const String & getStringAttribute(StringRef attributeName) const noexcept
bool hasTagNameIgnoringNamespace(StringRef possibleTagName) const
String getNamespace() const
void setAttribute(const Identifier &attributeName, const String &newValue)
TextFormat withoutHeader() const