avm2: Implement XML construction from XML and XMLList objects

This commit is contained in:
Aaron Hill 2023-03-15 16:19:30 -05:00 committed by Nathan Adams
parent 79dfeaf715
commit b140ce6d97
5 changed files with 69 additions and 32 deletions

View File

@ -9,6 +9,8 @@ use quick_xml::{
Reader,
};
use crate::avm2::{error::type_error, TObject};
use super::{object::E4XOrXml, string::AvmString, Activation, Error, Multiname, Value};
use crate::string::{WStr, WString};
@ -117,14 +119,29 @@ impl<'gc> E4XNode<'gc> {
/// The caller is responsible for validating that the number of top-level nodes
/// is correct (for XML, there should be exactly one.)
pub fn parse(
value: Value<'gc>,
mut value: Value<'gc>,
activation: &mut Activation<'_, 'gc>,
) -> Result<Vec<Self>, Error<'gc>> {
let string = match value {
let string = match &value {
// The docs claim that this throws a TypeError, but it actually doesn't
Value::Null | Value::Undefined => AvmString::default(),
// The docs claim that only String, Number or Boolean are accepted, but that's also a lie
value => value.coerce_to_string(activation)?,
val => {
if let Some(obj) = val.as_object() {
if let Some(xml) = obj.as_xml_object() {
value = xml.call_public_property("toXMLString", &[], activation)?;
} else if let Some(list) = obj.as_xml_list_object() {
if list.length() == 1 {
value = list.children_mut(activation.context.gc_context)[0]
.get_or_create_xml(activation)
.call_public_property("toXMLString", &[], activation)?;
} else {
return Err(Error::AvmError(type_error(activation, "Error #1088: The markup in the document following the root element must be well-formed.", 1088)?));
}
}
}
value.coerce_to_string(activation)?
}
};
let data_utf8 = string.to_utf8_lossy();
@ -342,15 +359,9 @@ impl<'gc> E4XNode<'gc> {
pub fn xml_to_xml_string(
&self,
_activation: &mut Activation<'_, 'gc>,
activation: &mut Activation<'_, 'gc>,
) -> Result<AvmString<'gc>, Error<'gc>> {
match &self.0.read().kind {
E4XNodeKind::Text(text) => Ok(*text),
E4XNodeKind::Element { .. } => {
Err(format!("XML.toXMLString(): Not yet implemented element {:?}", self).into())
}
other => Err(format!("XML.toXMLString(): Not yet implemented for {other:?}").into()),
}
return to_xml_string(E4XOrXml::E4X(*self), activation);
}
pub fn kind(&self) -> Ref<'_, E4XNodeKind<'gc>> {

View File

@ -15,30 +15,23 @@ pub fn init<'gc>(
let this = this.unwrap().as_xml_object().unwrap();
let value = args[0];
match E4XNode::parse(value, activation) {
Ok(nodes) => {
let node = match nodes.as_slice() {
// XML defaults to an empty text node when nothing was parsed
[] => E4XNode::text(activation.context.gc_context, AvmString::default()),
[node] => *node,
_ => {
return Err(Error::RustError(
format!(
"XML constructor must be called with a single node: found {:?}",
nodes
)
.into(),
))
}
};
this.set_node(activation.context.gc_context, node);
}
Err(e) => {
let nodes = E4XNode::parse(value, activation)?;
let node = match nodes.as_slice() {
// XML defaults to an empty text node when nothing was parsed
[] => E4XNode::text(activation.context.gc_context, AvmString::default()),
[node] => *node,
_ => {
return Err(Error::RustError(
format!("Failed to parse XML: {e:?}").into(),
format!(
"XML constructor must be called with a single node: found {:?}",
nodes
)
.into(),
))
}
}
};
this.set_node(activation.context.gc_context, node);
Ok(Value::Undefined)
}

View File

@ -8,6 +8,8 @@
public function Main() {
XML.prettyPrinting = false;
var byteArray: ByteArray = new ByteArray();
byteArray.writeUTFBytes("<foo><bar>test</bar></foo>");
byteArray.position = 0;
@ -20,6 +22,30 @@
objWithToString.toString = function() { return "<foo><bar>test</bar></foo>"; };
trace("// new XML(objWithToString).bar");
trace(new XML(objWithToString).bar);
var xmlObj = <outer></outer>;
var xmlCopy = new XML(xmlObj);
trace("xmlCopy().toXMLString(): " + xmlCopy.toXMLString());
trace("xmlObj === xmlCopy: " + (xmlObj === xmlCopy));
var emptyList = new XMLList();
try {
new XML(emptyList);
} catch (e) {
trace("Caught error: " + e);
trace(e.errorID);
}
var singleList = new XMLList("<outer><inner>Hello</inner><second>World</second></outer>");
trace("new XML(singleList): " + new XML(singleList));
var multiList = new XMLList("<first>Hello</first><second>World</second>");
try {
new XML(multiList);
} catch (e) {
trace("Caught error: " + e);
trace(e.errorID);
}
}
}

View File

@ -3,3 +3,10 @@ test
// new XML(objWithToString).bar
test
xmlCopy().toXMLString(): <outer/>
xmlObj === xmlCopy: false
Caught error: TypeError: Error #1088: The markup in the document following the root element must be well-formed.
1088
new XML(singleList): <outer><inner>Hello</inner><second>World</second></outer>
Caught error: TypeError: Error #1088: The markup in the document following the root element must be well-formed.
1088