git subrepo clone --branch=sono6good https://github.com/essej/JUCE.git deps/juce
subrepo: subdir: "deps/juce" merged: "b13f9084e" upstream: origin: "https://github.com/essej/JUCE.git" branch: "sono6good" commit: "b13f9084e" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo.git" commit: "2f68596"
This commit is contained in:
903
deps/juce/modules/juce_core/xml/juce_XmlDocument.cpp
vendored
Normal file
903
deps/juce/modules/juce_core/xml/juce_XmlDocument.cpp
vendored
Normal file
@ -0,0 +1,903 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
XmlDocument::XmlDocument (const String& text) : originalText (text) {}
|
||||
XmlDocument::XmlDocument (const File& file) : inputSource (new FileInputSource (file)) {}
|
||||
|
||||
XmlDocument::~XmlDocument() {}
|
||||
|
||||
std::unique_ptr<XmlElement> XmlDocument::parse (const File& file)
|
||||
{
|
||||
return XmlDocument (file).getDocumentElement();
|
||||
}
|
||||
|
||||
std::unique_ptr<XmlElement> XmlDocument::parse (const String& textToParse)
|
||||
{
|
||||
return XmlDocument (textToParse).getDocumentElement();
|
||||
}
|
||||
|
||||
std::unique_ptr<XmlElement> parseXML (const String& textToParse)
|
||||
{
|
||||
return XmlDocument (textToParse).getDocumentElement();
|
||||
}
|
||||
|
||||
std::unique_ptr<XmlElement> parseXML (const File& file)
|
||||
{
|
||||
return XmlDocument (file).getDocumentElement();
|
||||
}
|
||||
|
||||
std::unique_ptr<XmlElement> parseXMLIfTagMatches (const String& textToParse, StringRef requiredTag)
|
||||
{
|
||||
return XmlDocument (textToParse).getDocumentElementIfTagMatches (requiredTag);
|
||||
}
|
||||
|
||||
std::unique_ptr<XmlElement> parseXMLIfTagMatches (const File& file, StringRef requiredTag)
|
||||
{
|
||||
return XmlDocument (file).getDocumentElementIfTagMatches (requiredTag);
|
||||
}
|
||||
|
||||
void XmlDocument::setInputSource (InputSource* newSource) noexcept
|
||||
{
|
||||
inputSource.reset (newSource);
|
||||
}
|
||||
|
||||
void XmlDocument::setEmptyTextElementsIgnored (bool shouldBeIgnored) noexcept
|
||||
{
|
||||
ignoreEmptyTextElements = shouldBeIgnored;
|
||||
}
|
||||
|
||||
namespace XmlIdentifierChars
|
||||
{
|
||||
static bool isIdentifierCharSlow (juce_wchar c) noexcept
|
||||
{
|
||||
return CharacterFunctions::isLetterOrDigit (c)
|
||||
|| c == '_' || c == '-' || c == ':' || c == '.';
|
||||
}
|
||||
|
||||
static bool isIdentifierChar (juce_wchar c) noexcept
|
||||
{
|
||||
static const uint32 legalChars[] = { 0, 0x7ff6000, 0x87fffffe, 0x7fffffe, 0 };
|
||||
|
||||
return ((int) c < (int) numElementsInArray (legalChars) * 32) ? ((legalChars [c >> 5] & (uint32) (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) noexcept
|
||||
{
|
||||
while (isIdentifierChar (*p))
|
||||
++p;
|
||||
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<XmlElement> XmlDocument::getDocumentElement (const bool onlyReadOuterDocumentElement)
|
||||
{
|
||||
if (originalText.isEmpty() && inputSource != nullptr)
|
||||
{
|
||||
std::unique_ptr<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);
|
||||
auto* 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);
|
||||
}
|
||||
|
||||
std::unique_ptr<XmlElement> XmlDocument::getDocumentElementIfTagMatches (StringRef requiredTag)
|
||||
{
|
||||
if (auto xml = getDocumentElement (true))
|
||||
if (xml->hasTagName (requiredTag))
|
||||
return getDocumentElement (false);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
std::unique_ptr<InputStream> in (inputSource->createInputStreamFor (filename.trim().unquoted()));
|
||||
|
||||
if (in != nullptr)
|
||||
return in->readEntireStreamAsString();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
juce_wchar XmlDocument::readNextChar() noexcept
|
||||
{
|
||||
auto c = input.getAndAdvance();
|
||||
|
||||
if (c == 0)
|
||||
{
|
||||
outOfData = true;
|
||||
--input;
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
std::unique_ptr<XmlElement> XmlDocument::parseDocumentElement (String::CharPointerType textToParse,
|
||||
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();
|
||||
std::unique_ptr<XmlElement> result (readNextElement (! onlyReadOuterDocumentElement));
|
||||
|
||||
if (! errorOccurred)
|
||||
return result;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool XmlDocument::parseHeader()
|
||||
{
|
||||
skipNextWhiteSpace();
|
||||
|
||||
if (CharacterFunctions::compareUpTo (input, CharPointer_ASCII ("<?xml"), 5) == 0)
|
||||
{
|
||||
auto headerEnd = CharacterFunctions::find (input, CharPointer_ASCII ("?>"));
|
||||
|
||||
if (headerEnd.isEmpty())
|
||||
return false;
|
||||
|
||||
#if JUCE_DEBUG
|
||||
auto 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;
|
||||
auto dtdStart = input;
|
||||
|
||||
for (int n = 1; n > 0;)
|
||||
{
|
||||
auto 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.incrementToEndOfWhitespace();
|
||||
|
||||
if (input.isEmpty())
|
||||
{
|
||||
outOfData = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (*input == '<')
|
||||
{
|
||||
if (input[1] == '!'
|
||||
&& input[2] == '-'
|
||||
&& input[3] == '-')
|
||||
{
|
||||
input += 4;
|
||||
auto closeComment = input.indexOf (CharPointer_ASCII ("-->"));
|
||||
|
||||
if (closeComment < 0)
|
||||
{
|
||||
outOfData = true;
|
||||
break;
|
||||
}
|
||||
|
||||
input += closeComment + 3;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (input[1] == '?')
|
||||
{
|
||||
input += 2;
|
||||
auto closeBracket = input.indexOf (CharPointer_ASCII ("?>"));
|
||||
|
||||
if (closeBracket < 0)
|
||||
{
|
||||
outOfData = true;
|
||||
break;
|
||||
}
|
||||
|
||||
input += closeBracket + 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void XmlDocument::readQuotedString (String& result)
|
||||
{
|
||||
auto quote = readNextChar();
|
||||
|
||||
while (! outOfData)
|
||||
{
|
||||
auto c = readNextChar();
|
||||
|
||||
if (c == quote)
|
||||
break;
|
||||
|
||||
--input;
|
||||
|
||||
if (c == '&')
|
||||
{
|
||||
readEntity (result);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto start = input;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
auto character = *input;
|
||||
|
||||
if (character == quote)
|
||||
{
|
||||
result.appendCharPointer (start, input);
|
||||
++input;
|
||||
return;
|
||||
}
|
||||
|
||||
if (character == '&')
|
||||
{
|
||||
result.appendCharPointer (start, input);
|
||||
break;
|
||||
}
|
||||
|
||||
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;
|
||||
auto 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();
|
||||
auto 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))
|
||||
{
|
||||
auto attNameEnd = XmlIdentifierChars::findEndOfToken (input);
|
||||
|
||||
if (attNameEnd != input)
|
||||
{
|
||||
auto attNameStart = input;
|
||||
input = attNameEnd;
|
||||
skipNextWhiteSpace();
|
||||
|
||||
if (readNextChar() == '=')
|
||||
{
|
||||
skipNextWhiteSpace();
|
||||
auto nextChar = *input;
|
||||
|
||||
if (nextChar == '"' || nextChar == '\'')
|
||||
{
|
||||
auto* 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 (;;)
|
||||
{
|
||||
auto preWhitespaceInput = input;
|
||||
skipNextWhiteSpace();
|
||||
|
||||
if (outOfData)
|
||||
{
|
||||
setLastError ("unmatched tags", false);
|
||||
break;
|
||||
}
|
||||
|
||||
if (*input == '<')
|
||||
{
|
||||
auto c1 = input[1];
|
||||
|
||||
if (c1 == '/')
|
||||
{
|
||||
// our close tag..
|
||||
auto 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;
|
||||
auto inputStart = input;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
auto c0 = *input;
|
||||
|
||||
if (c0 == 0)
|
||||
{
|
||||
setLastError ("unterminated CDATA section", false);
|
||||
outOfData = true;
|
||||
break;
|
||||
}
|
||||
|
||||
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 (auto* n = readNextElement (true))
|
||||
childAppender.append (n);
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
else // must be a character block
|
||||
{
|
||||
input = preWhitespaceInput; // roll back to include the leading whitespace
|
||||
MemoryOutputStream textElementContent;
|
||||
bool contentShouldBeUsed = ! ignoreEmptyTextElements;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
auto c = *input;
|
||||
|
||||
if (c == '<')
|
||||
{
|
||||
if (input[1] == '!' && input[2] == '-' && input[3] == '-')
|
||||
{
|
||||
input += 4;
|
||||
auto 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)
|
||||
{
|
||||
auto oldInput = input;
|
||||
auto oldOutOfData = outOfData;
|
||||
|
||||
input = entity.getCharPointer();
|
||||
outOfData = false;
|
||||
|
||||
while (auto* n = readNextElement (true))
|
||||
childAppender.append (n);
|
||||
|
||||
input = oldInput;
|
||||
outOfData = oldOutOfData;
|
||||
}
|
||||
else
|
||||
{
|
||||
textElementContent << entity;
|
||||
contentShouldBeUsed = contentShouldBeUsed || entity.containsNonWhitespaceChars();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (;; ++input)
|
||||
{
|
||||
auto nextChar = *input;
|
||||
|
||||
if (nextChar == '\r')
|
||||
{
|
||||
nextChar = '\n';
|
||||
|
||||
if (input[1] == '\n')
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nextChar == '<' || nextChar == '&')
|
||||
break;
|
||||
|
||||
if (nextChar == 0)
|
||||
{
|
||||
setLastError ("unmatched tags", false);
|
||||
outOfData = true;
|
||||
return;
|
||||
}
|
||||
|
||||
textElementContent.appendUTF8Char (nextChar);
|
||||
contentShouldBeUsed = contentShouldBeUsed || ! CharacterFunctions::isWhitespace (nextChar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (contentShouldBeUsed)
|
||||
childAppender.append (XmlElement::createTextElement (textElementContent.toUTF8()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 == '#')
|
||||
{
|
||||
int64_t charCode = 0;
|
||||
++input;
|
||||
|
||||
if (*input == 'x' || *input == 'X')
|
||||
{
|
||||
++input;
|
||||
int numChars = 0;
|
||||
|
||||
while (input[0] != ';')
|
||||
{
|
||||
auto 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;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
const auto firstChar = input[0];
|
||||
|
||||
if (firstChar == 0)
|
||||
{
|
||||
setLastError ("unexpected end of input", true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (firstChar == ';')
|
||||
break;
|
||||
|
||||
if (++numChars > 12)
|
||||
{
|
||||
setLastError ("illegal escape sequence", true);
|
||||
break;
|
||||
}
|
||||
|
||||
charCode = charCode * 10 + ((int) firstChar - '0');
|
||||
++input;
|
||||
}
|
||||
|
||||
++input;
|
||||
}
|
||||
else
|
||||
{
|
||||
setLastError ("illegal escape sequence", true);
|
||||
result += '&';
|
||||
return;
|
||||
}
|
||||
|
||||
result << (juce_wchar) charCode;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto entityNameStart = input;
|
||||
auto 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] == '#')
|
||||
{
|
||||
auto 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())
|
||||
{
|
||||
auto fn = tokenisedDTD[tokenisedDTD.size() - 1];
|
||||
|
||||
tokenisedDTD.clear();
|
||||
tokenisedDTD.addTokens (getFileContents (fn), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
tokenisedDTD.clear();
|
||||
auto openBracket = dtdText.indexOfChar ('[');
|
||||
|
||||
if (openBracket > 0)
|
||||
{
|
||||
auto 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 (';'))
|
||||
{
|
||||
auto 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"))
|
||||
{
|
||||
auto ent = tokenisedDTD [i + 1].trimCharactersAtEnd (">").trim().unquoted();
|
||||
|
||||
// check for sub-entities..
|
||||
auto ampersand = ent.indexOfChar ('&');
|
||||
|
||||
while (ampersand >= 0)
|
||||
{
|
||||
auto semiColon = ent.indexOf (i + 1, ";");
|
||||
|
||||
if (semiColon < 0)
|
||||
{
|
||||
setLastError ("entity without terminating semi-colon", false);
|
||||
break;
|
||||
}
|
||||
|
||||
auto 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"))
|
||||
{
|
||||
auto ent = tokenisedDTD [i + 1].trimCharactersAtEnd (">");
|
||||
|
||||
if (ent.equalsIgnoreCase ("system"))
|
||||
return getFileContents (tokenisedDTD [i + 2].trimCharactersAtEnd (">"));
|
||||
|
||||
return ent.trim().unquoted();
|
||||
}
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
}
|
207
deps/juce/modules/juce_core/xml/juce_XmlDocument.h
vendored
Normal file
207
deps/juce/modules/juce_core/xml/juce_XmlDocument.h
vendored
Normal file
@ -0,0 +1,207 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
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"));
|
||||
|
||||
if (auto mainElement = myDocument.getDocumentElement())
|
||||
{
|
||||
..use the element
|
||||
}
|
||||
else
|
||||
{
|
||||
String error = myDocument.getLastParseError();
|
||||
}
|
||||
@endcode
|
||||
|
||||
Or you can use the helper functions for much less verbose parsing..
|
||||
|
||||
@code
|
||||
if (auto xml = parseXML (myXmlFile))
|
||||
{
|
||||
if (xml->hasTagName ("foobar"))
|
||||
{
|
||||
...etc
|
||||
}
|
||||
}
|
||||
@endcode
|
||||
|
||||
@see XmlElement
|
||||
|
||||
@tags{Core}
|
||||
*/
|
||||
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, or nullptr if there was an error.
|
||||
@see getLastParseError, getDocumentElementIfTagMatches
|
||||
*/
|
||||
std::unique_ptr<XmlElement> getDocumentElement (bool onlyReadOuterDocumentElement = false);
|
||||
|
||||
/** Does an inexpensive check to see whether the outer element has the given tag name, and
|
||||
then does a full parse if it matches.
|
||||
If the tag is different, or the XML parse fails, this will return nullptr.
|
||||
*/
|
||||
std::unique_ptr<XmlElement> getDocumentElementIfTagMatches (StringRef requiredTag);
|
||||
|
||||
/** 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, or nullptr if there was an error.
|
||||
*/
|
||||
static std::unique_ptr<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, or nullptr if there was an error.
|
||||
*/
|
||||
static std::unique_ptr<XmlElement> parse (const String& xmlData);
|
||||
|
||||
|
||||
//==============================================================================
|
||||
private:
|
||||
String originalText;
|
||||
String::CharPointerType input { nullptr };
|
||||
bool outOfData = false, errorOccurred = false;
|
||||
String lastError, dtdText;
|
||||
StringArray tokenisedDTD;
|
||||
bool needToLoadDTD = false, ignoreEmptyTextElements = true;
|
||||
std::unique_ptr<InputSource> inputSource;
|
||||
|
||||
std::unique_ptr<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)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Attempts to parse some XML text, returning a new XmlElement if it was valid.
|
||||
If the parse fails, this will return a nullptr - if you need more information about
|
||||
errors or more parsing options, see the XmlDocument class instead.
|
||||
@see XmlDocument, parseXMLIfTagMatches
|
||||
*/
|
||||
std::unique_ptr<XmlElement> parseXML (const String& textToParse);
|
||||
|
||||
/** Attempts to parse some XML text, returning a new XmlElement if it was valid.
|
||||
If the parse fails, this will return a nullptr - if you need more information about
|
||||
errors or more parsing options, see the XmlDocument class instead.
|
||||
@see XmlDocument, parseXMLIfTagMatches
|
||||
*/
|
||||
std::unique_ptr<XmlElement> parseXML (const File& fileToParse);
|
||||
|
||||
/** Does an inexpensive check to see whether the top-level element has the given tag
|
||||
name, and if that's true, does a full parse and returns the result.
|
||||
If the outer tag doesn't match, or the XML has errors, this will return nullptr;
|
||||
@see parseXML
|
||||
*/
|
||||
std::unique_ptr<XmlElement> parseXMLIfTagMatches (const String& textToParse, StringRef requiredTag);
|
||||
|
||||
/** Does an inexpensive check to see whether the top-level element has the given tag
|
||||
name, and if that's true, does a full parse and returns the result.
|
||||
If the outer tag doesn't match, or the XML has errors, this will return nullptr;
|
||||
@see parseXML
|
||||
*/
|
||||
std::unique_ptr<XmlElement> parseXMLIfTagMatches (const File& fileToParse, StringRef requiredTag);
|
||||
|
||||
|
||||
} // namespace juce
|
1055
deps/juce/modules/juce_core/xml/juce_XmlElement.cpp
vendored
Normal file
1055
deps/juce/modules/juce_core/xml/juce_XmlElement.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
883
deps/juce/modules/juce_core/xml/juce_XmlElement.h
vendored
Normal file
883
deps/juce/modules/juce_core/xml/juce_XmlElement.h
vendored
Normal file
@ -0,0 +1,883 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/** 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..
|
||||
for (auto* e : myElement->getChildIterator())
|
||||
{
|
||||
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 textual XML
|
||||
auto xmlString = animalsList.toString();
|
||||
@endcode
|
||||
|
||||
@see parseXML, parseXMLIfTagMatches, XmlDocument
|
||||
|
||||
@tags{Core}
|
||||
*/
|
||||
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&);
|
||||
|
||||
/** Move assignment operator */
|
||||
XmlElement& operator= (XmlElement&&) noexcept;
|
||||
|
||||
/** Move constructor */
|
||||
XmlElement (XmlElement&&) noexcept;
|
||||
|
||||
/** 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 attributes.
|
||||
|
||||
The elements are only considered equivalent if they contain the same attributes
|
||||
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;
|
||||
|
||||
//==============================================================================
|
||||
/** A struct containing options for formatting the text when representing an
|
||||
XML element as a string.
|
||||
*/
|
||||
struct TextFormat
|
||||
{
|
||||
/** Default constructor. */
|
||||
TextFormat();
|
||||
|
||||
String dtd; /**< If supplied, this DTD will be added to the document. */
|
||||
String customHeader; /**< If supplied, this header will be used (and customEncoding & addDefaultHeader will be ignored). */
|
||||
String customEncoding; /**< If not empty and addDefaultHeader is true, this will be set as the encoding. Otherwise, a default of "UTF-8" will be used */
|
||||
bool addDefaultHeader = true; /**< If true, a default header will be generated; otherwise just bare XML will be emitted. */
|
||||
int lineWrapLength = 60; /**< A maximum line length before wrapping is done. (If newLineChars is nullptr, this is ignored) */
|
||||
const char* newLineChars = "\r\n"; /**< Allows the newline characters to be set. If you set this to nullptr, then the whole XML document will be placed on a single line. */
|
||||
|
||||
TextFormat singleLine() const; /**< returns a copy of this format with newLineChars set to nullptr. */
|
||||
TextFormat withoutHeader() const; /**< returns a copy of this format with the addDefaultHeader flag set to false. */
|
||||
};
|
||||
|
||||
/** Returns a text version of this XML element.
|
||||
If your intention is to write the XML to a file or stream, it's probably more efficient to
|
||||
use writeTo() instead of creating an intermediate string.
|
||||
@see writeTo
|
||||
*/
|
||||
String toString (const TextFormat& format = {}) const;
|
||||
|
||||
/** Writes the document to a stream as UTF-8.
|
||||
@see writeTo, toString
|
||||
*/
|
||||
void writeTo (OutputStream& output, const TextFormat& format = {}) const;
|
||||
|
||||
/** Writes the document to a file as UTF-8.
|
||||
@see writeTo, toString
|
||||
*/
|
||||
bool writeTo (const File& destinationFile, const TextFormat& format = {}) 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;
|
||||
|
||||
/** Changes this elements tag name.
|
||||
@see getTagName
|
||||
*/
|
||||
void setTagName (StringRef newTagName);
|
||||
|
||||
//==============================================================================
|
||||
/** 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 an unsigned 64 bit integer.
|
||||
|
||||
This will try to find the attribute and convert it to an integer (using
|
||||
the String::getUInt64Value() 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
|
||||
*/
|
||||
uint64 getUInt64Attribute (StringRef attributeName, uint64 defaultReturnValue = 0) const;
|
||||
|
||||
/** Returns the value of a named attribute as floating-point.
|
||||
|
||||
This will try to find the attribute and convert it to a double (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 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, uint64 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 getChildIterator
|
||||
*/
|
||||
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
|
||||
getChildIterator() method.
|
||||
|
||||
@returns the sibling element that follows this one, or a nullptr if
|
||||
this is the last element in its parent
|
||||
|
||||
@see getNextElement, isTextElement, getChildIterator
|
||||
*/
|
||||
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, getChildIterator
|
||||
*/
|
||||
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)
|
||||
{
|
||||
auto num = getNumChildElements();
|
||||
|
||||
if (num > 1)
|
||||
{
|
||||
HeapBlock<XmlElement*> elems (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 section 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);
|
||||
|
||||
/** Checks if a given string is a valid XML name */
|
||||
static bool isValidXmlName (StringRef possibleName) noexcept;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
struct GetNextElement
|
||||
{
|
||||
XmlElement* getNext (const XmlElement& e) const { return e.getNextElement(); }
|
||||
};
|
||||
|
||||
struct GetNextElementWithTagName
|
||||
{
|
||||
GetNextElementWithTagName() = default;
|
||||
explicit GetNextElementWithTagName (String n) : name (std::move (n)) {}
|
||||
XmlElement* getNext (const XmlElement& e) const { return e.getNextElementWithTagName (name); }
|
||||
|
||||
String name;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
template <typename Traits>
|
||||
class Iterator : private Traits
|
||||
{
|
||||
public:
|
||||
using difference_type = ptrdiff_t;
|
||||
using value_type = XmlElement*;
|
||||
using pointer = const value_type*;
|
||||
using reference = value_type;
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
|
||||
Iterator() = default;
|
||||
|
||||
template <typename... Args>
|
||||
Iterator (XmlElement* e, Args&&... args)
|
||||
: Traits (std::forward<Args> (args)...), element (e) {}
|
||||
|
||||
Iterator begin() const { return *this; }
|
||||
Iterator end() const { return Iterator{}; }
|
||||
|
||||
bool operator== (const Iterator& other) const { return element == other.element; }
|
||||
bool operator!= (const Iterator& other) const { return ! operator== (other); }
|
||||
|
||||
reference operator*() const { return element; }
|
||||
pointer operator->() const { return &element; }
|
||||
|
||||
Iterator& operator++()
|
||||
{
|
||||
element = Traits::getNext (*element);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Iterator operator++(int)
|
||||
{
|
||||
auto copy = *this;
|
||||
++(*this);
|
||||
return copy;
|
||||
}
|
||||
|
||||
private:
|
||||
value_type element = nullptr;
|
||||
};
|
||||
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Allows iterating the children of an XmlElement using range-for syntax.
|
||||
|
||||
@code
|
||||
void doSomethingWithXmlChildren (const XmlElement& myParentXml)
|
||||
{
|
||||
for (auto* element : myParentXml.getChildIterator())
|
||||
doSomethingWithXmlElement (element);
|
||||
}
|
||||
@endcode
|
||||
*/
|
||||
Iterator<GetNextElement> getChildIterator() const
|
||||
{
|
||||
return Iterator<GetNextElement> { getFirstChildElement() };
|
||||
}
|
||||
|
||||
/** Allows iterating children of an XmlElement with a specific tag using range-for syntax.
|
||||
|
||||
@code
|
||||
void doSomethingWithXmlChildren (const XmlElement& myParentXml)
|
||||
{
|
||||
for (auto* element : myParentXml.getChildWithTagNameIterator ("MYTAG"))
|
||||
doSomethingWithXmlElement (element);
|
||||
}
|
||||
@endcode
|
||||
*/
|
||||
Iterator<GetNextElementWithTagName> getChildWithTagNameIterator (StringRef name) const
|
||||
{
|
||||
return Iterator<GetNextElementWithTagName> { getChildByName (name), name };
|
||||
}
|
||||
|
||||
#ifndef DOXYGEN
|
||||
[[deprecated]] void macroBasedForLoop() const noexcept {}
|
||||
|
||||
[[deprecated ("This has been deprecated in favour of the toString method.")]]
|
||||
String createDocument (StringRef dtdToUse,
|
||||
bool allOnOneLine = false,
|
||||
bool includeXmlHeader = true,
|
||||
StringRef encodingType = "UTF-8",
|
||||
int lineWrapLength = 60) const;
|
||||
|
||||
[[deprecated ("This has been deprecated in favour of the writeTo method.")]]
|
||||
void writeToStream (OutputStream& output,
|
||||
StringRef dtdToUse,
|
||||
bool allOnOneLine = false,
|
||||
bool includeXmlHeader = true,
|
||||
StringRef encodingType = "UTF-8",
|
||||
int lineWrapLength = 60) const;
|
||||
|
||||
[[deprecated ("This has been deprecated in favour of the writeTo method.")]]
|
||||
bool writeToFile (const File& destinationFile,
|
||||
StringRef dtdToUse,
|
||||
StringRef encodingType = "UTF-8",
|
||||
int lineWrapLength = 60) const;
|
||||
#endif
|
||||
|
||||
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&) = delete;
|
||||
};
|
||||
|
||||
friend class XmlDocument;
|
||||
friend class LinkedListPointer<XmlAttributeNode>;
|
||||
friend class LinkedListPointer<XmlElement>;
|
||||
friend class LinkedListPointer<XmlElement>::Appender;
|
||||
friend class NamedValueSet;
|
||||
|
||||
LinkedListPointer<XmlElement> nextListItem, firstChildElement;
|
||||
LinkedListPointer<XmlAttributeNode> attributes;
|
||||
String tagName;
|
||||
|
||||
XmlElement (int) noexcept;
|
||||
void copyChildrenAndAttributesFrom (const XmlElement&);
|
||||
void writeElementAsText (OutputStream&, int, int, const char*) 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*) = delete;
|
||||
|
||||
JUCE_LEAK_DETECTOR (XmlElement)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
#ifndef DOXYGEN
|
||||
|
||||
/** DEPRECATED: A handy macro to make it easy to iterate all the child elements in an XmlElement.
|
||||
|
||||
New code should avoid this macro, and instead use getChildIterator directly.
|
||||
|
||||
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 (auto* (childElementVariableName) : ((parentXmlElement).macroBasedForLoop(), (parentXmlElement).getChildIterator()))
|
||||
|
||||
/** DEPRECATED: A macro that makes it easy to iterate all the child elements of an XmlElement
|
||||
which have a specified tag.
|
||||
|
||||
New code should avoid this macro, and instead use getChildWithTagNameIterator directly.
|
||||
|
||||
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 (auto* (childElementVariableName) : ((parentXmlElement).macroBasedForLoop(), (parentXmlElement).getChildWithTagNameIterator ((requiredTagName))))
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
Reference in New Issue
Block a user