mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
GEEKING
This commit is contained in:
@@ -0,0 +1,648 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "RbxAssert.h"
|
||||
#include "V8Xml/XmlSerializer.h"
|
||||
#include "reflection/type.h"
|
||||
#include "rbx/Debug.h"
|
||||
#include <sstream>
|
||||
#include "util/base64.hpp"
|
||||
#include "util/exception.h"
|
||||
#include "V8DataModel/ContentProvider.h"
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
static const char* kCDATA_OPEN = "<![CDATA[";
|
||||
static const char* kCDATA_CLOSE = "]]>";
|
||||
|
||||
using std::vector;
|
||||
using std::string;
|
||||
|
||||
|
||||
bool isCloseTag(const char* s) {
|
||||
|
||||
if (s[0] != '<')
|
||||
return false;
|
||||
if (s[1] != '/')
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool endsWithClose(const std::string& test)
|
||||
{
|
||||
size_t size = test.size();
|
||||
if (size < 2)
|
||||
return false;
|
||||
const char* s = test.c_str();
|
||||
if (s[size-2] != '/')
|
||||
return false;
|
||||
if (s[size-1] != '>')
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
class Whitespaces
|
||||
{
|
||||
public:
|
||||
char data[256];
|
||||
Whitespaces()
|
||||
{
|
||||
memset(data, 0, 256);
|
||||
data['\n'] = 1;
|
||||
data['\t'] = 1;
|
||||
data[' '] = 1;
|
||||
data['\r'] = 1;
|
||||
data['\f'] = 1;
|
||||
}
|
||||
};
|
||||
|
||||
static Whitespaces whitespaces;
|
||||
|
||||
#define myIsWhiteSpace(c) (whitespaces.data[c])
|
||||
|
||||
|
||||
|
||||
void TextXmlParser::skipWhitespace()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
const int ch = buffer->sgetc();
|
||||
if (ch==EOF)
|
||||
return;
|
||||
if (!myIsWhiteSpace(static_cast<char>(ch)))
|
||||
return;
|
||||
buffer->sbumpc();
|
||||
}
|
||||
}
|
||||
|
||||
string TextXmlParser::readFirstTag()
|
||||
{
|
||||
// TODO: Opt: Can this be refined for speed???
|
||||
skipWhitespace();
|
||||
|
||||
char c = 0;
|
||||
// Skip past any "Byte-Order-Mark": http://en.wikipedia.org/wiki/Byte_Order_Mark
|
||||
int count = 0;
|
||||
do
|
||||
{
|
||||
if (buffer->sgetc()==EOF)
|
||||
throw std::runtime_error("Expected '<' but got EOF in Xml stream");
|
||||
if (count++>4)
|
||||
{
|
||||
std::string message = "tag expected after Byte-Order-Mark";
|
||||
throw std::runtime_error(message);
|
||||
}
|
||||
c = static_cast<char>(buffer->sbumpc());
|
||||
}
|
||||
while (c!='<');
|
||||
|
||||
std::string sb;
|
||||
sb += c;
|
||||
do
|
||||
{
|
||||
if (buffer->sgetc()==EOF)
|
||||
throw std::runtime_error("Expected '>' but got EOF in Xml stream");
|
||||
c = static_cast<char>(buffer->sbumpc());
|
||||
sb += c;
|
||||
}
|
||||
while (c != '>');
|
||||
|
||||
return sb;
|
||||
}
|
||||
|
||||
|
||||
|
||||
string TextXmlParser::readTag()
|
||||
{
|
||||
// TODO: Opt: Can this be refined for speed???
|
||||
skipWhitespace();
|
||||
|
||||
if (buffer->sgetc()==EOF)
|
||||
throw std::runtime_error("EOF encountered while reading Tag start");
|
||||
char c = static_cast<char>(buffer->sbumpc());
|
||||
if (c!='<')
|
||||
throw std::runtime_error("tag expected");
|
||||
|
||||
string sb;
|
||||
sb += c;
|
||||
|
||||
do
|
||||
{
|
||||
if (buffer->sgetc()==EOF)
|
||||
throw std::runtime_error("EOF encountered while reading Tag");
|
||||
c = static_cast<char>(buffer->sbumpc());
|
||||
sb += c;
|
||||
}
|
||||
while (c != '>');
|
||||
|
||||
return sb;
|
||||
}
|
||||
|
||||
bool needsDecoding(const std::string& source)
|
||||
{
|
||||
return source.find('&') != std::string::npos;
|
||||
}
|
||||
|
||||
string decodeString(const std::string& source)
|
||||
{
|
||||
string result;
|
||||
size_t pos = 0;
|
||||
while (pos<source.size()) {
|
||||
char c = source[pos++];
|
||||
if (c=='&')
|
||||
{
|
||||
// Get the entity between & and ;
|
||||
string entity;
|
||||
while (pos<source.size())
|
||||
{
|
||||
c = source[pos++];
|
||||
if (c!=';')
|
||||
entity += c;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
if (entity=="lt")
|
||||
result += '<';
|
||||
else if (entity=="gt")
|
||||
result += '>';
|
||||
else if (entity=="amp")
|
||||
result += '&';
|
||||
else if (entity=="quot")
|
||||
result += '"';
|
||||
else if (entity=="apos")
|
||||
result += '\'';
|
||||
else if (entity=="nbsp")
|
||||
// TODO: Should we support this??? Some files have it, I'm afraid
|
||||
result += ' ';
|
||||
else if (entity[0] == '#')
|
||||
{
|
||||
if (entity.size()<2)
|
||||
throw std::runtime_error("bad XML. No character code following #");
|
||||
|
||||
// TODO: Handle hexidecimal characters
|
||||
if (entity[1]=='x')
|
||||
throw std::runtime_error("Unable to parse hexidecimal character code");
|
||||
|
||||
result += atoi(entity.substr(1).c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Should we throw a parse error???
|
||||
RBXASSERT(false);
|
||||
result += "&" + entity + ";";
|
||||
}
|
||||
}
|
||||
else
|
||||
result += c;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
string TextXmlParser::readText(bool decode)
|
||||
{
|
||||
// <![CDATA[
|
||||
std::istream tmp(buffer);
|
||||
size_t curPos = tmp.tellg();
|
||||
char firstNine[9] = { 0 };
|
||||
buffer->sgetn(firstNine, 9);
|
||||
if (memcmp(firstNine, kCDATA_OPEN, 9) == 0)
|
||||
{
|
||||
char lastThree[3];
|
||||
lastThree[0] = buffer->sbumpc();
|
||||
lastThree[1] = buffer->sbumpc();
|
||||
lastThree[2] = buffer->sbumpc();
|
||||
std::stringstream ss;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (buffer->sgetc() == EOF)
|
||||
break;
|
||||
|
||||
if (memcmp(lastThree, kCDATA_CLOSE, 3) == 0)
|
||||
break;
|
||||
|
||||
ss << lastThree[0];
|
||||
lastThree[0] = lastThree[1];
|
||||
lastThree[1] = lastThree[2];
|
||||
lastThree[2] = buffer->sbumpc();
|
||||
}
|
||||
|
||||
// advance to EOF or <
|
||||
while (buffer->sgetc() != '<' && buffer->sgetc() != EOF)
|
||||
buffer->sbumpc();
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
else
|
||||
{
|
||||
tmp.seekg(curPos);
|
||||
}
|
||||
|
||||
skipWhitespace();
|
||||
|
||||
string sb;
|
||||
|
||||
while (true)
|
||||
{
|
||||
const int ch = buffer->sgetc();
|
||||
if (ch == EOF)
|
||||
break;
|
||||
if (static_cast<char>(ch) == '<')
|
||||
break;
|
||||
sb += buffer->sbumpc();
|
||||
}
|
||||
|
||||
// TODO: Optimize this by doing it inline with the above loop
|
||||
if (decode && needsDecoding(sb))
|
||||
return decodeString(sb);
|
||||
else
|
||||
return sb;
|
||||
}
|
||||
|
||||
void TextXmlWriter::xmlOrCDataEncodedWrite(std::ostream& stream, const std::string& textStr)
|
||||
{
|
||||
// if the text has a newline and does not have the CDATA close tag, then use cdata to encode
|
||||
if ((textStr.find("\n") != std::string::npos) &&
|
||||
(textStr.find(kCDATA_CLOSE) == std::string::npos))
|
||||
{
|
||||
stream << kCDATA_OPEN << textStr.c_str() << kCDATA_CLOSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
xmlEncodedWrite(stream, textStr);
|
||||
}
|
||||
}
|
||||
|
||||
void TextXmlWriter::xmlEncodedWrite(std::ostream& stream, const std::string& textStr)
|
||||
{
|
||||
const char* text = textStr.c_str();
|
||||
size_t l = textStr.size();
|
||||
|
||||
for (size_t i = 0; i < l; ++i) {
|
||||
// very primitive encoding of special characters!
|
||||
unsigned char c = *text++;
|
||||
if (c=='<')
|
||||
stream << "<";
|
||||
else if (c=='>')
|
||||
stream << ">";
|
||||
else if (c=='&')
|
||||
stream << "&";
|
||||
else if (c=='"')
|
||||
stream << """;
|
||||
else if (c=='\'')
|
||||
stream << "'";
|
||||
else if ((c<32 && c!=0xA && c!=0xD) || c>126)
|
||||
{
|
||||
char num[8];
|
||||
sprintf(num, "&#%d;", c);
|
||||
stream << num;
|
||||
}
|
||||
else
|
||||
stream << c;
|
||||
}
|
||||
}
|
||||
|
||||
void TextXmlWriter::writeOpenTag(const XmlElement* element, int depth)
|
||||
{
|
||||
for (int i = 0; i < depth; ++i)
|
||||
stream << '\t';
|
||||
|
||||
stream << '<' << element->getTag().toString();
|
||||
|
||||
const XmlAttribute* attribute = element->getFirstAttribute();
|
||||
while (attribute) {
|
||||
stream << ' ' << attribute->getTag().toString() << "=\"";
|
||||
xmlEncodedWrite(stream, attribute->toString(this));
|
||||
stream << '\"';
|
||||
attribute = element->getNextAttribute(attribute);
|
||||
}
|
||||
|
||||
stream << '>';
|
||||
}
|
||||
|
||||
void TextXmlWriter::writeCloseTag(const XmlElement* element, int depth)
|
||||
{
|
||||
for (int i = 0; i < depth; ++i)
|
||||
stream << '\t';
|
||||
stream << "</" << element->getTag().toString() << '>';
|
||||
}
|
||||
|
||||
string TextXmlParser::removeTag(const string& contents, int& index)
|
||||
{
|
||||
RBXASSERT (contents[0] == '<');
|
||||
int start = 1;
|
||||
while (myIsWhiteSpace(contents[start]) && (start < (int)contents.length()))
|
||||
start++;
|
||||
|
||||
index = start;
|
||||
while (!myIsWhiteSpace(contents[index]) && contents[index] != '>' && (index < (int)contents.length()))
|
||||
index++;
|
||||
RBXASSERT(index > start);
|
||||
|
||||
return contents.substr(start, index - start);
|
||||
}
|
||||
|
||||
static bool findNextToken(const string& contents, int& index)
|
||||
{
|
||||
// Find the first non-whitespace character starting at index
|
||||
const char* c = contents.c_str() + index;
|
||||
while (true)
|
||||
{
|
||||
RBXASSERT(*c);
|
||||
|
||||
if (*c == '>')
|
||||
return false;
|
||||
if (*c == 0) // for safety
|
||||
return false;
|
||||
if (!myIsWhiteSpace(*c))
|
||||
return true;
|
||||
|
||||
index++;
|
||||
c++;
|
||||
}
|
||||
}
|
||||
|
||||
XmlElement* TextXmlParser::parseAttributes(const string& currentTag)
|
||||
{
|
||||
int index = 0;
|
||||
const string tagName(removeTag(currentTag, index));
|
||||
XmlElement* newElement = new XmlElement(XmlTag::lookup(tagName));
|
||||
|
||||
while (::findNextToken(currentTag, index))
|
||||
{
|
||||
const size_t equal = currentTag.find('=', index);
|
||||
const string tag(currentTag.substr(index, equal - index));
|
||||
|
||||
// if we didn't find an equals, we need to exit or we can potentially be
|
||||
// stuck in an infinite loop and continuously generate attributes
|
||||
if( equal == std::string::npos )
|
||||
throw std::runtime_error("Unable to parse XML attributes. '=' not found");
|
||||
|
||||
const int firstQuote = equal + 1;
|
||||
const int lastQuote = currentTag.find('\"', firstQuote + 1);
|
||||
|
||||
if (lastQuote == std::string::npos)
|
||||
throw std::runtime_error("Unable to parse XML attributes. '\"' not found");
|
||||
|
||||
string text(currentTag.substr(firstQuote + 1, lastQuote - firstQuote - 1));
|
||||
if (needsDecoding(text))
|
||||
text = decodeString(text);
|
||||
|
||||
index = lastQuote + 1;
|
||||
|
||||
newElement->addAttribute(XmlTag::lookup(tag), text);
|
||||
}
|
||||
|
||||
return newElement;
|
||||
}
|
||||
|
||||
|
||||
XmlParser::XmlParser(std::streambuf* buffer)
|
||||
:buffer(buffer)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/*****
|
||||
|
||||
While Not EOF(Input XML Document)
|
||||
Tag = Next tag from the document
|
||||
LastOpenTag = Top tag in Stack
|
||||
|
||||
If Tag is an open tag
|
||||
Add Tag as the child of LastOpenTag
|
||||
Push Tag in Stack
|
||||
Else
|
||||
// Tag is a close tag
|
||||
If Tag is the matching close tag of LastOpenTag
|
||||
Pop Stack
|
||||
If Stack is empty
|
||||
Parse is complete
|
||||
End If
|
||||
Else
|
||||
// Invalid tag nesting
|
||||
Report error
|
||||
End If
|
||||
End If
|
||||
End While
|
||||
|
||||
The centerpiece of this algorithm is the tag stack, which keeps track of the open tags
|
||||
that have been taken from the input document but have not been matched by their close tags.
|
||||
The top item on the stack is always the last open tag encountered.
|
||||
|
||||
Except for the first tag, each new open tag will be a child tag of the last open tag.
|
||||
So the parser adds the new tag as a child of the last open tag and then pushes it onto
|
||||
the stack, where it becomes the new last open tag. On the other hand, if the input tag
|
||||
is a close tag, it has to match the last open tag. A non-matching close tag indicates
|
||||
an XML syntax error based on the proper-nesting rule. When the close tag matches the last
|
||||
open tag, the parser pops the last open tag from the stack because parsing for that tag is
|
||||
complete. This process continues until the stack is empty. At that point, you're finished
|
||||
parsing the entire document. Listing 2 shows the entire source code for the
|
||||
SimpleDOMParser.parse method.
|
||||
**/
|
||||
|
||||
std::auto_ptr<XmlElement> TextXmlParser::parse()
|
||||
{
|
||||
if (buffer->sgetc()==EOF)
|
||||
throw std::runtime_error("TextXmlParser::parse empty file");
|
||||
|
||||
bool firstTimeThrough = true;
|
||||
while (true) {
|
||||
std::string currentTag;
|
||||
|
||||
if (firstTimeThrough)
|
||||
{
|
||||
firstTimeThrough = false;
|
||||
// Skip the <?> tag
|
||||
currentTag = readFirstTag();
|
||||
if (currentTag.substr(0,2)=="<?")
|
||||
continue;
|
||||
}
|
||||
else
|
||||
currentTag = readTag(); // finds the text between "<" and ">" inclusive
|
||||
|
||||
XmlElement* currentElement = elements.empty() ? NULL : elements.top();
|
||||
|
||||
if (isCloseTag(currentTag.c_str())) {
|
||||
|
||||
// no open tag
|
||||
if (currentElement == NULL)
|
||||
throw RBX::runtime_error("TextXmlParser::parse - Got close tag %s without open tag.", currentTag.c_str());
|
||||
|
||||
// pop up the previous open tag
|
||||
elements.pop();
|
||||
|
||||
if (elements.empty()) {
|
||||
// document processing is over
|
||||
RBXASSERT(currentElement!=NULL);
|
||||
return std::auto_ptr<XmlElement>(currentElement);
|
||||
}
|
||||
}
|
||||
else {
|
||||
XmlElement* newElement = parseAttributes(currentTag);
|
||||
elements.push(newElement);
|
||||
|
||||
// special-case the "Content" tag
|
||||
// TODO: Move this into the Reflection::Property reading code instead?
|
||||
if (newElement->getTag()==RBX::Reflection::Type::singleton<RBX::ContentId>().tag)
|
||||
{
|
||||
XmlAttribute* xsinil = newElement->findAttribute(name_xsinil);
|
||||
bool val; //Note: 'nil' is already define on OSX
|
||||
if (xsinil!=NULL && xsinil->getValue(val) && val)
|
||||
{
|
||||
// no data
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this->readText(false)!="")
|
||||
{
|
||||
// Old files might include an integer "ContentId" rather than a sub-element
|
||||
}
|
||||
else
|
||||
{
|
||||
string contentChild = readTag(); // finds the text between "<" and ">" inclusive
|
||||
string tagName = contentChild.substr(1, contentChild.length()-2);
|
||||
|
||||
if (tagName.compare(0, 6, "binary") == 0) // The binary tag may have attributes, so we have to compare a substring
|
||||
{
|
||||
// We no longer support binary content
|
||||
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_WARNING, "Not reading binary data");
|
||||
readText(false);
|
||||
newElement->setValue(RBX::ContentId());
|
||||
}
|
||||
else if (tag_hash==tagName)
|
||||
{
|
||||
// We no longer support binary content
|
||||
readText(false);
|
||||
newElement->setValue(RBX::ContentId());
|
||||
}
|
||||
else if (tagName.compare(0, 3, "url") == 0)
|
||||
{
|
||||
newElement->setValue(RBX::ContentId(this->readText(true).c_str()));
|
||||
}
|
||||
else if (tag_null==tagName)
|
||||
{
|
||||
newElement->setValue(RBX::ContentId());
|
||||
}
|
||||
else
|
||||
throw RBX::runtime_error("TextXmlParser::parse - Unknown tag '%s'.", tagName.substr(0, 32).c_str());
|
||||
|
||||
std::string closingTag = readTag(); // closing tag
|
||||
if (!isCloseTag(closingTag.c_str()))
|
||||
throw RBX::runtime_error("TextXmlParser::parse - '%s' should be a closing tag", closingTag.substr(0, 32).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
// read the text between the open and close tag
|
||||
else
|
||||
newElement->setValue(readText(true));
|
||||
|
||||
// add new element as a child element of
|
||||
// the current element
|
||||
if (currentElement != NULL)
|
||||
currentElement->addChild(newElement);
|
||||
|
||||
if (endsWithClose(currentTag))
|
||||
// pop up this tag
|
||||
elements.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Write open tag(depth)
|
||||
Write text(0)
|
||||
If !children {
|
||||
write close tag(0)
|
||||
}
|
||||
else {
|
||||
CR
|
||||
|
||||
depth++
|
||||
|
||||
write each child(depth)
|
||||
|
||||
depth--
|
||||
|
||||
write close tag(depth)
|
||||
}
|
||||
|
||||
CR
|
||||
|
||||
return writer.data();
|
||||
*/
|
||||
|
||||
XmlWriter::XmlWriter(std::ostream& stream)
|
||||
: stream(stream)
|
||||
{
|
||||
}
|
||||
|
||||
void TextXmlWriter::serialize(const XmlElement* xmlNode)
|
||||
{
|
||||
serialize(xmlNode, 0);
|
||||
}
|
||||
|
||||
|
||||
void TextXmlWriter::serializeNode(const XmlElement* xmlNode, int depth)
|
||||
{
|
||||
// Special handling for RBX::ContentId
|
||||
// TODO: move to Reflection::Property?
|
||||
if (xmlNode->isValueType<RBX::ContentId>())
|
||||
{
|
||||
RBX::ContentId contentId;
|
||||
xmlNode->getValue(contentId);
|
||||
|
||||
writeOpenTag(xmlNode, depth);
|
||||
|
||||
if (xmlNode->findAttribute(name_xsinil)!=NULL)
|
||||
{
|
||||
// Just write out the tag and nothing inside
|
||||
return;
|
||||
}
|
||||
|
||||
if (contentId.isNull())
|
||||
{
|
||||
stream << "<null></null>";
|
||||
}
|
||||
else
|
||||
{
|
||||
stream << "<url>";
|
||||
xmlEncodedWrite(stream, contentId.c_str());
|
||||
stream << "</url>";
|
||||
}
|
||||
|
||||
return; // done!
|
||||
}
|
||||
|
||||
writeOpenTag(xmlNode, depth);
|
||||
xmlOrCDataEncodedWrite(stream, xmlNode->toString(this)); // may not have text
|
||||
}
|
||||
|
||||
|
||||
void TextXmlWriter::serialize(const XmlElement* xmlNode, int depth)
|
||||
{
|
||||
if (xmlNode) {
|
||||
|
||||
serializeNode(xmlNode, depth);
|
||||
|
||||
const XmlElement* child = xmlNode->firstChild();
|
||||
|
||||
if (child!=NULL) {
|
||||
do {
|
||||
stream << '\n';
|
||||
serialize(child, depth+1);
|
||||
} while ((child = xmlNode->nextChild(child)));
|
||||
stream << '\n';
|
||||
|
||||
writeCloseTag(xmlNode, depth);
|
||||
}
|
||||
else {
|
||||
// no children - write on same line
|
||||
writeCloseTag(xmlNode, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user