avm1: Merge #205, XML support
Support for AVM1 XML and XMLNode objects.
This commit is contained in:
commit
bcc51f2dd8
|
@ -1432,6 +1432,14 @@ name = "quick-error"
|
|||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.17.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "0.6.13"
|
||||
|
@ -1575,6 +1583,7 @@ dependencies = [
|
|||
"minimp3 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num_enum 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"puremp3 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quick-xml 0.17.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ruffle_macros 0.1.0",
|
||||
"smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -2563,6 +2572,7 @@ dependencies = [
|
|||
"checksum proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "0319972dcae462681daf4da1adeeaa066e3ebd29c69be96c6abb1259d2ee2bcc"
|
||||
"checksum puremp3 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f2b7efbb39e373af70c139e0611375fa6cad751fb93d528a610b55302710d883"
|
||||
"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
|
||||
"checksum quick-xml 0.17.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fe1e430bdcf30c9fdc25053b9c459bb1a4672af4617b6c783d7d91dc17c6bbb0"
|
||||
"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
|
||||
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
|
||||
"checksum rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412"
|
||||
|
|
|
@ -19,6 +19,7 @@ swf = { path = "../swf" }
|
|||
enumset = "0.4.2"
|
||||
smallvec = "1.1.0"
|
||||
num_enum = "0.4.2"
|
||||
quick-xml = "0.17.2"
|
||||
|
||||
[dependencies.jpeg-decoder]
|
||||
version = "0.1.18"
|
||||
|
|
|
@ -33,6 +33,9 @@ mod sound_object;
|
|||
mod stage_object;
|
||||
mod super_object;
|
||||
mod value;
|
||||
pub mod xml_attributes_object;
|
||||
pub mod xml_idmap_object;
|
||||
pub mod xml_object;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
|
|
@ -20,6 +20,7 @@ mod object;
|
|||
mod sound;
|
||||
mod stage;
|
||||
pub(crate) mod text_field;
|
||||
mod xml;
|
||||
|
||||
#[allow(non_snake_case, unused_must_use)] //can't use errors yet
|
||||
pub fn getURL<'a, 'gc>(
|
||||
|
@ -138,6 +139,7 @@ pub struct SystemPrototypes<'gc> {
|
|||
pub sound: Object<'gc>,
|
||||
pub text_field: Object<'gc>,
|
||||
pub array: Object<'gc>,
|
||||
pub xml_node: Object<'gc>,
|
||||
}
|
||||
|
||||
unsafe impl<'gc> gc_arena::Collect for SystemPrototypes<'gc> {
|
||||
|
@ -149,6 +151,7 @@ unsafe impl<'gc> gc_arena::Collect for SystemPrototypes<'gc> {
|
|||
self.sound.trace(cc);
|
||||
self.text_field.trace(cc);
|
||||
self.array.trace(cc);
|
||||
self.xml_node.trace(cc);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,6 +175,10 @@ pub fn create_globals<'gc>(
|
|||
let array_proto: Object<'gc> = array::create_proto(gc_context, object_proto, function_proto);
|
||||
|
||||
let color_proto: Object<'gc> = color::create_proto(gc_context, object_proto, function_proto);
|
||||
let xmlnode_proto: Object<'gc> =
|
||||
xml::create_xmlnode_proto(gc_context, object_proto, function_proto);
|
||||
|
||||
let xml_proto: Object<'gc> = xml::create_xml_proto(gc_context, xmlnode_proto, function_proto);
|
||||
|
||||
//TODO: These need to be constructors and should also set `.prototype` on each one
|
||||
let object = ScriptObject::function(
|
||||
|
@ -217,6 +224,18 @@ pub fn create_globals<'gc>(
|
|||
Some(function_proto),
|
||||
Some(array_proto),
|
||||
);
|
||||
let xmlnode = ScriptObject::function(
|
||||
gc_context,
|
||||
Executable::Native(xml::xmlnode_constructor),
|
||||
Some(function_proto),
|
||||
Some(xmlnode_proto),
|
||||
);
|
||||
let xml = ScriptObject::function(
|
||||
gc_context,
|
||||
Executable::Native(xml::xml_constructor),
|
||||
Some(function_proto),
|
||||
Some(xml_proto),
|
||||
);
|
||||
|
||||
let listeners = SystemListeners::new(gc_context, Some(array_proto));
|
||||
|
||||
|
@ -228,6 +247,8 @@ pub fn create_globals<'gc>(
|
|||
globals.define_value(gc_context, "MovieClip", movie_clip.into(), EnumSet::empty());
|
||||
globals.define_value(gc_context, "Sound", sound.into(), EnumSet::empty());
|
||||
globals.define_value(gc_context, "TextField", text_field.into(), EnumSet::empty());
|
||||
globals.define_value(gc_context, "XMLNode", xmlnode.into(), EnumSet::empty());
|
||||
globals.define_value(gc_context, "XML", xml.into(), EnumSet::empty());
|
||||
globals.force_set_function(
|
||||
"Number",
|
||||
number,
|
||||
|
@ -345,6 +366,7 @@ pub fn create_globals<'gc>(
|
|||
sound: sound_proto,
|
||||
text_field: text_field_proto,
|
||||
array: array_proto,
|
||||
xml_node: xmlnode_proto,
|
||||
},
|
||||
globals.into(),
|
||||
listeners,
|
||||
|
|
|
@ -0,0 +1,932 @@
|
|||
//! XML/XMLNode global classes
|
||||
|
||||
use crate::avm1::function::Executable;
|
||||
use crate::avm1::property::Attribute::*;
|
||||
use crate::avm1::return_value::ReturnValue;
|
||||
use crate::avm1::script_object::ScriptObject;
|
||||
use crate::avm1::xml_object::XMLObject;
|
||||
use crate::avm1::{Avm1, Error, Object, TObject, UpdateContext, Value};
|
||||
use crate::xml;
|
||||
use crate::xml::{XMLDocument, XMLNode};
|
||||
use enumset::EnumSet;
|
||||
use gc_arena::MutationContext;
|
||||
use quick_xml::Error as ParseError;
|
||||
|
||||
pub const XML_NO_ERROR: f64 = 0.0;
|
||||
#[allow(dead_code)]
|
||||
pub const XML_CDATA_NOT_TERMINATED: f64 = -2.0;
|
||||
pub const XML_DECL_NOT_TERMINATED: f64 = -3.0;
|
||||
#[allow(dead_code)]
|
||||
pub const XML_DOCTYPE_NOT_TERMINATED: f64 = -4.0;
|
||||
#[allow(dead_code)]
|
||||
pub const XML_COMMENT_NOT_TERMINATED: f64 = -5.0;
|
||||
pub const XML_ELEMENT_MALFORMED: f64 = -6.0;
|
||||
pub const XML_OUT_OF_MEMORY: f64 = -7.0;
|
||||
pub const XML_ATTRIBUTE_NOT_TERMINATED: f64 = -8.0;
|
||||
#[allow(dead_code)]
|
||||
pub const XML_MISMATCHED_START: f64 = -9.0;
|
||||
pub const XML_MISMATCHED_END: f64 = -10.0;
|
||||
|
||||
/// Returns true if a particular node can or cannot be exposed to AVM1.
|
||||
///
|
||||
/// Our internal XML tree representation supports node types that AVM1 XML did
|
||||
/// not. Those nodes are filtered from all attributes that return XML nodes to
|
||||
/// act as if those nodes did not exist. For example, `prevSibling` skips
|
||||
/// past incompatible nodes, etc.
|
||||
fn is_as2_compatible(node: XMLNode<'_>) -> bool {
|
||||
node.is_document_root() || node.is_element() || node.is_text()
|
||||
}
|
||||
|
||||
/// XMLNode constructor
|
||||
pub fn xmlnode_constructor<'gc>(
|
||||
avm: &mut Avm1<'gc>,
|
||||
ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
let blank_document = XMLDocument::new(ac.gc_context);
|
||||
|
||||
match (
|
||||
args.get(0).map(|v| v.as_number(avm, ac).map(|v| v as u32)),
|
||||
args.get(1).map(|v| v.clone().coerce_to_string(avm, ac)),
|
||||
this.as_xml_node(),
|
||||
) {
|
||||
(Some(Ok(1)), Some(Ok(ref strval)), Some(ref mut this_node)) => {
|
||||
let mut xmlelement = XMLNode::new_element(ac.gc_context, strval, blank_document)?;
|
||||
xmlelement.introduce_script_object(ac.gc_context, this);
|
||||
this_node.swap(ac.gc_context, xmlelement);
|
||||
}
|
||||
(Some(Ok(3)), Some(Ok(ref strval)), Some(ref mut this_node)) => {
|
||||
let mut xmlelement = XMLNode::new_text(ac.gc_context, strval, blank_document);
|
||||
xmlelement.introduce_script_object(ac.gc_context, this);
|
||||
this_node.swap(ac.gc_context, xmlelement);
|
||||
}
|
||||
//Invalid nodetype ID, string value missing, or not an XMLElement
|
||||
_ => {}
|
||||
};
|
||||
|
||||
Ok(Value::Undefined.into())
|
||||
}
|
||||
|
||||
pub fn xmlnode_append_child<'gc>(
|
||||
_avm: &mut Avm1<'gc>,
|
||||
ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
if let (Some(mut xmlnode), Some(Ok(Some(child_xmlnode)))) = (
|
||||
this.as_xml_node(),
|
||||
args.get(0).map(|n| n.as_object().map(|n| n.as_xml_node())),
|
||||
) {
|
||||
if let Ok(None) = child_xmlnode.parent() {
|
||||
let position = xmlnode.children_len();
|
||||
xmlnode.insert_child(ac.gc_context, position, child_xmlnode)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::Undefined.into())
|
||||
}
|
||||
|
||||
pub fn xmlnode_insert_before<'gc>(
|
||||
_avm: &mut Avm1<'gc>,
|
||||
ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
if let (Some(mut xmlnode), Some(Ok(Some(child_xmlnode))), Some(Ok(Some(insertpoint_xmlnode)))) = (
|
||||
this.as_xml_node(),
|
||||
args.get(0).map(|n| n.as_object().map(|n| n.as_xml_node())),
|
||||
args.get(1).map(|n| n.as_object().map(|n| n.as_xml_node())),
|
||||
) {
|
||||
if let Ok(None) = child_xmlnode.parent() {
|
||||
if let Some(position) = xmlnode.child_position(insertpoint_xmlnode) {
|
||||
xmlnode.insert_child(ac.gc_context, position, child_xmlnode)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::Undefined.into())
|
||||
}
|
||||
|
||||
pub fn xmlnode_clone_node<'gc>(
|
||||
avm: &mut Avm1<'gc>,
|
||||
ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
if let (Some(xmlnode), deep) = (
|
||||
this.as_xml_node(),
|
||||
args.get(0)
|
||||
.map(|v| v.as_bool(avm.current_swf_version()))
|
||||
.unwrap_or(false),
|
||||
) {
|
||||
let mut clone_node = xmlnode.duplicate(ac.gc_context, deep);
|
||||
|
||||
return Ok(Value::Object(
|
||||
clone_node.script_object(ac.gc_context, Some(avm.prototypes.xml_node)),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
Ok(Value::Undefined.into())
|
||||
}
|
||||
|
||||
pub fn xmlnode_get_namespace_for_prefix<'gc>(
|
||||
avm: &mut Avm1<'gc>,
|
||||
ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
if let (Some(xmlnode), Some(prefix_string)) = (
|
||||
this.as_xml_node(),
|
||||
args.get(0).map(|v| v.clone().coerce_to_string(avm, ac)),
|
||||
) {
|
||||
if let Some(uri) = xmlnode.lookup_uri_for_namespace(&prefix_string?) {
|
||||
Ok(uri.into())
|
||||
} else {
|
||||
Ok(Value::Null.into())
|
||||
}
|
||||
} else {
|
||||
Ok(Value::Undefined.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn xmlnode_get_prefix_for_namespace<'gc>(
|
||||
avm: &mut Avm1<'gc>,
|
||||
ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
if let (Some(xmlnode), Some(uri_string)) = (
|
||||
this.as_xml_node(),
|
||||
args.get(0).map(|v| v.clone().coerce_to_string(avm, ac)),
|
||||
) {
|
||||
if let Some(prefix) = xmlnode.lookup_namespace_for_uri(&uri_string?) {
|
||||
Ok(prefix.into())
|
||||
} else {
|
||||
Ok(Value::Null.into())
|
||||
}
|
||||
} else {
|
||||
Ok(Value::Undefined.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn xmlnode_has_child_nodes<'gc>(
|
||||
_avm: &mut Avm1<'gc>,
|
||||
_ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
if let Some(xmlnode) = this.as_xml_node() {
|
||||
Ok((xmlnode.children_len() > 0).into())
|
||||
} else {
|
||||
Ok(Value::Undefined.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn xmlnode_remove_node<'gc>(
|
||||
_avm: &mut Avm1<'gc>,
|
||||
ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
if let Some(node) = this.as_xml_node() {
|
||||
if let Ok(Some(mut parent)) = node.parent() {
|
||||
if let Err(e) = parent.remove_child(ac.gc_context, node) {
|
||||
log::warn!("Error in XML.removeNode: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::Undefined.into())
|
||||
}
|
||||
|
||||
pub fn xmlnode_to_string<'gc>(
|
||||
_avm: &mut Avm1<'gc>,
|
||||
_ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
if let Some(node) = this.as_xml_node() {
|
||||
let result = node.into_string(&mut is_as2_compatible);
|
||||
|
||||
return Ok(result
|
||||
.unwrap_or_else(|e| {
|
||||
log::warn!("XMLNode toString failed: {}", e);
|
||||
"".to_string()
|
||||
})
|
||||
.into());
|
||||
}
|
||||
|
||||
Ok("".to_string().into())
|
||||
}
|
||||
|
||||
pub fn xmlnode_local_name<'gc>(
|
||||
_avm: &mut Avm1<'gc>,
|
||||
_ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
Ok(this
|
||||
.as_xml_node()
|
||||
.and_then(|n| n.tag_name())
|
||||
.map(|n| n.local_name().to_string().into())
|
||||
.unwrap_or_else(|| Value::Null.into()))
|
||||
}
|
||||
|
||||
pub fn xmlnode_node_name<'gc>(
|
||||
_avm: &mut Avm1<'gc>,
|
||||
_ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
Ok(this
|
||||
.as_xml_node()
|
||||
.and_then(|n| n.tag_name())
|
||||
.map(|n| n.node_name().into())
|
||||
.unwrap_or_else(|| Value::Null.into()))
|
||||
}
|
||||
|
||||
pub fn xmlnode_node_type<'gc>(
|
||||
_avm: &mut Avm1<'gc>,
|
||||
_ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
Ok(this
|
||||
.as_xml_node()
|
||||
.map(|n| {
|
||||
match n.node_type() {
|
||||
xml::DOCUMENT_NODE => xml::ELEMENT_NODE,
|
||||
xml::DOCUMENT_TYPE_NODE => xml::TEXT_NODE,
|
||||
xml::COMMENT_NODE => xml::TEXT_NODE,
|
||||
n => n,
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.unwrap_or_else(|| Value::Undefined.into()))
|
||||
}
|
||||
|
||||
pub fn xmlnode_node_value<'gc>(
|
||||
_avm: &mut Avm1<'gc>,
|
||||
_ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
Ok(this
|
||||
.as_xml_node()
|
||||
.and_then(|n| n.node_value())
|
||||
.map(|n| n.into())
|
||||
.unwrap_or_else(|| Value::Null.into()))
|
||||
}
|
||||
|
||||
pub fn xmlnode_prefix<'gc>(
|
||||
_avm: &mut Avm1<'gc>,
|
||||
_ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
Ok(this
|
||||
.as_xml_node()
|
||||
.and_then(|n| n.tag_name())
|
||||
.map(|n| {
|
||||
n.prefix()
|
||||
.map(|n| n.to_string().into())
|
||||
.unwrap_or_else(|| "".to_string().into())
|
||||
})
|
||||
.unwrap_or_else(|| Value::Null.into()))
|
||||
}
|
||||
|
||||
pub fn xmlnode_child_nodes<'gc>(
|
||||
avm: &mut Avm1<'gc>,
|
||||
ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
if let Some(node) = this.as_xml_node() {
|
||||
let array = ScriptObject::array(ac.gc_context, Some(avm.prototypes.array));
|
||||
if let Some(children) = node.children() {
|
||||
let mut compatible_nodes = 0;
|
||||
for mut child in children {
|
||||
if !is_as2_compatible(child) {
|
||||
continue;
|
||||
}
|
||||
|
||||
array.set_array_element(
|
||||
compatible_nodes as usize,
|
||||
child
|
||||
.script_object(ac.gc_context, Some(avm.prototypes.xml_node))
|
||||
.into(),
|
||||
ac.gc_context,
|
||||
);
|
||||
|
||||
compatible_nodes += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(array.into());
|
||||
}
|
||||
|
||||
Ok(Value::Undefined.into())
|
||||
}
|
||||
|
||||
pub fn xmlnode_first_child<'gc>(
|
||||
avm: &mut Avm1<'gc>,
|
||||
ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
if let Some(node) = this.as_xml_node() {
|
||||
if let Some(mut children) = node.children() {
|
||||
return Ok(children
|
||||
.next()
|
||||
.map(|mut child| {
|
||||
child
|
||||
.script_object(ac.gc_context, Some(avm.prototypes.xml_node))
|
||||
.into()
|
||||
})
|
||||
.unwrap_or_else(|| Value::Null.into()));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::Undefined.into())
|
||||
}
|
||||
|
||||
pub fn xmlnode_last_child<'gc>(
|
||||
avm: &mut Avm1<'gc>,
|
||||
ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
if let Some(node) = this.as_xml_node() {
|
||||
if let Some(mut children) = node.children() {
|
||||
return Ok(children
|
||||
.next_back()
|
||||
.map(|mut child| {
|
||||
child
|
||||
.script_object(ac.gc_context, Some(avm.prototypes.xml_node))
|
||||
.into()
|
||||
})
|
||||
.unwrap_or_else(|| Value::Null.into()));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::Undefined.into())
|
||||
}
|
||||
|
||||
pub fn xmlnode_parent_node<'gc>(
|
||||
avm: &mut Avm1<'gc>,
|
||||
ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
if let Some(node) = this.as_xml_node() {
|
||||
return Ok(node
|
||||
.parent()
|
||||
.unwrap_or(None)
|
||||
.map(|mut parent| {
|
||||
parent
|
||||
.script_object(ac.gc_context, Some(avm.prototypes.xml_node))
|
||||
.into()
|
||||
})
|
||||
.unwrap_or_else(|| Value::Null.into()));
|
||||
}
|
||||
|
||||
Ok(Value::Undefined.into())
|
||||
}
|
||||
|
||||
pub fn xmlnode_previous_sibling<'gc>(
|
||||
avm: &mut Avm1<'gc>,
|
||||
ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
if let Some(node) = this.as_xml_node() {
|
||||
let mut prev = node.prev_sibling().unwrap_or(None);
|
||||
while let Some(my_prev) = prev {
|
||||
if is_as2_compatible(my_prev) {
|
||||
break;
|
||||
}
|
||||
|
||||
prev = my_prev.prev_sibling().unwrap_or(None);
|
||||
}
|
||||
|
||||
return Ok(prev
|
||||
.map(|mut prev| {
|
||||
prev.script_object(ac.gc_context, Some(avm.prototypes.xml_node))
|
||||
.into()
|
||||
})
|
||||
.unwrap_or_else(|| Value::Null.into()));
|
||||
}
|
||||
|
||||
Ok(Value::Undefined.into())
|
||||
}
|
||||
|
||||
pub fn xmlnode_next_sibling<'gc>(
|
||||
avm: &mut Avm1<'gc>,
|
||||
ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
if let Some(node) = this.as_xml_node() {
|
||||
let mut next = node.next_sibling().unwrap_or(None);
|
||||
while let Some(my_next) = next {
|
||||
if is_as2_compatible(my_next) {
|
||||
break;
|
||||
}
|
||||
|
||||
next = my_next.next_sibling().unwrap_or(None);
|
||||
}
|
||||
|
||||
return Ok(next
|
||||
.map(|mut next| {
|
||||
next.script_object(ac.gc_context, Some(avm.prototypes.xml_node))
|
||||
.into()
|
||||
})
|
||||
.unwrap_or_else(|| Value::Null.into()));
|
||||
}
|
||||
|
||||
Ok(Value::Undefined.into())
|
||||
}
|
||||
|
||||
pub fn xmlnode_attributes<'gc>(
|
||||
_avm: &mut Avm1<'gc>,
|
||||
ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
if let Some(mut node) = this.as_xml_node() {
|
||||
return Ok(node
|
||||
.attribute_script_object(ac.gc_context)
|
||||
.map(|o| o.into())
|
||||
.unwrap_or_else(|| Value::Undefined.into()));
|
||||
}
|
||||
|
||||
Ok(Value::Undefined.into())
|
||||
}
|
||||
|
||||
pub fn xmlnode_namespace_uri<'gc>(
|
||||
_avm: &mut Avm1<'gc>,
|
||||
_ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
if let Some(node) = this.as_xml_node() {
|
||||
if let Some(name) = node.tag_name() {
|
||||
return Ok(node
|
||||
.lookup_uri_for_namespace(name.prefix().unwrap_or(""))
|
||||
.map(|s| s.into())
|
||||
.unwrap_or_else(|| "".into()));
|
||||
}
|
||||
|
||||
return Ok(Value::Null.into());
|
||||
}
|
||||
|
||||
Ok(Value::Undefined.into())
|
||||
}
|
||||
|
||||
/// Construct the prototype for `XMLNode`.
|
||||
pub fn create_xmlnode_proto<'gc>(
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
proto: Object<'gc>,
|
||||
fn_proto: Object<'gc>,
|
||||
) -> Object<'gc> {
|
||||
let xmlnode_proto = XMLObject::empty_node(gc_context, Some(proto));
|
||||
|
||||
xmlnode_proto.add_property(
|
||||
gc_context,
|
||||
"localName",
|
||||
Executable::Native(xmlnode_local_name),
|
||||
None,
|
||||
ReadOnly.into(),
|
||||
);
|
||||
xmlnode_proto.add_property(
|
||||
gc_context,
|
||||
"nodeName",
|
||||
Executable::Native(xmlnode_node_name),
|
||||
None,
|
||||
ReadOnly.into(),
|
||||
);
|
||||
xmlnode_proto.add_property(
|
||||
gc_context,
|
||||
"nodeType",
|
||||
Executable::Native(xmlnode_node_type),
|
||||
None,
|
||||
ReadOnly.into(),
|
||||
);
|
||||
xmlnode_proto.add_property(
|
||||
gc_context,
|
||||
"nodeValue",
|
||||
Executable::Native(xmlnode_node_value),
|
||||
None,
|
||||
ReadOnly.into(),
|
||||
);
|
||||
xmlnode_proto.add_property(
|
||||
gc_context,
|
||||
"prefix",
|
||||
Executable::Native(xmlnode_prefix),
|
||||
None,
|
||||
ReadOnly.into(),
|
||||
);
|
||||
xmlnode_proto.add_property(
|
||||
gc_context,
|
||||
"childNodes",
|
||||
Executable::Native(xmlnode_child_nodes),
|
||||
None,
|
||||
ReadOnly.into(),
|
||||
);
|
||||
xmlnode_proto.add_property(
|
||||
gc_context,
|
||||
"firstChild",
|
||||
Executable::Native(xmlnode_first_child),
|
||||
None,
|
||||
ReadOnly.into(),
|
||||
);
|
||||
xmlnode_proto.add_property(
|
||||
gc_context,
|
||||
"lastChild",
|
||||
Executable::Native(xmlnode_last_child),
|
||||
None,
|
||||
ReadOnly.into(),
|
||||
);
|
||||
xmlnode_proto.add_property(
|
||||
gc_context,
|
||||
"parentNode",
|
||||
Executable::Native(xmlnode_parent_node),
|
||||
None,
|
||||
ReadOnly.into(),
|
||||
);
|
||||
xmlnode_proto.add_property(
|
||||
gc_context,
|
||||
"previousSibling",
|
||||
Executable::Native(xmlnode_previous_sibling),
|
||||
None,
|
||||
ReadOnly.into(),
|
||||
);
|
||||
xmlnode_proto.add_property(
|
||||
gc_context,
|
||||
"nextSibling",
|
||||
Executable::Native(xmlnode_next_sibling),
|
||||
None,
|
||||
ReadOnly.into(),
|
||||
);
|
||||
xmlnode_proto.add_property(
|
||||
gc_context,
|
||||
"attributes",
|
||||
Executable::Native(xmlnode_attributes),
|
||||
None,
|
||||
ReadOnly.into(),
|
||||
);
|
||||
xmlnode_proto.add_property(
|
||||
gc_context,
|
||||
"namespaceURI",
|
||||
Executable::Native(xmlnode_namespace_uri),
|
||||
None,
|
||||
ReadOnly.into(),
|
||||
);
|
||||
xmlnode_proto
|
||||
.as_script_object()
|
||||
.unwrap()
|
||||
.force_set_function(
|
||||
"appendChild",
|
||||
xmlnode_append_child,
|
||||
gc_context,
|
||||
EnumSet::empty(),
|
||||
Some(fn_proto),
|
||||
);
|
||||
xmlnode_proto
|
||||
.as_script_object()
|
||||
.unwrap()
|
||||
.force_set_function(
|
||||
"insertBefore",
|
||||
xmlnode_insert_before,
|
||||
gc_context,
|
||||
EnumSet::empty(),
|
||||
Some(fn_proto),
|
||||
);
|
||||
xmlnode_proto
|
||||
.as_script_object()
|
||||
.unwrap()
|
||||
.force_set_function(
|
||||
"cloneNode",
|
||||
xmlnode_clone_node,
|
||||
gc_context,
|
||||
EnumSet::empty(),
|
||||
Some(fn_proto),
|
||||
);
|
||||
xmlnode_proto
|
||||
.as_script_object()
|
||||
.unwrap()
|
||||
.force_set_function(
|
||||
"getNamespaceForPrefix",
|
||||
xmlnode_get_namespace_for_prefix,
|
||||
gc_context,
|
||||
EnumSet::empty(),
|
||||
Some(fn_proto),
|
||||
);
|
||||
xmlnode_proto
|
||||
.as_script_object()
|
||||
.unwrap()
|
||||
.force_set_function(
|
||||
"getPrefixForNamespace",
|
||||
xmlnode_get_prefix_for_namespace,
|
||||
gc_context,
|
||||
EnumSet::empty(),
|
||||
Some(fn_proto),
|
||||
);
|
||||
xmlnode_proto
|
||||
.as_script_object()
|
||||
.unwrap()
|
||||
.force_set_function(
|
||||
"hasChildNodes",
|
||||
xmlnode_has_child_nodes,
|
||||
gc_context,
|
||||
EnumSet::empty(),
|
||||
Some(fn_proto),
|
||||
);
|
||||
xmlnode_proto
|
||||
.as_script_object()
|
||||
.unwrap()
|
||||
.force_set_function(
|
||||
"removeNode",
|
||||
xmlnode_remove_node,
|
||||
gc_context,
|
||||
EnumSet::empty(),
|
||||
Some(fn_proto),
|
||||
);
|
||||
xmlnode_proto
|
||||
.as_script_object()
|
||||
.unwrap()
|
||||
.force_set_function(
|
||||
"toString",
|
||||
xmlnode_to_string,
|
||||
gc_context,
|
||||
EnumSet::empty(),
|
||||
Some(fn_proto),
|
||||
);
|
||||
|
||||
xmlnode_proto
|
||||
}
|
||||
|
||||
/// XML (document) constructor
|
||||
pub fn xml_constructor<'gc>(
|
||||
avm: &mut Avm1<'gc>,
|
||||
ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
match (
|
||||
args.get(0).map(|v| v.clone().coerce_to_string(avm, ac)),
|
||||
this.as_xml_node(),
|
||||
) {
|
||||
(Some(Ok(ref string)), Some(ref mut this_node)) => {
|
||||
let xmldoc = XMLDocument::new(ac.gc_context);
|
||||
let mut xmlnode = xmldoc.as_node();
|
||||
xmlnode.introduce_script_object(ac.gc_context, this);
|
||||
this_node.swap(ac.gc_context, xmlnode);
|
||||
|
||||
this_node.replace_with_str(ac.gc_context, string)?;
|
||||
}
|
||||
(None, Some(ref mut this_node)) => {
|
||||
let xmldoc = XMLDocument::new(ac.gc_context);
|
||||
let mut xmlnode = xmldoc.as_node();
|
||||
xmlnode.introduce_script_object(ac.gc_context, this);
|
||||
this_node.swap(ac.gc_context, xmlnode);
|
||||
}
|
||||
//Non-string argument or not an XML document
|
||||
_ => {}
|
||||
};
|
||||
|
||||
Ok(Value::Undefined.into())
|
||||
}
|
||||
|
||||
pub fn xml_create_element<'gc>(
|
||||
avm: &mut Avm1<'gc>,
|
||||
ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
let document = if let Some(node) = this.as_xml_node() {
|
||||
node.document()
|
||||
} else {
|
||||
XMLDocument::new(ac.gc_context)
|
||||
};
|
||||
|
||||
let nodename = args
|
||||
.get(0)
|
||||
.map(|v| v.clone().coerce_to_string(avm, ac).unwrap_or_default())
|
||||
.unwrap_or_default();
|
||||
let mut xml_node = XMLNode::new_element(ac.gc_context, &nodename, document)?;
|
||||
let object = XMLObject::from_xml_node(ac.gc_context, xml_node, Some(avm.prototypes().xml_node));
|
||||
|
||||
xml_node.introduce_script_object(ac.gc_context, object);
|
||||
|
||||
Ok(object.into())
|
||||
}
|
||||
|
||||
pub fn xml_create_text_node<'gc>(
|
||||
avm: &mut Avm1<'gc>,
|
||||
ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
let document = if let Some(node) = this.as_xml_node() {
|
||||
node.document()
|
||||
} else {
|
||||
XMLDocument::new(ac.gc_context)
|
||||
};
|
||||
|
||||
let text_node = args
|
||||
.get(0)
|
||||
.map(|v| v.clone().coerce_to_string(avm, ac).unwrap_or_default())
|
||||
.unwrap_or_default();
|
||||
let mut xml_node = XMLNode::new_text(ac.gc_context, &text_node, document);
|
||||
let object = XMLObject::from_xml_node(ac.gc_context, xml_node, Some(avm.prototypes().xml_node));
|
||||
|
||||
xml_node.introduce_script_object(ac.gc_context, object);
|
||||
|
||||
Ok(object.into())
|
||||
}
|
||||
|
||||
pub fn xml_parse_xml<'gc>(
|
||||
avm: &mut Avm1<'gc>,
|
||||
ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
if let Some(mut node) = this.as_xml_node() {
|
||||
let xmlstring =
|
||||
if let Some(Ok(xmlstring)) = args.get(0).map(|s| s.clone().coerce_to_string(avm, ac)) {
|
||||
xmlstring
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
|
||||
if let Some(children) = node.children() {
|
||||
for child in children.rev() {
|
||||
let result = node.remove_child(ac.gc_context, child);
|
||||
if let Err(e) = result {
|
||||
log::warn!("XML.parseXML: Error removing node contents: {}", e);
|
||||
return Ok(Value::Undefined.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let result = node.replace_with_str(ac.gc_context, &xmlstring);
|
||||
if let Err(e) = result {
|
||||
log::warn!("XML parsing error: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::Undefined.into())
|
||||
}
|
||||
|
||||
pub fn xml_doc_type_decl<'gc>(
|
||||
_avm: &mut Avm1<'gc>,
|
||||
_ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
if let Some(node) = this.as_xml_node() {
|
||||
if let Some(doctype) = node.document().doctype() {
|
||||
let result = doctype.into_string(&mut |_| true);
|
||||
|
||||
return Ok(result
|
||||
.unwrap_or_else(|e| {
|
||||
log::warn!("Error occured when serializing DOCTYPE: {}", e);
|
||||
"".to_string()
|
||||
})
|
||||
.into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::Undefined.into())
|
||||
}
|
||||
|
||||
pub fn xml_xml_decl<'gc>(
|
||||
_avm: &mut Avm1<'gc>,
|
||||
_ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
if let Some(node) = this.as_xml_node() {
|
||||
let result = node.document().xmldecl_string();
|
||||
|
||||
if let Err(e) = result {
|
||||
log::warn!("Could not generate XML declaration for document: {}", e);
|
||||
} else if let Ok(Some(result_str)) = result {
|
||||
return Ok(result_str.into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::Undefined.into())
|
||||
}
|
||||
|
||||
pub fn xml_id_map<'gc>(
|
||||
_avm: &mut Avm1<'gc>,
|
||||
ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
if let Some(node) = this.as_xml_node() {
|
||||
return Ok(node.document().idmap_script_object(ac.gc_context).into());
|
||||
}
|
||||
|
||||
Ok(Value::Undefined.into())
|
||||
}
|
||||
|
||||
pub fn xml_status<'gc>(
|
||||
_avm: &mut Avm1<'gc>,
|
||||
_ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
if let Some(node) = this.as_xml_node() {
|
||||
return match node.document().last_parse_error() {
|
||||
None => Ok(XML_NO_ERROR.into()),
|
||||
Some(err) => match err.ref_error() {
|
||||
ParseError::UnexpectedEof(_) => Ok(Value::Number(XML_ELEMENT_MALFORMED).into()),
|
||||
ParseError::EndEventMismatch { .. } => Ok(Value::Number(XML_MISMATCHED_END).into()),
|
||||
ParseError::XmlDeclWithoutVersion(_) => {
|
||||
Ok(Value::Number(XML_DECL_NOT_TERMINATED).into())
|
||||
}
|
||||
ParseError::NameWithQuote(_) => Ok(Value::Number(XML_ELEMENT_MALFORMED).into()),
|
||||
ParseError::NoEqAfterName(_) => Ok(Value::Number(XML_ELEMENT_MALFORMED).into()),
|
||||
ParseError::UnquotedValue(_) => {
|
||||
Ok(Value::Number(XML_ATTRIBUTE_NOT_TERMINATED).into())
|
||||
}
|
||||
ParseError::DuplicatedAttribute(_, _) => {
|
||||
Ok(Value::Number(XML_ELEMENT_MALFORMED).into())
|
||||
}
|
||||
_ => Ok(Value::Number(XML_OUT_OF_MEMORY).into()), //Not accounted for:
|
||||
//ParseError::UnexpectedToken(_)
|
||||
//ParseError::UnexpectedBang
|
||||
//ParseError::TextNotFound
|
||||
//ParseError::EscapeError(_)
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Ok(Value::Undefined.into())
|
||||
}
|
||||
|
||||
/// Construct the prototype for `XML`.
|
||||
pub fn create_xml_proto<'gc>(
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
proto: Object<'gc>,
|
||||
fn_proto: Object<'gc>,
|
||||
) -> Object<'gc> {
|
||||
let xml_proto = XMLObject::empty_node(gc_context, Some(proto));
|
||||
|
||||
xml_proto.add_property(
|
||||
gc_context,
|
||||
"docTypeDecl",
|
||||
Executable::Native(xml_doc_type_decl),
|
||||
None,
|
||||
ReadOnly.into(),
|
||||
);
|
||||
xml_proto.add_property(
|
||||
gc_context,
|
||||
"xmlDecl",
|
||||
Executable::Native(xml_xml_decl),
|
||||
None,
|
||||
ReadOnly.into(),
|
||||
);
|
||||
xml_proto.add_property(
|
||||
gc_context,
|
||||
"idMap",
|
||||
Executable::Native(xml_id_map),
|
||||
None,
|
||||
ReadOnly.into(),
|
||||
);
|
||||
xml_proto.add_property(
|
||||
gc_context,
|
||||
"status",
|
||||
Executable::Native(xml_status),
|
||||
None,
|
||||
ReadOnly.into(),
|
||||
);
|
||||
xml_proto.as_script_object().unwrap().force_set_function(
|
||||
"createElement",
|
||||
xml_create_element,
|
||||
gc_context,
|
||||
EnumSet::empty(),
|
||||
Some(fn_proto),
|
||||
);
|
||||
xml_proto.as_script_object().unwrap().force_set_function(
|
||||
"createTextNode",
|
||||
xml_create_text_node,
|
||||
gc_context,
|
||||
EnumSet::empty(),
|
||||
Some(fn_proto),
|
||||
);
|
||||
xml_proto.as_script_object().unwrap().force_set_function(
|
||||
"parseXML",
|
||||
xml_parse_xml,
|
||||
gc_context,
|
||||
EnumSet::empty(),
|
||||
Some(fn_proto),
|
||||
);
|
||||
|
||||
xml_proto
|
||||
}
|
|
@ -4,8 +4,12 @@ use crate::avm1::function::Executable;
|
|||
use crate::avm1::property::Attribute;
|
||||
use crate::avm1::return_value::ReturnValue;
|
||||
use crate::avm1::super_object::SuperObject;
|
||||
use crate::avm1::xml_attributes_object::XMLAttributesObject;
|
||||
use crate::avm1::xml_idmap_object::XMLIDMapObject;
|
||||
use crate::avm1::xml_object::XMLObject;
|
||||
use crate::avm1::{Avm1, Error, ScriptObject, SoundObject, StageObject, UpdateContext, Value};
|
||||
use crate::display_object::DisplayObject;
|
||||
use crate::xml::XMLNode;
|
||||
use enumset::EnumSet;
|
||||
use gc_arena::{Collect, MutationContext};
|
||||
use ruffle_macros::enum_trait_object;
|
||||
|
@ -22,6 +26,9 @@ use std::fmt::Debug;
|
|||
SoundObject(SoundObject<'gc>),
|
||||
StageObject(StageObject<'gc>),
|
||||
SuperObject(SuperObject<'gc>),
|
||||
XMLObject(XMLObject<'gc>),
|
||||
XMLAttributesObject(XMLAttributesObject<'gc>),
|
||||
XMLIDMapObject(XMLIDMapObject<'gc>),
|
||||
}
|
||||
)]
|
||||
pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy {
|
||||
|
@ -258,10 +265,19 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
|
|||
}
|
||||
|
||||
/// Get the underlying display node for this object, if it exists.
|
||||
fn as_display_object(&self) -> Option<DisplayObject<'gc>>;
|
||||
fn as_display_object(&self) -> Option<DisplayObject<'gc>> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Get the underlying executable for this object, if it exists.
|
||||
fn as_executable(&self) -> Option<Executable<'gc>>;
|
||||
fn as_executable(&self) -> Option<Executable<'gc>> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Get the underlying XML node for this object, if it exists.
|
||||
fn as_xml_node(&self) -> Option<XMLNode<'gc>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_ptr(&self) -> *const ObjectPtr;
|
||||
|
||||
|
|
|
@ -0,0 +1,233 @@
|
|||
//! AVM1 object type to represent the attributes of XML nodes
|
||||
|
||||
use crate::avm1::function::Executable;
|
||||
use crate::avm1::object::{ObjectPtr, TObject};
|
||||
use crate::avm1::property::Attribute;
|
||||
use crate::avm1::return_value::ReturnValue;
|
||||
use crate::avm1::{Avm1, Error, Object, ScriptObject, UpdateContext, Value};
|
||||
use crate::xml::{XMLName, XMLNode};
|
||||
use enumset::EnumSet;
|
||||
use gc_arena::{Collect, MutationContext};
|
||||
use std::collections::HashSet;
|
||||
use std::fmt;
|
||||
|
||||
/// A ScriptObject that is inherently tied to an XML node's attributes.
|
||||
///
|
||||
/// Note that this is *not* the same as the XMLNode object itself; for example,
|
||||
/// `XMLNode`s must store both their base object and attributes object
|
||||
/// separately.
|
||||
#[derive(Clone, Copy, Collect)]
|
||||
#[collect(no_drop)]
|
||||
pub struct XMLAttributesObject<'gc>(ScriptObject<'gc>, XMLNode<'gc>);
|
||||
|
||||
impl<'gc> XMLAttributesObject<'gc> {
|
||||
/// Construct an XMLAttributesObject for an already existing node's
|
||||
/// attributes.
|
||||
pub fn from_xml_node(
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
xml_node: XMLNode<'gc>,
|
||||
) -> Object<'gc> {
|
||||
XMLAttributesObject(ScriptObject::object(gc_context, None), xml_node).into()
|
||||
}
|
||||
|
||||
fn base(&self) -> ScriptObject<'gc> {
|
||||
match self {
|
||||
XMLAttributesObject(base, ..) => *base,
|
||||
}
|
||||
}
|
||||
|
||||
fn node(&self) -> XMLNode<'gc> {
|
||||
match self {
|
||||
XMLAttributesObject(_, node) => *node,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for XMLAttributesObject<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
XMLAttributesObject(base, node) => f
|
||||
.debug_tuple("XMLAttributesObject")
|
||||
.field(base)
|
||||
.field(node)
|
||||
.finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc> TObject<'gc> for XMLAttributesObject<'gc> {
|
||||
fn get_local(
|
||||
&self,
|
||||
name: &str,
|
||||
_avm: &mut Avm1<'gc>,
|
||||
_context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
_this: Object<'gc>,
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
Ok(self
|
||||
.node()
|
||||
.attribute_value(&XMLName::from_str(name))
|
||||
.map(|s| s.into())
|
||||
.unwrap_or_else(|| Value::Undefined)
|
||||
.into())
|
||||
}
|
||||
|
||||
fn set(
|
||||
&self,
|
||||
name: &str,
|
||||
value: Value<'gc>,
|
||||
avm: &mut Avm1<'gc>,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
) -> Result<(), Error> {
|
||||
self.node().set_attribute_value(
|
||||
context.gc_context,
|
||||
&XMLName::from_str(name),
|
||||
&value.clone().coerce_to_string(avm, context)?,
|
||||
);
|
||||
self.base().set(name, value, avm, context)
|
||||
}
|
||||
|
||||
fn call(
|
||||
&self,
|
||||
avm: &mut Avm1<'gc>,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
self.base().call(avm, context, this, args)
|
||||
}
|
||||
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
fn new(
|
||||
&self,
|
||||
_avm: &mut Avm1<'gc>,
|
||||
_context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
_this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<Object<'gc>, Error> {
|
||||
//TODO: `new xmlnode.attributes()` returns undefined, not an object
|
||||
Err("Cannot create new XML Attributes object".into())
|
||||
}
|
||||
|
||||
fn delete(&self, gc_context: MutationContext<'gc, '_>, name: &str) -> bool {
|
||||
self.node()
|
||||
.delete_attribute(gc_context, &XMLName::from_str(name));
|
||||
self.base().delete(gc_context, name)
|
||||
}
|
||||
|
||||
fn add_property(
|
||||
&self,
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
name: &str,
|
||||
get: Executable<'gc>,
|
||||
set: Option<Executable<'gc>>,
|
||||
attributes: EnumSet<Attribute>,
|
||||
) {
|
||||
self.base()
|
||||
.add_property(gc_context, name, get, set, attributes)
|
||||
}
|
||||
|
||||
fn define_value(
|
||||
&self,
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
name: &str,
|
||||
value: Value<'gc>,
|
||||
attributes: EnumSet<Attribute>,
|
||||
) {
|
||||
self.base()
|
||||
.define_value(gc_context, name, value, attributes)
|
||||
}
|
||||
|
||||
fn set_attributes(
|
||||
&mut self,
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
name: Option<&str>,
|
||||
set_attributes: EnumSet<Attribute>,
|
||||
clear_attributes: EnumSet<Attribute>,
|
||||
) {
|
||||
self.base()
|
||||
.set_attributes(gc_context, name, set_attributes, clear_attributes)
|
||||
}
|
||||
|
||||
fn proto(&self) -> Option<Object<'gc>> {
|
||||
self.base().proto()
|
||||
}
|
||||
|
||||
fn has_property(&self, name: &str) -> bool {
|
||||
self.base().has_property(name)
|
||||
}
|
||||
|
||||
fn has_own_property(&self, name: &str) -> bool {
|
||||
self.node()
|
||||
.attribute_value(&XMLName::from_str(name))
|
||||
.is_some()
|
||||
}
|
||||
|
||||
fn is_property_overwritable(&self, name: &str) -> bool {
|
||||
self.base().is_property_overwritable(name)
|
||||
}
|
||||
|
||||
fn is_property_enumerable(&self, name: &str) -> bool {
|
||||
self.base().is_property_enumerable(name)
|
||||
}
|
||||
|
||||
fn get_keys(&self) -> HashSet<String> {
|
||||
self.base().get_keys()
|
||||
}
|
||||
|
||||
fn as_string(&self) -> String {
|
||||
self.base().as_string()
|
||||
}
|
||||
|
||||
fn type_of(&self) -> &'static str {
|
||||
self.base().type_of()
|
||||
}
|
||||
|
||||
fn interfaces(&self) -> Vec<Object<'gc>> {
|
||||
self.base().interfaces()
|
||||
}
|
||||
|
||||
fn set_interfaces(&mut self, context: MutationContext<'gc, '_>, iface_list: Vec<Object<'gc>>) {
|
||||
self.base().set_interfaces(context, iface_list)
|
||||
}
|
||||
|
||||
fn as_script_object(&self) -> Option<ScriptObject<'gc>> {
|
||||
Some(self.base())
|
||||
}
|
||||
|
||||
fn as_xml_node(&self) -> Option<XMLNode<'gc>> {
|
||||
Some(self.node())
|
||||
}
|
||||
|
||||
fn as_ptr(&self) -> *const ObjectPtr {
|
||||
self.base().as_ptr() as *const ObjectPtr
|
||||
}
|
||||
|
||||
fn length(&self) -> usize {
|
||||
self.base().length()
|
||||
}
|
||||
|
||||
fn array(&self) -> Vec<Value<'gc>> {
|
||||
self.base().array()
|
||||
}
|
||||
|
||||
fn set_length(&self, gc_context: MutationContext<'gc, '_>, length: usize) {
|
||||
self.base().set_length(gc_context, length)
|
||||
}
|
||||
|
||||
fn array_element(&self, index: usize) -> Value<'gc> {
|
||||
self.base().array_element(index)
|
||||
}
|
||||
|
||||
fn set_array_element(
|
||||
&self,
|
||||
index: usize,
|
||||
value: Value<'gc>,
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
) -> usize {
|
||||
self.base().set_array_element(index, value, gc_context)
|
||||
}
|
||||
|
||||
fn delete_array_element(&self, index: usize, gc_context: MutationContext<'gc, '_>) {
|
||||
self.base().delete_array_element(index, gc_context)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,228 @@
|
|||
//! AVM1 object type to represent the attributes of XML nodes
|
||||
|
||||
use crate::avm1::function::Executable;
|
||||
use crate::avm1::object::{ObjectPtr, TObject};
|
||||
use crate::avm1::property::Attribute;
|
||||
use crate::avm1::return_value::ReturnValue;
|
||||
use crate::avm1::{Avm1, Error, Object, ScriptObject, UpdateContext, Value};
|
||||
use crate::xml::{XMLDocument, XMLNode};
|
||||
use enumset::EnumSet;
|
||||
use gc_arena::{Collect, MutationContext};
|
||||
use std::collections::HashSet;
|
||||
use std::fmt;
|
||||
|
||||
/// An Object that is inherently tied to an XML document's ID map.
|
||||
///
|
||||
/// Note that this is *not* the same as the XML root object itself; and
|
||||
/// furthermore this object is linked to the document, not the root node of the
|
||||
/// document.
|
||||
#[derive(Clone, Copy, Collect)]
|
||||
#[collect(no_drop)]
|
||||
pub struct XMLIDMapObject<'gc>(ScriptObject<'gc>, XMLDocument<'gc>);
|
||||
|
||||
impl<'gc> XMLIDMapObject<'gc> {
|
||||
/// Construct an XMLIDMapObject for an already existing node's
|
||||
/// attributes.
|
||||
pub fn from_xml_document(
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
xml_doc: XMLDocument<'gc>,
|
||||
) -> Object<'gc> {
|
||||
XMLIDMapObject(ScriptObject::object(gc_context, None), xml_doc).into()
|
||||
}
|
||||
|
||||
fn base(&self) -> ScriptObject<'gc> {
|
||||
match self {
|
||||
XMLIDMapObject(base, ..) => *base,
|
||||
}
|
||||
}
|
||||
|
||||
fn document(&self) -> XMLDocument<'gc> {
|
||||
match self {
|
||||
XMLIDMapObject(_, document) => *document,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for XMLIDMapObject<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
XMLIDMapObject(base, document) => f
|
||||
.debug_tuple("XMLIDMapObject")
|
||||
.field(base)
|
||||
.field(document)
|
||||
.finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc> TObject<'gc> for XMLIDMapObject<'gc> {
|
||||
fn get_local(
|
||||
&self,
|
||||
name: &str,
|
||||
avm: &mut Avm1<'gc>,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
if let Some(mut node) = self.document().get_node_by_id(name) {
|
||||
Ok(node
|
||||
.script_object(context.gc_context, Some(avm.prototypes().xml_node))
|
||||
.into())
|
||||
} else {
|
||||
self.base().get_local(name, avm, context, this)
|
||||
}
|
||||
}
|
||||
|
||||
fn set(
|
||||
&self,
|
||||
name: &str,
|
||||
value: Value<'gc>,
|
||||
avm: &mut Avm1<'gc>,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
) -> Result<(), Error> {
|
||||
self.base().set(name, value, avm, context)
|
||||
}
|
||||
|
||||
fn call(
|
||||
&self,
|
||||
avm: &mut Avm1<'gc>,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
self.base().call(avm, context, this, args)
|
||||
}
|
||||
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
fn new(
|
||||
&self,
|
||||
_avm: &mut Avm1<'gc>,
|
||||
_context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
_this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<Object<'gc>, Error> {
|
||||
//TODO: `new xmlnode.attributes()` returns undefined, not an object
|
||||
Err("Cannot create new XML Attributes object".into())
|
||||
}
|
||||
|
||||
fn delete(&self, gc_context: MutationContext<'gc, '_>, name: &str) -> bool {
|
||||
self.base().delete(gc_context, name)
|
||||
}
|
||||
|
||||
fn add_property(
|
||||
&self,
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
name: &str,
|
||||
get: Executable<'gc>,
|
||||
set: Option<Executable<'gc>>,
|
||||
attributes: EnumSet<Attribute>,
|
||||
) {
|
||||
self.base()
|
||||
.add_property(gc_context, name, get, set, attributes)
|
||||
}
|
||||
|
||||
fn define_value(
|
||||
&self,
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
name: &str,
|
||||
value: Value<'gc>,
|
||||
attributes: EnumSet<Attribute>,
|
||||
) {
|
||||
self.base()
|
||||
.define_value(gc_context, name, value, attributes)
|
||||
}
|
||||
|
||||
fn set_attributes(
|
||||
&mut self,
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
name: Option<&str>,
|
||||
set_attributes: EnumSet<Attribute>,
|
||||
clear_attributes: EnumSet<Attribute>,
|
||||
) {
|
||||
self.base()
|
||||
.set_attributes(gc_context, name, set_attributes, clear_attributes)
|
||||
}
|
||||
|
||||
fn proto(&self) -> Option<Object<'gc>> {
|
||||
self.base().proto()
|
||||
}
|
||||
|
||||
fn has_property(&self, name: &str) -> bool {
|
||||
self.base().has_property(name)
|
||||
}
|
||||
|
||||
fn has_own_property(&self, name: &str) -> bool {
|
||||
self.document().get_node_by_id(name).is_some() || self.base().has_own_property(name)
|
||||
}
|
||||
|
||||
fn is_property_overwritable(&self, name: &str) -> bool {
|
||||
self.base().is_property_overwritable(name)
|
||||
}
|
||||
|
||||
fn is_property_enumerable(&self, name: &str) -> bool {
|
||||
self.base().is_property_enumerable(name)
|
||||
}
|
||||
|
||||
fn get_keys(&self) -> HashSet<String> {
|
||||
let keys = self.base().get_keys();
|
||||
keys.union(&self.document().get_node_ids())
|
||||
.map(|s| s.to_string())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn as_string(&self) -> String {
|
||||
self.base().as_string()
|
||||
}
|
||||
|
||||
fn type_of(&self) -> &'static str {
|
||||
self.base().type_of()
|
||||
}
|
||||
|
||||
fn interfaces(&self) -> Vec<Object<'gc>> {
|
||||
self.base().interfaces()
|
||||
}
|
||||
|
||||
fn set_interfaces(&mut self, context: MutationContext<'gc, '_>, iface_list: Vec<Object<'gc>>) {
|
||||
self.base().set_interfaces(context, iface_list)
|
||||
}
|
||||
|
||||
fn as_script_object(&self) -> Option<ScriptObject<'gc>> {
|
||||
Some(self.base())
|
||||
}
|
||||
|
||||
fn as_xml_node(&self) -> Option<XMLNode<'gc>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_ptr(&self) -> *const ObjectPtr {
|
||||
self.base().as_ptr() as *const ObjectPtr
|
||||
}
|
||||
|
||||
fn length(&self) -> usize {
|
||||
self.base().length()
|
||||
}
|
||||
|
||||
fn array(&self) -> Vec<Value<'gc>> {
|
||||
self.base().array()
|
||||
}
|
||||
|
||||
fn set_length(&self, gc_context: MutationContext<'gc, '_>, length: usize) {
|
||||
self.base().set_length(gc_context, length)
|
||||
}
|
||||
|
||||
fn array_element(&self, index: usize) -> Value<'gc> {
|
||||
self.base().array_element(index)
|
||||
}
|
||||
|
||||
fn set_array_element(
|
||||
&self,
|
||||
index: usize,
|
||||
value: Value<'gc>,
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
) -> usize {
|
||||
self.base().set_array_element(index, value, gc_context)
|
||||
}
|
||||
|
||||
fn delete_array_element(&self, index: usize, gc_context: MutationContext<'gc, '_>) {
|
||||
self.base().delete_array_element(index, gc_context)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,221 @@
|
|||
//! AVM1 object type to represent XML nodes
|
||||
|
||||
use crate::avm1::function::Executable;
|
||||
use crate::avm1::object::{ObjectPtr, TObject};
|
||||
use crate::avm1::property::Attribute;
|
||||
use crate::avm1::return_value::ReturnValue;
|
||||
use crate::avm1::{Avm1, Error, Object, ScriptObject, UpdateContext, Value};
|
||||
use crate::xml::{XMLDocument, XMLNode};
|
||||
use enumset::EnumSet;
|
||||
use gc_arena::{Collect, MutationContext};
|
||||
use std::collections::HashSet;
|
||||
use std::fmt;
|
||||
|
||||
/// A ScriptObject that is inherently tied to an XML node.
|
||||
#[derive(Clone, Copy, Collect)]
|
||||
#[collect(no_drop)]
|
||||
pub struct XMLObject<'gc>(ScriptObject<'gc>, XMLNode<'gc>);
|
||||
|
||||
impl<'gc> XMLObject<'gc> {
|
||||
/// Construct a new XML node and object pair.
|
||||
pub fn empty_node(
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
proto: Option<Object<'gc>>,
|
||||
) -> Object<'gc> {
|
||||
let empty_document = XMLDocument::new(gc_context);
|
||||
let mut xml_node = XMLNode::new_text(gc_context, "", empty_document);
|
||||
let base_object = ScriptObject::object(gc_context, proto);
|
||||
let object = XMLObject(base_object, xml_node).into();
|
||||
|
||||
xml_node.introduce_script_object(gc_context, object);
|
||||
|
||||
object
|
||||
}
|
||||
|
||||
/// Construct an XMLObject for an already existing node.
|
||||
pub fn from_xml_node(
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
xml_node: XMLNode<'gc>,
|
||||
proto: Option<Object<'gc>>,
|
||||
) -> Object<'gc> {
|
||||
XMLObject(ScriptObject::object(gc_context, proto), xml_node).into()
|
||||
}
|
||||
|
||||
fn base(&self) -> ScriptObject<'gc> {
|
||||
match self {
|
||||
XMLObject(base, ..) => *base,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for XMLObject<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
XMLObject(base, node) => f.debug_tuple("XMLObject").field(base).field(node).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc> TObject<'gc> for XMLObject<'gc> {
|
||||
fn get_local(
|
||||
&self,
|
||||
name: &str,
|
||||
avm: &mut Avm1<'gc>,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
self.base().get_local(name, avm, context, this)
|
||||
}
|
||||
|
||||
fn set(
|
||||
&self,
|
||||
name: &str,
|
||||
value: Value<'gc>,
|
||||
avm: &mut Avm1<'gc>,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
) -> Result<(), Error> {
|
||||
self.base().set(name, value, avm, context)
|
||||
}
|
||||
|
||||
fn call(
|
||||
&self,
|
||||
avm: &mut Avm1<'gc>,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
self.base().call(avm, context, this, args)
|
||||
}
|
||||
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
fn new(
|
||||
&self,
|
||||
_avm: &mut Avm1<'gc>,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<Object<'gc>, Error> {
|
||||
Ok(XMLObject::empty_node(context.gc_context, Some(this)))
|
||||
}
|
||||
|
||||
fn delete(&self, gc_context: MutationContext<'gc, '_>, name: &str) -> bool {
|
||||
self.base().delete(gc_context, name)
|
||||
}
|
||||
|
||||
fn add_property(
|
||||
&self,
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
name: &str,
|
||||
get: Executable<'gc>,
|
||||
set: Option<Executable<'gc>>,
|
||||
attributes: EnumSet<Attribute>,
|
||||
) {
|
||||
self.base()
|
||||
.add_property(gc_context, name, get, set, attributes)
|
||||
}
|
||||
|
||||
fn define_value(
|
||||
&self,
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
name: &str,
|
||||
value: Value<'gc>,
|
||||
attributes: EnumSet<Attribute>,
|
||||
) {
|
||||
self.base()
|
||||
.define_value(gc_context, name, value, attributes)
|
||||
}
|
||||
|
||||
fn set_attributes(
|
||||
&mut self,
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
name: Option<&str>,
|
||||
set_attributes: EnumSet<Attribute>,
|
||||
clear_attributes: EnumSet<Attribute>,
|
||||
) {
|
||||
self.base()
|
||||
.set_attributes(gc_context, name, set_attributes, clear_attributes)
|
||||
}
|
||||
|
||||
fn proto(&self) -> Option<Object<'gc>> {
|
||||
self.base().proto()
|
||||
}
|
||||
|
||||
fn has_property(&self, name: &str) -> bool {
|
||||
self.base().has_property(name)
|
||||
}
|
||||
|
||||
fn has_own_property(&self, name: &str) -> bool {
|
||||
self.base().has_own_property(name)
|
||||
}
|
||||
|
||||
fn is_property_overwritable(&self, name: &str) -> bool {
|
||||
self.base().is_property_overwritable(name)
|
||||
}
|
||||
|
||||
fn is_property_enumerable(&self, name: &str) -> bool {
|
||||
self.base().is_property_enumerable(name)
|
||||
}
|
||||
|
||||
fn get_keys(&self) -> HashSet<String> {
|
||||
self.base().get_keys()
|
||||
}
|
||||
|
||||
fn as_string(&self) -> String {
|
||||
self.base().as_string()
|
||||
}
|
||||
|
||||
fn type_of(&self) -> &'static str {
|
||||
self.base().type_of()
|
||||
}
|
||||
|
||||
fn interfaces(&self) -> Vec<Object<'gc>> {
|
||||
self.base().interfaces()
|
||||
}
|
||||
|
||||
fn set_interfaces(&mut self, context: MutationContext<'gc, '_>, iface_list: Vec<Object<'gc>>) {
|
||||
self.base().set_interfaces(context, iface_list)
|
||||
}
|
||||
|
||||
fn as_script_object(&self) -> Option<ScriptObject<'gc>> {
|
||||
Some(self.base())
|
||||
}
|
||||
|
||||
fn as_xml_node(&self) -> Option<XMLNode<'gc>> {
|
||||
match self {
|
||||
XMLObject(_base, node) => Some(*node),
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ptr(&self) -> *const ObjectPtr {
|
||||
self.base().as_ptr() as *const ObjectPtr
|
||||
}
|
||||
|
||||
fn length(&self) -> usize {
|
||||
self.base().length()
|
||||
}
|
||||
|
||||
fn array(&self) -> Vec<Value<'gc>> {
|
||||
self.base().array()
|
||||
}
|
||||
|
||||
fn set_length(&self, gc_context: MutationContext<'gc, '_>, length: usize) {
|
||||
self.base().set_length(gc_context, length)
|
||||
}
|
||||
|
||||
fn array_element(&self, index: usize) -> Value<'gc> {
|
||||
self.base().array_element(index)
|
||||
}
|
||||
|
||||
fn set_array_element(
|
||||
&self,
|
||||
index: usize,
|
||||
value: Value<'gc>,
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
) -> usize {
|
||||
self.base().set_array_element(index, value, gc_context)
|
||||
}
|
||||
|
||||
fn delete_array_element(&self, index: usize, gc_context: MutationContext<'gc, '_>) {
|
||||
self.base().delete_array_element(index, gc_context)
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@ mod prelude;
|
|||
pub mod shape_utils;
|
||||
pub mod tag_utils;
|
||||
mod transform;
|
||||
mod xml;
|
||||
|
||||
pub mod backend;
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
//! Garbage-collectable XML DOM impl
|
||||
|
||||
mod document;
|
||||
mod error;
|
||||
mod namespace;
|
||||
mod tree;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub use document::XMLDocument;
|
||||
pub use error::Error;
|
||||
pub use error::ParseError;
|
||||
pub use namespace::XMLName;
|
||||
pub use tree::XMLNode;
|
||||
|
||||
pub const ELEMENT_NODE: u8 = 1;
|
||||
pub const TEXT_NODE: u8 = 3;
|
||||
pub const COMMENT_NODE: u8 = 8;
|
||||
pub const DOCUMENT_NODE: u8 = 9;
|
||||
pub const DOCUMENT_TYPE_NODE: u8 = 10;
|
|
@ -0,0 +1,279 @@
|
|||
//! XML Document
|
||||
|
||||
use crate::avm1::xml_idmap_object::XMLIDMapObject;
|
||||
use crate::avm1::Object;
|
||||
use crate::xml::{Error, ParseError, XMLName, XMLNode};
|
||||
use gc_arena::{Collect, GcCell, MutationContext};
|
||||
use quick_xml::events::{BytesDecl, Event};
|
||||
use quick_xml::{Error as QXError, Writer};
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
use std::fmt;
|
||||
use std::io::Cursor;
|
||||
|
||||
/// The entirety of an XML document.
|
||||
#[derive(Copy, Clone, Collect)]
|
||||
#[collect(no_drop)]
|
||||
pub struct XMLDocument<'gc>(GcCell<'gc, XMLDocumentData<'gc>>);
|
||||
|
||||
#[derive(Clone, Collect)]
|
||||
#[collect(no_drop)]
|
||||
pub struct XMLDocumentData<'gc> {
|
||||
/// The root node of the XML document.
|
||||
root: Option<XMLNode<'gc>>,
|
||||
|
||||
/// Whether or not the document has a document declaration.
|
||||
has_xmldecl: bool,
|
||||
|
||||
/// The XML version string, if set.
|
||||
version: String,
|
||||
|
||||
/// The XML document encoding, if set.
|
||||
encoding: Option<String>,
|
||||
|
||||
/// The XML standalone flag, if set.
|
||||
standalone: Option<String>,
|
||||
|
||||
/// The XML doctype, if set.
|
||||
doctype: Option<XMLNode<'gc>>,
|
||||
|
||||
/// The document's ID map.
|
||||
///
|
||||
/// When nodes are parsed into the document by way of `parseXML` or the
|
||||
/// document constructor, they get put into this list here, which is used
|
||||
/// to populate the document's `idMap`.
|
||||
idmap: BTreeMap<String, XMLNode<'gc>>,
|
||||
|
||||
/// The script object associated with this XML node, if any.
|
||||
idmap_script_object: Option<Object<'gc>>,
|
||||
|
||||
/// The last parse error encountered, if any.
|
||||
last_parse_error: Option<ParseError>,
|
||||
}
|
||||
|
||||
impl<'gc> XMLDocument<'gc> {
|
||||
/// Construct a new, empty XML document.
|
||||
pub fn new(mc: MutationContext<'gc, '_>) -> Self {
|
||||
let document = Self(GcCell::allocate(
|
||||
mc,
|
||||
XMLDocumentData {
|
||||
root: None,
|
||||
has_xmldecl: false,
|
||||
version: "1.0".to_string(),
|
||||
encoding: None,
|
||||
standalone: None,
|
||||
doctype: None,
|
||||
idmap: BTreeMap::new(),
|
||||
idmap_script_object: None,
|
||||
last_parse_error: None,
|
||||
},
|
||||
));
|
||||
let root = XMLNode::new_document_root(mc, document);
|
||||
|
||||
document.0.write(mc).root = Some(root);
|
||||
|
||||
document
|
||||
}
|
||||
|
||||
/// Yield the document in node form.
|
||||
///
|
||||
/// If the document does not have a node, then this function will panic.
|
||||
pub fn as_node(self) -> XMLNode<'gc> {
|
||||
self.0
|
||||
.read()
|
||||
.root
|
||||
.expect("Document must always have a root node")
|
||||
}
|
||||
|
||||
/// Create a duplicate copy of this document.
|
||||
///
|
||||
/// The contents of the document will not be duplicated. This results in a
|
||||
/// rootless document that is not safe to use without first linking another
|
||||
/// root node into it. (See `link_root_node`.)
|
||||
pub fn duplicate(self, gc_context: MutationContext<'gc, '_>) -> Self {
|
||||
let self_read = self.0.read();
|
||||
Self(GcCell::allocate(
|
||||
gc_context,
|
||||
XMLDocumentData {
|
||||
root: None,
|
||||
has_xmldecl: self_read.has_xmldecl,
|
||||
version: self_read.version.clone(),
|
||||
encoding: self_read.encoding.clone(),
|
||||
standalone: self_read.standalone.clone(),
|
||||
doctype: None,
|
||||
idmap: BTreeMap::new(),
|
||||
idmap_script_object: None,
|
||||
last_parse_error: None,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
/// Set the root node of the document, if possible.
|
||||
///
|
||||
/// If the proposed root is not an `XMLNode::DocumentRoot`, then a fresh
|
||||
/// document root node will be created and the root will be adopted into
|
||||
/// it.
|
||||
///
|
||||
/// If the document already has a root node, nothing happens.
|
||||
pub fn link_root_node(
|
||||
&mut self,
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
proposed_root: XMLNode<'gc>,
|
||||
) {
|
||||
match (
|
||||
&mut *self.0.write(gc_context),
|
||||
proposed_root.is_document_root(),
|
||||
) {
|
||||
(XMLDocumentData { root, .. }, true) if root.is_none() => {
|
||||
*root = Some(proposed_root);
|
||||
}
|
||||
(XMLDocumentData { root, .. }, false) if root.is_none() => {
|
||||
*root = Some(XMLNode::new_document_root(gc_context, *self));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the DOCTYPE of the document, if possible.
|
||||
///
|
||||
/// If the proposed doctype is not an `XMLNode::DocType`, or the document
|
||||
/// already has a doctype, nothing happens.
|
||||
pub fn link_doctype(
|
||||
&mut self,
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
proposed_doctype: XMLNode<'gc>,
|
||||
) {
|
||||
let mut self_write = self.0.write(gc_context);
|
||||
|
||||
if self_write.doctype.is_none() && proposed_doctype.is_doctype() {
|
||||
self_write.doctype = Some(proposed_doctype);
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieve the first DocType node in the document.
|
||||
pub fn doctype(self) -> Option<XMLNode<'gc>> {
|
||||
self.0.read().doctype
|
||||
}
|
||||
|
||||
/// Process events being passed into some node of the document.
|
||||
///
|
||||
/// There are certain nodes which have document-wide implications if parsed
|
||||
/// into any node within the document. These are processed here.
|
||||
pub fn process_event(self, mc: MutationContext<'gc, '_>, event: &Event) -> Result<(), Error> {
|
||||
if let Event::Decl(bd) = event {
|
||||
let mut self_write = self.0.write(mc);
|
||||
|
||||
self_write.has_xmldecl = true;
|
||||
self_write.version = String::from_utf8(bd.version()?.into_owned())?;
|
||||
self_write.encoding = if let Some(encoding) = bd.encoding() {
|
||||
Some(String::from_utf8(encoding?.into_owned())?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self_write.standalone = if let Some(standalone) = bd.standalone() {
|
||||
Some(String::from_utf8(standalone?.into_owned())?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generate a string matching the XML document declaration, if there is
|
||||
/// one.
|
||||
pub fn xmldecl_string(self) -> Result<Option<String>, Error> {
|
||||
let self_read = self.0.read();
|
||||
|
||||
if self_read.has_xmldecl {
|
||||
let mut result = Vec::new();
|
||||
let mut writer = Writer::new(Cursor::new(&mut result));
|
||||
let bd = BytesDecl::new(
|
||||
&self_read.version.as_bytes(),
|
||||
self_read.encoding.as_ref().map(|s| s.as_bytes()),
|
||||
self_read.standalone.as_ref().map(|s| s.as_bytes()),
|
||||
);
|
||||
writer.write_event(Event::Decl(bd))?;
|
||||
|
||||
Ok(Some(String::from_utf8(result)?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtain the script object for the document's `idMap` property, or create
|
||||
/// one if it doesn't exist
|
||||
pub fn idmap_script_object(&mut self, gc_context: MutationContext<'gc, '_>) -> Object<'gc> {
|
||||
let mut object = self.0.read().idmap_script_object;
|
||||
if object.is_none() {
|
||||
object = Some(XMLIDMapObject::from_xml_document(gc_context, *self));
|
||||
self.0.write(gc_context).idmap_script_object = object;
|
||||
}
|
||||
|
||||
object.unwrap()
|
||||
}
|
||||
|
||||
/// Update the idmap object with a given new node.
|
||||
pub fn update_idmap(&mut self, mc: MutationContext<'gc, '_>, node: XMLNode<'gc>) {
|
||||
if let Some(id) = node.attribute_value(&XMLName::from_str("id")) {
|
||||
self.0.write(mc).idmap.insert(id, node);
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieve a node from the idmap.
|
||||
///
|
||||
/// This only retrieves nodes that had this `id` *at the time of string
|
||||
/// parsing*. Nodes which obtained the `id` after the fact, or nodes with
|
||||
/// the `id` that were added to the document after the fact, will not be
|
||||
/// returned by this function.
|
||||
pub fn get_node_by_id(self, id: &str) -> Option<XMLNode<'gc>> {
|
||||
self.0.read().idmap.get(id).copied()
|
||||
}
|
||||
|
||||
/// Retrieve all IDs currently present in the idmap.
|
||||
pub fn get_node_ids(self) -> HashSet<String> {
|
||||
let mut result = HashSet::new();
|
||||
|
||||
for key in self.0.read().idmap.keys() {
|
||||
result.insert(key.to_string());
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Log the result of an XML parse, saving the error for later inspection
|
||||
/// if necessary.
|
||||
pub fn log_parse_result<O>(
|
||||
self,
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
maybe_error: Result<O, QXError>,
|
||||
) -> Result<O, ParseError> {
|
||||
match maybe_error {
|
||||
Ok(v) => Ok(v),
|
||||
Err(e) => {
|
||||
let new_error = ParseError::from_quickxml_error(e);
|
||||
|
||||
self.0.write(gc_context).last_parse_error = Some(new_error.clone());
|
||||
|
||||
Err(new_error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the last parse error within this document, if any.
|
||||
pub fn last_parse_error(self) -> Option<ParseError> {
|
||||
self.0.read().last_parse_error.clone()
|
||||
}
|
||||
|
||||
/// Clear the previous parse error.
|
||||
pub fn clear_parse_error(self, gc_context: MutationContext<'gc, '_>) {
|
||||
self.0.write(gc_context).last_parse_error = None;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc> fmt::Debug for XMLDocument<'gc> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("XMLDocument")
|
||||
.field("root", &self.0.read().root)
|
||||
.finish()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
//! Error types used in XML handling
|
||||
|
||||
use gc_arena::{Collect, CollectionContext};
|
||||
use quick_xml::Error as QXError;
|
||||
use std::error::Error as StdError;
|
||||
use std::fmt::Error as FmtError;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::rc::Rc;
|
||||
|
||||
/// Boxed error type.
|
||||
pub type Error = Box<dyn std::error::Error>;
|
||||
|
||||
/// Boxed `quick_xml` error
|
||||
///
|
||||
/// We can't clone `quick_xml` errors, nor can we clone several of the error
|
||||
/// types it wraps over, so this creates an RC boxed version of the error that
|
||||
/// can then be used elsewhere.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ParseError(Rc<QXError>);
|
||||
|
||||
unsafe impl Collect for ParseError {
|
||||
/// ParseError does not contain GC pointers.
|
||||
fn trace(&self, _cc: CollectionContext<'_>) {}
|
||||
}
|
||||
|
||||
impl ParseError {
|
||||
///Convert a quick_xml error into a `ParseError`.
|
||||
pub fn from_quickxml_error(err: QXError) -> Self {
|
||||
ParseError(Rc::new(err))
|
||||
}
|
||||
|
||||
pub fn ref_error(&self) -> &QXError {
|
||||
&*self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ParseError {
|
||||
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), FmtError> {
|
||||
self.0.fmt(fmt)
|
||||
}
|
||||
}
|
||||
|
||||
impl StdError for ParseError {
|
||||
#[allow(deprecated)]
|
||||
fn cause(&self) -> Option<&dyn StdError> {
|
||||
self.0.cause()
|
||||
}
|
||||
|
||||
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||
self.0.source()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
//! XML namespacing support
|
||||
|
||||
use crate::xml::Error;
|
||||
use gc_arena::Collect;
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
|
||||
/// Represents a scoped name within XML.
|
||||
///
|
||||
/// All names in XML are optionally namespaced. Each namespace is represented
|
||||
/// as a string; the document contains a mapping of namespaces to URIs.
|
||||
///
|
||||
/// The special namespace `xmlns` is used to map namespace strings to URIs; it
|
||||
/// should not be used for user-specified namespaces.
|
||||
#[derive(Clone, Collect, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[collect(no_drop)]
|
||||
pub struct XMLName {
|
||||
/// The name of the XML namespace this name is scoped to.
|
||||
///
|
||||
/// Names without a namespace use the default namespace.
|
||||
///
|
||||
/// Namespaces may be resolved to a URI by consulting the encapsulating
|
||||
/// document.
|
||||
namespace: Option<String>,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl XMLName {
|
||||
/// Construct an XML name from it's parts (name and namespace).
|
||||
pub fn from_parts(namespace: Option<&str>, name: &str) -> Self {
|
||||
XMLName {
|
||||
namespace: namespace.map(|s| s.to_string()),
|
||||
name: name.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
|
||||
Self::from_bytes_cow(Cow::Borrowed(bytes))
|
||||
}
|
||||
|
||||
pub fn from_str(strval: &str) -> Self {
|
||||
Self::from_str_cow(Cow::Borrowed(strval))
|
||||
}
|
||||
|
||||
pub fn from_bytes_cow(bytes: Cow<[u8]>) -> Result<Self, Error> {
|
||||
let full_name = match bytes {
|
||||
Cow::Borrowed(ln) => Cow::Borrowed(std::str::from_utf8(ln)?),
|
||||
Cow::Owned(ln) => Cow::Owned(String::from_utf8(ln)?),
|
||||
};
|
||||
|
||||
Ok(Self::from_str_cow(full_name))
|
||||
}
|
||||
|
||||
pub fn from_str_cow(full_name: Cow<str>) -> Self {
|
||||
if let Some(colon_index) = full_name.find(':') {
|
||||
Self {
|
||||
namespace: Some(full_name[0..colon_index].to_owned()),
|
||||
name: full_name[colon_index + 1..].to_owned(),
|
||||
}
|
||||
} else {
|
||||
Self {
|
||||
namespace: None,
|
||||
name: full_name.into_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieve the local part of this name.
|
||||
pub fn local_name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
/// Retrieve the prefix part of this name, if available.
|
||||
pub fn prefix(&self) -> Option<&str> {
|
||||
self.namespace.as_deref()
|
||||
}
|
||||
|
||||
/// Return the fully qualified part of the name.
|
||||
///
|
||||
/// This consists of the namespace, if present, plus a colon and local name.
|
||||
pub fn node_name(&self) -> String {
|
||||
if let Some(ref ns) = self.namespace {
|
||||
format!("{}:{}", ns, self.name)
|
||||
} else {
|
||||
self.name.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for XMLName {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("XMLName")
|
||||
.field("namespace", &self.namespace)
|
||||
.field("name", &self.name)
|
||||
.finish()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
//! XML tests
|
||||
|
||||
use crate::xml;
|
||||
use crate::xml::{XMLDocument, XMLName};
|
||||
use gc_arena::rootless_arena;
|
||||
|
||||
/// Tests very basic parsing of a single-element document.
|
||||
#[test]
|
||||
fn parse_single_element() {
|
||||
rootless_arena(|mc| {
|
||||
let xml = XMLDocument::new(mc);
|
||||
xml.as_node()
|
||||
.replace_with_str(mc, "<test></test>")
|
||||
.expect("Parsed document");
|
||||
dbg!(xml);
|
||||
let mut roots = xml
|
||||
.as_node()
|
||||
.children()
|
||||
.expect("Parsed document should be capable of having child nodes");
|
||||
|
||||
let root = roots.next().expect("Parsed document should have a root");
|
||||
assert_eq!(root.node_type(), xml::ELEMENT_NODE);
|
||||
assert_eq!(root.tag_name(), Some(XMLName::from_str("test")));
|
||||
|
||||
let mut root_children = root.children().unwrap();
|
||||
assert!(root_children.next().is_none());
|
||||
|
||||
assert!(roots.next().is_none());
|
||||
})
|
||||
}
|
||||
|
||||
/// Tests double-ended traversal of child nodes via DoubleEndedIterator.
|
||||
#[test]
|
||||
fn double_ended_children() {
|
||||
rootless_arena(|mc| {
|
||||
let xml = XMLDocument::new(mc);
|
||||
xml.as_node()
|
||||
.replace_with_str(
|
||||
mc,
|
||||
"<test></test><test2></test2><test3></test3><test4></test4><test5></test5>",
|
||||
)
|
||||
.expect("Parsed document");
|
||||
|
||||
let mut roots = xml
|
||||
.as_node()
|
||||
.children()
|
||||
.expect("Parsed document should be capable of having child nodes");
|
||||
|
||||
let root = roots.next().expect("Should have first root");
|
||||
assert_eq!(root.node_type(), xml::ELEMENT_NODE);
|
||||
assert_eq!(root.tag_name(), Some(XMLName::from_str("test")));
|
||||
|
||||
let root = roots.next_back().expect("Should have last root");
|
||||
assert_eq!(root.node_type(), xml::ELEMENT_NODE);
|
||||
assert_eq!(root.tag_name(), Some(XMLName::from_str("test5")));
|
||||
|
||||
let root = roots.next().expect("Should have next root");
|
||||
assert_eq!(root.node_type(), xml::ELEMENT_NODE);
|
||||
assert_eq!(root.tag_name(), Some(XMLName::from_str("test2")));
|
||||
|
||||
let root = roots.next_back().expect("Should have second-to-last root");
|
||||
assert_eq!(root.node_type(), xml::ELEMENT_NODE);
|
||||
assert_eq!(root.tag_name(), Some(XMLName::from_str("test4")));
|
||||
|
||||
let root = roots.next().expect("Should have next root");
|
||||
assert_eq!(root.node_type(), xml::ELEMENT_NODE);
|
||||
assert_eq!(root.tag_name(), Some(XMLName::from_str("test3")));
|
||||
|
||||
assert!(roots.next().is_none());
|
||||
assert!(roots.next_back().is_none());
|
||||
})
|
||||
}
|
||||
|
||||
/// Tests round-trip XML writing behavior.
|
||||
#[test]
|
||||
fn round_trip_tostring() {
|
||||
let test_string = "<test><!-- Comment -->This is a text node</test>";
|
||||
|
||||
rootless_arena(|mc| {
|
||||
let xml = XMLDocument::new(mc);
|
||||
xml.as_node()
|
||||
.replace_with_str(mc, test_string)
|
||||
.expect("Parsed document");
|
||||
|
||||
let result = xml
|
||||
.as_node()
|
||||
.into_string(&mut |_| true)
|
||||
.expect("Successful toString");
|
||||
|
||||
assert_eq!(test_string, result);
|
||||
})
|
||||
}
|
||||
|
||||
/// Tests filtered XML writing behavior.
|
||||
#[test]
|
||||
fn round_trip_filtered_tostring() {
|
||||
let test_string = "<test><!-- Comment -->This is a text node</test>";
|
||||
|
||||
rootless_arena(|mc| {
|
||||
let xml = XMLDocument::new(mc);
|
||||
xml.as_node()
|
||||
.replace_with_str(mc, test_string)
|
||||
.expect("Parsed document");
|
||||
|
||||
let result = xml
|
||||
.as_node()
|
||||
.into_string(&mut |node| !node.is_comment())
|
||||
.expect("Successful toString");
|
||||
|
||||
assert_eq!("<test>This is a text node</test>", result);
|
||||
})
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -105,6 +105,27 @@ swf_tests! {
|
|||
(strictequals_swf6, "avm1/strictequals_swf6", 1),
|
||||
(global_is_bare, "avm1/global_is_bare", 1),
|
||||
(as2_oop, "avm1/as2_oop", 1),
|
||||
(xml, "avm1/xml", 1),
|
||||
(xml_namespaces, "avm1/xml_namespaces", 1),
|
||||
(xml_node_namespaceuri, "avm1/xml_node_namespaceuri", 1),
|
||||
(xml_node_weirdnamespace, "avm1/xml_node_weirdnamespace", 1),
|
||||
(xml_clone_expandos, "avm1/xml_clone_expandos", 1),
|
||||
(xml_has_child_nodes, "avm1/xml_has_child_nodes", 1),
|
||||
(xml_first_last_child, "avm1/xml_first_last_child", 1),
|
||||
(xml_parent_and_child, "avm1/xml_parent_and_child", 1),
|
||||
(xml_siblings, "avm1/xml_siblings", 1),
|
||||
(xml_attributes_read, "avm1/xml_attributes_read", 1),
|
||||
(xml_append_child, "avm1/xml_append_child", 1),
|
||||
(xml_append_child_with_parent, "avm1/xml_append_child_with_parent", 1),
|
||||
(xml_remove_node, "avm1/xml_remove_node", 1),
|
||||
(xml_insert_before, "avm1/xml_insert_before", 1),
|
||||
(xml_to_string, "avm1/xml_to_string", 1),
|
||||
(xml_to_string_comment, "avm1/xml_to_string_comment", 1),
|
||||
(xml_idmap, "avm1/xml_idmap", 1),
|
||||
(xml_inspect_doctype, "avm1/xml_inspect_doctype", 1),
|
||||
#[ignore] (xml_inspect_xmldecl, "avm1/xml_inspect_xmldecl", 1),
|
||||
(xml_inspect_createmethods, "avm1/xml_inspect_createmethods", 1),
|
||||
(xml_inspect_parsexml, "avm1/xml_inspect_parsexml", 1),
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
1
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
Children:
|
||||
Child 0
|
||||
1
|
||||
test
|
||||
null
|
||||
test
|
||||
|
||||
Children:
|
||||
End children.
|
||||
End children.
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,8 @@
|
|||
true
|
||||
true
|
||||
null
|
||||
true
|
||||
true
|
||||
null
|
||||
true
|
||||
true
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,20 @@
|
|||
true
|
||||
null
|
||||
true
|
||||
true
|
||||
null
|
||||
true
|
||||
Swapping nodes...
|
||||
true
|
||||
null
|
||||
true
|
||||
true
|
||||
null
|
||||
true
|
||||
Failing to swap nodes...
|
||||
true
|
||||
null
|
||||
true
|
||||
true
|
||||
null
|
||||
true
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,3 @@
|
|||
|
||||
attr y string
|
||||
undefined
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,19 @@
|
|||
Shallow copy
|
||||
0
|
||||
0
|
||||
test
|
||||
Deep copy
|
||||
1
|
||||
0
|
||||
test
|
||||
All expandos
|
||||
xml_expando
|
||||
xmlnode_expando
|
||||
undefined
|
||||
undefined
|
||||
undefined
|
||||
undefined
|
||||
cloneNode w/o arg
|
||||
false
|
||||
0
|
||||
undefined
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,8 @@
|
|||
true
|
||||
true
|
||||
null
|
||||
null
|
||||
true
|
||||
true
|
||||
undefined
|
||||
undefined
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,3 @@
|
|||
false
|
||||
true
|
||||
false
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,21 @@
|
|||
true
|
||||
true
|
||||
idmap has test
|
||||
idmap has test2
|
||||
Manually added new node via appendChild
|
||||
Checking old copy of IDMAP...
|
||||
idmap has test
|
||||
idmap has test2
|
||||
Checking new copy of IDMAP...
|
||||
idmap has test
|
||||
idmap has test2
|
||||
Parsed another node with parseXML...
|
||||
idmap has test
|
||||
idmap has test2
|
||||
idmap has test4
|
||||
Checking for IDMAP expandos...
|
||||
idmap has test
|
||||
idmap has test124
|
||||
idmap has test2
|
||||
idmap has test4
|
||||
trace
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,20 @@
|
|||
true
|
||||
null
|
||||
true
|
||||
true
|
||||
null
|
||||
true
|
||||
Swapping nodes...
|
||||
true
|
||||
null
|
||||
true
|
||||
true
|
||||
null
|
||||
true
|
||||
Failing to swap nodes...
|
||||
true
|
||||
null
|
||||
true
|
||||
true
|
||||
null
|
||||
true
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,15 @@
|
|||
Type: 1
|
||||
Name: null
|
||||
Value: null
|
||||
Attributes defined
|
||||
Has 1 children
|
||||
Type: 1
|
||||
Name: test
|
||||
Value: null
|
||||
Attributes defined
|
||||
Has 1 children
|
||||
Type: 3
|
||||
Name: null
|
||||
Value: This is a test
|
||||
Attributes defined
|
||||
Has 0 children
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,7 @@
|
|||
Type: 1
|
||||
Name: null
|
||||
Value: null
|
||||
Attributes defined
|
||||
Has 0 children
|
||||
Doctype: <!DOCTYPE myfancydoctype>
|
||||
XML decl: undefined
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,62 @@
|
|||
Empty XML document
|
||||
Type: 1
|
||||
Name: null
|
||||
Value: null
|
||||
Attributes defined
|
||||
Has 0 children
|
||||
Replaced XML document
|
||||
Type: 1
|
||||
Name: null
|
||||
Value: null
|
||||
Attributes defined
|
||||
Has 2 children
|
||||
Type: 1
|
||||
Name: test
|
||||
Value: null
|
||||
Attributes defined
|
||||
Has 1 children
|
||||
Type: 3
|
||||
Name: null
|
||||
Value: I'm a test text node.
|
||||
Attributes defined
|
||||
Has 0 children
|
||||
Type: 3
|
||||
Name: null
|
||||
Value: Here's more text.
|
||||
Attributes defined
|
||||
Has 0 children
|
||||
Second replaced XML document
|
||||
Type: 1
|
||||
Name: null
|
||||
Value: null
|
||||
Attributes defined
|
||||
Has 2 children
|
||||
Type: 3
|
||||
Name: null
|
||||
Value: Now it's different:
|
||||
Attributes defined
|
||||
Has 0 children
|
||||
Type: 1
|
||||
Name: test2
|
||||
Value: null
|
||||
Attributes defined
|
||||
Has 0 children
|
||||
First document's first node
|
||||
Type: 1
|
||||
Name: test
|
||||
Value: null
|
||||
Attributes defined
|
||||
Has 1 children
|
||||
Type: 3
|
||||
Name: null
|
||||
Value: I'm a test text node.
|
||||
Attributes defined
|
||||
Has 0 children
|
||||
Parent: null
|
||||
First document's second node
|
||||
Type: 3
|
||||
Name: null
|
||||
Value: Here's more text.
|
||||
Attributes defined
|
||||
Has 0 children
|
||||
Parent: null
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,7 @@
|
|||
Type: 1
|
||||
Name: null
|
||||
Value: null
|
||||
Attributes defined
|
||||
Has 0 children
|
||||
Doctype: undefined
|
||||
XML decl: <?xml version='1.1' encoding='utf-8' standalone='yes' ?>
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,12 @@
|
|||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
https://example.com
|
||||
test
|
||||
null
|
||||
null
|
||||
https://example.com/2
|
||||
test
|
||||
https://example.com/2
|
||||
test
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,4 @@
|
|||
null
|
||||
|
||||
http://example.com/2
|
||||
http://example.com/3
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,4 @@
|
|||
null
|
||||
|
||||
http://example.com/2plain
|
||||
http://example.com/3
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,5 @@
|
|||
true
|
||||
true
|
||||
true
|
||||
true
|
||||
null
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,22 @@
|
|||
true
|
||||
null
|
||||
true
|
||||
true
|
||||
null
|
||||
null
|
||||
true
|
||||
true
|
||||
null
|
||||
After node removal
|
||||
true
|
||||
1
|
||||
true
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
true
|
||||
true
|
||||
null
|
||||
null
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,10 @@
|
|||
null
|
||||
true
|
||||
true
|
||||
true
|
||||
true
|
||||
true
|
||||
true
|
||||
null
|
||||
null
|
||||
null
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,6 @@
|
|||
<test />
|
||||
I'm a <b>text node</b>!
|
||||
<test><test2 /></test>
|
||||
I'm a <b>text node</b>!
|
||||
<test><test2>"Test" Node 2</test2></test>
|
||||
I'm a <b>text node</b>!
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
<test>This is a text node</test>
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue