This commit is contained in:
watrabi
2025-09-18 17:55:52 -04:00
commit 977f1ff4b8
15030 changed files with 17324420 additions and 0 deletions
File diff suppressed because it is too large Load Diff
+264
View File
@@ -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;
}
+535
View File
@@ -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;
}
}
+116
View File
@@ -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;
}
}
+467
View File
@@ -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 "";
}
}
+648
View File
@@ -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 << "&lt;";
else if (c=='>')
stream << "&gt;";
else if (c=='&')
stream << "&amp;";
else if (c=='"')
stream << "&quot;";
else if (c=='\'')
stream << "&apos;";
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);
}
}
}