avm2: Implement XML.children, XMLList.children, and related methods

This commit is contained in:
Aaron Hill 2023-02-27 12:56:32 -06:00
parent 7f58b92348
commit c04b463f1f
11 changed files with 144 additions and 12 deletions

View File

@ -10,6 +10,8 @@ package {
AS3 native function name():Object;
AS3 native function localName():Object;
AS3 native function toXMLString():String;
AS3 native function children():XMLList;
AS3 native function toString():String;
@ -32,6 +34,11 @@ package {
return self.AS3::toXMLString();
};
prototype.children = function():String {
var self:XML = this;
return self.AS3::children();
};
prototype.toString = function():String {
if (this === prototype) {
return "";
@ -39,6 +46,5 @@ package {
var self:XML = this;
return self.AS3::toString();
};
}
}

View File

@ -10,6 +10,7 @@ package {
AS3 native function hasSimpleContent():Boolean;
AS3 native function length():int
AS3 native function children():XMLList;
public native function toString():String;
}

View File

@ -1,8 +1,8 @@
//! XML builtin and prototype
use crate::avm2::e4x::E4XNode;
use crate::avm2::e4x::{E4XNode, E4XNodeKind};
pub use crate::avm2::object::xml_allocator;
use crate::avm2::object::{QNameObject, TObject};
use crate::avm2::object::{E4XOrXml, QNameObject, TObject, XmlListObject};
use crate::avm2::{Activation, Error, Object, QName, Value};
use crate::avm2_stub_method;
@ -81,3 +81,23 @@ pub fn to_xml_string<'gc>(
let node = xml.node();
Ok(Value::String(node.xml_to_xml_string(activation)?))
}
pub fn children<'gc>(
activation: &mut Activation<'_, 'gc>,
this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let xml = this.unwrap().as_xml_object().unwrap();
let children = if let E4XNodeKind::Element { children, .. } = &*xml.node().kind() {
// FIXME - avoid clone
children.clone()
} else {
Vec::new()
};
Ok(XmlListObject::new(
activation,
children.iter().map(|node| E4XOrXml::E4X(*node)).collect(),
)
.into())
}

View File

@ -4,7 +4,7 @@ pub use crate::avm2::object::xml_list_allocator;
use crate::{
avm2::{
e4x::{simple_content_to_string, E4XNode, E4XNodeKind},
object::E4XOrXml,
object::{E4XOrXml, XmlListObject},
Activation, Error, Object, TObject, Value,
},
avm2_stub_method,
@ -79,3 +79,19 @@ pub fn length<'gc>(
let children = list.children();
Ok(children.len().into())
}
pub fn children<'gc>(
activation: &mut Activation<'_, 'gc>,
this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let list = this.unwrap().as_xml_list_object().unwrap();
let children = list.children();
let mut sub_children = Vec::new();
for child in &*children {
if let E4XNodeKind::Element { ref children, .. } = &*child.node().kind() {
sub_children.extend(children.iter().map(|node| E4XOrXml::E4X(*node)));
}
}
Ok(XmlListObject::new(activation, sub_children).into())
}

View File

@ -195,4 +195,35 @@ impl<'gc> TObject<'gc> for XmlListObject<'gc> {
) -> Result<(), Error<'gc>> {
Err("Modifying an XMLList object is not yet implemented".into())
}
fn get_next_enumerant(
self,
last_index: u32,
_activation: &mut Activation<'_, 'gc>,
) -> Result<Option<u32>, Error<'gc>> {
let read = self.0.read();
if (last_index as usize) < read.children.len() {
return Ok(Some(last_index + 1));
}
Ok(None)
}
fn get_enumerant_name(
self,
index: u32,
_activation: &mut Activation<'_, 'gc>,
) -> Result<Value<'gc>, Error<'gc>> {
let children_len = self.0.read().children.len() as u32;
if children_len >= index {
Ok(index
.checked_sub(1)
.map(|index| index.into())
.unwrap_or(Value::Undefined))
} else {
Ok(self
.base()
.get_enumerant_name(index - children_len)
.unwrap_or(Value::Undefined))
}
}
}

View File

@ -149,12 +149,33 @@ impl<'gc> TObject<'gc> for XmlObject<'gc> {
read.base.get_property_local(name, activation)
}
fn set_property_local(
self,
_name: &Multiname<'gc>,
_value: Value<'gc>,
_activation: &mut Activation<'_, 'gc>,
) -> Result<(), Error<'gc>> {
Err("Modifying an XML object is not yet implemented".into())
fn has_own_property(self, name: &Multiname<'gc>) -> bool {
let read = self.0.read();
// FIXME - see if we can deduplicate this with get_property_local in
// an efficient way
if name.contains_public_namespace() {
if let Some(local_name) = name.local_name() {
// The only supported numerical index is 0
if let Ok(index) = local_name.parse::<usize>() {
return index == 0;
}
if let E4XNodeKind::Element {
children,
attributes,
} = &*read.node.kind()
{
let search_children = if name.is_attribute() {
attributes
} else {
children
};
return search_children.iter().any(|child| child.matches_name(name));
}
}
}
read.base.has_own_dynamic_property(name)
}
}

View File

@ -0,0 +1,26 @@
package {
public class Test {
public static function test() {
var outer = <outer>
<child kind="A">First Child</child>
<child kind="B">Second Child</child>
<child kind="A">Third Child: <p>Inner element</p></child>
</outer>;
trace("Children length: " + outer.children().length());
trace("'child' in outer: " + ('child' in outer));
for each (var child in outer.children()) {
trace("Child kind= " + child.@kind);
}
for each (var innerChild in outer.children().children()) {
trace("Inner child localName " + innerChild.localName());
}
var empty = <myelem/>;
trace("Empty children: " + empty.children().length());
}
}
}

View File

@ -0,0 +1,10 @@
Children length: 3
'child' in outer: true
Child kind= A
Child kind= B
Child kind= A
Inner child localName null
Inner child localName null
Inner child localName null
Inner child localName p
Empty children: 0

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
num_frames = 1