fix macOS build (following Projucer changes made in Windows, which removed /Applications/JUCE/modules from its headers). move JUCE headers under source control, so that Windows and macOS can both build against same version of JUCE. remove AUv3 target (I think it's an iOS thing, so it will never work with this macOS fluidsynth dylib).
This commit is contained in:
865
modules/juce_core/xml/juce_XmlDocument.cpp
Normal file
865
modules/juce_core/xml/juce_XmlDocument.cpp
Normal file
@ -0,0 +1,865 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
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() {}
|
||||
|
||||
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* 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] & (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;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
std::unique_ptr<XmlElement> result (readNextElement (! onlyReadOuterDocumentElement));
|
||||
|
||||
if (! errorOccurred)
|
||||
return result.release();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
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 = input.findEndOfWhitespace();
|
||||
|
||||
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 == '#')
|
||||
{
|
||||
int 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;
|
||||
|
||||
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
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
175
modules/juce_core/xml/juce_XmlDocument.h
Normal file
175
modules/juce_core/xml/juce_XmlDocument.h
Normal file
@ -0,0 +1,175 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
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"));
|
||||
std::unique_ptr<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
|
||||
std::unique_ptr<XmlElement> xml (XmlDocument::parse (myXmlFile));
|
||||
|
||||
if (xml != nullptr && 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 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 { nullptr };
|
||||
bool outOfData = false, errorOccurred = false;
|
||||
String lastError, dtdText;
|
||||
StringArray tokenisedDTD;
|
||||
bool needToLoadDTD = false, ignoreEmptyTextElements = true;
|
||||
std::unique_ptr<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)
|
||||
};
|
||||
|
||||
} // namespace juce
|
926
modules/juce_core/xml/juce_XmlElement.cpp
Normal file
926
modules/juce_core/xml/juce_XmlElement.cpp
Normal file
@ -0,0 +1,926 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
inline static bool isValidXmlNameStartCharacter (juce_wchar character) noexcept
|
||||
{
|
||||
return character == ':'
|
||||
|| character == '_'
|
||||
|| (character >= 'a' && character <= 'z')
|
||||
|| (character >= 'A' && character <= 'Z')
|
||||
|| (character >= 0xc0 && character <= 0xd6)
|
||||
|| (character >= 0xd8 && character <= 0xf6)
|
||||
|| (character >= 0xf8 && character <= 0x2ff)
|
||||
|| (character >= 0x370 && character <= 0x37d)
|
||||
|| (character >= 0x37f && character <= 0x1fff)
|
||||
|| (character >= 0x200c && character <= 0x200d)
|
||||
|| (character >= 0x2070 && character <= 0x218f)
|
||||
|| (character >= 0x2c00 && character <= 0x2fef)
|
||||
|| (character >= 0x3001 && character <= 0xd7ff)
|
||||
|| (character >= 0xf900 && character <= 0xfdcf)
|
||||
|| (character >= 0xfdf0 && character <= 0xfffd)
|
||||
|| (character >= 0x10000 && character <= 0xeffff);
|
||||
}
|
||||
|
||||
inline static bool isValidXmlNameBodyCharacter (juce_wchar character) noexcept
|
||||
{
|
||||
return isValidXmlNameStartCharacter (character)
|
||||
|| character == '-'
|
||||
|| character == '.'
|
||||
|| character == 0xb7
|
||||
|| (character >= '0' && character <= '9')
|
||||
|| (character >= 0x300 && character <= 0x036f)
|
||||
|| (character >= 0x203f && character <= 0x2040);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
jassert (isValidXmlName (name));
|
||||
}
|
||||
|
||||
XmlElement::XmlAttributeNode::XmlAttributeNode (String::CharPointerType nameStart, String::CharPointerType nameEnd)
|
||||
: name (nameStart, nameEnd)
|
||||
{
|
||||
jassert (isValidXmlName (name));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
XmlElement::XmlElement (const String& tag)
|
||||
: tagName (StringPool::getGlobalPool().getPooledString (tag))
|
||||
{
|
||||
jassert (isValidXmlName (tagName));
|
||||
}
|
||||
|
||||
XmlElement::XmlElement (const char* tag)
|
||||
: tagName (StringPool::getGlobalPool().getPooledString (tag))
|
||||
{
|
||||
jassert (isValidXmlName (tagName));
|
||||
}
|
||||
|
||||
XmlElement::XmlElement (StringRef tag)
|
||||
: tagName (StringPool::getGlobalPool().getPooledString (tag))
|
||||
{
|
||||
jassert (isValidXmlName (tagName));
|
||||
}
|
||||
|
||||
XmlElement::XmlElement (const Identifier& tag)
|
||||
: tagName (tag.toString())
|
||||
{
|
||||
jassert (isValidXmlName (tagName));
|
||||
}
|
||||
|
||||
XmlElement::XmlElement (String::CharPointerType tagNameStart, String::CharPointerType tagNameEnd)
|
||||
: tagName (StringPool::getGlobalPool().getPooledString (tagNameStart, tagNameEnd))
|
||||
{
|
||||
jassert (isValidXmlName (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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
auto t = text.getCharPointer();
|
||||
|
||||
for (;;)
|
||||
{
|
||||
auto 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;
|
||||
|
||||
{
|
||||
auto attIndent = (size_t) (indentationLevel + tagName.length() + 1);
|
||||
int lineLen = 0;
|
||||
|
||||
for (auto* att = attributes.get(); att != nullptr; att = att->nextListItem)
|
||||
{
|
||||
if (lineLen > lineWrapLength && indentationLevel >= 0)
|
||||
{
|
||||
outputStream << newLine;
|
||||
writeSpaces (outputStream, attIndent);
|
||||
lineLen = 0;
|
||||
}
|
||||
|
||||
auto startPos = outputStream.getPosition();
|
||||
outputStream.writeByte (' ');
|
||||
outputStream << att->name;
|
||||
outputStream.write ("=\"", 2);
|
||||
escapeIllegalXmlChars (outputStream, att->value, true);
|
||||
outputStream.writeByte ('"');
|
||||
lineLen += (int) (outputStream.getPosition() - startPos);
|
||||
}
|
||||
}
|
||||
|
||||
if (auto* child = firstChildElement.get())
|
||||
{
|
||||
outputStream.writeByte ('>');
|
||||
bool lastWasTextNode = false;
|
||||
|
||||
for (; 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,
|
||||
bool allOnOneLine, bool includeXmlHeader,
|
||||
StringRef encodingType, 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, int lineWrapLength) const
|
||||
{
|
||||
TemporaryFile tempFile (file);
|
||||
|
||||
{
|
||||
FileOutputStream out (tempFile.getFile());
|
||||
|
||||
if (! out.openedOk())
|
||||
return false;
|
||||
|
||||
writeToStream (out, dtdToUse, false, true, encodingType, lineWrapLength);
|
||||
out.flush(); // (called explicitly to force an fsync on posix)
|
||||
|
||||
if (out.getStatus().failed())
|
||||
return false;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
auto* e = nextListItem.get();
|
||||
|
||||
while (e != nullptr && ! e->hasTagName (requiredTagName))
|
||||
e = e->nextListItem;
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
void XmlElement::setTagName (StringRef newTagName)
|
||||
{
|
||||
jassert (isValidXmlName (newTagName));
|
||||
tagName = StringPool::getGlobalPool().getPooledString (newTagName);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
int XmlElement::getNumAttributes() const noexcept
|
||||
{
|
||||
return attributes.size();
|
||||
}
|
||||
|
||||
static const String& getEmptyStringRef() noexcept
|
||||
{
|
||||
static String empty;
|
||||
return empty;
|
||||
}
|
||||
|
||||
const String& XmlElement::getAttributeName (const int index) const noexcept
|
||||
{
|
||||
if (auto* att = attributes[index].get())
|
||||
return att->name.toString();
|
||||
|
||||
return getEmptyStringRef();
|
||||
}
|
||||
|
||||
const String& XmlElement::getAttributeValue (const int index) const noexcept
|
||||
{
|
||||
if (auto* att = attributes[index].get())
|
||||
return att->value;
|
||||
|
||||
return getEmptyStringRef();
|
||||
}
|
||||
|
||||
XmlElement::XmlAttributeNode* XmlElement::getAttribute (StringRef attributeName) const noexcept
|
||||
{
|
||||
for (auto* att = attributes.get(); 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 (auto* att = getAttribute (attributeName))
|
||||
return att->value;
|
||||
|
||||
return getEmptyStringRef();
|
||||
}
|
||||
|
||||
String XmlElement::getStringAttribute (StringRef attributeName, const String& defaultReturnValue) const
|
||||
{
|
||||
if (auto* att = getAttribute (attributeName))
|
||||
return att->value;
|
||||
|
||||
return defaultReturnValue;
|
||||
}
|
||||
|
||||
int XmlElement::getIntAttribute (StringRef attributeName, const int defaultReturnValue) const
|
||||
{
|
||||
if (auto* att = getAttribute (attributeName))
|
||||
return att->value.getIntValue();
|
||||
|
||||
return defaultReturnValue;
|
||||
}
|
||||
|
||||
double XmlElement::getDoubleAttribute (StringRef attributeName, const double defaultReturnValue) const
|
||||
{
|
||||
if (auto* att = getAttribute (attributeName))
|
||||
return att->value.getDoubleValue();
|
||||
|
||||
return defaultReturnValue;
|
||||
}
|
||||
|
||||
bool XmlElement::getBoolAttribute (StringRef attributeName, const bool defaultReturnValue) const
|
||||
{
|
||||
if (auto* att = getAttribute (attributeName))
|
||||
{
|
||||
auto 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 (auto* 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 (auto* att = attributes.get(); ; 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 (auto* 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 (auto* child = firstChildElement.get(); 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 (auto* child = firstChildElement.get(); 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)
|
||||
{
|
||||
auto newElement = new XmlElement (childTagName);
|
||||
addChildElement (newElement);
|
||||
return newElement;
|
||||
}
|
||||
|
||||
bool XmlElement::replaceChildElement (XmlElement* const currentChildElement,
|
||||
XmlElement* const newNode) noexcept
|
||||
{
|
||||
if (newNode != nullptr)
|
||||
{
|
||||
if (auto* 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 (auto* att = attributes.get(); att != nullptr; att = att->nextListItem)
|
||||
{
|
||||
if (! other->compareAttribute (att->name, att->value))
|
||||
return false;
|
||||
|
||||
++totalAtts;
|
||||
}
|
||||
|
||||
if (totalAtts != other->getNumAttributes())
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto* thisAtt = attributes.get();
|
||||
auto* otherAtt = other->attributes.get();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
auto* thisChild = firstChildElement.get();
|
||||
auto* otherChild = other->firstChildElement.get();
|
||||
|
||||
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 (auto* child = firstChildElement.get(); child != nullptr;)
|
||||
{
|
||||
auto* nextChild = child->nextListItem.get();
|
||||
|
||||
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 (auto* child = firstChildElement.get(); child != nullptr; child = child->nextListItem)
|
||||
{
|
||||
if (elementToLookFor == child)
|
||||
return this;
|
||||
|
||||
if (auto* found = child->findParentElementOf (elementToLookFor))
|
||||
return found;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void XmlElement::getChildElementsAsArray (XmlElement** elems) const noexcept
|
||||
{
|
||||
firstChildElement.copyToArray (elems);
|
||||
}
|
||||
|
||||
void XmlElement::reorderChildElements (XmlElement** elems, int num) noexcept
|
||||
{
|
||||
auto* e = elems[0];
|
||||
firstChildElement = e;
|
||||
|
||||
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 (auto* child = firstChildElement.get(); child != nullptr; child = child->nextListItem)
|
||||
mem << child->getAllSubText();
|
||||
|
||||
return mem.toUTF8();
|
||||
}
|
||||
|
||||
String XmlElement::getChildElementAllSubText (StringRef childTagName, const String& defaultReturnValue) const
|
||||
{
|
||||
if (auto* child = getChildByName (childTagName))
|
||||
return child->getAllSubText();
|
||||
|
||||
return defaultReturnValue;
|
||||
}
|
||||
|
||||
XmlElement* XmlElement::createTextElement (const String& text)
|
||||
{
|
||||
auto e = new XmlElement ((int) 0);
|
||||
e->setAttribute (juce_xmltextContentAttributeName, text);
|
||||
return e;
|
||||
}
|
||||
|
||||
bool XmlElement::isValidXmlName (StringRef text) noexcept
|
||||
{
|
||||
if (text.isEmpty() || ! isValidXmlNameStartCharacter (text.text.getAndAdvance()))
|
||||
return false;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (text.isEmpty())
|
||||
return true;
|
||||
|
||||
if (! isValidXmlNameBodyCharacter (text.text.getAndAdvance()))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void XmlElement::addTextElement (const String& text)
|
||||
{
|
||||
addChildElement (createTextElement (text));
|
||||
}
|
||||
|
||||
void XmlElement::deleteAllTextElements() noexcept
|
||||
{
|
||||
for (auto* child = firstChildElement.get(); child != nullptr;)
|
||||
{
|
||||
auto* next = child->nextListItem.get();
|
||||
|
||||
if (child->isTextElement())
|
||||
removeChildElement (child, true);
|
||||
|
||||
child = next;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
773
modules/juce_core/xml/juce_XmlElement.h
Normal file
773
modules/juce_core/xml/juce_XmlElement.h
Normal file
@ -0,0 +1,773 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/** 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 (auto* 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 (auto* 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());
|
||||
@endcode
|
||||
|
||||
@see 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;
|
||||
|
||||
//==============================================================================
|
||||
/** 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;
|
||||
|
||||
/** 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 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 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 a nullptr 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)
|
||||
{
|
||||
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 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;
|
||||
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*) = delete;
|
||||
|
||||
JUCE_LEAK_DETECTOR (XmlElement)
|
||||
};
|
||||
|
||||
} // namespace juce
|
Reference in New Issue
Block a user