avm2: Limited XML namespace support

This commit is contained in:
Tom Schuster 2023-08-04 01:12:51 +02:00 committed by TÖRÖK Attila
parent 56b97e7b40
commit 7639d045ad
8 changed files with 173 additions and 118 deletions

View File

@ -6,7 +6,8 @@ use std::{
use gc_arena::{Collect, GcCell, MutationContext};
use quick_xml::{
events::{BytesStart, Event},
Reader,
name::ResolveResult,
NsReader,
};
use crate::{avm2::TObject, xml::custom_unescape};
@ -26,6 +27,7 @@ pub struct E4XNode<'gc>(GcCell<'gc, E4XNodeData<'gc>>);
#[collect(no_drop)]
pub struct E4XNodeData<'gc> {
parent: Option<E4XNode<'gc>>,
namespace: Option<AvmString<'gc>>,
local_name: Option<AvmString<'gc>>,
kind: E4XNodeKind<'gc>,
}
@ -72,6 +74,7 @@ impl<'gc> E4XNode<'gc> {
mc,
E4XNodeData {
parent: None,
namespace: None,
local_name: None,
kind: E4XNodeKind::Element {
attributes: vec![],
@ -86,17 +89,24 @@ impl<'gc> E4XNode<'gc> {
mc,
E4XNodeData {
parent,
namespace: None,
local_name: None,
kind: E4XNodeKind::Text(text),
},
))
}
pub fn element(mc: MutationContext<'gc, '_>, name: AvmString<'gc>, parent: Self) -> Self {
pub fn element(
mc: MutationContext<'gc, '_>,
namespace: Option<AvmString<'gc>>,
name: AvmString<'gc>,
parent: Self,
) -> Self {
E4XNode(GcCell::new(
mc,
E4XNodeData {
parent: Some(parent),
namespace,
local_name: Some(name),
kind: E4XNodeKind::Element {
attributes: vec![],
@ -116,6 +126,7 @@ impl<'gc> E4XNode<'gc> {
mc,
E4XNodeData {
parent: Some(parent),
namespace: None,
local_name: Some(name),
kind: E4XNodeKind::Attribute(value),
},
@ -195,6 +206,7 @@ impl<'gc> E4XNode<'gc> {
mc,
E4XNodeData {
parent: None,
namespace: this.namespace,
local_name: this.local_name,
kind,
},
@ -305,7 +317,7 @@ impl<'gc> E4XNode<'gc> {
};
let data_utf8 = string.to_utf8_lossy();
let mut parser = Reader::from_str(&data_utf8);
let mut parser = NsReader::from_str(&data_utf8);
let mut open_tags: Vec<E4XNode<'gc>> = vec![];
let mut top_level = vec![];
@ -373,6 +385,7 @@ impl<'gc> E4XNode<'gc> {
activation.context.gc_context,
E4XNodeData {
parent: None,
namespace: None,
local_name: None,
kind: if is_text {
E4XNodeKind::Text(text)
@ -393,8 +406,9 @@ impl<'gc> E4XNode<'gc> {
match &event {
Event::Start(bs) => {
let child = E4XNode::from_start_event(activation, bs, parser.decoder())
.map_err(|_| malformed_element(activation))?;
let child =
E4XNode::from_start_event(activation, &parser, bs, parser.decoder())
.map_err(|_| malformed_element(activation))?;
if let Some(current_tag) = open_tags.last_mut() {
current_tag.append_child(activation.context.gc_context, child)?;
@ -402,7 +416,7 @@ impl<'gc> E4XNode<'gc> {
open_tags.push(child);
}
Event::Empty(bs) => {
let node = E4XNode::from_start_event(activation, bs, parser.decoder())
let node = E4XNode::from_start_event(activation, &parser, bs, parser.decoder())
.map_err(|_| malformed_element(activation))?;
push_childless_node(node, &mut open_tags, &mut top_level, activation)?;
}
@ -448,6 +462,7 @@ impl<'gc> E4XNode<'gc> {
activation.context.gc_context,
E4XNodeData {
parent: None,
namespace: None,
local_name: None,
kind: E4XNodeKind::Comment(text),
},
@ -485,6 +500,7 @@ impl<'gc> E4XNode<'gc> {
activation.context.gc_context,
E4XNodeData {
parent: None,
namespace: None,
local_name: Some(name),
kind: E4XNodeKind::ProcessingInstruction(value),
},
@ -506,36 +522,58 @@ impl<'gc> E4XNode<'gc> {
/// valid encoded UTF-8 data. (Other encoding support is planned later.)
pub fn from_start_event(
activation: &mut Activation<'_, 'gc>,
parser: &NsReader<&[u8]>,
bs: &BytesStart<'_>,
decoder: quick_xml::Decoder,
) -> Result<Self, quick_xml::Error> {
// FIXME - handle namespace
let name =
AvmString::new_utf8_bytes(activation.context.gc_context, bs.local_name().into_inner());
let mut attribute_nodes = Vec::new();
let attributes: Result<Vec<_>, _> = bs.attributes().collect();
for attribute in attributes? {
let key = AvmString::new_utf8_bytes(
activation.context.gc_context,
attribute.key.into_inner(),
);
let (ns, local_name) = parser.resolve_attribute(attribute.key);
let namespace = match ns {
ResolveResult::Bound(ns) => Some(AvmString::new_utf8_bytes(
activation.context.gc_context,
ns.into_inner(),
)),
ResolveResult::Unknown(ns) if ns == b"xmlns" => continue,
// TODO: Throw error on unknown prefix
_ => None,
};
let name =
AvmString::new_utf8_bytes(activation.context.gc_context, local_name.into_inner());
let value_str = custom_unescape(&attribute.value, decoder)?;
let value =
AvmString::new_utf8_bytes(activation.context.gc_context, value_str.as_bytes());
let attribute_data = E4XNodeData {
parent: None,
local_name: Some(key),
namespace,
local_name: Some(name),
kind: E4XNodeKind::Attribute(value),
};
let attribute = E4XNode(GcCell::new(activation.context.gc_context, attribute_data));
attribute_nodes.push(attribute);
}
let (ns, local_name) = parser.resolve_element(bs.name());
let namespace = match ns {
ResolveResult::Bound(ns) => Some(AvmString::new_utf8_bytes(
activation.context.gc_context,
ns.into_inner(),
)),
// TODO: Throw error on unknown prefix
_ => None,
};
let name =
AvmString::new_utf8_bytes(activation.context.gc_context, local_name.into_inner());
let data = E4XNodeData {
parent: None,
namespace,
local_name: Some(name),
kind: E4XNodeKind::Element {
attributes: attribute_nodes,
@ -555,6 +593,10 @@ impl<'gc> E4XNode<'gc> {
Ok(result)
}
pub fn namespace(&self) -> Option<AvmString<'gc>> {
self.0.read().namespace
}
pub fn local_name(&self) -> Option<AvmString<'gc>> {
self.0.read().local_name
}
@ -573,16 +615,25 @@ impl<'gc> E4XNode<'gc> {
return false;
}
// FIXME - we need to handle namespaces here
if name.is_any_name() {
if !name.is_any_name() && self.local_name() != name.local_name() {
return false;
}
if name.is_any_namespace() {
return true;
}
if let Some(local_name) = self.local_name() {
Some(local_name) == name.local_name()
} else {
false
if name.has_explicit_namespace() {
return self.namespace() == name.explict_namespace();
}
// By default `xml.*` matches in all namespaces, unless an explicit
// namespace is given (`xml.ns::*`).
// However normal properties like "xml.prop" match only in the
// default namespace.
// TODO: Implement this by better handling default namespaces.
// See also "The QName Constructor Called as a Function".
name.is_any_name() || self.namespace().is_none()
}
pub fn descendants(&self, name: &Multiname<'gc>, out: &mut Vec<E4XOrXml<'gc>>) {

View File

@ -334,29 +334,37 @@ pub fn append_child<'gc>(
}
} else {
// Appending a non-XML/XMLList object
let last_child_name = if let E4XNodeKind::Element { children, .. } = &*xml.node().kind() {
let num_children = children.len();
let (last_child_namespace, last_child_name) =
if let E4XNodeKind::Element { children, .. } = &*xml.node().kind() {
let num_children = children.len();
match num_children {
0 => None,
_ => children[num_children - 1].local_name(),
}
} else {
// FIXME - figure out exactly when appending is allowed in FP,
// and throw the proper AVM error.
return Err(Error::RustError(
format!(
"Cannot append child {child:?} to node {:?}",
xml.node().kind()
)
.into(),
));
};
match num_children {
0 => (None, None),
_ => (
children[num_children - 1].namespace(),
children[num_children - 1].local_name(),
),
}
} else {
// FIXME - figure out exactly when appending is allowed in FP,
// and throw the proper AVM error.
return Err(Error::RustError(
format!(
"Cannot append child {child:?} to node {:?}",
xml.node().kind()
)
.into(),
));
};
let text = child.coerce_to_string(activation)?;
if let Some(last_child_name) = last_child_name {
let element_node =
E4XNode::element(activation.context.gc_context, last_child_name, *xml.node()); // Creating an element requires passing a parent node, unlike creating a text node
let element_node = E4XNode::element(
activation.context.gc_context,
last_child_namespace,
last_child_name,
*xml.node(),
); // Creating an element requires passing a parent node, unlike creating a text node
let text_node = E4XNode::text(activation.context.gc_context, text, None);

View File

@ -349,6 +349,13 @@ impl<'gc> Multiname<'gc> {
}
}
pub fn explict_namespace(&self) -> Option<AvmString<'gc>> {
match self.ns {
NamespaceSet::Single(ns) if ns.is_namespace() && !ns.is_public() => Some(ns.as_uri()),
_ => None,
}
}
/// Indicates if this multiname matches any type.
pub fn is_any_name(&self) -> bool {
self.name.is_none()

View File

@ -275,35 +275,31 @@ impl<'gc> TObject<'gc> for XmlListObject<'gc> {
}
}
}
let matched_children = write
.children
.iter_mut()
.flat_map(|child| {
let child_prop = child
.get_or_create_xml(activation)
.get_property_local(name, activation)
.unwrap();
if let Some(prop_xml) =
child_prop.as_object().and_then(|obj| obj.as_xml_object())
{
vec![E4XOrXml::Xml(prop_xml)]
} else if let Some(prop_xml_list) = child_prop
.as_object()
.and_then(|obj| obj.as_xml_list_object())
{
// Flatten children
prop_xml_list.children().clone()
} else {
vec![]
}
})
.collect();
return Ok(XmlListObject::new(activation, matched_children, Some(self.into())).into());
}
write.base.get_property_local(name, activation)
let matched_children = write
.children
.iter_mut()
.flat_map(|child| {
let child_prop = child
.get_or_create_xml(activation)
.get_property_local(name, activation)
.unwrap();
if let Some(prop_xml) = child_prop.as_object().and_then(|obj| obj.as_xml_object()) {
vec![E4XOrXml::Xml(prop_xml)]
} else if let Some(prop_xml_list) = child_prop
.as_object()
.and_then(|obj| obj.as_xml_list_object())
{
// Flatten children
prop_xml_list.children().clone()
} else {
vec![]
}
})
.collect();
Ok(XmlListObject::new(activation, matched_children, Some(self.into())).into())
}
fn call_property_local(

View File

@ -194,36 +194,34 @@ impl<'gc> TObject<'gc> for XmlObject<'gc> {
}
}
}
let matched_children = if let E4XNodeKind::Element {
children,
attributes,
} = &*read.node.kind()
{
let search_children = if name.is_attribute() {
attributes
} else {
children
};
search_children
.iter()
.filter_map(|child| {
if child.matches_name(name) {
Some(E4XOrXml::E4X(*child))
} else {
None
}
})
.collect::<Vec<_>>()
} else {
Vec::new()
};
return Ok(XmlListObject::new(activation, matched_children, Some(self.into())).into());
}
read.base.get_property_local(name, activation)
let matched_children = if let E4XNodeKind::Element {
children,
attributes,
} = &*read.node.kind()
{
let search_children = if name.is_attribute() {
attributes
} else {
children
};
search_children
.iter()
.filter_map(|child| {
if child.matches_name(name) {
Some(E4XOrXml::E4X(*child))
} else {
None
}
})
.collect::<Vec<_>>()
} else {
Vec::new()
};
return Ok(XmlListObject::new(activation, matched_children, Some(self.into())).into());
}
fn call_property_local(
@ -310,14 +308,6 @@ impl<'gc> TObject<'gc> for XmlObject<'gc> {
value: Value<'gc>,
activation: &mut Activation<'_, 'gc>,
) -> Result<(), Error<'gc>> {
if name.has_explicit_namespace() {
return Err(format!(
"Can not set property {:?} with an explicit namespace yet",
name
)
.into());
}
let mc = activation.context.gc_context;
if name.is_attribute() {
@ -374,8 +364,12 @@ impl<'gc> TObject<'gc> for XmlObject<'gc> {
.collect();
match matches.as_slice() {
[] => {
let element_with_text =
E4XNode::element(mc, name.local_name().unwrap(), write.node);
let element_with_text = E4XNode::element(
mc,
name.explict_namespace(),
name.local_name().unwrap(),
write.node,
);
element_with_text.append_child(mc, E4XNode::text(mc, text, Some(self_node)))?;
children.push(element_with_text);
Ok(())

View File

@ -12,16 +12,11 @@ var xml = <xml xmlns:example="http://example.org/">
trace("xml.*::foo.toString()", xml.*::foo.toString());
trace("xml.*::hello.toString()", xml.*::hello.toString());
var ns = new Namespace("http://example.org/");
var example = new Namespace("http://example.org/");
trace("xml.foo.toString()", xml.foo.toString());
trace("xml.example::foo.toString()", xml.example::foo.toString());
trace("xml.hello.toString()", xml.hello.toString());
trace("xml.example::hello.toString()", xml.example::hello.toString());
// Enable these tests when namespaces are supported.
// trace("xml.ns::foo.toString()", xml.ns::foo.toString());
// trace("xml.hello.toString()", xml.hello.toString());
// xml.ns::test = "abc";
try {
trace("xml.ns::test", xml.ns::test);
} catch (e) {
// This should not actually error.
trace(e.toString());
}
xml.example::test = "abc";
trace("xml.example::test", xml.example::test);

View File

@ -1,3 +1,7 @@
xml.*::foo.toString() abc
xml.*::hello.toString() world
ReferenceError: Error #1069: Property http://example.org/::test not found on XML and there is no default value.
xml.foo.toString() abc
xml.example::foo.toString()
xml.hello.toString()
xml.example::hello.toString() world
xml.example::test abc