use std::{
io::Read,
collections::HashMap,
};
use log::trace;
use rbx_reflection::RbxPropertyTypeDescriptor;
use rbx_dom_weak::{
RbxId,
RbxInstanceProperties,
RbxTree,
RbxValue,
RbxValueConversion,
RbxValueType,
SharedString,
};
use crate::{
core::find_canonical_property_descriptor,
types::read_value_xml,
error::{DecodeError, DecodeErrorKind},
};
use crate::deserializer_core::{XmlEventReader, XmlReadEvent};
pub fn decode_internal<R: Read>(source: R, options: DecodeOptions) -> Result<RbxTree, DecodeError> {
let mut tree = RbxTree::new(RbxInstanceProperties {
class_name: "DataModel".to_owned(),
name: "DataModel".to_owned(),
properties: HashMap::new(),
});
let root_id = tree.get_root_id();
let mut iterator = XmlEventReader::from_source(source);
let mut state = ParseState::new(&mut tree, options);
deserialize_root(&mut iterator, &mut state, root_id)?;
apply_referent_rewrites(&mut state);
apply_shared_string_rewrites(&mut state);
Ok(tree)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DecodePropertyBehavior {
IgnoreUnknown,
ReadUnknown,
ErrorOnUnknown,
NoReflection,
#[doc(hidden)]
__Nonexhaustive,
}
#[derive(Debug, Clone)]
pub struct DecodeOptions {
property_behavior: DecodePropertyBehavior,
}
impl DecodeOptions {
#[inline]
pub fn new() -> Self {
DecodeOptions {
property_behavior: DecodePropertyBehavior::IgnoreUnknown,
}
}
#[inline]
pub fn property_behavior(self, property_behavior: DecodePropertyBehavior) -> Self {
DecodeOptions {
property_behavior,
..self
}
}
pub(crate) fn use_reflection(&self) -> bool {
self.property_behavior != DecodePropertyBehavior::NoReflection
}
}
impl Default for DecodeOptions {
fn default() -> DecodeOptions {
DecodeOptions::new()
}
}
pub struct ParseState<'a> {
tree: &'a mut RbxTree,
options: DecodeOptions,
metadata: HashMap<String, String>,
referents_to_ids: HashMap<String, RbxId>,
referent_rewrites: Vec<ReferentRewrite>,
known_shared_strings: HashMap<String, SharedString>,
shared_string_rewrites: Vec<SharedStringRewrite>,
}
struct ReferentRewrite {
id: RbxId,
property_name: String,
referent_value: String,
}
struct SharedStringRewrite {
id: RbxId,
property_name: String,
shared_string_hash: String,
}
impl<'a> ParseState<'a> {
fn new(tree: &mut RbxTree, options: DecodeOptions) -> ParseState {
ParseState {
tree,
options,
metadata: HashMap::new(),
referents_to_ids: HashMap::new(),
referent_rewrites: Vec::new(),
known_shared_strings: HashMap::new(),
shared_string_rewrites: Vec::new(),
}
}
pub fn add_referent_rewrite(&mut self, id: RbxId, property_name: String, referent_value: String) {
self.referent_rewrites.push(ReferentRewrite {
id,
property_name,
referent_value,
});
}
pub fn add_shared_string_rewrite(&mut self, id: RbxId, property_name: String, shared_string_hash: String) {
self.shared_string_rewrites.push(SharedStringRewrite {
id,
property_name,
shared_string_hash,
});
}
}
fn apply_referent_rewrites(state: &mut ParseState) {
for rewrite in &state.referent_rewrites {
let new_value = match state.referents_to_ids.get(&rewrite.referent_value) {
Some(id) => *id,
None => continue
};
let instance = state.tree.get_instance_mut(rewrite.id)
.expect("rbx_xml bug: had ID in referent rewrite list that didn't end up in the tree");
instance.properties.insert(rewrite.property_name.clone(), RbxValue::Ref {
value: Some(new_value),
});
}
}
fn apply_shared_string_rewrites(state: &mut ParseState) {
for rewrite in &state.shared_string_rewrites {
let new_value = match state.known_shared_strings.get(&rewrite.shared_string_hash) {
Some(v) => v.clone(),
None => continue
};
let instance = state.tree.get_instance_mut(rewrite.id)
.expect("rbx_xml bug: had ID in SharedString rewrite list that didn't end up in the tree");
instance.properties.insert(rewrite.property_name.clone(), RbxValue::SharedString {
value: new_value,
});
}
}
fn deserialize_root<R: Read>(
reader: &mut XmlEventReader<R>,
state: &mut ParseState,
parent_id: RbxId
) -> Result<(), DecodeError> {
match reader.expect_next()? {
XmlReadEvent::StartDocument { .. } => {},
_ => unreachable!(),
}
let doc_attributes = reader.expect_start_with_name("roblox")?;
let mut doc_version = None;
for attribute in doc_attributes.into_iter() {
match attribute.name.local_name.as_str() {
"version" => doc_version = Some(attribute.value),
_ => {}
}
}
let doc_version = doc_version
.ok_or_else(|| reader.error(DecodeErrorKind::MissingAttribute("version")))?;
if doc_version != "4" {
return Err(reader.error(DecodeErrorKind::WrongDocVersion(doc_version)));
}
loop {
match reader.expect_peek()? {
XmlReadEvent::StartElement { name, .. } => {
match name.local_name.as_str() {
"Item" => {
deserialize_instance(reader, state, parent_id)?;
}
"External" => {
reader.eat_unknown_tag()?;
}
"Meta" => {
deserialize_metadata(reader, state)?;
}
"SharedStrings" => {
deserialize_shared_string_dict(reader, state)?;
}
_ => {
let event = reader.expect_next().unwrap();
return Err(reader.error(DecodeErrorKind::UnexpectedXmlEvent(event)));
}
}
}
XmlReadEvent::EndElement { name } => {
if name.local_name == "roblox" {
reader.expect_next().unwrap();
break;
} else {
let event = reader.expect_next().unwrap();
return Err(reader.error(DecodeErrorKind::UnexpectedXmlEvent(event)));
}
}
XmlReadEvent::EndDocument => break,
_ => {
let event = reader.expect_next().unwrap();
return Err(reader.error(DecodeErrorKind::UnexpectedXmlEvent(event)));
}
}
}
Ok(())
}
fn deserialize_metadata<R: Read>(reader: &mut XmlEventReader<R>, state: &mut ParseState) -> Result<(), DecodeError> {
let name = {
let attributes = reader.expect_start_with_name("Meta")?;
let mut name = None;
for attribute in attributes.into_iter() {
match attribute.name.local_name.as_str() {
"name" => name = Some(attribute.value),
_ => {}
}
}
name.ok_or_else(|| reader.error(DecodeErrorKind::MissingAttribute("name")))?
};
let value = reader.read_characters()?;
reader.expect_end_with_name("Meta")?;
state.metadata.insert(name, value);
Ok(())
}
fn deserialize_shared_string_dict<R: Read>(
reader: &mut XmlEventReader<R>,
state: &mut ParseState,
) -> Result<(), DecodeError> {
reader.expect_start_with_name("SharedStrings")?;
loop {
match reader.expect_peek()? {
XmlReadEvent::StartElement { name, .. } => {
if name.local_name == "SharedString" {
deserialize_shared_string(reader, state)?;
} else {
let event = reader.expect_next().unwrap();
return Err(reader.error(DecodeErrorKind::UnexpectedXmlEvent(event)));
}
}
XmlReadEvent::EndElement { name } => {
if name.local_name == "SharedStrings" {
break;
} else {
let event = reader.expect_next().unwrap();
return Err(reader.error(DecodeErrorKind::UnexpectedXmlEvent(event)));
}
}
_ => {
let event = reader.expect_next().unwrap();
return Err(reader.error(DecodeErrorKind::UnexpectedXmlEvent(event)));
}
}
}
reader.expect_end_with_name("SharedStrings")?;
Ok(())
}
fn deserialize_shared_string<R: Read>(
reader: &mut XmlEventReader<R>,
state: &mut ParseState,
) -> Result<(), DecodeError> {
let attributes = reader.expect_start_with_name("SharedString")?;
let mut md5_hash = None;
for attribute in attributes.into_iter() {
if attribute.name.local_name == "md5" {
md5_hash = Some(attribute.value);
break;
}
}
let md5_hash = md5_hash
.ok_or_else(|| reader.error(DecodeErrorKind::MissingAttribute("md5")))?;
let encoded_buffer = reader.read_characters()?
.replace("\n", "");
let buffer = base64::decode(&encoded_buffer)
.map_err(|e| reader.error(e))?;
let value = SharedString::new(buffer);
state.known_shared_strings.insert(md5_hash, value);
reader.expect_end_with_name("SharedString")?;
Ok(())
}
fn deserialize_instance<R: Read>(
reader: &mut XmlEventReader<R>,
state: &mut ParseState,
parent_id: RbxId,
) -> Result<(), DecodeError> {
let (class_name, referent) = {
let attributes = reader.expect_start_with_name("Item")?;
let mut class = None;
let mut referent = None;
for attribute in attributes.into_iter() {
match attribute.name.local_name.as_str() {
"class" => class = Some(attribute.value),
"referent" => referent = Some(attribute.value),
_ => {},
}
}
let class = class
.ok_or_else(|| reader.error(DecodeErrorKind::MissingAttribute("class")))?;
(class, referent)
};
trace!("Class {} with referent {:?}", class_name, referent);
let instance_props = RbxInstanceProperties {
class_name,
name: String::new(),
properties: HashMap::new(),
};
let instance_id = state.tree.insert_instance(instance_props, parent_id);
if let Some(referent) = referent {
state.referents_to_ids.insert(referent, instance_id);
}
let mut properties: HashMap<String, RbxValue> = HashMap::new();
loop {
match reader.expect_peek()? {
XmlReadEvent::StartElement { name, .. } => match name.local_name.as_str() {
"Properties" => {
deserialize_properties(reader, state, instance_id, &mut properties)?;
}
"Item" => {
deserialize_instance(reader, state, instance_id)?;
}
_ => {
let event = reader.expect_next().unwrap();
return Err(reader.error(DecodeErrorKind::UnexpectedXmlEvent(event)));
}
}
XmlReadEvent::EndElement { name } => {
if name.local_name != "Item" {
let event = reader.expect_next().unwrap();
return Err(reader.error(DecodeErrorKind::UnexpectedXmlEvent(event)));
}
reader.expect_next().unwrap();
break;
}
_ => {
let event = reader.expect_next().unwrap();
return Err(reader.error(DecodeErrorKind::UnexpectedXmlEvent(event)));
}
}
}
let instance = state.tree.get_instance_mut(instance_id).unwrap();
instance.name = match properties.remove("Name") {
Some(value) => match value {
RbxValue::String { value } => value,
_ => return Err(reader.error(DecodeErrorKind::NameMustBeString(value.get_type()))),
},
None => instance.class_name.clone(),
};
instance.properties = properties;
Ok(())
}
fn deserialize_properties<R: Read>(
reader: &mut XmlEventReader<R>,
state: &mut ParseState,
instance_id: RbxId,
props: &mut HashMap<String, RbxValue>,
) -> Result<(), DecodeError> {
reader.expect_start_with_name("Properties")?;
let class_name = state.tree.get_instance(instance_id)
.expect("Couldn't find instance to deserialize properties into")
.class_name.clone();
loop {
let (xml_type_name, xml_property_name) = {
match reader.expect_peek()? {
XmlReadEvent::StartElement { name, attributes, .. } => {
let mut xml_property_name = None;
for attribute in attributes {
if attribute.name.local_name.as_str() == "name" {
xml_property_name = Some(attribute.value.to_owned());
break;
}
}
let xml_property_name = match xml_property_name {
Some(value) => value,
None => return Err(reader.error(DecodeErrorKind::MissingAttribute("name")))
};
(name.local_name.to_owned(), xml_property_name)
},
XmlReadEvent::EndElement { name } => {
if name.local_name == "Properties" {
reader.expect_next()?;
return Ok(())
} else {
let err = DecodeErrorKind::UnexpectedXmlEvent(reader.expect_next()?);
return Err(reader.error(err));
}
},
_ => {
let err = DecodeErrorKind::UnexpectedXmlEvent(reader.expect_next()?);
return Err(reader.error(err));
}
}
};
let maybe_descriptor = if state.options.use_reflection() {
find_canonical_property_descriptor(&class_name, &xml_property_name)
} else {
None
};
if let Some(descriptor) = maybe_descriptor {
let xml_value = read_value_xml(reader, state, &xml_type_name, instance_id, descriptor.name())?;
let expected_type = match descriptor.property_type() {
RbxPropertyTypeDescriptor::Data(value_type) => *value_type,
RbxPropertyTypeDescriptor::Enum(_enum_name) => RbxValueType::Enum,
RbxPropertyTypeDescriptor::UnimplementedType(_) => {
match state.options.property_behavior {
DecodePropertyBehavior::IgnoreUnknown => {
continue;
}
DecodePropertyBehavior::ReadUnknown => {
xml_value.get_type()
}
DecodePropertyBehavior::ErrorOnUnknown => {
return Err(reader.error(DecodeErrorKind::UnknownProperty {
class_name,
property_name: xml_property_name,
}));
}
DecodePropertyBehavior::NoReflection | DecodePropertyBehavior::__Nonexhaustive => {
unreachable!();
}
}
},
};
let value = match xml_value.try_convert_ref(expected_type) {
RbxValueConversion::Converted(value) => value,
RbxValueConversion::Unnecessary => xml_value,
RbxValueConversion::Failed => {
return Err(reader.error(DecodeErrorKind::UnsupportedPropertyConversion {
class_name: class_name.clone(),
property_name: descriptor.name().to_string(),
expected_type,
actual_type: xml_value.get_type(),
}));
}
};
props.insert(descriptor.name().to_string(), value);
} else {
match state.options.property_behavior {
DecodePropertyBehavior::IgnoreUnknown => {
read_value_xml(reader, state, &xml_type_name, instance_id, &xml_property_name)?;
}
DecodePropertyBehavior::ReadUnknown | DecodePropertyBehavior::NoReflection => {
let value = read_value_xml(reader, state, &xml_type_name, instance_id, &xml_property_name)?;
props.insert(xml_property_name, value);
}
DecodePropertyBehavior::ErrorOnUnknown => {
return Err(reader.error(DecodeErrorKind::UnknownProperty {
class_name,
property_name: xml_property_name,
}));
}
DecodePropertyBehavior::__Nonexhaustive => unreachable!()
}
}
}
}