- port to minGw

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