- port to minGw
git-svn-id: http://moon:8086/svn/software/trunk/projects/JaySynth@235 b431acfa-c32f-4a4a-93f1-934dc6c82436
This commit is contained in:
@@ -0,0 +1,891 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the juce_core module of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with
|
||||
or without fee is hereby granted, provided that the above copyright notice and this
|
||||
permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
|
||||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
|
||||
NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
||||
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
|
||||
IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
NOTE! This permissive ISC license applies ONLY to files within the juce_core module!
|
||||
All other JUCE modules are covered by a dual GPL/commercial license, so if you are
|
||||
using any other modules, be sure to check that you also comply with their license.
|
||||
|
||||
For more details, visit www.juce.com
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
XmlDocument::XmlDocument (const String& documentText)
|
||||
: originalText (documentText),
|
||||
input (nullptr),
|
||||
outOfData (false),
|
||||
errorOccurred (false),
|
||||
needToLoadDTD (false),
|
||||
ignoreEmptyTextElements (true)
|
||||
{
|
||||
}
|
||||
|
||||
XmlDocument::XmlDocument (const File& file)
|
||||
: input (nullptr),
|
||||
outOfData (false),
|
||||
errorOccurred (false),
|
||||
needToLoadDTD (false),
|
||||
ignoreEmptyTextElements (true),
|
||||
inputSource (new FileInputSource (file))
|
||||
{
|
||||
}
|
||||
|
||||
XmlDocument::~XmlDocument()
|
||||
{
|
||||
}
|
||||
|
||||
XmlElement* XmlDocument::parse (const File& file)
|
||||
{
|
||||
XmlDocument doc (file);
|
||||
return doc.getDocumentElement();
|
||||
}
|
||||
|
||||
XmlElement* XmlDocument::parse (const String& xmlData)
|
||||
{
|
||||
XmlDocument doc (xmlData);
|
||||
return doc.getDocumentElement();
|
||||
}
|
||||
|
||||
void XmlDocument::setInputSource (InputSource* const newSource) noexcept
|
||||
{
|
||||
inputSource = newSource;
|
||||
}
|
||||
|
||||
void XmlDocument::setEmptyTextElementsIgnored (const bool shouldBeIgnored) noexcept
|
||||
{
|
||||
ignoreEmptyTextElements = shouldBeIgnored;
|
||||
}
|
||||
|
||||
namespace XmlIdentifierChars
|
||||
{
|
||||
static bool isIdentifierCharSlow (const juce_wchar c) noexcept
|
||||
{
|
||||
return CharacterFunctions::isLetterOrDigit (c)
|
||||
|| c == '_' || c == '-' || c == ':' || c == '.';
|
||||
}
|
||||
|
||||
static bool isIdentifierChar (const juce_wchar c) noexcept
|
||||
{
|
||||
static const uint32 legalChars[] = { 0, 0x7ff6000, 0x87fffffe, 0x7fffffe, 0 };
|
||||
|
||||
return ((int) c < (int) numElementsInArray (legalChars) * 32) ? ((legalChars [c >> 5] & (1 << (c & 31))) != 0)
|
||||
: isIdentifierCharSlow (c);
|
||||
}
|
||||
|
||||
/*static void generateIdentifierCharConstants()
|
||||
{
|
||||
uint32 n[8] = { 0 };
|
||||
for (int i = 0; i < 256; ++i)
|
||||
if (isIdentifierCharSlow (i))
|
||||
n[i >> 5] |= (1 << (i & 31));
|
||||
|
||||
String s;
|
||||
for (int i = 0; i < 8; ++i)
|
||||
s << "0x" << String::toHexString ((int) n[i]) << ", ";
|
||||
|
||||
DBG (s);
|
||||
}*/
|
||||
|
||||
static String::CharPointerType findEndOfToken (String::CharPointerType p)
|
||||
{
|
||||
while (isIdentifierChar (*p))
|
||||
++p;
|
||||
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
XmlElement* XmlDocument::getDocumentElement (const bool onlyReadOuterDocumentElement)
|
||||
{
|
||||
if (originalText.isEmpty() && inputSource != nullptr)
|
||||
{
|
||||
ScopedPointer<InputStream> in (inputSource->createInputStream());
|
||||
|
||||
if (in != nullptr)
|
||||
{
|
||||
MemoryOutputStream data;
|
||||
data.writeFromInputStream (*in, onlyReadOuterDocumentElement ? 8192 : -1);
|
||||
|
||||
#if JUCE_STRING_UTF_TYPE == 8
|
||||
if (data.getDataSize() > 2)
|
||||
{
|
||||
data.writeByte (0);
|
||||
const char* text = static_cast<const char*> (data.getData());
|
||||
|
||||
if (CharPointer_UTF16::isByteOrderMarkBigEndian (text)
|
||||
|| CharPointer_UTF16::isByteOrderMarkLittleEndian (text))
|
||||
{
|
||||
originalText = data.toString();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (CharPointer_UTF8::isByteOrderMark (text))
|
||||
text += 3;
|
||||
|
||||
// parse the input buffer directly to avoid copying it all to a string..
|
||||
return parseDocumentElement (String::CharPointerType (text), onlyReadOuterDocumentElement);
|
||||
}
|
||||
}
|
||||
#else
|
||||
originalText = data.toString();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
return parseDocumentElement (originalText.getCharPointer(), onlyReadOuterDocumentElement);
|
||||
}
|
||||
|
||||
const String& XmlDocument::getLastParseError() const noexcept
|
||||
{
|
||||
return lastError;
|
||||
}
|
||||
|
||||
void XmlDocument::setLastError (const String& desc, const bool carryOn)
|
||||
{
|
||||
lastError = desc;
|
||||
errorOccurred = ! carryOn;
|
||||
}
|
||||
|
||||
String XmlDocument::getFileContents (const String& filename) const
|
||||
{
|
||||
if (inputSource != nullptr)
|
||||
{
|
||||
const ScopedPointer<InputStream> in (inputSource->createInputStreamFor (filename.trim().unquoted()));
|
||||
|
||||
if (in != nullptr)
|
||||
return in->readEntireStreamAsString();
|
||||
}
|
||||
|
||||
return String::empty;
|
||||
}
|
||||
|
||||
juce_wchar XmlDocument::readNextChar() noexcept
|
||||
{
|
||||
const juce_wchar c = input.getAndAdvance();
|
||||
|
||||
if (c == 0)
|
||||
{
|
||||
outOfData = true;
|
||||
--input;
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
XmlElement* XmlDocument::parseDocumentElement (String::CharPointerType textToParse,
|
||||
const bool onlyReadOuterDocumentElement)
|
||||
{
|
||||
input = textToParse;
|
||||
errorOccurred = false;
|
||||
outOfData = false;
|
||||
needToLoadDTD = true;
|
||||
|
||||
if (textToParse.isEmpty())
|
||||
{
|
||||
lastError = "not enough input";
|
||||
}
|
||||
else if (! parseHeader())
|
||||
{
|
||||
lastError = "malformed header";
|
||||
}
|
||||
else if (! parseDTD())
|
||||
{
|
||||
lastError = "malformed DTD";
|
||||
}
|
||||
else
|
||||
{
|
||||
lastError.clear();
|
||||
|
||||
ScopedPointer<XmlElement> result (readNextElement (! onlyReadOuterDocumentElement));
|
||||
|
||||
if (! errorOccurred)
|
||||
return result.release();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool XmlDocument::parseHeader()
|
||||
{
|
||||
skipNextWhiteSpace();
|
||||
|
||||
if (CharacterFunctions::compareUpTo (input, CharPointer_ASCII ("<?xml"), 5) == 0)
|
||||
{
|
||||
const String::CharPointerType headerEnd (CharacterFunctions::find (input, CharPointer_ASCII ("?>")));
|
||||
|
||||
if (headerEnd.isEmpty())
|
||||
return false;
|
||||
|
||||
#if JUCE_DEBUG
|
||||
const String encoding (String (input, headerEnd)
|
||||
.fromFirstOccurrenceOf ("encoding", false, true)
|
||||
.fromFirstOccurrenceOf ("=", false, false)
|
||||
.fromFirstOccurrenceOf ("\"", false, false)
|
||||
.upToFirstOccurrenceOf ("\"", false, false).trim());
|
||||
|
||||
/* If you load an XML document with a non-UTF encoding type, it may have been
|
||||
loaded wrongly.. Since all the files are read via the normal juce file streams,
|
||||
they're treated as UTF-8, so by the time it gets to the parser, the encoding will
|
||||
have been lost. Best plan is to stick to utf-8 or if you have specific files to
|
||||
read, use your own code to convert them to a unicode String, and pass that to the
|
||||
XML parser.
|
||||
*/
|
||||
jassert (encoding.isEmpty() || encoding.startsWithIgnoreCase ("utf-"));
|
||||
#endif
|
||||
|
||||
input = headerEnd + 2;
|
||||
skipNextWhiteSpace();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool XmlDocument::parseDTD()
|
||||
{
|
||||
if (CharacterFunctions::compareUpTo (input, CharPointer_ASCII ("<!DOCTYPE"), 9) == 0)
|
||||
{
|
||||
input += 9;
|
||||
const String::CharPointerType dtdStart (input);
|
||||
|
||||
for (int n = 1; n > 0;)
|
||||
{
|
||||
const juce_wchar c = readNextChar();
|
||||
|
||||
if (outOfData)
|
||||
return false;
|
||||
|
||||
if (c == '<')
|
||||
++n;
|
||||
else if (c == '>')
|
||||
--n;
|
||||
}
|
||||
|
||||
dtdText = String (dtdStart, input - 1).trim();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void XmlDocument::skipNextWhiteSpace()
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
input = input.findEndOfWhitespace();
|
||||
|
||||
if (input.isEmpty())
|
||||
{
|
||||
outOfData = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (*input == '<')
|
||||
{
|
||||
if (input[1] == '!'
|
||||
&& input[2] == '-'
|
||||
&& input[3] == '-')
|
||||
{
|
||||
input += 4;
|
||||
const int closeComment = input.indexOf (CharPointer_ASCII ("-->"));
|
||||
|
||||
if (closeComment < 0)
|
||||
{
|
||||
outOfData = true;
|
||||
break;
|
||||
}
|
||||
|
||||
input += closeComment + 3;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (input[1] == '?')
|
||||
{
|
||||
input += 2;
|
||||
const int closeBracket = input.indexOf (CharPointer_ASCII ("?>"));
|
||||
|
||||
if (closeBracket < 0)
|
||||
{
|
||||
outOfData = true;
|
||||
break;
|
||||
}
|
||||
|
||||
input += closeBracket + 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void XmlDocument::readQuotedString (String& result)
|
||||
{
|
||||
const juce_wchar quote = readNextChar();
|
||||
|
||||
while (! outOfData)
|
||||
{
|
||||
const juce_wchar c = readNextChar();
|
||||
|
||||
if (c == quote)
|
||||
break;
|
||||
|
||||
--input;
|
||||
|
||||
if (c == '&')
|
||||
{
|
||||
readEntity (result);
|
||||
}
|
||||
else
|
||||
{
|
||||
const String::CharPointerType start (input);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
const juce_wchar character = *input;
|
||||
|
||||
if (character == quote)
|
||||
{
|
||||
result.appendCharPointer (start, input);
|
||||
++input;
|
||||
return;
|
||||
}
|
||||
else if (character == '&')
|
||||
{
|
||||
result.appendCharPointer (start, input);
|
||||
break;
|
||||
}
|
||||
else if (character == 0)
|
||||
{
|
||||
setLastError ("unmatched quotes", false);
|
||||
outOfData = true;
|
||||
break;
|
||||
}
|
||||
|
||||
++input;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
XmlElement* XmlDocument::readNextElement (const bool alsoParseSubElements)
|
||||
{
|
||||
XmlElement* node = nullptr;
|
||||
|
||||
skipNextWhiteSpace();
|
||||
if (outOfData)
|
||||
return nullptr;
|
||||
|
||||
if (*input == '<')
|
||||
{
|
||||
++input;
|
||||
String::CharPointerType endOfToken (XmlIdentifierChars::findEndOfToken (input));
|
||||
|
||||
if (endOfToken == input)
|
||||
{
|
||||
// no tag name - but allow for a gap after the '<' before giving an error
|
||||
skipNextWhiteSpace();
|
||||
endOfToken = XmlIdentifierChars::findEndOfToken (input);
|
||||
|
||||
if (endOfToken == input)
|
||||
{
|
||||
setLastError ("tag name missing", false);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
node = new XmlElement (input, endOfToken);
|
||||
input = endOfToken;
|
||||
LinkedListPointer<XmlElement::XmlAttributeNode>::Appender attributeAppender (node->attributes);
|
||||
|
||||
// look for attributes
|
||||
for (;;)
|
||||
{
|
||||
skipNextWhiteSpace();
|
||||
|
||||
const juce_wchar c = *input;
|
||||
|
||||
// empty tag..
|
||||
if (c == '/' && input[1] == '>')
|
||||
{
|
||||
input += 2;
|
||||
break;
|
||||
}
|
||||
|
||||
// parse the guts of the element..
|
||||
if (c == '>')
|
||||
{
|
||||
++input;
|
||||
|
||||
if (alsoParseSubElements)
|
||||
readChildElements (*node);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// get an attribute..
|
||||
if (XmlIdentifierChars::isIdentifierChar (c))
|
||||
{
|
||||
String::CharPointerType attNameEnd (XmlIdentifierChars::findEndOfToken (input));
|
||||
|
||||
if (attNameEnd != input)
|
||||
{
|
||||
const String::CharPointerType attNameStart (input);
|
||||
input = attNameEnd;
|
||||
|
||||
skipNextWhiteSpace();
|
||||
|
||||
if (readNextChar() == '=')
|
||||
{
|
||||
skipNextWhiteSpace();
|
||||
|
||||
const juce_wchar nextChar = *input;
|
||||
|
||||
if (nextChar == '"' || nextChar == '\'')
|
||||
{
|
||||
XmlElement::XmlAttributeNode* const newAtt
|
||||
= new XmlElement::XmlAttributeNode (attNameStart, attNameEnd);
|
||||
|
||||
readQuotedString (newAtt->value);
|
||||
attributeAppender.append (newAtt);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
setLastError ("expected '=' after attribute '"
|
||||
+ String (attNameStart, attNameEnd) + "'", false);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (! outOfData)
|
||||
setLastError ("illegal character found in " + node->getTagName() + ": '" + c + "'", false);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
void XmlDocument::readChildElements (XmlElement& parent)
|
||||
{
|
||||
LinkedListPointer<XmlElement>::Appender childAppender (parent.firstChildElement);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
const String::CharPointerType preWhitespaceInput (input);
|
||||
skipNextWhiteSpace();
|
||||
|
||||
if (outOfData)
|
||||
{
|
||||
setLastError ("unmatched tags", false);
|
||||
break;
|
||||
}
|
||||
|
||||
if (*input == '<')
|
||||
{
|
||||
const juce_wchar c1 = input[1];
|
||||
|
||||
if (c1 == '/')
|
||||
{
|
||||
// our close tag..
|
||||
const int closeTag = input.indexOf ((juce_wchar) '>');
|
||||
|
||||
if (closeTag >= 0)
|
||||
input += closeTag + 1;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (c1 == '!' && CharacterFunctions::compareUpTo (input + 2, CharPointer_ASCII ("[CDATA["), 7) == 0)
|
||||
{
|
||||
input += 9;
|
||||
const String::CharPointerType inputStart (input);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
const juce_wchar c0 = *input;
|
||||
|
||||
if (c0 == 0)
|
||||
{
|
||||
setLastError ("unterminated CDATA section", false);
|
||||
outOfData = true;
|
||||
break;
|
||||
}
|
||||
else if (c0 == ']'
|
||||
&& input[1] == ']'
|
||||
&& input[2] == '>')
|
||||
{
|
||||
childAppender.append (XmlElement::createTextElement (String (inputStart, input)));
|
||||
input += 3;
|
||||
break;
|
||||
}
|
||||
|
||||
++input;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// this is some other element, so parse and add it..
|
||||
if (XmlElement* const n = readNextElement (true))
|
||||
childAppender.append (n);
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
else // must be a character block
|
||||
{
|
||||
input = preWhitespaceInput; // roll back to include the leading whitespace
|
||||
String textElementContent;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
const juce_wchar c = *input;
|
||||
|
||||
if (c == '<')
|
||||
{
|
||||
if (input[1] == '!' && input[2] == '-' && input[3] == '-')
|
||||
{
|
||||
input += 4;
|
||||
const int closeComment = input.indexOf (CharPointer_ASCII ("-->"));
|
||||
|
||||
if (closeComment < 0)
|
||||
{
|
||||
setLastError ("unterminated comment", false);
|
||||
outOfData = true;
|
||||
return;
|
||||
}
|
||||
|
||||
input += closeComment + 3;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (c == 0)
|
||||
{
|
||||
setLastError ("unmatched tags", false);
|
||||
outOfData = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (c == '&')
|
||||
{
|
||||
String entity;
|
||||
readEntity (entity);
|
||||
|
||||
if (entity.startsWithChar ('<') && entity [1] != 0)
|
||||
{
|
||||
const String::CharPointerType oldInput (input);
|
||||
const bool oldOutOfData = outOfData;
|
||||
|
||||
input = entity.getCharPointer();
|
||||
outOfData = false;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
XmlElement* const n = readNextElement (true);
|
||||
|
||||
if (n == nullptr)
|
||||
break;
|
||||
|
||||
childAppender.append (n);
|
||||
}
|
||||
|
||||
input = oldInput;
|
||||
outOfData = oldOutOfData;
|
||||
}
|
||||
else
|
||||
{
|
||||
textElementContent += entity;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const String::CharPointerType start (input);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
const juce_wchar nextChar = *input;
|
||||
|
||||
if (nextChar == '<' || nextChar == '&')
|
||||
break;
|
||||
|
||||
if (nextChar == 0)
|
||||
{
|
||||
setLastError ("unmatched tags", false);
|
||||
outOfData = true;
|
||||
return;
|
||||
}
|
||||
|
||||
++input;
|
||||
}
|
||||
|
||||
textElementContent.appendCharPointer (start, input);
|
||||
}
|
||||
}
|
||||
|
||||
if ((! ignoreEmptyTextElements) || textElementContent.containsNonWhitespaceChars())
|
||||
childAppender.append (XmlElement::createTextElement (textElementContent));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void XmlDocument::readEntity (String& result)
|
||||
{
|
||||
// skip over the ampersand
|
||||
++input;
|
||||
|
||||
if (input.compareIgnoreCaseUpTo (CharPointer_ASCII ("amp;"), 4) == 0)
|
||||
{
|
||||
input += 4;
|
||||
result += '&';
|
||||
}
|
||||
else if (input.compareIgnoreCaseUpTo (CharPointer_ASCII ("quot;"), 5) == 0)
|
||||
{
|
||||
input += 5;
|
||||
result += '"';
|
||||
}
|
||||
else if (input.compareIgnoreCaseUpTo (CharPointer_ASCII ("apos;"), 5) == 0)
|
||||
{
|
||||
input += 5;
|
||||
result += '\'';
|
||||
}
|
||||
else if (input.compareIgnoreCaseUpTo (CharPointer_ASCII ("lt;"), 3) == 0)
|
||||
{
|
||||
input += 3;
|
||||
result += '<';
|
||||
}
|
||||
else if (input.compareIgnoreCaseUpTo (CharPointer_ASCII ("gt;"), 3) == 0)
|
||||
{
|
||||
input += 3;
|
||||
result += '>';
|
||||
}
|
||||
else if (*input == '#')
|
||||
{
|
||||
int charCode = 0;
|
||||
++input;
|
||||
|
||||
if (*input == 'x' || *input == 'X')
|
||||
{
|
||||
++input;
|
||||
int numChars = 0;
|
||||
|
||||
while (input[0] != ';')
|
||||
{
|
||||
const int hexValue = CharacterFunctions::getHexDigitValue (input[0]);
|
||||
|
||||
if (hexValue < 0 || ++numChars > 8)
|
||||
{
|
||||
setLastError ("illegal escape sequence", true);
|
||||
break;
|
||||
}
|
||||
|
||||
charCode = (charCode << 4) | hexValue;
|
||||
++input;
|
||||
}
|
||||
|
||||
++input;
|
||||
}
|
||||
else if (input[0] >= '0' && input[0] <= '9')
|
||||
{
|
||||
int numChars = 0;
|
||||
|
||||
while (input[0] != ';')
|
||||
{
|
||||
if (++numChars > 12)
|
||||
{
|
||||
setLastError ("illegal escape sequence", true);
|
||||
break;
|
||||
}
|
||||
|
||||
charCode = charCode * 10 + ((int) input[0] - '0');
|
||||
++input;
|
||||
}
|
||||
|
||||
++input;
|
||||
}
|
||||
else
|
||||
{
|
||||
setLastError ("illegal escape sequence", true);
|
||||
result += '&';
|
||||
return;
|
||||
}
|
||||
|
||||
result << (juce_wchar) charCode;
|
||||
}
|
||||
else
|
||||
{
|
||||
const String::CharPointerType entityNameStart (input);
|
||||
const int closingSemiColon = input.indexOf ((juce_wchar) ';');
|
||||
|
||||
if (closingSemiColon < 0)
|
||||
{
|
||||
outOfData = true;
|
||||
result += '&';
|
||||
}
|
||||
else
|
||||
{
|
||||
input += closingSemiColon + 1;
|
||||
|
||||
result += expandExternalEntity (String (entityNameStart, (size_t) closingSemiColon));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String XmlDocument::expandEntity (const String& ent)
|
||||
{
|
||||
if (ent.equalsIgnoreCase ("amp")) return String::charToString ('&');
|
||||
if (ent.equalsIgnoreCase ("quot")) return String::charToString ('"');
|
||||
if (ent.equalsIgnoreCase ("apos")) return String::charToString ('\'');
|
||||
if (ent.equalsIgnoreCase ("lt")) return String::charToString ('<');
|
||||
if (ent.equalsIgnoreCase ("gt")) return String::charToString ('>');
|
||||
|
||||
if (ent[0] == '#')
|
||||
{
|
||||
const juce_wchar char1 = ent[1];
|
||||
|
||||
if (char1 == 'x' || char1 == 'X')
|
||||
return String::charToString (static_cast <juce_wchar> (ent.substring (2).getHexValue32()));
|
||||
|
||||
if (char1 >= '0' && char1 <= '9')
|
||||
return String::charToString (static_cast <juce_wchar> (ent.substring (1).getIntValue()));
|
||||
|
||||
setLastError ("illegal escape sequence", false);
|
||||
return String::charToString ('&');
|
||||
}
|
||||
|
||||
return expandExternalEntity (ent);
|
||||
}
|
||||
|
||||
String XmlDocument::expandExternalEntity (const String& entity)
|
||||
{
|
||||
if (needToLoadDTD)
|
||||
{
|
||||
if (dtdText.isNotEmpty())
|
||||
{
|
||||
dtdText = dtdText.trimCharactersAtEnd (">");
|
||||
tokenisedDTD.addTokens (dtdText, true);
|
||||
|
||||
if (tokenisedDTD [tokenisedDTD.size() - 2].equalsIgnoreCase ("system")
|
||||
&& tokenisedDTD [tokenisedDTD.size() - 1].isQuotedString())
|
||||
{
|
||||
const String fn (tokenisedDTD [tokenisedDTD.size() - 1]);
|
||||
|
||||
tokenisedDTD.clear();
|
||||
tokenisedDTD.addTokens (getFileContents (fn), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
tokenisedDTD.clear();
|
||||
const int openBracket = dtdText.indexOfChar ('[');
|
||||
|
||||
if (openBracket > 0)
|
||||
{
|
||||
const int closeBracket = dtdText.lastIndexOfChar (']');
|
||||
|
||||
if (closeBracket > openBracket)
|
||||
tokenisedDTD.addTokens (dtdText.substring (openBracket + 1,
|
||||
closeBracket), true);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = tokenisedDTD.size(); --i >= 0;)
|
||||
{
|
||||
if (tokenisedDTD[i].startsWithChar ('%')
|
||||
&& tokenisedDTD[i].endsWithChar (';'))
|
||||
{
|
||||
const String parsed (getParameterEntity (tokenisedDTD[i].substring (1, tokenisedDTD[i].length() - 1)));
|
||||
StringArray newToks;
|
||||
newToks.addTokens (parsed, true);
|
||||
|
||||
tokenisedDTD.remove (i);
|
||||
|
||||
for (int j = newToks.size(); --j >= 0;)
|
||||
tokenisedDTD.insert (i, newToks[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
needToLoadDTD = false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < tokenisedDTD.size(); ++i)
|
||||
{
|
||||
if (tokenisedDTD[i] == entity)
|
||||
{
|
||||
if (tokenisedDTD[i - 1].equalsIgnoreCase ("<!entity"))
|
||||
{
|
||||
String ent (tokenisedDTD [i + 1].trimCharactersAtEnd (">").trim().unquoted());
|
||||
|
||||
// check for sub-entities..
|
||||
int ampersand = ent.indexOfChar ('&');
|
||||
|
||||
while (ampersand >= 0)
|
||||
{
|
||||
const int semiColon = ent.indexOf (i + 1, ";");
|
||||
|
||||
if (semiColon < 0)
|
||||
{
|
||||
setLastError ("entity without terminating semi-colon", false);
|
||||
break;
|
||||
}
|
||||
|
||||
const String resolved (expandEntity (ent.substring (i + 1, semiColon)));
|
||||
|
||||
ent = ent.substring (0, ampersand)
|
||||
+ resolved
|
||||
+ ent.substring (semiColon + 1);
|
||||
|
||||
ampersand = ent.indexOfChar (semiColon + 1, '&');
|
||||
}
|
||||
|
||||
return ent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setLastError ("unknown entity", true);
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
String XmlDocument::getParameterEntity (const String& entity)
|
||||
{
|
||||
for (int i = 0; i < tokenisedDTD.size(); ++i)
|
||||
{
|
||||
if (tokenisedDTD[i] == entity
|
||||
&& tokenisedDTD [i - 1] == "%"
|
||||
&& tokenisedDTD [i - 2].equalsIgnoreCase ("<!entity"))
|
||||
{
|
||||
const String ent (tokenisedDTD [i + 1].trimCharactersAtEnd (">"));
|
||||
|
||||
if (ent.equalsIgnoreCase ("system"))
|
||||
return getFileContents (tokenisedDTD [i + 2].trimCharactersAtEnd (">"));
|
||||
|
||||
return ent.trim().unquoted();
|
||||
}
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the juce_core module of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with
|
||||
or without fee is hereby granted, provided that the above copyright notice and this
|
||||
permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
|
||||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
|
||||
NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
||||
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
|
||||
IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
NOTE! This permissive ISC license applies ONLY to files within the juce_core module!
|
||||
All other JUCE modules are covered by a dual GPL/commercial license, so if you are
|
||||
using any other modules, be sure to check that you also comply with their license.
|
||||
|
||||
For more details, visit www.juce.com
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_XMLDOCUMENT_H_INCLUDED
|
||||
#define JUCE_XMLDOCUMENT_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Parses a text-based XML document and creates an XmlElement object from it.
|
||||
|
||||
The parser will parse DTDs to load external entities but won't
|
||||
check the document for validity against the DTD.
|
||||
|
||||
e.g.
|
||||
@code
|
||||
|
||||
XmlDocument myDocument (File ("myfile.xml"));
|
||||
ScopedPointer<XmlElement> mainElement (myDocument.getDocumentElement());
|
||||
|
||||
if (mainElement == nullptr)
|
||||
{
|
||||
String error = myDocument.getLastParseError();
|
||||
}
|
||||
else
|
||||
{
|
||||
..use the element
|
||||
}
|
||||
|
||||
@endcode
|
||||
|
||||
Or you can use the static helper methods for quick parsing..
|
||||
|
||||
@code
|
||||
ScopedPointer<XmlElement> xml (XmlDocument::parse (myXmlFile));
|
||||
|
||||
if (xml != nullptr && xml->hasTagName ("foobar"))
|
||||
{
|
||||
...etc
|
||||
@endcode
|
||||
|
||||
@see XmlElement
|
||||
*/
|
||||
class JUCE_API XmlDocument
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an XmlDocument from the xml text.
|
||||
The text doesn't actually get parsed until the getDocumentElement() method is called.
|
||||
*/
|
||||
XmlDocument (const String& documentText);
|
||||
|
||||
/** Creates an XmlDocument from a file.
|
||||
The text doesn't actually get parsed until the getDocumentElement() method is called.
|
||||
*/
|
||||
XmlDocument (const File& file);
|
||||
|
||||
/** Destructor. */
|
||||
~XmlDocument();
|
||||
|
||||
//==============================================================================
|
||||
/** Creates an XmlElement object to represent the main document node.
|
||||
|
||||
This method will do the actual parsing of the text, and if there's a
|
||||
parse error, it may returns nullptr (and you can find out the error using
|
||||
the getLastParseError() method).
|
||||
|
||||
See also the parse() methods, which provide a shorthand way to quickly
|
||||
parse a file or string.
|
||||
|
||||
@param onlyReadOuterDocumentElement if true, the parser will only read the
|
||||
first section of the file, and will only
|
||||
return the outer document element - this
|
||||
allows quick checking of large files to
|
||||
see if they contain the correct type of
|
||||
tag, without having to parse the entire file
|
||||
@returns a new XmlElement which the caller will need to delete, or null if
|
||||
there was an error.
|
||||
@see getLastParseError
|
||||
*/
|
||||
XmlElement* getDocumentElement (bool onlyReadOuterDocumentElement = false);
|
||||
|
||||
/** Returns the parsing error that occurred the last time getDocumentElement was called.
|
||||
|
||||
@returns the error, or an empty string if there was no error.
|
||||
*/
|
||||
const String& getLastParseError() const noexcept;
|
||||
|
||||
/** Sets an input source object to use for parsing documents that reference external entities.
|
||||
|
||||
If the document has been created from a file, this probably won't be needed, but
|
||||
if you're parsing some text and there might be a DTD that references external
|
||||
files, you may need to create a custom input source that can retrieve the
|
||||
other files it needs.
|
||||
|
||||
The object that is passed-in will be deleted automatically when no longer needed.
|
||||
|
||||
@see InputSource
|
||||
*/
|
||||
void setInputSource (InputSource* newSource) noexcept;
|
||||
|
||||
/** Sets a flag to change the treatment of empty text elements.
|
||||
|
||||
If this is true (the default state), then any text elements that contain only
|
||||
whitespace characters will be ingored during parsing. If you need to catch
|
||||
whitespace-only text, then you should set this to false before calling the
|
||||
getDocumentElement() method.
|
||||
*/
|
||||
void setEmptyTextElementsIgnored (bool shouldBeIgnored) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** A handy static method that parses a file.
|
||||
This is a shortcut for creating an XmlDocument object and calling getDocumentElement() on it.
|
||||
@returns a new XmlElement which the caller will need to delete, or null if there was an error.
|
||||
*/
|
||||
static XmlElement* parse (const File& file);
|
||||
|
||||
/** A handy static method that parses some XML data.
|
||||
This is a shortcut for creating an XmlDocument object and calling getDocumentElement() on it.
|
||||
@returns a new XmlElement which the caller will need to delete, or null if there was an error.
|
||||
*/
|
||||
static XmlElement* parse (const String& xmlData);
|
||||
|
||||
|
||||
//==============================================================================
|
||||
private:
|
||||
String originalText;
|
||||
String::CharPointerType input;
|
||||
bool outOfData, errorOccurred;
|
||||
|
||||
String lastError, dtdText;
|
||||
StringArray tokenisedDTD;
|
||||
bool needToLoadDTD, ignoreEmptyTextElements;
|
||||
ScopedPointer<InputSource> inputSource;
|
||||
|
||||
XmlElement* parseDocumentElement (String::CharPointerType, bool outer);
|
||||
void setLastError (const String&, bool carryOn);
|
||||
bool parseHeader();
|
||||
bool parseDTD();
|
||||
void skipNextWhiteSpace();
|
||||
juce_wchar readNextChar() noexcept;
|
||||
XmlElement* readNextElement (bool alsoParseSubElements);
|
||||
void readChildElements (XmlElement&);
|
||||
void readQuotedString (String&);
|
||||
void readEntity (String&);
|
||||
|
||||
String getFileContents (const String&) const;
|
||||
String expandEntity (const String&);
|
||||
String expandExternalEntity (const String&);
|
||||
String getParameterEntity (const String&);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (XmlDocument)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_XMLDOCUMENT_H_INCLUDED
|
||||
@@ -0,0 +1,888 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the juce_core module of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with
|
||||
or without fee is hereby granted, provided that the above copyright notice and this
|
||||
permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
|
||||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
|
||||
NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
||||
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
|
||||
IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
NOTE! This permissive ISC license applies ONLY to files within the juce_core module!
|
||||
All other JUCE modules are covered by a dual GPL/commercial license, so if you are
|
||||
using any other modules, be sure to check that you also comply with their license.
|
||||
|
||||
For more details, visit www.juce.com
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
XmlElement::XmlAttributeNode::XmlAttributeNode (const XmlAttributeNode& other) noexcept
|
||||
: name (other.name),
|
||||
value (other.value)
|
||||
{
|
||||
}
|
||||
|
||||
XmlElement::XmlAttributeNode::XmlAttributeNode (const Identifier& n, const String& v) noexcept
|
||||
: name (n), value (v)
|
||||
{
|
||||
#if JUCE_DEBUG
|
||||
// this checks whether the attribute name string contains any illegal characters..
|
||||
for (String::CharPointerType t (name.getCharPointer()); ! t.isEmpty(); ++t)
|
||||
jassert (t.isLetterOrDigit() || *t == '_' || *t == '-' || *t == ':');
|
||||
#endif
|
||||
}
|
||||
|
||||
XmlElement::XmlAttributeNode::XmlAttributeNode (String::CharPointerType nameStart, String::CharPointerType nameEnd)
|
||||
: name (nameStart, nameEnd)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static void sanityCheckTagName (const String& tag)
|
||||
{
|
||||
(void) tag;
|
||||
|
||||
// the tag name mustn't be empty, or it'll look like a text element!
|
||||
jassert (tag.containsNonWhitespaceChars());
|
||||
|
||||
// The tag can't contain spaces or other characters that would create invalid XML!
|
||||
jassert (! tag.containsAnyOf (" <>/&(){}"));
|
||||
}
|
||||
|
||||
XmlElement::XmlElement (const String& tag)
|
||||
: tagName (StringPool::getGlobalPool().getPooledString (tag))
|
||||
{
|
||||
sanityCheckTagName (tagName);
|
||||
}
|
||||
|
||||
XmlElement::XmlElement (const char* tag)
|
||||
: tagName (StringPool::getGlobalPool().getPooledString (tag))
|
||||
{
|
||||
sanityCheckTagName (tagName);
|
||||
}
|
||||
|
||||
XmlElement::XmlElement (StringRef tag)
|
||||
: tagName (StringPool::getGlobalPool().getPooledString (tag))
|
||||
{
|
||||
sanityCheckTagName (tagName);
|
||||
}
|
||||
|
||||
XmlElement::XmlElement (const Identifier& tag)
|
||||
: tagName (tag.toString())
|
||||
{
|
||||
sanityCheckTagName (tagName);
|
||||
}
|
||||
|
||||
XmlElement::XmlElement (String::CharPointerType tagNameStart, String::CharPointerType tagNameEnd)
|
||||
: tagName (StringPool::getGlobalPool().getPooledString (tagNameStart, tagNameEnd))
|
||||
{
|
||||
sanityCheckTagName (tagName);
|
||||
}
|
||||
|
||||
XmlElement::XmlElement (int /*dummy*/) noexcept
|
||||
{
|
||||
}
|
||||
|
||||
XmlElement::XmlElement (const XmlElement& other)
|
||||
: tagName (other.tagName)
|
||||
{
|
||||
copyChildrenAndAttributesFrom (other);
|
||||
}
|
||||
|
||||
XmlElement& XmlElement::operator= (const XmlElement& other)
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
removeAllAttributes();
|
||||
deleteAllChildElements();
|
||||
tagName = other.tagName;
|
||||
copyChildrenAndAttributesFrom (other);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS
|
||||
XmlElement::XmlElement (XmlElement&& other) noexcept
|
||||
: nextListItem (static_cast<LinkedListPointer<XmlElement>&&> (other.nextListItem)),
|
||||
firstChildElement (static_cast<LinkedListPointer<XmlElement>&&> (other.firstChildElement)),
|
||||
attributes (static_cast<LinkedListPointer<XmlAttributeNode>&&> (other.attributes)),
|
||||
tagName (static_cast<String&&> (other.tagName))
|
||||
{
|
||||
}
|
||||
|
||||
XmlElement& XmlElement::operator= (XmlElement&& other) noexcept
|
||||
{
|
||||
jassert (this != &other); // hopefully the compiler should make this situation impossible!
|
||||
|
||||
removeAllAttributes();
|
||||
deleteAllChildElements();
|
||||
|
||||
nextListItem = static_cast<LinkedListPointer<XmlElement>&&> (other.nextListItem);
|
||||
firstChildElement = static_cast<LinkedListPointer<XmlElement>&&> (other.firstChildElement);
|
||||
attributes = static_cast<LinkedListPointer<XmlAttributeNode>&&> (other.attributes);
|
||||
tagName = static_cast<String&&> (other.tagName);
|
||||
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
|
||||
void XmlElement::copyChildrenAndAttributesFrom (const XmlElement& other)
|
||||
{
|
||||
jassert (firstChildElement.get() == nullptr);
|
||||
firstChildElement.addCopyOfList (other.firstChildElement);
|
||||
|
||||
jassert (attributes.get() == nullptr);
|
||||
attributes.addCopyOfList (other.attributes);
|
||||
}
|
||||
|
||||
XmlElement::~XmlElement() noexcept
|
||||
{
|
||||
firstChildElement.deleteAll();
|
||||
attributes.deleteAll();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
namespace XmlOutputFunctions
|
||||
{
|
||||
#if 0 // (These functions are just used to generate the lookup table used below)
|
||||
bool isLegalXmlCharSlow (const juce_wchar character) noexcept
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
void generateLegalCharLookupTable()
|
||||
{
|
||||
uint8 n[32] = { 0 };
|
||||
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);
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool isLegalXmlChar (const uint32 c) noexcept
|
||||
{
|
||||
static const unsigned char legalChars[] = { 0, 0, 0, 0, 187, 255, 255, 175, 255,
|
||||
255, 255, 191, 254, 255, 255, 127 };
|
||||
return c < sizeof (legalChars) * 8
|
||||
&& (legalChars [c >> 3] & (1 << (c & 7))) != 0;
|
||||
}
|
||||
|
||||
static void escapeIllegalXmlChars (OutputStream& outputStream, const String& text, const bool changeNewLines)
|
||||
{
|
||||
String::CharPointerType t (text.getCharPointer());
|
||||
|
||||
for (;;)
|
||||
{
|
||||
const uint32 character = (uint32) t.getAndAdvance();
|
||||
|
||||
if (character == 0)
|
||||
break;
|
||||
|
||||
if (isLegalXmlChar (character))
|
||||
{
|
||||
outputStream << (char) character;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (character)
|
||||
{
|
||||
case '&': outputStream << "&"; break;
|
||||
case '"': outputStream << """; break;
|
||||
case '>': outputStream << ">"; break;
|
||||
case '<': outputStream << "<"; break;
|
||||
|
||||
case '\n':
|
||||
case '\r':
|
||||
if (! changeNewLines)
|
||||
{
|
||||
outputStream << (char) character;
|
||||
break;
|
||||
}
|
||||
// Note: deliberate fall-through here!
|
||||
default:
|
||||
outputStream << "&#" << ((int) character) << ';';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void writeSpaces (OutputStream& out, const size_t numSpaces)
|
||||
{
|
||||
out.writeRepeatedByte (' ', numSpaces);
|
||||
}
|
||||
}
|
||||
|
||||
void XmlElement::writeElementAsText (OutputStream& outputStream,
|
||||
const int indentationLevel,
|
||||
const int lineWrapLength) const
|
||||
{
|
||||
using namespace XmlOutputFunctions;
|
||||
|
||||
if (indentationLevel >= 0)
|
||||
writeSpaces (outputStream, (size_t) indentationLevel);
|
||||
|
||||
if (! isTextElement())
|
||||
{
|
||||
outputStream.writeByte ('<');
|
||||
outputStream << tagName;
|
||||
|
||||
{
|
||||
const size_t attIndent = (size_t) (indentationLevel + tagName.length() + 1);
|
||||
int lineLen = 0;
|
||||
|
||||
for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem)
|
||||
{
|
||||
if (lineLen > lineWrapLength && indentationLevel >= 0)
|
||||
{
|
||||
outputStream << newLine;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (firstChildElement != nullptr)
|
||||
{
|
||||
outputStream.writeByte ('>');
|
||||
|
||||
bool lastWasTextNode = false;
|
||||
|
||||
for (XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem)
|
||||
{
|
||||
if (child->isTextElement())
|
||||
{
|
||||
escapeIllegalXmlChars (outputStream, child->getText(), false);
|
||||
lastWasTextNode = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (indentationLevel >= 0 && ! lastWasTextNode)
|
||||
outputStream << newLine;
|
||||
|
||||
child->writeElementAsText (outputStream,
|
||||
lastWasTextNode ? 0 : (indentationLevel + (indentationLevel >= 0 ? 2 : 0)), lineWrapLength);
|
||||
lastWasTextNode = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (indentationLevel >= 0 && ! lastWasTextNode)
|
||||
{
|
||||
outputStream << newLine;
|
||||
writeSpaces (outputStream, (size_t) indentationLevel);
|
||||
}
|
||||
|
||||
outputStream.write ("</", 2);
|
||||
outputStream << tagName;
|
||||
outputStream.writeByte ('>');
|
||||
}
|
||||
else
|
||||
{
|
||||
outputStream.write ("/>", 2);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
escapeIllegalXmlChars (outputStream, getText(), false);
|
||||
}
|
||||
}
|
||||
|
||||
String XmlElement::createDocument (StringRef dtdToUse,
|
||||
const bool allOnOneLine,
|
||||
const bool includeXmlHeader,
|
||||
StringRef encodingType,
|
||||
const int lineWrapLength) const
|
||||
{
|
||||
MemoryOutputStream mem (2048);
|
||||
writeToStream (mem, dtdToUse, allOnOneLine, includeXmlHeader, encodingType, lineWrapLength);
|
||||
|
||||
return mem.toUTF8();
|
||||
}
|
||||
|
||||
void XmlElement::writeToStream (OutputStream& output,
|
||||
StringRef dtdToUse,
|
||||
const bool allOnOneLine,
|
||||
const bool includeXmlHeader,
|
||||
StringRef encodingType,
|
||||
const int lineWrapLength) const
|
||||
{
|
||||
using namespace XmlOutputFunctions;
|
||||
|
||||
if (includeXmlHeader)
|
||||
{
|
||||
output << "<?xml version=\"1.0\" encoding=\"" << encodingType << "\"?>";
|
||||
|
||||
if (allOnOneLine)
|
||||
output.writeByte (' ');
|
||||
else
|
||||
output << newLine << newLine;
|
||||
}
|
||||
|
||||
if (dtdToUse.isNotEmpty())
|
||||
{
|
||||
output << dtdToUse;
|
||||
|
||||
if (allOnOneLine)
|
||||
output.writeByte (' ');
|
||||
else
|
||||
output << newLine;
|
||||
}
|
||||
|
||||
writeElementAsText (output, allOnOneLine ? -1 : 0, lineWrapLength);
|
||||
|
||||
if (! allOnOneLine)
|
||||
output << newLine;
|
||||
}
|
||||
|
||||
bool XmlElement::writeToFile (const File& file,
|
||||
StringRef dtdToUse,
|
||||
StringRef encodingType,
|
||||
const int lineWrapLength) const
|
||||
{
|
||||
TemporaryFile tempFile (file);
|
||||
|
||||
{
|
||||
FileOutputStream out (tempFile.getFile());
|
||||
|
||||
if (! out.openedOk())
|
||||
return false;
|
||||
|
||||
writeToStream (out, dtdToUse, false, true, encodingType, lineWrapLength);
|
||||
}
|
||||
|
||||
return tempFile.overwriteTargetFileWithTemporary();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool XmlElement::hasTagName (StringRef possibleTagName) const noexcept
|
||||
{
|
||||
const bool matches = tagName.equalsIgnoreCase (possibleTagName);
|
||||
|
||||
// XML tags should be case-sensitive, so although this method allows a
|
||||
// case-insensitive match to pass, you should try to avoid this.
|
||||
jassert ((! matches) || tagName == possibleTagName);
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
String XmlElement::getNamespace() const
|
||||
{
|
||||
return tagName.upToFirstOccurrenceOf (":", false, false);
|
||||
}
|
||||
|
||||
String XmlElement::getTagNameWithoutNamespace() const
|
||||
{
|
||||
return tagName.fromLastOccurrenceOf (":", false, false);
|
||||
}
|
||||
|
||||
bool XmlElement::hasTagNameIgnoringNamespace (StringRef possibleTagName) const
|
||||
{
|
||||
return hasTagName (possibleTagName) || getTagNameWithoutNamespace() == possibleTagName;
|
||||
}
|
||||
|
||||
XmlElement* XmlElement::getNextElementWithTagName (StringRef requiredTagName) const
|
||||
{
|
||||
XmlElement* e = nextListItem;
|
||||
|
||||
while (e != nullptr && ! e->hasTagName (requiredTagName))
|
||||
e = e->nextListItem;
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
int XmlElement::getNumAttributes() const noexcept
|
||||
{
|
||||
return attributes.size();
|
||||
}
|
||||
|
||||
const String& XmlElement::getAttributeName (const int index) const noexcept
|
||||
{
|
||||
if (const XmlAttributeNode* const att = attributes [index])
|
||||
return att->name.toString();
|
||||
|
||||
return String::empty;
|
||||
}
|
||||
|
||||
const String& XmlElement::getAttributeValue (const int index) const noexcept
|
||||
{
|
||||
if (const XmlAttributeNode* const att = attributes [index])
|
||||
return att->value;
|
||||
|
||||
return String::empty;
|
||||
}
|
||||
|
||||
XmlElement::XmlAttributeNode* XmlElement::getAttribute (StringRef attributeName) const noexcept
|
||||
{
|
||||
for (XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem)
|
||||
if (att->name == attributeName)
|
||||
return att;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool XmlElement::hasAttribute (StringRef attributeName) const noexcept
|
||||
{
|
||||
return getAttribute (attributeName) != nullptr;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
const String& XmlElement::getStringAttribute (StringRef attributeName) const noexcept
|
||||
{
|
||||
if (const XmlAttributeNode* att = getAttribute (attributeName))
|
||||
return att->value;
|
||||
|
||||
return String::empty;
|
||||
}
|
||||
|
||||
String XmlElement::getStringAttribute (StringRef attributeName, const String& defaultReturnValue) const
|
||||
{
|
||||
if (const XmlAttributeNode* att = getAttribute (attributeName))
|
||||
return att->value;
|
||||
|
||||
return defaultReturnValue;
|
||||
}
|
||||
|
||||
int XmlElement::getIntAttribute (StringRef attributeName, const int defaultReturnValue) const
|
||||
{
|
||||
if (const XmlAttributeNode* att = getAttribute (attributeName))
|
||||
return att->value.getIntValue();
|
||||
|
||||
return defaultReturnValue;
|
||||
}
|
||||
|
||||
double XmlElement::getDoubleAttribute (StringRef attributeName, const double defaultReturnValue) const
|
||||
{
|
||||
if (const XmlAttributeNode* att = getAttribute (attributeName))
|
||||
return att->value.getDoubleValue();
|
||||
|
||||
return defaultReturnValue;
|
||||
}
|
||||
|
||||
bool XmlElement::getBoolAttribute (StringRef attributeName, const bool defaultReturnValue) const
|
||||
{
|
||||
if (const XmlAttributeNode* att = getAttribute (attributeName))
|
||||
{
|
||||
const juce_wchar firstChar = *(att->value.getCharPointer().findEndOfWhitespace());
|
||||
|
||||
return firstChar == '1'
|
||||
|| firstChar == 't'
|
||||
|| firstChar == 'y'
|
||||
|| firstChar == 'T'
|
||||
|| firstChar == 'Y';
|
||||
}
|
||||
|
||||
return defaultReturnValue;
|
||||
}
|
||||
|
||||
bool XmlElement::compareAttribute (StringRef attributeName,
|
||||
StringRef stringToCompareAgainst,
|
||||
const bool ignoreCase) const noexcept
|
||||
{
|
||||
if (const XmlAttributeNode* att = getAttribute (attributeName))
|
||||
return ignoreCase ? att->value.equalsIgnoreCase (stringToCompareAgainst)
|
||||
: att->value == stringToCompareAgainst;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void XmlElement::setAttribute (const Identifier& attributeName, const String& value)
|
||||
{
|
||||
if (attributes == nullptr)
|
||||
{
|
||||
attributes = new XmlAttributeNode (attributeName, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (XmlAttributeNode* att = attributes; ; att = att->nextListItem)
|
||||
{
|
||||
if (att->name == attributeName)
|
||||
{
|
||||
att->value = value;
|
||||
break;
|
||||
}
|
||||
|
||||
if (att->nextListItem == nullptr)
|
||||
{
|
||||
att->nextListItem = new XmlAttributeNode (attributeName, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void XmlElement::setAttribute (const Identifier& attributeName, const int number)
|
||||
{
|
||||
setAttribute (attributeName, String (number));
|
||||
}
|
||||
|
||||
void XmlElement::setAttribute (const Identifier& attributeName, const double number)
|
||||
{
|
||||
setAttribute (attributeName, String (number, 20));
|
||||
}
|
||||
|
||||
void XmlElement::removeAttribute (const Identifier& attributeName) noexcept
|
||||
{
|
||||
for (LinkedListPointer<XmlAttributeNode>* att = &attributes;
|
||||
att->get() != nullptr;
|
||||
att = &(att->get()->nextListItem))
|
||||
{
|
||||
if (att->get()->name == attributeName)
|
||||
{
|
||||
delete att->removeNext();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void XmlElement::removeAllAttributes() noexcept
|
||||
{
|
||||
attributes.deleteAll();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
int XmlElement::getNumChildElements() const noexcept
|
||||
{
|
||||
return firstChildElement.size();
|
||||
}
|
||||
|
||||
XmlElement* XmlElement::getChildElement (const int index) const noexcept
|
||||
{
|
||||
return firstChildElement [index].get();
|
||||
}
|
||||
|
||||
XmlElement* XmlElement::getChildByName (StringRef childName) const noexcept
|
||||
{
|
||||
jassert (! childName.isEmpty());
|
||||
|
||||
for (XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem)
|
||||
if (child->hasTagName (childName))
|
||||
return child;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
XmlElement* XmlElement::getChildByAttribute (StringRef attributeName, StringRef attributeValue) const noexcept
|
||||
{
|
||||
jassert (! attributeName.isEmpty());
|
||||
|
||||
for (XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem)
|
||||
if (child->compareAttribute (attributeName, attributeValue))
|
||||
return child;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void XmlElement::addChildElement (XmlElement* const newNode) noexcept
|
||||
{
|
||||
if (newNode != nullptr)
|
||||
{
|
||||
// The element being added must not be a child of another node!
|
||||
jassert (newNode->nextListItem == nullptr);
|
||||
|
||||
firstChildElement.append (newNode);
|
||||
}
|
||||
}
|
||||
|
||||
void XmlElement::insertChildElement (XmlElement* const newNode, int indexToInsertAt) noexcept
|
||||
{
|
||||
if (newNode != nullptr)
|
||||
{
|
||||
// The element being added must not be a child of another node!
|
||||
jassert (newNode->nextListItem == nullptr);
|
||||
|
||||
firstChildElement.insertAtIndex (indexToInsertAt, newNode);
|
||||
}
|
||||
}
|
||||
|
||||
void XmlElement::prependChildElement (XmlElement* newNode) noexcept
|
||||
{
|
||||
if (newNode != nullptr)
|
||||
{
|
||||
// The element being added must not be a child of another node!
|
||||
jassert (newNode->nextListItem == nullptr);
|
||||
|
||||
firstChildElement.insertNext (newNode);
|
||||
}
|
||||
}
|
||||
|
||||
XmlElement* XmlElement::createNewChildElement (StringRef childTagName)
|
||||
{
|
||||
XmlElement* const newElement = new XmlElement (childTagName);
|
||||
addChildElement (newElement);
|
||||
return newElement;
|
||||
}
|
||||
|
||||
bool XmlElement::replaceChildElement (XmlElement* const currentChildElement,
|
||||
XmlElement* const newNode) noexcept
|
||||
{
|
||||
if (newNode != nullptr)
|
||||
{
|
||||
if (LinkedListPointer<XmlElement>* const p = firstChildElement.findPointerTo (currentChildElement))
|
||||
{
|
||||
if (currentChildElement != newNode)
|
||||
delete p->replaceNext (newNode);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void XmlElement::removeChildElement (XmlElement* const childToRemove,
|
||||
const bool shouldDeleteTheChild) noexcept
|
||||
{
|
||||
if (childToRemove != nullptr)
|
||||
{
|
||||
firstChildElement.remove (childToRemove);
|
||||
|
||||
if (shouldDeleteTheChild)
|
||||
delete childToRemove;
|
||||
}
|
||||
}
|
||||
|
||||
bool XmlElement::isEquivalentTo (const XmlElement* const other,
|
||||
const bool ignoreOrderOfAttributes) const noexcept
|
||||
{
|
||||
if (this != other)
|
||||
{
|
||||
if (other == nullptr || tagName != other->tagName)
|
||||
return false;
|
||||
|
||||
if (ignoreOrderOfAttributes)
|
||||
{
|
||||
int totalAtts = 0;
|
||||
|
||||
for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem)
|
||||
{
|
||||
if (! other->compareAttribute (att->name, att->value))
|
||||
return false;
|
||||
|
||||
++totalAtts;
|
||||
}
|
||||
|
||||
if (totalAtts != other->getNumAttributes())
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
const XmlAttributeNode* thisAtt = attributes;
|
||||
const XmlAttributeNode* otherAtt = other->attributes;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (thisAtt == nullptr || otherAtt == nullptr)
|
||||
{
|
||||
if (thisAtt == otherAtt) // both nullptr, so it's a match
|
||||
break;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (thisAtt->name != otherAtt->name
|
||||
|| thisAtt->value != otherAtt->value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
thisAtt = thisAtt->nextListItem;
|
||||
otherAtt = otherAtt->nextListItem;
|
||||
}
|
||||
}
|
||||
|
||||
const XmlElement* thisChild = firstChildElement;
|
||||
const XmlElement* otherChild = other->firstChildElement;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (thisChild == nullptr || otherChild == nullptr)
|
||||
{
|
||||
if (thisChild == otherChild) // both 0, so it's a match
|
||||
break;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! thisChild->isEquivalentTo (otherChild, ignoreOrderOfAttributes))
|
||||
return false;
|
||||
|
||||
thisChild = thisChild->nextListItem;
|
||||
otherChild = otherChild->nextListItem;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void XmlElement::deleteAllChildElements() noexcept
|
||||
{
|
||||
firstChildElement.deleteAll();
|
||||
}
|
||||
|
||||
void XmlElement::deleteAllChildElementsWithTagName (StringRef name) noexcept
|
||||
{
|
||||
for (XmlElement* child = firstChildElement; child != nullptr;)
|
||||
{
|
||||
XmlElement* const nextChild = child->nextListItem;
|
||||
|
||||
if (child->hasTagName (name))
|
||||
removeChildElement (child, true);
|
||||
|
||||
child = nextChild;
|
||||
}
|
||||
}
|
||||
|
||||
bool XmlElement::containsChildElement (const XmlElement* const possibleChild) const noexcept
|
||||
{
|
||||
return firstChildElement.contains (possibleChild);
|
||||
}
|
||||
|
||||
XmlElement* XmlElement::findParentElementOf (const XmlElement* const elementToLookFor) noexcept
|
||||
{
|
||||
if (this == elementToLookFor || elementToLookFor == nullptr)
|
||||
return nullptr;
|
||||
|
||||
for (XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem)
|
||||
{
|
||||
if (elementToLookFor == child)
|
||||
return this;
|
||||
|
||||
if (XmlElement* const found = child->findParentElementOf (elementToLookFor))
|
||||
return found;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void XmlElement::getChildElementsAsArray (XmlElement** elems) const noexcept
|
||||
{
|
||||
firstChildElement.copyToArray (elems);
|
||||
}
|
||||
|
||||
void XmlElement::reorderChildElements (XmlElement** const elems, const int num) noexcept
|
||||
{
|
||||
XmlElement* e = firstChildElement = elems[0];
|
||||
|
||||
for (int i = 1; i < num; ++i)
|
||||
{
|
||||
e->nextListItem = elems[i];
|
||||
e = e->nextListItem;
|
||||
}
|
||||
|
||||
e->nextListItem = nullptr;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool XmlElement::isTextElement() const noexcept
|
||||
{
|
||||
return tagName.isEmpty();
|
||||
}
|
||||
|
||||
static const String juce_xmltextContentAttributeName ("text");
|
||||
|
||||
const String& XmlElement::getText() const noexcept
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (isTextElement())
|
||||
setAttribute (juce_xmltextContentAttributeName, newText);
|
||||
else
|
||||
jassertfalse; // you can only change the text in a text element, not a normal one.
|
||||
}
|
||||
|
||||
String XmlElement::getAllSubText() const
|
||||
{
|
||||
if (isTextElement())
|
||||
return getText();
|
||||
|
||||
if (getNumChildElements() == 1)
|
||||
return firstChildElement.get()->getAllSubText();
|
||||
|
||||
MemoryOutputStream mem (1024);
|
||||
|
||||
for (const XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem)
|
||||
mem << child->getAllSubText();
|
||||
|
||||
return mem.toUTF8();
|
||||
}
|
||||
|
||||
String XmlElement::getChildElementAllSubText (StringRef childTagName, const String& defaultReturnValue) const
|
||||
{
|
||||
if (const XmlElement* const child = getChildByName (childTagName))
|
||||
return child->getAllSubText();
|
||||
|
||||
return defaultReturnValue;
|
||||
}
|
||||
|
||||
XmlElement* XmlElement::createTextElement (const String& text)
|
||||
{
|
||||
XmlElement* const e = new XmlElement ((int) 0);
|
||||
e->setAttribute (juce_xmltextContentAttributeName, text);
|
||||
return e;
|
||||
}
|
||||
|
||||
void XmlElement::addTextElement (const String& text)
|
||||
{
|
||||
addChildElement (createTextElement (text));
|
||||
}
|
||||
|
||||
void XmlElement::deleteAllTextElements() noexcept
|
||||
{
|
||||
for (XmlElement* child = firstChildElement; child != nullptr;)
|
||||
{
|
||||
XmlElement* const next = child->nextListItem;
|
||||
|
||||
if (child->isTextElement())
|
||||
removeChildElement (child, true);
|
||||
|
||||
child = next;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,770 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the juce_core module of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with
|
||||
or without fee is hereby granted, provided that the above copyright notice and this
|
||||
permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
|
||||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
|
||||
NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
||||
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
|
||||
IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
NOTE! This permissive ISC license applies ONLY to files within the juce_core module!
|
||||
All other JUCE modules are covered by a dual GPL/commercial license, so if you are
|
||||
using any other modules, be sure to check that you also comply with their license.
|
||||
|
||||
For more details, visit www.juce.com
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_XMLELEMENT_H_INCLUDED
|
||||
#define JUCE_XMLELEMENT_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** A handy macro to make it easy to iterate all the child elements in an XmlElement.
|
||||
|
||||
The parentXmlElement should be a reference to the parent XML, and the childElementVariableName
|
||||
will be the name of a pointer to each child element.
|
||||
|
||||
E.g. @code
|
||||
XmlElement* myParentXml = createSomeKindOfXmlDocument();
|
||||
|
||||
forEachXmlChildElement (*myParentXml, child)
|
||||
{
|
||||
if (child->hasTagName ("FOO"))
|
||||
doSomethingWithXmlElement (child);
|
||||
}
|
||||
|
||||
@endcode
|
||||
|
||||
@see forEachXmlChildElementWithTagName
|
||||
*/
|
||||
#define forEachXmlChildElement(parentXmlElement, childElementVariableName) \
|
||||
\
|
||||
for (juce::XmlElement* childElementVariableName = (parentXmlElement).getFirstChildElement(); \
|
||||
childElementVariableName != nullptr; \
|
||||
childElementVariableName = childElementVariableName->getNextElement())
|
||||
|
||||
/** A macro that makes it easy to iterate all the child elements of an XmlElement
|
||||
which have a specified tag.
|
||||
|
||||
This does the same job as the forEachXmlChildElement macro, but only for those
|
||||
elements that have a particular tag name.
|
||||
|
||||
The parentXmlElement should be a reference to the parent XML, and the childElementVariableName
|
||||
will be the name of a pointer to each child element. The requiredTagName is the
|
||||
tag name to match.
|
||||
|
||||
E.g. @code
|
||||
XmlElement* myParentXml = createSomeKindOfXmlDocument();
|
||||
|
||||
forEachXmlChildElementWithTagName (*myParentXml, child, "MYTAG")
|
||||
{
|
||||
// the child object is now guaranteed to be a <MYTAG> element..
|
||||
doSomethingWithMYTAGElement (child);
|
||||
}
|
||||
|
||||
@endcode
|
||||
|
||||
@see forEachXmlChildElement
|
||||
*/
|
||||
#define forEachXmlChildElementWithTagName(parentXmlElement, childElementVariableName, requiredTagName) \
|
||||
\
|
||||
for (juce::XmlElement* childElementVariableName = (parentXmlElement).getChildByName (requiredTagName); \
|
||||
childElementVariableName != nullptr; \
|
||||
childElementVariableName = childElementVariableName->getNextElementWithTagName (requiredTagName))
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Used to build a tree of elements representing an XML document.
|
||||
|
||||
An XML document can be parsed into a tree of XmlElements, each of which
|
||||
represents an XML tag structure, and which may itself contain other
|
||||
nested elements.
|
||||
|
||||
An XmlElement can also be converted back into a text document, and has
|
||||
lots of useful methods for manipulating its attributes and sub-elements,
|
||||
so XmlElements can actually be used as a handy general-purpose data
|
||||
structure.
|
||||
|
||||
Here's an example of parsing some elements: @code
|
||||
// check we're looking at the right kind of document..
|
||||
if (myElement->hasTagName ("ANIMALS"))
|
||||
{
|
||||
// now we'll iterate its sub-elements looking for 'giraffe' elements..
|
||||
forEachXmlChildElement (*myElement, e)
|
||||
{
|
||||
if (e->hasTagName ("GIRAFFE"))
|
||||
{
|
||||
// found a giraffe, so use some of its attributes..
|
||||
|
||||
String giraffeName = e->getStringAttribute ("name");
|
||||
int giraffeAge = e->getIntAttribute ("age");
|
||||
bool isFriendly = e->getBoolAttribute ("friendly");
|
||||
}
|
||||
}
|
||||
}
|
||||
@endcode
|
||||
|
||||
And here's an example of how to create an XML document from scratch: @code
|
||||
// create an outer node called "ANIMALS"
|
||||
XmlElement animalsList ("ANIMALS");
|
||||
|
||||
for (int i = 0; i < numAnimals; ++i)
|
||||
{
|
||||
// create an inner element..
|
||||
XmlElement* giraffe = new XmlElement ("GIRAFFE");
|
||||
|
||||
giraffe->setAttribute ("name", "nigel");
|
||||
giraffe->setAttribute ("age", 10);
|
||||
giraffe->setAttribute ("friendly", true);
|
||||
|
||||
// ..and add our new element to the parent node
|
||||
animalsList.addChildElement (giraffe);
|
||||
}
|
||||
|
||||
// now we can turn the whole thing into a text document..
|
||||
String myXmlDoc = animalsList.createDocument (String::empty);
|
||||
@endcode
|
||||
|
||||
@see XmlDocument
|
||||
*/
|
||||
class JUCE_API XmlElement
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an XmlElement with this tag name. */
|
||||
explicit XmlElement (const String& tagName);
|
||||
|
||||
/** Creates an XmlElement with this tag name. */
|
||||
explicit XmlElement (const char* tagName);
|
||||
|
||||
/** Creates an XmlElement with this tag name. */
|
||||
explicit XmlElement (const Identifier& tagName);
|
||||
|
||||
/** Creates an XmlElement with this tag name. */
|
||||
explicit XmlElement (StringRef tagName);
|
||||
|
||||
/** Creates an XmlElement with this tag name. */
|
||||
XmlElement (String::CharPointerType tagNameBegin, String::CharPointerType tagNameEnd);
|
||||
|
||||
/** Creates a (deep) copy of another element. */
|
||||
XmlElement (const XmlElement&);
|
||||
|
||||
/** Creates a (deep) copy of another element. */
|
||||
XmlElement& operator= (const XmlElement&);
|
||||
|
||||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS
|
||||
XmlElement (XmlElement&&) noexcept;
|
||||
XmlElement& operator= (XmlElement&&) noexcept;
|
||||
#endif
|
||||
|
||||
/** Deleting an XmlElement will also delete all of its child elements. */
|
||||
~XmlElement() noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Compares two XmlElements to see if they contain the same text and attiributes.
|
||||
|
||||
The elements are only considered equivalent if they contain the same attiributes
|
||||
with the same values, and have the same sub-nodes.
|
||||
|
||||
@param other the other element to compare to
|
||||
@param ignoreOrderOfAttributes if true, this means that two elements with the
|
||||
same attributes in a different order will be
|
||||
considered the same; if false, the attributes must
|
||||
be in the same order as well
|
||||
*/
|
||||
bool isEquivalentTo (const XmlElement* other,
|
||||
bool ignoreOrderOfAttributes) const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns an XML text document that represents this element.
|
||||
|
||||
The string returned can be parsed to recreate the same XmlElement that
|
||||
was used to create it.
|
||||
|
||||
@param dtdToUse the DTD to add to the document
|
||||
@param allOnOneLine if true, this means that the document will not contain any
|
||||
linefeeds, so it'll be smaller but not very easy to read.
|
||||
@param includeXmlHeader whether to add the "<?xml version..etc" line at the start of the
|
||||
document
|
||||
@param encodingType the character encoding format string to put into the xml
|
||||
header
|
||||
@param lineWrapLength the line length that will be used before items get placed on
|
||||
a new line. This isn't an absolute maximum length, it just
|
||||
determines how lists of attributes get broken up
|
||||
@see writeToStream, writeToFile
|
||||
*/
|
||||
String createDocument (StringRef dtdToUse,
|
||||
bool allOnOneLine = false,
|
||||
bool includeXmlHeader = true,
|
||||
StringRef encodingType = "UTF-8",
|
||||
int lineWrapLength = 60) const;
|
||||
|
||||
/** Writes the document to a stream as UTF-8.
|
||||
|
||||
@param output the stream to write to
|
||||
@param dtdToUse the DTD to add to the document
|
||||
@param allOnOneLine if true, this means that the document will not contain any
|
||||
linefeeds, so it'll be smaller but not very easy to read.
|
||||
@param includeXmlHeader whether to add the "<?xml version..etc" line at the start of the
|
||||
document
|
||||
@param encodingType the character encoding format string to put into the xml
|
||||
header
|
||||
@param lineWrapLength the line length that will be used before items get placed on
|
||||
a new line. This isn't an absolute maximum length, it just
|
||||
determines how lists of attributes get broken up
|
||||
@see writeToFile, createDocument
|
||||
*/
|
||||
void writeToStream (OutputStream& output,
|
||||
StringRef dtdToUse,
|
||||
bool allOnOneLine = false,
|
||||
bool includeXmlHeader = true,
|
||||
StringRef encodingType = "UTF-8",
|
||||
int lineWrapLength = 60) const;
|
||||
|
||||
/** Writes the element to a file as an XML document.
|
||||
|
||||
To improve safety in case something goes wrong while writing the file, this
|
||||
will actually write the document to a new temporary file in the same
|
||||
directory as the destination file, and if this succeeds, it will rename this
|
||||
new file as the destination file (overwriting any existing file that was there).
|
||||
|
||||
@param destinationFile the file to write to. If this already exists, it will be
|
||||
overwritten.
|
||||
@param dtdToUse the DTD to add to the document
|
||||
@param encodingType the character encoding format string to put into the xml
|
||||
header
|
||||
@param lineWrapLength the line length that will be used before items get placed on
|
||||
a new line. This isn't an absolute maximum length, it just
|
||||
determines how lists of attributes get broken up
|
||||
@returns true if the file is written successfully; false if something goes wrong
|
||||
in the process
|
||||
@see createDocument
|
||||
*/
|
||||
bool writeToFile (const File& destinationFile,
|
||||
StringRef dtdToUse,
|
||||
StringRef encodingType = "UTF-8",
|
||||
int lineWrapLength = 60) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns this element's tag type name.
|
||||
E.g. for an element such as \<MOOSE legs="4" antlers="2">, this would return "MOOSE".
|
||||
@see hasTagName
|
||||
*/
|
||||
const String& getTagName() const noexcept { return tagName; }
|
||||
|
||||
/** Returns the namespace portion of the tag-name, or an empty string if none is specified. */
|
||||
String getNamespace() const;
|
||||
|
||||
/** Returns the part of the tag-name that follows any namespace declaration. */
|
||||
String getTagNameWithoutNamespace() const;
|
||||
|
||||
/** Tests whether this element has a particular tag name.
|
||||
@param possibleTagName the tag name you're comparing it with
|
||||
@see getTagName
|
||||
*/
|
||||
bool hasTagName (StringRef possibleTagName) const noexcept;
|
||||
|
||||
/** Tests whether this element has a particular tag name, ignoring any XML namespace prefix.
|
||||
So a test for e.g. "xyz" will return true for "xyz" and also "foo:xyz", "bar::xyz", etc.
|
||||
@see getTagName
|
||||
*/
|
||||
bool hasTagNameIgnoringNamespace (StringRef possibleTagName) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of XML attributes this element contains.
|
||||
|
||||
E.g. for an element such as \<MOOSE legs="4" antlers="2">, this would
|
||||
return 2.
|
||||
*/
|
||||
int getNumAttributes() const noexcept;
|
||||
|
||||
/** Returns the name of one of the elements attributes.
|
||||
|
||||
E.g. for an element such as \<MOOSE legs="4" antlers="2">, then
|
||||
getAttributeName(1) would return "antlers".
|
||||
|
||||
@see getAttributeValue, getStringAttribute
|
||||
*/
|
||||
const String& getAttributeName (int attributeIndex) const noexcept;
|
||||
|
||||
/** Returns the value of one of the elements attributes.
|
||||
|
||||
E.g. for an element such as \<MOOSE legs="4" antlers="2">, then
|
||||
getAttributeName(1) would return "2".
|
||||
|
||||
@see getAttributeName, getStringAttribute
|
||||
*/
|
||||
const String& getAttributeValue (int attributeIndex) const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
// Attribute-handling methods..
|
||||
|
||||
/** Checks whether the element contains an attribute with a certain name. */
|
||||
bool hasAttribute (StringRef attributeName) const noexcept;
|
||||
|
||||
/** Returns the value of a named attribute.
|
||||
@param attributeName the name of the attribute to look up
|
||||
*/
|
||||
const String& getStringAttribute (StringRef attributeName) const noexcept;
|
||||
|
||||
/** Returns the value of a named attribute.
|
||||
@param attributeName the name of the attribute to look up
|
||||
@param defaultReturnValue a value to return if the element doesn't have an attribute
|
||||
with this name
|
||||
*/
|
||||
String getStringAttribute (StringRef attributeName, const String& defaultReturnValue) const;
|
||||
|
||||
/** Compares the value of a named attribute with a value passed-in.
|
||||
|
||||
@param attributeName the name of the attribute to look up
|
||||
@param stringToCompareAgainst the value to compare it with
|
||||
@param ignoreCase whether the comparison should be case-insensitive
|
||||
@returns true if the value of the attribute is the same as the string passed-in;
|
||||
false if it's different (or if no such attribute exists)
|
||||
*/
|
||||
bool compareAttribute (StringRef attributeName,
|
||||
StringRef stringToCompareAgainst,
|
||||
bool ignoreCase = false) const noexcept;
|
||||
|
||||
/** Returns the value of a named attribute as an integer.
|
||||
|
||||
This will try to find the attribute and convert it to an integer (using
|
||||
the String::getIntValue() method).
|
||||
|
||||
@param attributeName the name of the attribute to look up
|
||||
@param defaultReturnValue a value to return if the element doesn't have an attribute
|
||||
with this name
|
||||
@see setAttribute
|
||||
*/
|
||||
int getIntAttribute (StringRef attributeName, int defaultReturnValue = 0) const;
|
||||
|
||||
/** Returns the value of a named attribute as floating-point.
|
||||
|
||||
This will try to find the attribute and convert it to an integer (using
|
||||
the String::getDoubleValue() method).
|
||||
|
||||
@param attributeName the name of the attribute to look up
|
||||
@param defaultReturnValue a value to return if the element doesn't have an attribute
|
||||
with this name
|
||||
@see setAttribute
|
||||
*/
|
||||
double getDoubleAttribute (StringRef attributeName, double defaultReturnValue = 0.0) const;
|
||||
|
||||
/** Returns the value of a named attribute as a boolean.
|
||||
|
||||
This will try to find the attribute and interpret it as a boolean. To do this,
|
||||
it'll return true if the value is "1", "true", "y", etc, or false for other
|
||||
values.
|
||||
|
||||
@param attributeName the name of the attribute to look up
|
||||
@param defaultReturnValue a value to return if the element doesn't have an attribute
|
||||
with this name
|
||||
*/
|
||||
bool getBoolAttribute (StringRef attributeName, bool defaultReturnValue = false) const;
|
||||
|
||||
/** Adds a named attribute to the element.
|
||||
|
||||
If the element already contains an attribute with this name, it's value will
|
||||
be updated to the new value. If there's no such attribute yet, a new one will
|
||||
be added.
|
||||
|
||||
Note that there are other setAttribute() methods that take integers,
|
||||
doubles, etc. to make it easy to store numbers.
|
||||
|
||||
@param attributeName the name of the attribute to set
|
||||
@param newValue the value to set it to
|
||||
@see removeAttribute
|
||||
*/
|
||||
void setAttribute (const Identifier& attributeName, const String& newValue);
|
||||
|
||||
/** Adds a named attribute to the element, setting it to an integer value.
|
||||
|
||||
If the element already contains an attribute with this name, it's value will
|
||||
be updated to the new value. If there's no such attribute yet, a new one will
|
||||
be added.
|
||||
|
||||
Note that there are other setAttribute() methods that take integers,
|
||||
doubles, etc. to make it easy to store numbers.
|
||||
|
||||
@param attributeName the name of the attribute to set
|
||||
@param newValue the value to set it to
|
||||
*/
|
||||
void setAttribute (const Identifier& attributeName, int newValue);
|
||||
|
||||
/** Adds a named attribute to the element, setting it to a floating-point value.
|
||||
|
||||
If the element already contains an attribute with this name, it's value will
|
||||
be updated to the new value. If there's no such attribute yet, a new one will
|
||||
be added.
|
||||
|
||||
Note that there are other setAttribute() methods that take integers,
|
||||
doubles, etc. to make it easy to store numbers.
|
||||
|
||||
@param attributeName the name of the attribute to set
|
||||
@param newValue the value to set it to
|
||||
*/
|
||||
void setAttribute (const Identifier& attributeName, double newValue);
|
||||
|
||||
/** Removes a named attribute from the element.
|
||||
|
||||
@param attributeName the name of the attribute to remove
|
||||
@see removeAllAttributes
|
||||
*/
|
||||
void removeAttribute (const Identifier& attributeName) noexcept;
|
||||
|
||||
/** Removes all attributes from this element. */
|
||||
void removeAllAttributes() noexcept;
|
||||
|
||||
//==============================================================================
|
||||
// Child element methods..
|
||||
|
||||
/** Returns the first of this element's sub-elements.
|
||||
see getNextElement() for an example of how to iterate the sub-elements.
|
||||
@see forEachXmlChildElement
|
||||
*/
|
||||
XmlElement* getFirstChildElement() const noexcept { return firstChildElement; }
|
||||
|
||||
/** Returns the next of this element's siblings.
|
||||
|
||||
This can be used for iterating an element's sub-elements, e.g.
|
||||
@code
|
||||
XmlElement* child = myXmlDocument->getFirstChildElement();
|
||||
|
||||
while (child != nullptr)
|
||||
{
|
||||
...do stuff with this child..
|
||||
|
||||
child = child->getNextElement();
|
||||
}
|
||||
@endcode
|
||||
|
||||
Note that when iterating the child elements, some of them might be
|
||||
text elements as well as XML tags - use isTextElement() to work this
|
||||
out.
|
||||
|
||||
Also, it's much easier and neater to use this method indirectly via the
|
||||
forEachXmlChildElement macro.
|
||||
|
||||
@returns the sibling element that follows this one, or zero if this is the last
|
||||
element in its parent
|
||||
|
||||
@see getNextElement, isTextElement, forEachXmlChildElement
|
||||
*/
|
||||
inline XmlElement* getNextElement() const noexcept { return nextListItem; }
|
||||
|
||||
/** Returns the next of this element's siblings which has the specified tag
|
||||
name.
|
||||
|
||||
This is like getNextElement(), but will scan through the list until it
|
||||
finds an element with the given tag name.
|
||||
|
||||
@see getNextElement, forEachXmlChildElementWithTagName
|
||||
*/
|
||||
XmlElement* getNextElementWithTagName (StringRef requiredTagName) const;
|
||||
|
||||
/** Returns the number of sub-elements in this element.
|
||||
@see getChildElement
|
||||
*/
|
||||
int getNumChildElements() const noexcept;
|
||||
|
||||
/** Returns the sub-element at a certain index.
|
||||
|
||||
It's not very efficient to iterate the sub-elements by index - see
|
||||
getNextElement() for an example of how best to iterate.
|
||||
|
||||
@returns the n'th child of this element, or nullptr if the index is out-of-range
|
||||
@see getNextElement, isTextElement, getChildByName
|
||||
*/
|
||||
XmlElement* getChildElement (int index) const noexcept;
|
||||
|
||||
/** Returns the first sub-element with a given tag-name.
|
||||
|
||||
@param tagNameToLookFor the tag name of the element you want to find
|
||||
@returns the first element with this tag name, or nullptr if none is found
|
||||
@see getNextElement, isTextElement, getChildElement, getChildByAttribute
|
||||
*/
|
||||
XmlElement* getChildByName (StringRef tagNameToLookFor) const noexcept;
|
||||
|
||||
/** Returns the first sub-element which has an attribute that matches the given value.
|
||||
|
||||
@param attributeName the name of the attribute to check
|
||||
@param attributeValue the target value of the attribute
|
||||
@returns the first element with this attribute value, or nullptr if none is found
|
||||
@see getChildByName
|
||||
*/
|
||||
XmlElement* getChildByAttribute (StringRef attributeName,
|
||||
StringRef attributeValue) const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Appends an element to this element's list of children.
|
||||
|
||||
Child elements are deleted automatically when their parent is deleted, so
|
||||
make sure the object that you pass in will not be deleted by anything else,
|
||||
and make sure it's not already the child of another element.
|
||||
|
||||
Note that due to the XmlElement using a singly-linked-list, prependChildElement()
|
||||
is an O(1) operation, but addChildElement() is an O(N) operation - so if
|
||||
you're adding large number of elements, you may prefer to do so in reverse order!
|
||||
|
||||
@see getFirstChildElement, getNextElement, getNumChildElements,
|
||||
getChildElement, removeChildElement
|
||||
*/
|
||||
void addChildElement (XmlElement* newChildElement) noexcept;
|
||||
|
||||
/** Inserts an element into this element's list of children.
|
||||
|
||||
Child elements are deleted automatically when their parent is deleted, so
|
||||
make sure the object that you pass in will not be deleted by anything else,
|
||||
and make sure it's not already the child of another element.
|
||||
|
||||
@param newChildElement the element to add
|
||||
@param indexToInsertAt the index at which to insert the new element - if this is
|
||||
below zero, it will be added to the end of the list
|
||||
@see addChildElement, insertChildElement
|
||||
*/
|
||||
void insertChildElement (XmlElement* newChildElement,
|
||||
int indexToInsertAt) noexcept;
|
||||
|
||||
/** Inserts an element at the beginning of this element's list of children.
|
||||
|
||||
Child elements are deleted automatically when their parent is deleted, so
|
||||
make sure the object that you pass in will not be deleted by anything else,
|
||||
and make sure it's not already the child of another element.
|
||||
|
||||
Note that due to the XmlElement using a singly-linked-list, prependChildElement()
|
||||
is an O(1) operation, but addChildElement() is an O(N) operation - so if
|
||||
you're adding large number of elements, you may prefer to do so in reverse order!
|
||||
|
||||
@see addChildElement, insertChildElement
|
||||
*/
|
||||
void prependChildElement (XmlElement* newChildElement) noexcept;
|
||||
|
||||
/** Creates a new element with the given name and returns it, after adding it
|
||||
as a child element.
|
||||
|
||||
This is a handy method that means that instead of writing this:
|
||||
@code
|
||||
XmlElement* newElement = new XmlElement ("foobar");
|
||||
myParentElement->addChildElement (newElement);
|
||||
@endcode
|
||||
|
||||
..you could just write this:
|
||||
@code
|
||||
XmlElement* newElement = myParentElement->createNewChildElement ("foobar");
|
||||
@endcode
|
||||
*/
|
||||
XmlElement* createNewChildElement (StringRef tagName);
|
||||
|
||||
/** Replaces one of this element's children with another node.
|
||||
|
||||
If the current element passed-in isn't actually a child of this element,
|
||||
this will return false and the new one won't be added. Otherwise, the
|
||||
existing element will be deleted, replaced with the new one, and it
|
||||
will return true.
|
||||
*/
|
||||
bool replaceChildElement (XmlElement* currentChildElement,
|
||||
XmlElement* newChildNode) noexcept;
|
||||
|
||||
/** Removes a child element.
|
||||
|
||||
@param childToRemove the child to look for and remove
|
||||
@param shouldDeleteTheChild if true, the child will be deleted, if false it'll
|
||||
just remove it
|
||||
*/
|
||||
void removeChildElement (XmlElement* childToRemove,
|
||||
bool shouldDeleteTheChild) noexcept;
|
||||
|
||||
/** Deletes all the child elements in the element.
|
||||
@see removeChildElement, deleteAllChildElementsWithTagName
|
||||
*/
|
||||
void deleteAllChildElements() noexcept;
|
||||
|
||||
/** Deletes all the child elements with a given tag name.
|
||||
@see removeChildElement
|
||||
*/
|
||||
void deleteAllChildElementsWithTagName (StringRef tagName) noexcept;
|
||||
|
||||
/** Returns true if the given element is a child of this one. */
|
||||
bool containsChildElement (const XmlElement* possibleChild) const noexcept;
|
||||
|
||||
/** Recursively searches all sub-elements of this one, looking for an element
|
||||
which is the direct parent of the specified element.
|
||||
|
||||
Because elements don't store a pointer to their parent, if you have one
|
||||
and need to find its parent, the only way to do so is to exhaustively
|
||||
search the whole tree for it.
|
||||
|
||||
If the given child is found somewhere in this element's hierarchy, then
|
||||
this method will return its parent. If not, it will return nullptr.
|
||||
*/
|
||||
XmlElement* findParentElementOf (const XmlElement* childToSearchFor) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Sorts the child elements using a comparator.
|
||||
|
||||
This will use a comparator object to sort the elements into order. The object
|
||||
passed must have a method of the form:
|
||||
@code
|
||||
int compareElements (const XmlElement* first, const XmlElement* second);
|
||||
@endcode
|
||||
|
||||
..and this method must return:
|
||||
- a value of < 0 if the first comes before the second
|
||||
- a value of 0 if the two objects are equivalent
|
||||
- a value of > 0 if the second comes before the first
|
||||
|
||||
To improve performance, the compareElements() method can be declared as static or const.
|
||||
|
||||
@param comparator the comparator to use for comparing elements.
|
||||
@param retainOrderOfEquivalentItems if this is true, then items which the comparator
|
||||
says are equivalent will be kept in the order in which they
|
||||
currently appear in the array. This is slower to perform, but
|
||||
may be important in some cases. If it's false, a faster algorithm
|
||||
is used, but equivalent elements may be rearranged.
|
||||
*/
|
||||
template <class ElementComparator>
|
||||
void sortChildElements (ElementComparator& comparator,
|
||||
bool retainOrderOfEquivalentItems = false)
|
||||
{
|
||||
const int num = getNumChildElements();
|
||||
|
||||
if (num > 1)
|
||||
{
|
||||
HeapBlock <XmlElement*> elems ((size_t) num);
|
||||
getChildElementsAsArray (elems);
|
||||
sortArray (comparator, (XmlElement**) elems, 0, num - 1, retainOrderOfEquivalentItems);
|
||||
reorderChildElements (elems, num);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if this element is a section of text.
|
||||
|
||||
Elements can either be an XML tag element or a secton of text, so this
|
||||
is used to find out what kind of element this one is.
|
||||
|
||||
@see getAllText, addTextElement, deleteAllTextElements
|
||||
*/
|
||||
bool isTextElement() const noexcept;
|
||||
|
||||
/** Returns the text for a text element.
|
||||
|
||||
Note that if you have an element like this:
|
||||
|
||||
@code<xyz>hello</xyz>@endcode
|
||||
|
||||
then calling getText on the "xyz" element won't return "hello", because that is
|
||||
actually stored in a special text sub-element inside the xyz element. To get the
|
||||
"hello" string, you could either call getText on the (unnamed) sub-element, or
|
||||
use getAllSubText() to do this automatically.
|
||||
|
||||
Note that leading and trailing whitespace will be included in the string - to remove
|
||||
if, just call String::trim() on the result.
|
||||
|
||||
@see isTextElement, getAllSubText, getChildElementAllSubText
|
||||
*/
|
||||
const String& getText() const noexcept;
|
||||
|
||||
/** Sets the text in a text element.
|
||||
|
||||
Note that this is only a valid call if this element is a text element. If it's
|
||||
not, then no action will be performed. If you're trying to add text inside a normal
|
||||
element, you probably want to use addTextElement() instead.
|
||||
*/
|
||||
void setText (const String& newText);
|
||||
|
||||
/** Returns all the text from this element's child nodes.
|
||||
|
||||
This iterates all the child elements and when it finds text elements,
|
||||
it concatenates their text into a big string which it returns.
|
||||
|
||||
E.g. @code<xyz>hello <x>there</x> world</xyz>@endcode
|
||||
if you called getAllSubText on the "xyz" element, it'd return "hello there world".
|
||||
|
||||
Note that leading and trailing whitespace will be included in the string - to remove
|
||||
if, just call String::trim() on the result.
|
||||
|
||||
@see isTextElement, getChildElementAllSubText, getText, addTextElement
|
||||
*/
|
||||
String getAllSubText() const;
|
||||
|
||||
/** Returns all the sub-text of a named child element.
|
||||
|
||||
If there is a child element with the given tag name, this will return
|
||||
all of its sub-text (by calling getAllSubText() on it). If there is
|
||||
no such child element, this will return the default string passed-in.
|
||||
|
||||
@see getAllSubText
|
||||
*/
|
||||
String getChildElementAllSubText (StringRef childTagName,
|
||||
const String& defaultReturnValue) const;
|
||||
|
||||
/** Appends a section of text to this element.
|
||||
@see isTextElement, getText, getAllSubText
|
||||
*/
|
||||
void addTextElement (const String& text);
|
||||
|
||||
/** Removes all the text elements from this element.
|
||||
@see isTextElement, getText, getAllSubText, addTextElement
|
||||
*/
|
||||
void deleteAllTextElements() noexcept;
|
||||
|
||||
/** Creates a text element that can be added to a parent element. */
|
||||
static XmlElement* createTextElement (const String& text);
|
||||
|
||||
//==============================================================================
|
||||
private:
|
||||
struct XmlAttributeNode
|
||||
{
|
||||
XmlAttributeNode (const XmlAttributeNode&) noexcept;
|
||||
XmlAttributeNode (const Identifier&, const String&) noexcept;
|
||||
XmlAttributeNode (String::CharPointerType, String::CharPointerType);
|
||||
|
||||
LinkedListPointer<XmlAttributeNode> nextListItem;
|
||||
Identifier name;
|
||||
String value;
|
||||
|
||||
private:
|
||||
XmlAttributeNode& operator= (const XmlAttributeNode&) JUCE_DELETED_FUNCTION;
|
||||
};
|
||||
|
||||
friend class XmlDocument;
|
||||
friend class LinkedListPointer<XmlAttributeNode>;
|
||||
friend class LinkedListPointer<XmlElement>;
|
||||
friend class LinkedListPointer<XmlElement>::Appender;
|
||||
friend class NamedValueSet;
|
||||
|
||||
LinkedListPointer<XmlElement> nextListItem;
|
||||
LinkedListPointer<XmlElement> firstChildElement;
|
||||
LinkedListPointer<XmlAttributeNode> attributes;
|
||||
String tagName;
|
||||
|
||||
XmlElement (int) noexcept;
|
||||
void copyChildrenAndAttributesFrom (const XmlElement&);
|
||||
void writeElementAsText (OutputStream&, int indentationLevel, int lineWrapLength) const;
|
||||
void getChildElementsAsArray (XmlElement**) const noexcept;
|
||||
void reorderChildElements (XmlElement**, int) noexcept;
|
||||
XmlAttributeNode* getAttribute (StringRef) const noexcept;
|
||||
|
||||
// Sigh.. L"" or _T("") string literals are problematic in general, and really inappropriate
|
||||
// for XML tags. Use a UTF-8 encoded literal instead, or if you're really determined to use
|
||||
// UTF-16, cast it to a String and use the other constructor.
|
||||
XmlElement (const wchar_t*) JUCE_DELETED_FUNCTION;
|
||||
|
||||
JUCE_LEAK_DETECTOR (XmlElement)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_XMLELEMENT_H_INCLUDED
|
||||
Reference in New Issue
Block a user