2018-06-17 20:34:53 +08:00
|
|
|
/*
|
|
|
|
==============================================================================
|
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2019-06-23 03:41:38 +08:00
|
|
|
std::unique_ptr<XmlElement> parseXML (const String& textToParse)
|
|
|
|
{
|
|
|
|
return std::unique_ptr<XmlElement> (XmlDocument::parse (textToParse));
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<XmlElement> parseXML (const File& fileToParse)
|
|
|
|
{
|
|
|
|
return std::unique_ptr<XmlElement> (XmlDocument::parse (fileToParse));
|
|
|
|
}
|
|
|
|
|
2018-06-17 20:34:53 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|