mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-18 00:54:19 +00:00
1159 lines
31 KiB
C++
1159 lines
31 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
This file is part of the JUCE library - "Jules' Utility Class Extensions"
|
|
Copyright 2004-10 by Raw Material Software Ltd.
|
|
|
|
------------------------------------------------------------------------------
|
|
|
|
JUCE can be redistributed and/or modified under the terms of the GNU General
|
|
Public License (Version 2), as published by the Free Software Foundation.
|
|
A copy of the license is included in the JUCE distribution, or can be found
|
|
online at www.gnu.org/licenses.
|
|
|
|
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
|
|
------------------------------------------------------------------------------
|
|
|
|
To release a closed-source product which uses JUCE, commercial licenses are
|
|
available: visit www.rawmaterialsoftware.com/juce for more information.
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
#include "../core/juce_StandardHeader.h"
|
|
|
|
BEGIN_JUCE_NAMESPACE
|
|
|
|
|
|
#include "juce_XmlElement.h"
|
|
#include "../io/streams/juce_MemoryOutputStream.h"
|
|
#include "../io/files/juce_TemporaryFile.h"
|
|
#include "../threads/juce_Thread.h"
|
|
#include "../containers/juce_ScopedPointer.h"
|
|
|
|
|
|
//==============================================================================
|
|
XmlElement::XmlAttributeNode::XmlAttributeNode (const XmlAttributeNode& other) throw()
|
|
: name (other.name),
|
|
value (other.value),
|
|
next (0)
|
|
{
|
|
}
|
|
|
|
XmlElement::XmlAttributeNode::XmlAttributeNode (const String& name_, const String& value_) throw()
|
|
: name (name_),
|
|
value (value_),
|
|
next (0)
|
|
{
|
|
}
|
|
|
|
//==============================================================================
|
|
XmlElement::XmlElement (const String& tagName_) throw()
|
|
: tagName (tagName_),
|
|
firstChildElement (0),
|
|
nextElement (0),
|
|
attributes (0)
|
|
{
|
|
// the tag name mustn't be empty, or it'll look like a text element!
|
|
jassert (tagName_.containsNonWhitespaceChars())
|
|
|
|
// The tag can't contain spaces or other characters that would create invalid XML!
|
|
jassert (! tagName_.containsAnyOf (" <>/&"));
|
|
}
|
|
|
|
XmlElement::XmlElement (int /*dummy*/) throw()
|
|
: firstChildElement (0),
|
|
nextElement (0),
|
|
attributes (0)
|
|
{
|
|
}
|
|
|
|
XmlElement::XmlElement (const XmlElement& other) throw()
|
|
: tagName (other.tagName),
|
|
firstChildElement (0),
|
|
nextElement (0),
|
|
attributes (0)
|
|
{
|
|
copyChildrenAndAttributesFrom (other);
|
|
}
|
|
|
|
XmlElement& XmlElement::operator= (const XmlElement& other) throw()
|
|
{
|
|
if (this != &other)
|
|
{
|
|
removeAllAttributes();
|
|
deleteAllChildElements();
|
|
|
|
tagName = other.tagName;
|
|
|
|
copyChildrenAndAttributesFrom (other);
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
void XmlElement::copyChildrenAndAttributesFrom (const XmlElement& other) throw()
|
|
{
|
|
XmlElement* child = other.firstChildElement;
|
|
XmlElement* lastChild = 0;
|
|
|
|
while (child != 0)
|
|
{
|
|
XmlElement* const copiedChild = new XmlElement (*child);
|
|
|
|
if (lastChild != 0)
|
|
lastChild->nextElement = copiedChild;
|
|
else
|
|
firstChildElement = copiedChild;
|
|
|
|
lastChild = copiedChild;
|
|
child = child->nextElement;
|
|
}
|
|
|
|
const XmlAttributeNode* att = other.attributes;
|
|
XmlAttributeNode* lastAtt = 0;
|
|
|
|
while (att != 0)
|
|
{
|
|
XmlAttributeNode* const newAtt = new XmlAttributeNode (*att);
|
|
|
|
if (lastAtt != 0)
|
|
lastAtt->next = newAtt;
|
|
else
|
|
attributes = newAtt;
|
|
|
|
lastAtt = newAtt;
|
|
att = att->next;
|
|
}
|
|
}
|
|
|
|
XmlElement::~XmlElement() throw()
|
|
{
|
|
XmlElement* child = firstChildElement;
|
|
|
|
while (child != 0)
|
|
{
|
|
XmlElement* const nextChild = child->nextElement;
|
|
delete child;
|
|
child = nextChild;
|
|
}
|
|
|
|
XmlAttributeNode* att = attributes;
|
|
|
|
while (att != 0)
|
|
{
|
|
XmlAttributeNode* const nextAtt = att->next;
|
|
delete att;
|
|
att = nextAtt;
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
namespace XmlOutputFunctions
|
|
{
|
|
/*static bool isLegalXmlCharSlow (const juce_wchar character) throw()
|
|
{
|
|
if ((character >= 'a' && character <= 'z')
|
|
|| (character >= 'A' && character <= 'Z')
|
|
|| (character >= '0' && character <= '9'))
|
|
return true;
|
|
|
|
const char* t = " .,;:-()_+=?!'#@[]/\\*%~{}";
|
|
|
|
do
|
|
{
|
|
if (((juce_wchar) (uint8) *t) == character)
|
|
return true;
|
|
}
|
|
while (*++t != 0);
|
|
|
|
return false;
|
|
}
|
|
|
|
static void generateLegalCharConstants()
|
|
{
|
|
uint8 n[32];
|
|
zerostruct (n);
|
|
for (int i = 0; i < 256; ++i)
|
|
if (isLegalXmlCharSlow (i))
|
|
n[i >> 3] |= (1 << (i & 7));
|
|
|
|
String s;
|
|
for (int i = 0; i < 32; ++i)
|
|
s << (int) n[i] << ", ";
|
|
|
|
DBG (s);
|
|
}*/
|
|
|
|
static bool isLegalXmlChar (const uint32 c) throw()
|
|
{
|
|
static const unsigned char legalChars[] = { 0, 0, 0, 0, 171, 255, 255, 175, 255, 255, 255, 191, 254, 255, 255, 111 };
|
|
|
|
return c < sizeof (legalChars) * 8
|
|
&& (legalChars [c >> 3] & (1 << (c & 7))) != 0;
|
|
}
|
|
|
|
static void escapeIllegalXmlChars (OutputStream& outputStream, const String& text, const bool changeNewLines)
|
|
{
|
|
const juce_wchar* t = text;
|
|
|
|
for (;;)
|
|
{
|
|
const juce_wchar character = *t++;
|
|
|
|
if (character == 0)
|
|
{
|
|
break;
|
|
}
|
|
else if (isLegalXmlChar ((uint32) character))
|
|
{
|
|
outputStream << (char) character;
|
|
}
|
|
else
|
|
{
|
|
switch (character)
|
|
{
|
|
case '&': outputStream << "&"; break;
|
|
case '"': outputStream << """; break;
|
|
case '>': outputStream << ">"; break;
|
|
case '<': outputStream << "<"; break;
|
|
|
|
case '\n':
|
|
if (changeNewLines)
|
|
outputStream << " ";
|
|
else
|
|
outputStream << (char) character;
|
|
|
|
break;
|
|
|
|
case '\r':
|
|
if (changeNewLines)
|
|
outputStream << " ";
|
|
else
|
|
outputStream << (char) character;
|
|
|
|
break;
|
|
|
|
default:
|
|
outputStream << "&#" << ((int) (unsigned int) character) << ';';
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void writeSpaces (OutputStream& out, int numSpaces)
|
|
{
|
|
if (numSpaces > 0)
|
|
{
|
|
const char* const blanks = " ";
|
|
const int blankSize = (int) sizeof (blanks) - 1;
|
|
|
|
while (numSpaces > blankSize)
|
|
{
|
|
out.write (blanks, blankSize);
|
|
numSpaces -= blankSize;
|
|
}
|
|
|
|
out.write (blanks, numSpaces);
|
|
}
|
|
}
|
|
}
|
|
|
|
void XmlElement::writeElementAsText (OutputStream& outputStream,
|
|
const int indentationLevel,
|
|
const int lineWrapLength) const
|
|
{
|
|
using namespace XmlOutputFunctions;
|
|
writeSpaces (outputStream, indentationLevel);
|
|
|
|
if (! isTextElement())
|
|
{
|
|
outputStream.writeByte ('<');
|
|
outputStream << tagName;
|
|
|
|
const int attIndent = indentationLevel + tagName.length() + 1;
|
|
int lineLen = 0;
|
|
|
|
const XmlAttributeNode* att = attributes;
|
|
while (att != 0)
|
|
{
|
|
if (lineLen > lineWrapLength && indentationLevel >= 0)
|
|
{
|
|
outputStream.write ("\r\n", 2);
|
|
writeSpaces (outputStream, attIndent);
|
|
lineLen = 0;
|
|
}
|
|
|
|
const int64 startPos = outputStream.getPosition();
|
|
outputStream.writeByte (' ');
|
|
outputStream << att->name;
|
|
outputStream.write ("=\"", 2);
|
|
escapeIllegalXmlChars (outputStream, att->value, true);
|
|
outputStream.writeByte ('"');
|
|
lineLen += (int) (outputStream.getPosition() - startPos);
|
|
|
|
att = att->next;
|
|
}
|
|
|
|
if (firstChildElement != 0)
|
|
{
|
|
XmlElement* child = firstChildElement;
|
|
|
|
if (child->nextElement == 0 && child->isTextElement())
|
|
{
|
|
outputStream.writeByte ('>');
|
|
escapeIllegalXmlChars (outputStream, child->getText(), false);
|
|
}
|
|
else
|
|
{
|
|
if (indentationLevel >= 0)
|
|
outputStream.write (">\r\n", 3);
|
|
else
|
|
outputStream.writeByte ('>');
|
|
|
|
bool lastWasTextNode = false;
|
|
|
|
while (child != 0)
|
|
{
|
|
if (child->isTextElement())
|
|
{
|
|
if ((! lastWasTextNode) && (indentationLevel >= 0))
|
|
writeSpaces (outputStream, indentationLevel + 2);
|
|
|
|
escapeIllegalXmlChars (outputStream, child->getText(), false);
|
|
lastWasTextNode = true;
|
|
}
|
|
else
|
|
{
|
|
if (indentationLevel >= 0)
|
|
{
|
|
if (lastWasTextNode)
|
|
outputStream.write ("\r\n", 2);
|
|
|
|
child->writeElementAsText (outputStream, indentationLevel + 2, lineWrapLength);
|
|
}
|
|
else
|
|
{
|
|
child->writeElementAsText (outputStream, indentationLevel, lineWrapLength);
|
|
}
|
|
|
|
lastWasTextNode = false;
|
|
}
|
|
|
|
child = child->nextElement;
|
|
}
|
|
|
|
if (indentationLevel >= 0)
|
|
{
|
|
if (lastWasTextNode)
|
|
outputStream.write ("\r\n", 2);
|
|
|
|
writeSpaces (outputStream, indentationLevel);
|
|
}
|
|
}
|
|
|
|
outputStream.write ("</", 2);
|
|
outputStream << tagName;
|
|
|
|
if (indentationLevel >= 0)
|
|
outputStream.write (">\r\n", 3);
|
|
else
|
|
outputStream.writeByte ('>');
|
|
}
|
|
else
|
|
{
|
|
if (indentationLevel >= 0)
|
|
outputStream.write ("/>\r\n", 4);
|
|
else
|
|
outputStream.write ("/>", 2);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (indentationLevel >= 0)
|
|
writeSpaces (outputStream, indentationLevel + 2);
|
|
|
|
escapeIllegalXmlChars (outputStream, getText(), false);
|
|
}
|
|
}
|
|
|
|
const String XmlElement::createDocument (const String& dtdToUse,
|
|
const bool allOnOneLine,
|
|
const bool includeXmlHeader,
|
|
const String& encodingType,
|
|
const int lineWrapLength) const
|
|
{
|
|
MemoryOutputStream mem (2048, 4096);
|
|
writeToStream (mem, dtdToUse, allOnOneLine, includeXmlHeader, encodingType, lineWrapLength);
|
|
|
|
return mem.toUTF8();
|
|
}
|
|
|
|
void XmlElement::writeToStream (OutputStream& output,
|
|
const String& dtdToUse,
|
|
const bool allOnOneLine,
|
|
const bool includeXmlHeader,
|
|
const String& encodingType,
|
|
const int lineWrapLength) const
|
|
{
|
|
if (includeXmlHeader)
|
|
output << "<?xml version=\"1.0\" encoding=\"" << encodingType
|
|
<< (allOnOneLine ? "\"?> " : "\"?>\r\n\r\n");
|
|
|
|
if (dtdToUse.isNotEmpty())
|
|
output << dtdToUse << (allOnOneLine ? " " : "\r\n");
|
|
|
|
writeElementAsText (output, allOnOneLine ? -1 : 0, lineWrapLength);
|
|
}
|
|
|
|
bool XmlElement::writeToFile (const File& file,
|
|
const String& dtdToUse,
|
|
const String& encodingType,
|
|
const int lineWrapLength) const
|
|
{
|
|
if (file.hasWriteAccess())
|
|
{
|
|
TemporaryFile tempFile (file);
|
|
ScopedPointer <FileOutputStream> out (tempFile.getFile().createOutputStream());
|
|
|
|
if (out != 0)
|
|
{
|
|
writeToStream (*out, dtdToUse, false, true, encodingType, lineWrapLength);
|
|
out = 0;
|
|
|
|
return tempFile.overwriteTargetFileWithTemporary();
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//==============================================================================
|
|
bool XmlElement::hasTagName (const String& tagNameWanted) const throw()
|
|
{
|
|
#ifdef JUCE_DEBUG
|
|
// if debugging, check that the case is actually the same, because
|
|
// valid xml is case-sensitive, and although this lets it pass, it's
|
|
// better not to..
|
|
if (tagName.equalsIgnoreCase (tagNameWanted))
|
|
{
|
|
jassert (tagName == tagNameWanted);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
#else
|
|
return tagName.equalsIgnoreCase (tagNameWanted);
|
|
#endif
|
|
}
|
|
|
|
XmlElement* XmlElement::getNextElementWithTagName (const String& requiredTagName) const
|
|
{
|
|
XmlElement* e = nextElement;
|
|
|
|
while (e != 0 && ! e->hasTagName (requiredTagName))
|
|
e = e->nextElement;
|
|
|
|
return e;
|
|
}
|
|
|
|
//==============================================================================
|
|
int XmlElement::getNumAttributes() const throw()
|
|
{
|
|
const XmlAttributeNode* att = attributes;
|
|
int count = 0;
|
|
|
|
while (att != 0)
|
|
{
|
|
att = att->next;
|
|
++count;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
const String& XmlElement::getAttributeName (const int index) const throw()
|
|
{
|
|
const XmlAttributeNode* att = attributes;
|
|
int count = 0;
|
|
|
|
while (att != 0)
|
|
{
|
|
if (count == index)
|
|
return att->name;
|
|
|
|
att = att->next;
|
|
++count;
|
|
}
|
|
|
|
return String::empty;
|
|
}
|
|
|
|
const String& XmlElement::getAttributeValue (const int index) const throw()
|
|
{
|
|
const XmlAttributeNode* att = attributes;
|
|
int count = 0;
|
|
|
|
while (att != 0)
|
|
{
|
|
if (count == index)
|
|
return att->value;
|
|
|
|
att = att->next;
|
|
++count;
|
|
}
|
|
|
|
return String::empty;
|
|
}
|
|
|
|
bool XmlElement::hasAttribute (const String& attributeName) const throw()
|
|
{
|
|
const XmlAttributeNode* att = attributes;
|
|
|
|
while (att != 0)
|
|
{
|
|
if (att->name.equalsIgnoreCase (attributeName))
|
|
return true;
|
|
|
|
att = att->next;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//==============================================================================
|
|
const String& XmlElement::getStringAttribute (const String& attributeName) const throw()
|
|
{
|
|
const XmlAttributeNode* att = attributes;
|
|
|
|
while (att != 0)
|
|
{
|
|
if (att->name.equalsIgnoreCase (attributeName))
|
|
return att->value;
|
|
|
|
att = att->next;
|
|
}
|
|
|
|
return String::empty;
|
|
}
|
|
|
|
const String XmlElement::getStringAttribute (const String& attributeName, const String& defaultReturnValue) const
|
|
{
|
|
const XmlAttributeNode* att = attributes;
|
|
|
|
while (att != 0)
|
|
{
|
|
if (att->name.equalsIgnoreCase (attributeName))
|
|
return att->value;
|
|
|
|
att = att->next;
|
|
}
|
|
|
|
return defaultReturnValue;
|
|
}
|
|
|
|
int XmlElement::getIntAttribute (const String& attributeName, const int defaultReturnValue) const
|
|
{
|
|
const XmlAttributeNode* att = attributes;
|
|
|
|
while (att != 0)
|
|
{
|
|
if (att->name.equalsIgnoreCase (attributeName))
|
|
return att->value.getIntValue();
|
|
|
|
att = att->next;
|
|
}
|
|
|
|
return defaultReturnValue;
|
|
}
|
|
|
|
double XmlElement::getDoubleAttribute (const String& attributeName, const double defaultReturnValue) const
|
|
{
|
|
const XmlAttributeNode* att = attributes;
|
|
|
|
while (att != 0)
|
|
{
|
|
if (att->name.equalsIgnoreCase (attributeName))
|
|
return att->value.getDoubleValue();
|
|
|
|
att = att->next;
|
|
}
|
|
|
|
return defaultReturnValue;
|
|
}
|
|
|
|
bool XmlElement::getBoolAttribute (const String& attributeName, const bool defaultReturnValue) const
|
|
{
|
|
const XmlAttributeNode* att = attributes;
|
|
|
|
while (att != 0)
|
|
{
|
|
if (att->name.equalsIgnoreCase (attributeName))
|
|
{
|
|
juce_wchar firstChar = att->value[0];
|
|
|
|
if (CharacterFunctions::isWhitespace (firstChar))
|
|
firstChar = att->value.trimStart() [0];
|
|
|
|
return firstChar == '1'
|
|
|| firstChar == 't'
|
|
|| firstChar == 'y'
|
|
|| firstChar == 'T'
|
|
|| firstChar == 'Y';
|
|
}
|
|
|
|
att = att->next;
|
|
}
|
|
|
|
return defaultReturnValue;
|
|
}
|
|
|
|
bool XmlElement::compareAttribute (const String& attributeName,
|
|
const String& stringToCompareAgainst,
|
|
const bool ignoreCase) const throw()
|
|
{
|
|
const XmlAttributeNode* att = attributes;
|
|
|
|
while (att != 0)
|
|
{
|
|
if (att->name.equalsIgnoreCase (attributeName))
|
|
{
|
|
if (ignoreCase)
|
|
return att->value.equalsIgnoreCase (stringToCompareAgainst);
|
|
else
|
|
return att->value == stringToCompareAgainst;
|
|
}
|
|
|
|
att = att->next;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//==============================================================================
|
|
void XmlElement::setAttribute (const String& attributeName, const String& value)
|
|
{
|
|
#ifdef JUCE_DEBUG
|
|
// check the identifier being passed in is legal..
|
|
const juce_wchar* t = attributeName;
|
|
while (*t != 0)
|
|
{
|
|
jassert (CharacterFunctions::isLetterOrDigit (*t)
|
|
|| *t == '_'
|
|
|| *t == '-'
|
|
|| *t == ':');
|
|
++t;
|
|
}
|
|
#endif
|
|
|
|
if (attributes == 0)
|
|
{
|
|
attributes = new XmlAttributeNode (attributeName, value);
|
|
}
|
|
else
|
|
{
|
|
XmlAttributeNode* att = attributes;
|
|
|
|
for (;;)
|
|
{
|
|
if (att->name.equalsIgnoreCase (attributeName))
|
|
{
|
|
att->value = value;
|
|
break;
|
|
}
|
|
else if (att->next == 0)
|
|
{
|
|
att->next = new XmlAttributeNode (attributeName, value);
|
|
break;
|
|
}
|
|
|
|
att = att->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
void XmlElement::setAttribute (const String& attributeName, const int number)
|
|
{
|
|
setAttribute (attributeName, String (number));
|
|
}
|
|
|
|
void XmlElement::setAttribute (const String& attributeName, const double number)
|
|
{
|
|
setAttribute (attributeName, String (number));
|
|
}
|
|
|
|
void XmlElement::removeAttribute (const String& attributeName) throw()
|
|
{
|
|
XmlAttributeNode* att = attributes;
|
|
XmlAttributeNode* lastAtt = 0;
|
|
|
|
while (att != 0)
|
|
{
|
|
if (att->name.equalsIgnoreCase (attributeName))
|
|
{
|
|
if (lastAtt == 0)
|
|
attributes = att->next;
|
|
else
|
|
lastAtt->next = att->next;
|
|
|
|
delete att;
|
|
break;
|
|
}
|
|
|
|
lastAtt = att;
|
|
att = att->next;
|
|
}
|
|
}
|
|
|
|
void XmlElement::removeAllAttributes() throw()
|
|
{
|
|
while (attributes != 0)
|
|
{
|
|
XmlAttributeNode* const nextAtt = attributes->next;
|
|
delete attributes;
|
|
attributes = nextAtt;
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
int XmlElement::getNumChildElements() const throw()
|
|
{
|
|
int count = 0;
|
|
const XmlElement* child = firstChildElement;
|
|
|
|
while (child != 0)
|
|
{
|
|
++count;
|
|
child = child->nextElement;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
XmlElement* XmlElement::getChildElement (const int index) const throw()
|
|
{
|
|
int count = 0;
|
|
XmlElement* child = firstChildElement;
|
|
|
|
while (child != 0 && count < index)
|
|
{
|
|
child = child->nextElement;
|
|
++count;
|
|
}
|
|
|
|
return child;
|
|
}
|
|
|
|
XmlElement* XmlElement::getChildByName (const String& childName) const throw()
|
|
{
|
|
XmlElement* child = firstChildElement;
|
|
|
|
while (child != 0)
|
|
{
|
|
if (child->hasTagName (childName))
|
|
break;
|
|
|
|
child = child->nextElement;
|
|
}
|
|
|
|
return child;
|
|
}
|
|
|
|
void XmlElement::addChildElement (XmlElement* const newNode) throw()
|
|
{
|
|
if (newNode != 0)
|
|
{
|
|
if (firstChildElement == 0)
|
|
{
|
|
firstChildElement = newNode;
|
|
}
|
|
else
|
|
{
|
|
XmlElement* child = firstChildElement;
|
|
|
|
while (child->nextElement != 0)
|
|
child = child->nextElement;
|
|
|
|
child->nextElement = newNode;
|
|
|
|
// if this is non-zero, then something's probably
|
|
// gone wrong..
|
|
jassert (newNode->nextElement == 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void XmlElement::insertChildElement (XmlElement* const newNode,
|
|
int indexToInsertAt) throw()
|
|
{
|
|
if (newNode != 0)
|
|
{
|
|
removeChildElement (newNode, false);
|
|
|
|
if (indexToInsertAt == 0)
|
|
{
|
|
newNode->nextElement = firstChildElement;
|
|
firstChildElement = newNode;
|
|
}
|
|
else
|
|
{
|
|
if (firstChildElement == 0)
|
|
{
|
|
firstChildElement = newNode;
|
|
}
|
|
else
|
|
{
|
|
if (indexToInsertAt < 0)
|
|
indexToInsertAt = std::numeric_limits<int>::max();
|
|
|
|
XmlElement* child = firstChildElement;
|
|
|
|
while (child->nextElement != 0 && --indexToInsertAt > 0)
|
|
child = child->nextElement;
|
|
|
|
newNode->nextElement = child->nextElement;
|
|
child->nextElement = newNode;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
XmlElement* XmlElement::createNewChildElement (const String& tagName)
|
|
{
|
|
XmlElement* const newElement = new XmlElement (tagName);
|
|
addChildElement (newElement);
|
|
return newElement;
|
|
}
|
|
|
|
bool XmlElement::replaceChildElement (XmlElement* const currentChildElement,
|
|
XmlElement* const newNode) throw()
|
|
{
|
|
if (newNode != 0)
|
|
{
|
|
XmlElement* child = firstChildElement;
|
|
XmlElement* previousNode = 0;
|
|
|
|
while (child != 0)
|
|
{
|
|
if (child == currentChildElement)
|
|
{
|
|
if (child != newNode)
|
|
{
|
|
if (previousNode == 0)
|
|
firstChildElement = newNode;
|
|
else
|
|
previousNode->nextElement = newNode;
|
|
|
|
newNode->nextElement = child->nextElement;
|
|
|
|
delete child;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
previousNode = child;
|
|
child = child->nextElement;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void XmlElement::removeChildElement (XmlElement* const childToRemove,
|
|
const bool shouldDeleteTheChild) throw()
|
|
{
|
|
if (childToRemove != 0)
|
|
{
|
|
if (firstChildElement == childToRemove)
|
|
{
|
|
firstChildElement = childToRemove->nextElement;
|
|
childToRemove->nextElement = 0;
|
|
}
|
|
else
|
|
{
|
|
XmlElement* child = firstChildElement;
|
|
XmlElement* last = 0;
|
|
|
|
while (child != 0)
|
|
{
|
|
if (child == childToRemove)
|
|
{
|
|
if (last == 0)
|
|
firstChildElement = child->nextElement;
|
|
else
|
|
last->nextElement = child->nextElement;
|
|
|
|
childToRemove->nextElement = 0;
|
|
break;
|
|
}
|
|
|
|
last = child;
|
|
child = child->nextElement;
|
|
}
|
|
}
|
|
|
|
if (shouldDeleteTheChild)
|
|
delete childToRemove;
|
|
}
|
|
}
|
|
|
|
bool XmlElement::isEquivalentTo (const XmlElement* const other,
|
|
const bool ignoreOrderOfAttributes) const throw()
|
|
{
|
|
if (this != other)
|
|
{
|
|
if (other == 0 || tagName != other->tagName)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (ignoreOrderOfAttributes)
|
|
{
|
|
int totalAtts = 0;
|
|
const XmlAttributeNode* att = attributes;
|
|
|
|
while (att != 0)
|
|
{
|
|
if (! other->compareAttribute (att->name, att->value))
|
|
return false;
|
|
|
|
att = att->next;
|
|
++totalAtts;
|
|
}
|
|
|
|
if (totalAtts != other->getNumAttributes())
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
const XmlAttributeNode* thisAtt = attributes;
|
|
const XmlAttributeNode* otherAtt = other->attributes;
|
|
|
|
for (;;)
|
|
{
|
|
if (thisAtt == 0 || otherAtt == 0)
|
|
{
|
|
if (thisAtt == otherAtt) // both 0, so it's a match
|
|
break;
|
|
|
|
return false;
|
|
}
|
|
|
|
if (thisAtt->name != otherAtt->name
|
|
|| thisAtt->value != otherAtt->value)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
thisAtt = thisAtt->next;
|
|
otherAtt = otherAtt->next;
|
|
}
|
|
}
|
|
|
|
const XmlElement* thisChild = firstChildElement;
|
|
const XmlElement* otherChild = other->firstChildElement;
|
|
|
|
for (;;)
|
|
{
|
|
if (thisChild == 0 || otherChild == 0)
|
|
{
|
|
if (thisChild == otherChild) // both 0, so it's a match
|
|
break;
|
|
|
|
return false;
|
|
}
|
|
|
|
if (! thisChild->isEquivalentTo (otherChild, ignoreOrderOfAttributes))
|
|
return false;
|
|
|
|
thisChild = thisChild->nextElement;
|
|
otherChild = otherChild->nextElement;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void XmlElement::deleteAllChildElements() throw()
|
|
{
|
|
while (firstChildElement != 0)
|
|
{
|
|
XmlElement* const nextChild = firstChildElement->nextElement;
|
|
delete firstChildElement;
|
|
firstChildElement = nextChild;
|
|
}
|
|
}
|
|
|
|
void XmlElement::deleteAllChildElementsWithTagName (const String& name) throw()
|
|
{
|
|
XmlElement* child = firstChildElement;
|
|
|
|
while (child != 0)
|
|
{
|
|
if (child->hasTagName (name))
|
|
{
|
|
XmlElement* const nextChild = child->nextElement;
|
|
removeChildElement (child, true);
|
|
child = nextChild;
|
|
}
|
|
else
|
|
{
|
|
child = child->nextElement;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool XmlElement::containsChildElement (const XmlElement* const possibleChild) const throw()
|
|
{
|
|
const XmlElement* child = firstChildElement;
|
|
|
|
while (child != 0)
|
|
{
|
|
if (child == possibleChild)
|
|
return true;
|
|
|
|
child = child->nextElement;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
XmlElement* XmlElement::findParentElementOf (const XmlElement* const elementToLookFor) throw()
|
|
{
|
|
if (this == elementToLookFor || elementToLookFor == 0)
|
|
return 0;
|
|
|
|
XmlElement* child = firstChildElement;
|
|
|
|
while (child != 0)
|
|
{
|
|
if (elementToLookFor == child)
|
|
return this;
|
|
|
|
XmlElement* const found = child->findParentElementOf (elementToLookFor);
|
|
|
|
if (found != 0)
|
|
return found;
|
|
|
|
child = child->nextElement;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void XmlElement::getChildElementsAsArray (XmlElement** elems) const throw()
|
|
{
|
|
XmlElement* e = firstChildElement;
|
|
|
|
while (e != 0)
|
|
{
|
|
*elems++ = e;
|
|
e = e->nextElement;
|
|
}
|
|
}
|
|
|
|
void XmlElement::reorderChildElements (XmlElement** const elems, const int num) throw()
|
|
{
|
|
XmlElement* e = firstChildElement = elems[0];
|
|
|
|
for (int i = 1; i < num; ++i)
|
|
{
|
|
e->nextElement = elems[i];
|
|
e = e->nextElement;
|
|
}
|
|
|
|
e->nextElement = 0;
|
|
}
|
|
|
|
//==============================================================================
|
|
bool XmlElement::isTextElement() const throw()
|
|
{
|
|
return tagName.isEmpty();
|
|
}
|
|
|
|
static const juce_wchar* const juce_xmltextContentAttributeName = T("text");
|
|
|
|
const String XmlElement::getText() const throw()
|
|
{
|
|
jassert (isTextElement()); // you're trying to get the text from an element that
|
|
// isn't actually a text element.. If this contains text sub-nodes, you
|
|
// probably want to use getAllSubText instead.
|
|
|
|
return getStringAttribute (juce_xmltextContentAttributeName);
|
|
}
|
|
|
|
void XmlElement::setText (const String& newText) throw()
|
|
{
|
|
if (isTextElement())
|
|
{
|
|
setAttribute (juce_xmltextContentAttributeName, newText);
|
|
}
|
|
else
|
|
{
|
|
jassertfalse // you can only change the text in a text element, not a normal one.
|
|
}
|
|
}
|
|
|
|
const String XmlElement::getAllSubText() const throw()
|
|
{
|
|
String result;
|
|
String::Concatenator concatenator (result);
|
|
const XmlElement* child = firstChildElement;
|
|
|
|
while (child != 0)
|
|
{
|
|
if (child->isTextElement())
|
|
concatenator.append (child->getText());
|
|
|
|
child = child->nextElement;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
const String XmlElement::getChildElementAllSubText (const String& childTagName,
|
|
const String& defaultReturnValue) const throw()
|
|
{
|
|
const XmlElement* const child = getChildByName (childTagName);
|
|
|
|
if (child != 0)
|
|
return child->getAllSubText();
|
|
|
|
return defaultReturnValue;
|
|
}
|
|
|
|
XmlElement* XmlElement::createTextElement (const String& text) throw()
|
|
{
|
|
XmlElement* const e = new XmlElement ((int) 0);
|
|
e->setAttribute (juce_xmltextContentAttributeName, text);
|
|
return e;
|
|
}
|
|
|
|
void XmlElement::addTextElement (const String& text) throw()
|
|
{
|
|
addChildElement (createTextElement (text));
|
|
}
|
|
|
|
void XmlElement::deleteAllTextElements() throw()
|
|
{
|
|
XmlElement* child = firstChildElement;
|
|
|
|
while (child != 0)
|
|
{
|
|
XmlElement* const next = child->nextElement;
|
|
|
|
if (child->isTextElement())
|
|
removeChildElement (child, true);
|
|
|
|
child = next;
|
|
}
|
|
}
|
|
|
|
END_JUCE_NAMESPACE
|