avm2: Limited XML namespace support
This commit is contained in:
parent
56b97e7b40
commit
7639d045ad
|
@ -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>>) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(())
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
Binary file not shown.
Loading…
Reference in New Issue