mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-07 13:57:48 +00:00
GEEKING
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,264 @@
|
||||
/* Copyright 2003-2005 ROBLOX Corporation, All Rights Reserved */
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "V8Xml/SerializerV2.h"
|
||||
#include "V8Xml/XmlSerializer.h"
|
||||
#include "V8DataModel/DataModel.h"
|
||||
#include "v8datamodel/contentprovider.h"
|
||||
#include "rbx/Debug.h"
|
||||
#include "util/standardout.h"
|
||||
|
||||
#include "v8xml/SerializerBinary.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
#ifdef _WIN32
|
||||
using std::mem_fun1;
|
||||
#else
|
||||
#include <ext/functional>
|
||||
using __gnu_cxx::mem_fun1;
|
||||
#endif
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
using namespace RBX;
|
||||
|
||||
// An implementation of IReferenceBinder
|
||||
class ArchiveBinder : public RBX::MergeBinder
|
||||
{
|
||||
private:
|
||||
typedef RBX::MergeBinder Super;
|
||||
// A map of temporary IDs to Referents
|
||||
std::map<std::string, InstanceHandle> idMap;
|
||||
|
||||
struct IDREFBinding {
|
||||
const XmlNameValuePair* valueIDREF;
|
||||
Reflection::DescribedBase* propertyOwner;
|
||||
const IIDREF* idref;
|
||||
};
|
||||
std::list<IDREFBinding> idrefBindings;
|
||||
|
||||
public:
|
||||
|
||||
virtual bool processID(const XmlNameValuePair* valueID, Reflection::DescribedBase* source) {
|
||||
if (!Super::processID(valueID, source)) {
|
||||
std::string s;
|
||||
bool foundString = valueID->getValue(s);
|
||||
RBXASSERT(foundString);
|
||||
// RBXASSERT(idMap.find(s)==idMap.end()); // This is in BugHost for Erik - implies we are writing references multiple times
|
||||
// See this file to make the assert pop:
|
||||
// game.Workspace:insertContent("http://watrbx.wtf/asset/?id=2286288")
|
||||
|
||||
idMap[s].linkTo(shared_from(source));
|
||||
if (source)
|
||||
{
|
||||
source->setXmlId(s);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
virtual bool processIDREF(const XmlNameValuePair* valueIDREF, Reflection::DescribedBase* propertyOwner, const IIDREF* idref) {
|
||||
if (!Super::processIDREF(valueIDREF, propertyOwner, idref)) {
|
||||
IDREFBinding binding = {valueIDREF, propertyOwner, idref};
|
||||
idrefBindings.push_back( binding );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool resolveIDREF(IDREFBinding binding)
|
||||
{
|
||||
std::string s;
|
||||
bool foundString = binding.valueIDREF->getValue(s);
|
||||
RBXASSERT(foundString);
|
||||
|
||||
// The following 3 cases should have been handled during the processIDREF phase
|
||||
RBXASSERT (value_IDREF_nil!=s);
|
||||
RBXASSERT (value_IDREF_null!=s);
|
||||
RBXASSERT (s!="");
|
||||
|
||||
// Find the InstanceHandle belonging to the requested ID
|
||||
std::map<std::string, InstanceHandle>::iterator iter = idMap.find(s);
|
||||
if (iter!=idMap.end())
|
||||
{
|
||||
// TODO: should we give the handle over to valueIDREF?
|
||||
assign(binding.idref, binding.propertyOwner, iter->second);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Unable to find the requested InstanceHandle
|
||||
assign(binding.idref, binding.propertyOwner, InstanceHandle(NULL));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool resolveRefs()
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
count += count_if(
|
||||
idrefBindings.begin(),
|
||||
idrefBindings.end(),
|
||||
std::bind1st(mem_fun1(&ArchiveBinder::resolveIDREF),this)
|
||||
);
|
||||
|
||||
return Super::resolveRefs() && (count == idrefBindings.size());
|
||||
}
|
||||
};
|
||||
|
||||
void SerializerV2::load(std::istream& stream, RBX::DataModel* dataModel)
|
||||
{
|
||||
// See file format spec in CWorkspace::Save()
|
||||
char header[8];
|
||||
if (!stream.read(header, 8).good())
|
||||
throw std::runtime_error("SerializerV2::load can't read header");
|
||||
|
||||
stream.clear();
|
||||
stream.seekg (0, std::ios::beg);
|
||||
|
||||
if (memcmp(header, SerializerBinary::kMagicHeader, 8) == 0)
|
||||
{
|
||||
// read the binary content
|
||||
SerializerBinary::deserialize(stream, dataModel);
|
||||
}
|
||||
else
|
||||
{
|
||||
// read the XML content
|
||||
loadXML(stream, dataModel);
|
||||
}
|
||||
}
|
||||
|
||||
void SerializerV2::loadInstances(std::istream& stream, RBX::Instances& result)
|
||||
{
|
||||
char header[8];
|
||||
if (!stream.read(header, 8).good())
|
||||
throw std::runtime_error("SerializerV2::loadInstances can't read header");
|
||||
|
||||
stream.clear();
|
||||
stream.seekg(0, std::ios::beg);
|
||||
|
||||
if (memcmp(header, SerializerBinary::kMagicHeader, 8) == 0)
|
||||
{
|
||||
// read the binary content
|
||||
SerializerBinary::deserialize(stream, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
// read the XML content
|
||||
TextXmlParser machine(stream.rdbuf());
|
||||
std::auto_ptr<XmlElement> root(machine.parse());
|
||||
|
||||
ArchiveBinder binder;
|
||||
loadInstancesXML(root.get(), result, binder, RBX::SerializationCreator);
|
||||
}
|
||||
}
|
||||
|
||||
void SerializerV2::loadXML(std::istream& stream, RBX::DataModel* dataModel)
|
||||
{
|
||||
TextXmlParser machine(stream.rdbuf());
|
||||
std::auto_ptr<XmlElement> root(machine.parse());
|
||||
|
||||
if (root->getTag() == tag_roblox)
|
||||
{
|
||||
if(const XmlAttribute* version = root->findAttribute(tag_version))
|
||||
{
|
||||
if (!version || !version->getValue(schemaVersionLoading))
|
||||
{
|
||||
throw std::runtime_error("SerializerV2::loadXML no version number");
|
||||
}
|
||||
else if (schemaVersionLoading<4)
|
||||
{
|
||||
throw std::runtime_error("SerializerV2::loadXML schemaVersionLoading<4");
|
||||
}
|
||||
else
|
||||
{
|
||||
ArchiveBinder binder;
|
||||
|
||||
dataModel->readChildren(root.get(), binder, SerializationCreator);
|
||||
|
||||
binder.resolveRefs();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
schemaVersionLoading = 1;
|
||||
throw std::runtime_error("SerializerV2::loadXML ill-formed XML. No Roblox tag");
|
||||
}
|
||||
|
||||
// Should we need to do this???
|
||||
dataModel->setDirty(false);
|
||||
}
|
||||
|
||||
shared_ptr<Instance> SerializerV2::loadInstanceXML(const XmlElement* itemElement, IReferenceBinder& binder, CreatorRole creatorRole)
|
||||
{
|
||||
const RBX::Name* className = NULL;
|
||||
if (itemElement->findAttributeValue(tag_class, className))
|
||||
{
|
||||
shared_ptr<Instance> instance = Creatable<Instance>::createByName(*className, RBX::SerializationCreator);
|
||||
if (instance)
|
||||
{
|
||||
instance->read(itemElement, binder, creatorRole);
|
||||
return instance;
|
||||
}
|
||||
else
|
||||
StandardOut::singleton()->printf(MESSAGE_WARNING, "Unknown object class \"%s\" while reading XML", className ? className->c_str() : "");
|
||||
}
|
||||
return shared_ptr<Instance>();
|
||||
}
|
||||
|
||||
void SerializerV2::loadInstancesFromText(const XmlElement* root, Instances& result)
|
||||
{
|
||||
ArchiveBinder binder;
|
||||
loadInstancesXML(root, result, binder, RBX::SerializationCreator);
|
||||
}
|
||||
|
||||
void SerializerV2::loadInstancesXML(const XmlElement* root, Instances& result, IReferenceBinder& binder, CreatorRole creatorRole)
|
||||
{
|
||||
// TODO: This code has a lot in common with Instance::readChildren and with SerializerV2::merge
|
||||
// Find a way of combining these code chunks
|
||||
bool v4model = false;
|
||||
|
||||
if (root->getTag() == tag_roblox) {
|
||||
const XmlAttribute* version = root->findAttribute(tag_version);
|
||||
if (version!=NULL && version->getValue(schemaVersionLoading) && schemaVersionLoading >= 4) {
|
||||
v4model = true;
|
||||
|
||||
const XmlElement* childElement = root->findFirstChildByTag(tag_Item);
|
||||
while (childElement)
|
||||
{
|
||||
if(shared_ptr<Instance> instance = loadInstanceXML(childElement, binder, creatorRole))
|
||||
result.push_back(instance);
|
||||
childElement = root->findNextChildWithSameTag(childElement);
|
||||
}
|
||||
binder.resolveRefs();
|
||||
// TODO: ASSERT
|
||||
//RBXASSERT(resolvedBindings);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
XmlElement* SerializerV2::newRootElement()
|
||||
{
|
||||
return newRootElement("");
|
||||
}
|
||||
XmlElement* SerializerV2::newRootElement(const std::string& type)
|
||||
{
|
||||
static const XmlTag& tag_xmlnsxmime = Name::declare("xmlns:xmime");
|
||||
|
||||
XmlElement* root = new XmlElement(tag_roblox);
|
||||
root->addAttribute(tag_xmlnsxmime, "http://www.w3.org/2005/05/xmlmime");
|
||||
root->addAttribute(tag_xmlnsxsi, "http://www.w3.org/2001/XMLSchema-instance");
|
||||
root->addAttribute(tag_xsinoNamespaceSchemaLocation, "http://www.watrbx.wtf/roblox.xsd");
|
||||
root->addAttribute(tag_version, SerializerV2::CURRENT_SCHEMA_VERSION);
|
||||
if(!type.empty()){
|
||||
root->addAttribute(tag_assettype, type);
|
||||
}
|
||||
|
||||
// Used for schema validation with Roblox.xsd:
|
||||
root->addChild(new XmlElement(tag_External, &value_IDREF_null));
|
||||
root->addChild(new XmlElement(tag_External, &value_IDREF_nil));
|
||||
|
||||
return root;
|
||||
}
|
||||
@@ -0,0 +1,535 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "V8Xml/WebParser.h"
|
||||
#include "V8Xml/XmlSerializer.h"
|
||||
#include "V8Xml/Serializer.h"
|
||||
#include "rbx/make_shared.h"
|
||||
#include "Util/SafeToLower.h"
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
|
||||
#include "rapidjson/document.h"
|
||||
#include "rapidjson/writer.h"
|
||||
#include "rapidjson/stringbuffer.h"
|
||||
|
||||
namespace RBX
|
||||
{
|
||||
bool WebParser::parseWebListResponse(std::istream& stream, RBX::Reflection::ValueArray& result)
|
||||
{
|
||||
TextXmlParser machine(stream.rdbuf());
|
||||
std::auto_ptr<XmlElement> root(machine.parse());
|
||||
if(root->getTag() == tag_WebList)
|
||||
{
|
||||
return loadList(root.get(), result);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool WebParser::parseWebGenericResponse(std::istream& stream, RBX::Reflection::Variant& result)
|
||||
{
|
||||
TextXmlParser machine(stream.rdbuf());
|
||||
std::auto_ptr<XmlElement> root(machine.parse());
|
||||
return parseWebGenericResponse(root.get(), result);
|
||||
}
|
||||
bool WebParser::parseWebGenericResponse(const XmlElement* root, RBX::Reflection::Variant& result)
|
||||
{
|
||||
if (root->getTag() == tag_WebTable)
|
||||
{
|
||||
shared_ptr<Reflection::ValueMap> table(new Reflection::ValueMap());
|
||||
if(loadTable(root, *table)){
|
||||
result = shared_ptr<const RBX::Reflection::ValueMap>(table);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if(root->getTag() == tag_WebList)
|
||||
{
|
||||
shared_ptr<Reflection::ValueArray> list(rbx::make_shared<Reflection::ValueArray>());
|
||||
if(loadList(root, *list)){
|
||||
result = shared_ptr<const RBX::Reflection::ValueArray>(list);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if(root->getTag() == tag_WebValue)
|
||||
{
|
||||
if(loadValue(root, result)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool WebParser::loadList(const XmlElement* listElement, RBX::Reflection::ValueArray& result)
|
||||
{
|
||||
const XmlElement* childEntry = listElement->findFirstChildByTag(tag_WebValue);
|
||||
while (childEntry)
|
||||
{
|
||||
Reflection::Variant value;
|
||||
if(loadValue(childEntry, value)){
|
||||
result.push_back(value);
|
||||
}
|
||||
else{
|
||||
return false;
|
||||
}
|
||||
childEntry = listElement->findNextChildWithSameTag(childEntry);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool WebParser::loadTable(const XmlElement* tableElement, RBX::Reflection::ValueMap& result)
|
||||
{
|
||||
const XmlElement* childEntry = tableElement->findFirstChildByTag(tag_WebEntry);
|
||||
while (childEntry)
|
||||
{
|
||||
std::string key;
|
||||
Reflection::Variant value;
|
||||
if(loadEntry(childEntry, key, value)){
|
||||
result[key] = value;
|
||||
}
|
||||
else{
|
||||
return false;
|
||||
}
|
||||
childEntry = tableElement->findNextChildWithSameTag(childEntry);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool WebParser::loadValue(const XmlElement* valueElement, RBX::Reflection::Variant& value)
|
||||
{
|
||||
if(const XmlElement* tableElement = valueElement->findFirstChildByTag(tag_WebTable)){
|
||||
shared_ptr<Reflection::ValueMap> table(new Reflection::ValueMap());
|
||||
if(loadTable(tableElement, *table)){
|
||||
value = shared_ptr<const RBX::Reflection::ValueMap>(table);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if((tableElement = valueElement->findFirstChildByTag(tag_WebList))){
|
||||
shared_ptr<Reflection::ValueArray> list(rbx::make_shared<Reflection::ValueArray>());
|
||||
if(loadList(tableElement, *list)){
|
||||
value = shared_ptr<const RBX::Reflection::ValueArray>(list);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else{
|
||||
if(const XmlAttribute* typeAttribute = valueElement->findAttribute(tag_WebType))
|
||||
{
|
||||
std::string type;
|
||||
if(typeAttribute->getValue(type))
|
||||
{
|
||||
if(type == "boolean")
|
||||
{
|
||||
bool boolResult;
|
||||
if(valueElement->getValue(boolResult)){
|
||||
value = boolResult;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if(type == "string")
|
||||
{
|
||||
std::string stringResult;
|
||||
if(valueElement->getValue(stringResult)){
|
||||
value = stringResult;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if(type == "number")
|
||||
{
|
||||
double doubleResult;
|
||||
if(valueElement->getValue(doubleResult)){
|
||||
value = doubleResult;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if(type == "integer")
|
||||
{
|
||||
int intResult;
|
||||
if (valueElement->getValue(intResult))
|
||||
{
|
||||
value = intResult;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if(type == "instance")
|
||||
{
|
||||
if(const XmlElement* robloxRoot = valueElement->findFirstChildByTag(tag_roblox)){
|
||||
Serializer serializer;
|
||||
Instances instances;
|
||||
serializer.loadInstancesFromText(robloxRoot, instances);
|
||||
if(instances.size() == 1)
|
||||
{
|
||||
value = instances[0];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//fallback to legacy parsing
|
||||
if(valueElement->isValueType<std::string>()){
|
||||
std::string stringResult;
|
||||
if(valueElement->getValue(stringResult)){
|
||||
std::string lowerStringResult = stringResult;
|
||||
safeToLower(lowerStringResult);
|
||||
if(lowerStringResult == "true")
|
||||
value = true;
|
||||
else if(lowerStringResult == "false")
|
||||
value = false;
|
||||
else
|
||||
value = stringResult;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool WebParser::loadEntry(const XmlElement* entryElement, std::string& key, RBX::Reflection::Variant& value)
|
||||
{
|
||||
bool gotKey = false;
|
||||
bool gotValue = false;
|
||||
if(const XmlElement* keyElement = entryElement->findFirstChildByTag(tag_WebKey)){
|
||||
if(keyElement->isValueType<std::string>()){
|
||||
if(keyElement->getValue(key)){
|
||||
gotKey = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(const XmlElement* valueElement = entryElement->findFirstChildByTag(tag_WebValue)){
|
||||
gotValue = loadValue(valueElement, value);
|
||||
}
|
||||
return gotKey && gotValue;
|
||||
}
|
||||
|
||||
bool legacyPopulateValueTableFromPtree(const boost::property_tree::ptree& propTree, shared_ptr<Reflection::ValueTable>& valueTable)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(propTree.size() > 0)
|
||||
{
|
||||
boost::property_tree::ptree::const_iterator end = propTree.end();
|
||||
int count = 1; // standard to start at 1 for lua
|
||||
for (boost::property_tree::ptree::const_iterator it = propTree.begin(); it != end; ++it)
|
||||
{
|
||||
// make sure we aren't overwriting the empty key in the table
|
||||
std::string key = it->first;
|
||||
if(key.empty())
|
||||
{
|
||||
std::ostringstream convert;
|
||||
convert << count;
|
||||
std::string countString = convert.str();
|
||||
key = countString;
|
||||
}
|
||||
|
||||
// unfortunately this is the easiest way to get types from ptree
|
||||
if(it->second.get_value_optional<int>())
|
||||
(*valueTable)[key] = it->second.get_value<int>();
|
||||
else if(it->second.get_value_optional<bool>())
|
||||
(*valueTable)[key] = it->second.get_value<bool>();
|
||||
else if(it->second.get_value_optional<std::string>())
|
||||
{
|
||||
std::string stringValue = it->second.get_value<std::string>();
|
||||
if(!stringValue.empty())
|
||||
(*valueTable)[key] = stringValue;
|
||||
}
|
||||
|
||||
if(it->second.size() > 0) // we have a nested table, get the info
|
||||
{
|
||||
shared_ptr<Reflection::ValueTable> subMap(rbx::make_shared<Reflection::ValueTable>());
|
||||
if(!legacyPopulateValueTableFromPtree(it->second,subMap))
|
||||
return false;
|
||||
|
||||
Reflection::Variant variantValue = shared_ptr<const RBX::Reflection::ValueTable>(subMap);
|
||||
(*valueTable)[key] = variantValue;
|
||||
}
|
||||
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(std::exception const&)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Reflection::Variant populateValueTableFromPtree(const boost::property_tree::ptree& propTree)
|
||||
{
|
||||
if(propTree.size() > 0)
|
||||
{
|
||||
boost::property_tree::ptree::const_iterator end = propTree.end();
|
||||
int count = 1; // standard to start at 1 for lua
|
||||
|
||||
Reflection::Variant value;
|
||||
shared_ptr<Reflection::ValueTable> subMap;
|
||||
shared_ptr<Reflection::ValueArray> subArray;
|
||||
|
||||
|
||||
for (boost::property_tree::ptree::const_iterator it = propTree.begin(); it != end; ++it)
|
||||
{
|
||||
// make sure we aren't overwriting the empty key in the table
|
||||
std::string key = it->first;
|
||||
|
||||
// unfortunately this is the easiest way to get types from ptree
|
||||
if(it->second.get_value_optional<int>())
|
||||
value = it->second.get_value<int>();
|
||||
else if(it->second.get_value_optional<bool>())
|
||||
value = it->second.get_value<bool>();
|
||||
else if(it->second.get_value_optional<std::string>())
|
||||
{
|
||||
std::string stringValue = it->second.get_value<std::string>();
|
||||
if(!stringValue.empty())
|
||||
value = stringValue;
|
||||
}
|
||||
|
||||
if(it->second.size() > 0) // we have a nested table, get the info
|
||||
{
|
||||
value = populateValueTableFromPtree(it->second);
|
||||
}
|
||||
|
||||
if(key.empty())
|
||||
{
|
||||
RBXASSERT(!subMap);
|
||||
if(!subArray)
|
||||
subArray = rbx::make_shared<Reflection::ValueArray>();
|
||||
subArray->push_back(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
RBXASSERT(!subArray);
|
||||
if(!subMap)
|
||||
subMap = rbx::make_shared<Reflection::ValueTable>();
|
||||
(*subMap)[key] = value;
|
||||
}
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
return subMap ?
|
||||
Reflection::Variant(shared_ptr<const Reflection::ValueTable>(subMap)) :
|
||||
Reflection::Variant(shared_ptr<const Reflection::ValueArray>(subArray));
|
||||
}
|
||||
else
|
||||
{
|
||||
return Reflection::Variant();
|
||||
}
|
||||
}
|
||||
|
||||
Reflection::Variant populateValueTableFromRapidJson(rapidjson::Value& node)
|
||||
{
|
||||
Reflection::Variant v;
|
||||
if(node.IsInt())
|
||||
v = node.GetInt();
|
||||
else if(node.IsNumber())
|
||||
v = node.GetDouble();
|
||||
else if(node.IsBool())
|
||||
v = node.GetBool();
|
||||
else if(node.IsString())
|
||||
v = std::string(node.GetString());
|
||||
else if(node.IsObject())
|
||||
{
|
||||
shared_ptr<Reflection::ValueTable> subMap = rbx::make_shared<Reflection::ValueTable>();
|
||||
for(rapidjson::Value::MemberIterator it = node.MemberBegin(); it != node.MemberEnd(); ++it)
|
||||
{
|
||||
Reflection::Variant subValue = populateValueTableFromRapidJson(it->value);
|
||||
(*subMap)[it->name.GetString()] = subValue;
|
||||
}
|
||||
v = shared_ptr<const Reflection::ValueTable>(subMap);
|
||||
}
|
||||
else if(node.IsArray())
|
||||
{
|
||||
shared_ptr<Reflection::ValueArray> subArray = rbx::make_shared<Reflection::ValueArray>();
|
||||
for(rapidjson::Value::ValueIterator it = node.Begin(); it != node.End(); ++it)
|
||||
{
|
||||
Reflection::Variant subValue = populateValueTableFromRapidJson(*it);
|
||||
subArray->push_back(subValue);
|
||||
}
|
||||
v = shared_ptr<const Reflection::ValueArray>(subArray);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
bool WebParser::parseJSONObject(const std::string& rawWebResponse, Reflection::Variant& result)
|
||||
{
|
||||
rapidjson::Document root;
|
||||
root.Parse<0>(rawWebResponse.c_str());
|
||||
|
||||
if(root.HasParseError())
|
||||
return false;
|
||||
|
||||
RBXASSERT(root.IsObject() || root.IsArray());
|
||||
|
||||
result = populateValueTableFromRapidJson(root);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool checkStringForASCII(const std::string& value)
|
||||
{
|
||||
for(std::string::const_iterator it = value.begin(); it != value.end(); ++it)
|
||||
{
|
||||
if(*it < 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool writeToRapidJSON(const Reflection::Variant& value, rapidjson::Value& node, WebParser::NonJSONBehavior skip,
|
||||
rapidjson::Document::AllocatorType& allocator)
|
||||
{
|
||||
if(value.isVoid())
|
||||
node.SetNull();
|
||||
else if(value.isFloat())
|
||||
node.SetDouble(value.get<double>());
|
||||
else if(value.isType<bool>())
|
||||
node.SetBool(value.get<bool>());
|
||||
else if(value.isNumber())
|
||||
node.SetInt(value.get<int>());
|
||||
else if(value.isString())
|
||||
{
|
||||
std::string strValue = value.get<std::string>();
|
||||
if(!checkStringForASCII(strValue))
|
||||
return false;
|
||||
|
||||
node.SetString(strValue.c_str(),allocator);
|
||||
}
|
||||
else if(value.isType<shared_ptr<const Reflection::ValueTable> >())
|
||||
{
|
||||
node.SetObject();
|
||||
shared_ptr<const Reflection::ValueTable> table = value.get<shared_ptr<const Reflection::ValueTable> >();
|
||||
for(Reflection::ValueTable::const_iterator it = table->begin(); it != table->end(); ++it)
|
||||
{
|
||||
rapidjson::Value subNode;
|
||||
if(!writeToRapidJSON(it->second, subNode, skip, allocator))
|
||||
return false;
|
||||
node.AddMember(it->first.c_str(), subNode, allocator);
|
||||
}
|
||||
}
|
||||
else if(value.isType<shared_ptr<const Reflection::ValueArray> >())
|
||||
{
|
||||
node.SetArray();
|
||||
shared_ptr<const Reflection::ValueArray> array = value.get<shared_ptr<const Reflection::ValueArray> >();
|
||||
for(Reflection::ValueArray::const_iterator it = array->begin(); it != array->end(); ++it)
|
||||
{
|
||||
rapidjson::Value subNode;
|
||||
if(!writeToRapidJSON(*it, subNode, skip, allocator))
|
||||
return false;
|
||||
|
||||
node.PushBack(subNode, allocator);
|
||||
}
|
||||
}
|
||||
else if(skip == WebParser::SkipNonJSON)
|
||||
{
|
||||
node.SetNull();
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WebParser::writeJSON(const Reflection::Variant& value, std::string& result, NonJSONBehavior skip)
|
||||
{
|
||||
rapidjson::Document root;
|
||||
if (!writeToRapidJSON(value, root, skip, root.GetAllocator()))
|
||||
return false;
|
||||
|
||||
rapidjson::StringBuffer buffer;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
||||
root.Accept(writer);
|
||||
|
||||
result = buffer.GetString();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WebParser::parseJSONTable(const std::string& rawWebResponse, shared_ptr<const Reflection::ValueTable>& valueTable)
|
||||
{
|
||||
Reflection::Variant result;
|
||||
if(!parseJSONObject(rawWebResponse, result))
|
||||
return false;
|
||||
|
||||
if(!result.isType<shared_ptr<const Reflection::ValueTable> >())
|
||||
return false;
|
||||
|
||||
valueTable = result.cast<shared_ptr<const RBX::Reflection::ValueTable> >();
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
bool WebParser::parseJSONArray(const std::string& rawWebResponse, shared_ptr<const Reflection::ValueArray>& valueArray)
|
||||
{
|
||||
Reflection::Variant result;
|
||||
if(!parseJSONObject(rawWebResponse, result))
|
||||
return false;
|
||||
|
||||
if(!result.isType<shared_ptr<const Reflection::ValueArray> >())
|
||||
return false;
|
||||
|
||||
valueArray = result.cast<shared_ptr<const RBX::Reflection::ValueArray> >();
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
bool WebParser::legacyParseWebJSONResponse(std::stringstream& rawWebResponse, shared_ptr<const Reflection::ValueTable>& valueTable)
|
||||
{
|
||||
// if the stringstream has nothing to be read, return
|
||||
if (rawWebResponse.rdbuf()->in_avail() == 0)
|
||||
return false;
|
||||
|
||||
// parse the raw stream into a data structure we can read from
|
||||
boost::property_tree::ptree propTree;
|
||||
try
|
||||
{
|
||||
boost::mutex::scoped_lock(JSONmutex);
|
||||
boost::property_tree::read_json(rawWebResponse, propTree);
|
||||
}
|
||||
catch (std::exception const&)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
shared_ptr<Reflection::ValueTable> table(rbx::make_shared<Reflection::ValueTable>());
|
||||
bool result = legacyPopulateValueTableFromPtree(propTree, table);
|
||||
|
||||
if(result)
|
||||
valueTable = table;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool WebParser::ptreeParseWebJSONResponse(std::stringstream& rawWebResponse, shared_ptr<const Reflection::ValueTable>& valueTable)
|
||||
{
|
||||
// if the stringstream has nothing to be read, return
|
||||
if (rawWebResponse.rdbuf()->in_avail() == 0)
|
||||
return false;
|
||||
|
||||
Reflection::Variant result;
|
||||
// parse the raw stream into a data structure we can read from
|
||||
boost::property_tree::ptree propTree;
|
||||
try
|
||||
{
|
||||
{
|
||||
boost::mutex::scoped_lock(JSONmutex);
|
||||
boost::property_tree::read_json(rawWebResponse, propTree);
|
||||
}
|
||||
|
||||
result = populateValueTableFromPtree(propTree);
|
||||
}
|
||||
catch (std::exception const&)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(result.isType<shared_ptr<const Reflection::ValueTable> >())
|
||||
{
|
||||
valueTable = result.get<shared_ptr<const Reflection::ValueTable> >();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "V8Xml/WebSerializer.h"
|
||||
#include "V8Xml/Serializer.h"
|
||||
#include "rbx/make_shared.h"
|
||||
|
||||
namespace RBX
|
||||
{
|
||||
XmlElement* WebSerializer::writeTable(const RBX::Reflection::ValueMap& result)
|
||||
{
|
||||
XmlElement* table = new XmlElement(tag_WebTable);
|
||||
|
||||
RBX::Reflection::ValueMap::const_iterator end = result.end();
|
||||
for(RBX::Reflection::ValueMap::const_iterator iter = result.begin(); iter != end; ++iter)
|
||||
{
|
||||
if(XmlElement* child = writeEntry(iter->first, iter->second))
|
||||
{
|
||||
table->addChild(child);
|
||||
}
|
||||
}
|
||||
return table;
|
||||
}
|
||||
XmlElement* WebSerializer::writeList(const RBX::Reflection::ValueArray& result)
|
||||
{
|
||||
XmlElement* list = new XmlElement(tag_WebList);
|
||||
|
||||
RBX::Reflection::ValueArray::const_iterator end = result.end();
|
||||
for(RBX::Reflection::ValueArray::const_iterator iter = result.begin(); iter != end; ++iter)
|
||||
{
|
||||
if(XmlElement* child = writeValue(*iter))
|
||||
list->addChild(child);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
XmlElement* WebSerializer::writeEntry(const std::string& key, const RBX::Reflection::Variant& value)
|
||||
{
|
||||
if(XmlElement* valueElement = writeValue(value))
|
||||
{
|
||||
XmlElement* entry = new XmlElement(tag_WebEntry);
|
||||
{
|
||||
XmlElement* keyElement = new XmlElement(tag_WebKey);
|
||||
keyElement->setValue(key);
|
||||
entry->addChild(keyElement);
|
||||
}
|
||||
entry->addChild(valueElement);
|
||||
return entry;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
XmlElement* WebSerializer::writeValue(const RBX::Reflection::Variant& value)
|
||||
{
|
||||
if(value.isType<double>() || value.isType<float>())
|
||||
{
|
||||
XmlElement* result = new XmlElement(tag_WebValue);
|
||||
result->addAttribute(tag_WebType, "number");
|
||||
result->setValue(value.get<std::string>());
|
||||
return result;
|
||||
}
|
||||
if(value.isType<std::string>())
|
||||
{
|
||||
XmlElement* result = new XmlElement(tag_WebValue);
|
||||
result->addAttribute(tag_WebType, "string");
|
||||
result->setValue(value.get<std::string>());
|
||||
return result;
|
||||
}
|
||||
if(value.isType<bool>())
|
||||
{
|
||||
XmlElement* result = new XmlElement(tag_WebValue);
|
||||
result->addAttribute(tag_WebType, "boolean");
|
||||
result->setValue(value.get<bool>() ? "true" : "false");
|
||||
return result;
|
||||
}
|
||||
if(value.isType<shared_ptr<RBX::Instance> >())
|
||||
{
|
||||
shared_ptr<Instance> instance = value.get<shared_ptr<Instance> >();
|
||||
if(instance)
|
||||
{
|
||||
if(XmlElement* instanceRoot = Instance::toNewXmlRoot(instance.get(), SerializationCreator))
|
||||
{
|
||||
XmlElement* result = new XmlElement(tag_WebValue);
|
||||
result->addAttribute(tag_WebType, "instance");
|
||||
result->addChild(instanceRoot);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
if(value.isType<shared_ptr<const RBX::Reflection::ValueMap> >())
|
||||
{
|
||||
shared_ptr<const RBX::Reflection::ValueMap> valueMap = value.get<shared_ptr<const RBX::Reflection::ValueMap> >();
|
||||
if(XmlElement* valueMapRoot = writeTable(*valueMap))
|
||||
{
|
||||
XmlElement* result = new XmlElement(tag_WebValue);
|
||||
result->addAttribute(tag_WebType, "table");
|
||||
result->addChild(valueMapRoot);
|
||||
return result;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
if(value.isType<shared_ptr<const RBX::Reflection::ValueArray> >())
|
||||
{
|
||||
shared_ptr<const RBX::Reflection::ValueArray> valueList = value.get<shared_ptr<const RBX::Reflection::ValueArray> >();
|
||||
if(XmlElement* valueListRoot = writeList(*valueList))
|
||||
{
|
||||
XmlElement* result = new XmlElement(tag_WebValue);
|
||||
result->addAttribute(tag_WebType, "list");
|
||||
result->addChild(valueListRoot);
|
||||
return result;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,467 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "RbxAssert.h"
|
||||
#include "V8Xml/XmlElement.h"
|
||||
#include "V8Xml/XmlSerializer.h"
|
||||
#include "Util/Guid.h"
|
||||
#include "Util/Utilities.h"
|
||||
#include "rbx/Debug.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
using namespace G3D;
|
||||
|
||||
using namespace RBX;
|
||||
|
||||
const RBX::Name& value_IDREF_null = Name::declare("null");
|
||||
const RBX::Name& value_IDREF_nil = Name::declare("nil");
|
||||
|
||||
const XmlTag& name_xsinil = Name::declare("xsi:nil");
|
||||
const XmlTag& name_xsitype = Name::declare("xsi:type");
|
||||
const XmlTag& tag_xmlnsxsi = Name::declare("xmlns:xsi");
|
||||
|
||||
// TODO: Put these in a file that knows about the Roblox schema
|
||||
const XmlTag& name_root = Name::declare("root");
|
||||
const XmlTag& name_referent = Name::declare("referent");
|
||||
const XmlTag& tag_roblox = Name::declare("roblox");
|
||||
const XmlTag& tag_version = Name::declare("version");
|
||||
const XmlTag& tag_assettype = Name::declare("assettype");
|
||||
const XmlTag& tag_External = Name::declare("External");
|
||||
const XmlTag& name_Ref = Name::declare("Ref");
|
||||
const XmlTag& name_token = Name::declare("token");
|
||||
const XmlTag& name_name = Name::declare("name");
|
||||
const XmlTag& tag_Refs = Name::declare("Refs");
|
||||
const XmlTag& tag_X = Name::declare("X");
|
||||
const XmlTag& tag_Y = Name::declare("Y");
|
||||
const XmlTag& tag_Z = Name::declare("Z");
|
||||
|
||||
const XmlTag& tag_R00 = Name::declare("R00");
|
||||
const XmlTag& tag_R01 = Name::declare("R01");
|
||||
const XmlTag& tag_R02 = Name::declare("R02");
|
||||
const XmlTag& tag_R10 = Name::declare("R10");
|
||||
const XmlTag& tag_R11 = Name::declare("R11");
|
||||
const XmlTag& tag_R12 = Name::declare("R12");
|
||||
const XmlTag& tag_R20 = Name::declare("R20");
|
||||
const XmlTag& tag_R21 = Name::declare("R21");
|
||||
const XmlTag& tag_R22 = Name::declare("R22");
|
||||
|
||||
const XmlTag& tag_R = Name::declare("R");
|
||||
const XmlTag& tag_G = Name::declare("G");
|
||||
const XmlTag& tag_B = Name::declare("B");
|
||||
const XmlTag& tag_class = Name::declare("class");
|
||||
const XmlTag& tag_Item = Name::declare("Item");
|
||||
const XmlTag& tag_Properties = Name::declare("Properties");
|
||||
const XmlTag& tag_Feature = Name::declare("Feature");
|
||||
|
||||
const XmlTag& tag_hash = Name::declare("hash");
|
||||
const XmlTag& tag_null = Name::lookup("null"); // already declared elsewhere
|
||||
const XmlTag& tag_mimeType = Name::declare("mimeType");
|
||||
|
||||
const XmlTag& tag_S = Name::declare("S");
|
||||
const XmlTag& tag_O = Name::declare("O");
|
||||
|
||||
const XmlTag& tag_XS = Name::declare("XS");
|
||||
const XmlTag& tag_XO = Name::declare("XO");
|
||||
const XmlTag& tag_YS = Name::declare("YS");
|
||||
const XmlTag& tag_YO = Name::declare("YO");
|
||||
|
||||
const XmlTag& tag_faces = Name::declare("faces");
|
||||
const XmlTag& tag_axes = Name::declare("axes");
|
||||
|
||||
const XmlTag& tag_Origin = Name::declare("origin");
|
||||
const XmlTag& tag_Direction = Name::declare("direction");
|
||||
|
||||
const XmlTag& tag_Min = Name::declare("min");
|
||||
const XmlTag& tag_Max = Name::declare("max");
|
||||
|
||||
const XmlTag& tag_WebTable = Name::declare("Table");
|
||||
const XmlTag& tag_WebList = Name::declare("List");
|
||||
const XmlTag& tag_WebEntry = Name::declare("Entry");
|
||||
const XmlTag& tag_WebKey = Name::declare("Key");
|
||||
const XmlTag& tag_WebValue = Name::declare("Value");
|
||||
const XmlTag& tag_WebType = Name::declare("Type");
|
||||
|
||||
const XmlTag& tag_customPhysProp = Name::declare("CustomPhysics");
|
||||
const XmlTag& tag_customDensity = Name::declare("Density");
|
||||
const XmlTag& tag_customFriction = Name::declare("Friction");
|
||||
const XmlTag& tag_customElasticity = Name::declare("Elasticity");
|
||||
const XmlTag& tag_customFrictionWeight = Name::declare("FrictionWeight");
|
||||
const XmlTag& tag_customElasticityWeight = Name::declare("ElasticityWeight");
|
||||
|
||||
const XmlTag& tag_xsinoNamespaceSchemaLocation = Name::declare("xsi:noNamespaceSchemaLocation");
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool XmlElement::isXsiNil() const {
|
||||
const XmlAttribute* xnil = findAttribute(name_xsinil);
|
||||
bool isNil;
|
||||
return xnil!=NULL && xnil->getValue(isNil) && isNil;
|
||||
}
|
||||
|
||||
const XmlElement* XmlElement::findFirstChildByTag(const XmlTag& _tag) const {
|
||||
for (const XmlElement* child = firstChild(); child!=NULL; child = child->nextSibling())
|
||||
if (child->getTag()==_tag)
|
||||
return child;
|
||||
return NULL;
|
||||
}
|
||||
const XmlElement* XmlElement::findNextChildWithSameTag(const XmlElement* node) const {
|
||||
for (const XmlElement* child = node->nextSibling(); child!=NULL; child = child->nextSibling())
|
||||
if (child->getTag()==node->getTag())
|
||||
return child;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const XmlAttribute* XmlElement::findAttribute(const XmlTag& _tag) const {
|
||||
for (const XmlAttribute* attribute = getFirstAttribute(); attribute!=NULL; attribute = getNextAttribute(attribute))
|
||||
if (attribute->getTag()==_tag)
|
||||
return attribute;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
XmlAttribute* XmlElement::findAttribute(const XmlTag& _tag) {
|
||||
for (XmlAttribute* attribute = getFirstAttribute(); attribute!=NULL; attribute = getNextAttribute(attribute))
|
||||
if (attribute->getTag()==_tag)
|
||||
return attribute;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void XmlNameValuePair::clearValue() const {
|
||||
switch (valueType) {
|
||||
case STRING:
|
||||
delete stringValue;
|
||||
break;
|
||||
case CONTENTID:
|
||||
delete contentIdValue;
|
||||
break;
|
||||
case HANDLE:
|
||||
delete handleValue;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
valueType = NONE;
|
||||
}
|
||||
|
||||
bool XmlNameValuePair::isValueEqual(const RBX::Name* value) const {
|
||||
switch (valueType) {
|
||||
case STRING:
|
||||
return *value==*stringValue;
|
||||
case NAME:
|
||||
return *value==*nameValue;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool XmlNameValuePair::getValue(const RBX::Name*& value) const
|
||||
{
|
||||
if (valueType==NAME) {
|
||||
value = nameValue;
|
||||
return true;
|
||||
}
|
||||
if (valueType==STRING) {
|
||||
value = &RBX::Name::declare(stringValue->c_str());
|
||||
clearValue();
|
||||
nameValue = value;
|
||||
valueType = NAME;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template<>
|
||||
bool XmlNameValuePair::isValueType<ContentId>() const
|
||||
{
|
||||
return valueType==CONTENTID;
|
||||
}
|
||||
|
||||
template<>
|
||||
bool XmlNameValuePair::isValueType<std::string>() const
|
||||
{
|
||||
return valueType==STRING;
|
||||
}
|
||||
|
||||
template<>
|
||||
bool XmlNameValuePair::isValueType<int>() const
|
||||
{
|
||||
return valueType==INT;
|
||||
}
|
||||
|
||||
template<>
|
||||
bool XmlNameValuePair::isValueType<unsigned int>() const
|
||||
{
|
||||
return valueType==UINT;
|
||||
}
|
||||
|
||||
template<>
|
||||
bool XmlNameValuePair::isValueType<bool>() const
|
||||
{
|
||||
return valueType==BOOL;
|
||||
}
|
||||
|
||||
template<>
|
||||
bool XmlNameValuePair::isValueType<float>() const
|
||||
{
|
||||
return valueType==FLOAT;
|
||||
}
|
||||
|
||||
template<>
|
||||
bool XmlNameValuePair::isValueType<double>() const
|
||||
{
|
||||
return valueType==DOUBLE;
|
||||
}
|
||||
|
||||
template<>
|
||||
bool XmlNameValuePair::isValueType<const RBX::Name*>() const
|
||||
{
|
||||
return valueType==NAME;
|
||||
}
|
||||
|
||||
template<>
|
||||
bool XmlNameValuePair::isValueType<RBX::InstanceHandle>() const
|
||||
{
|
||||
return valueType==HANDLE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool XmlNameValuePair::getValue(RBX::ContentId& value) const
|
||||
{
|
||||
if (valueType==CONTENTID) {
|
||||
value = *contentIdValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (valueType==STRING) {
|
||||
value = ContentId(*stringValue);
|
||||
clearValue();
|
||||
contentIdValue = new ContentId(value);
|
||||
valueType = CONTENTID;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool XmlNameValuePair::getValue(std::string& value) const
|
||||
{
|
||||
if (valueType==STRING) {
|
||||
value = *stringValue;
|
||||
return true;
|
||||
}
|
||||
if (valueType==NAME) {
|
||||
value = nameValue ? nameValue->c_str() : "";
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool XmlNameValuePair::getValue(int& value) const
|
||||
{
|
||||
if (valueType==INT) {
|
||||
value = intValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (valueType==STRING) {
|
||||
if (StringConverter<int>::convertToValue(*stringValue, value)) {
|
||||
clearValue();
|
||||
intValue = value;
|
||||
valueType = INT;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool XmlNameValuePair::getValue(unsigned int& value) const
|
||||
{
|
||||
if (valueType==UINT) {
|
||||
value = uintValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (valueType==STRING) {
|
||||
if (StringConverter<unsigned int>::convertToValue(*stringValue, value)) {
|
||||
clearValue();
|
||||
uintValue = value;
|
||||
valueType = UINT;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool XmlNameValuePair::getValue(bool& value) const
|
||||
{
|
||||
if (valueType==BOOL) {
|
||||
value = boolValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (valueType==STRING) {
|
||||
if (StringConverter<bool>::convertToValue(*stringValue, value)) {
|
||||
clearValue();
|
||||
boolValue = value;
|
||||
valueType = BOOL;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool XmlNameValuePair::getValue(float& value) const
|
||||
{
|
||||
if (valueType==FLOAT) {
|
||||
value = floatValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (valueType==STRING) {
|
||||
if (RBX::StringConverter<float>::convertToValue(*stringValue, value)) {
|
||||
clearValue();
|
||||
floatValue = value;
|
||||
valueType = FLOAT;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
RBXASSERT(valueType!=DOUBLE); // No provision (yet) for converting from double back to float
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool XmlNameValuePair::getValue(double& value) const
|
||||
{
|
||||
if (valueType==DOUBLE) {
|
||||
value = doubleValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (valueType==FLOAT) {
|
||||
value = (double)floatValue;
|
||||
clearValue();
|
||||
doubleValue = value;
|
||||
valueType = DOUBLE;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (valueType==STRING) {
|
||||
if (RBX::StringConverter<double>::convertToValue(*stringValue, value)) {
|
||||
clearValue();
|
||||
doubleValue = value;
|
||||
valueType = DOUBLE;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool XmlNameValuePair::getValue(RBX::InstanceHandle &value) const {
|
||||
|
||||
if (valueType==NAME && *nameValue==value_IDREF_null) {
|
||||
clearValue();
|
||||
handleValue = new RBX::InstanceHandle(NULL);
|
||||
valueType = HANDLE;
|
||||
} else if (valueType==STRING && value_IDREF_null==*stringValue) {
|
||||
clearValue();
|
||||
handleValue = new RBX::InstanceHandle(NULL);
|
||||
valueType = HANDLE;
|
||||
} else if (valueType==STRING && *stringValue=="") {
|
||||
// legacy files didn't use the "null" keyword
|
||||
clearValue();
|
||||
handleValue = new RBX::InstanceHandle(NULL);
|
||||
valueType = HANDLE;
|
||||
}
|
||||
|
||||
if (valueType==HANDLE) {
|
||||
value = *handleValue;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
std::string XmlNameValuePair::toString(XmlWriter* writer) const
|
||||
{
|
||||
switch (valueType)
|
||||
{
|
||||
case BOOL:
|
||||
{
|
||||
return RBX::StringConverter<bool>::convertToString(boolValue);
|
||||
};
|
||||
|
||||
case INT:
|
||||
{
|
||||
return RBX::StringConverter<int>::convertToString(intValue);
|
||||
};
|
||||
|
||||
case UINT:
|
||||
{
|
||||
return RBX::StringConverter<unsigned int>::convertToString(uintValue);
|
||||
};
|
||||
|
||||
case FLOAT:
|
||||
{
|
||||
return RBX::StringConverter<float>::convertToString(floatValue);
|
||||
};
|
||||
|
||||
case DOUBLE:
|
||||
{
|
||||
return RBX::StringConverter<double>::convertToString(doubleValue);
|
||||
};
|
||||
|
||||
case NAME:
|
||||
return nameValue->toString();
|
||||
|
||||
case HANDLE:
|
||||
if (handleValue->getTarget()==NULL)
|
||||
return value_IDREF_null.toString();
|
||||
else {
|
||||
shared_ptr<Reflection::DescribedBase> base = handleValue->getTarget();
|
||||
const std::string* lastId = base->getXmlId();
|
||||
|
||||
if (lastId == NULL || !writer->isValidId(*lastId, *handleValue))
|
||||
{
|
||||
std::string newId;
|
||||
Guid::generateRBXGUID(newId);
|
||||
RBXASSERT(writer->isValidId(newId, *handleValue));
|
||||
|
||||
// set the id back into the object in case we serialize again before loading
|
||||
base->setXmlId(newId);
|
||||
lastId = base->getXmlId();
|
||||
RBXASSERT(lastId != NULL);
|
||||
}
|
||||
|
||||
writer->recordId(*lastId, *handleValue);
|
||||
return *lastId;
|
||||
}
|
||||
|
||||
case STRING:
|
||||
return *stringValue;
|
||||
|
||||
case CONTENTID:
|
||||
return contentIdValue->toString();
|
||||
|
||||
case NONE:
|
||||
return "";
|
||||
|
||||
default:
|
||||
RBXASSERT(false);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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