xml: Refactor `XmlNode::into_string`

* Don't use `quick_xml::Writer` for formatting the XML, being much
more simple.
* Return `WString` instead of `String`, reducing `to_utf8_lossy()`
calls except when the string needs to be escaped (attribute values
and text contents).
This commit is contained in:
relrelb 2022-02-13 01:21:35 +02:00 committed by relrelb
parent a3288fa20c
commit dbddefb44d
4 changed files with 44 additions and 68 deletions

View File

@ -80,10 +80,10 @@ fn serialize_value<'gc>(
let length = o.length(activation).unwrap();
Some(AmfValue::ECMAArray(vec![], values, length as u32))
} else if let Some(xml_node) = o.as_xml_node() {
xml_node
.into_string()
.map(|xml_string| AmfValue::XML(xml_string, true))
.ok()
Some(AmfValue::XML(
xml_node.into_string().to_utf8_lossy().into_owned(),
true,
))
} else if let Some(date) = o.as_date_object() {
date.date_time()
.map(|date_time| AmfValue::Date(date_time.timestamp_millis() as f64, None))

View File

@ -294,7 +294,7 @@ fn spawn_xml_fetch<'gc>(
let request_options = if let Some(node) = send_object {
// Send `node` as string.
RequestOptions::post(Some((
node.into_string().unwrap_or_default().into_bytes(),
node.into_string().to_utf8_lossy().into_owned().into_bytes(),
"application/x-www-form-urlencoded".to_string(),
)))
} else {

View File

@ -211,16 +211,7 @@ fn to_string<'gc>(
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
if let Some(node) = this.as_xml_node() {
let result = node.into_string();
return Ok(AvmString::new_utf8(
activation.context.gc_context,
result.unwrap_or_else(|e| {
avm_warn!(activation, "XMLNode toString failed: {}", e);
"".to_string()
}),
)
.into());
return Ok(AvmString::new(activation.context.gc_context, node.into_string()).into());
}
Ok("".into())

View File

@ -8,13 +8,11 @@ use crate::string::{AvmString, WStr, WString};
use crate::xml;
use crate::xml::Error;
use gc_arena::{Collect, GcCell, MutationContext};
use quick_xml::events::attributes::Attribute;
use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event};
use quick_xml::Writer;
use quick_xml::escape::escape;
use quick_xml::events::{BytesStart, BytesText};
use smallvec::alloc::borrow::Cow;
use std::collections::BTreeMap;
use std::fmt;
use std::io::{Cursor, Write};
use std::mem::swap;
/// Represents a node in the XML tree.
@ -828,80 +826,67 @@ impl<'gc> XmlNode<'gc> {
}
/// Convert the given node to a string of UTF-8 encoded XML.
pub fn into_string(self) -> Result<String, Error> {
let mut buf = Vec::new();
let mut writer = Writer::new(Cursor::new(&mut buf));
self.write_node_to_event_writer(&mut writer)?;
Ok(String::from_utf8(buf)?)
pub fn into_string(self) -> WString {
let mut result = WString::new();
self.write_node_to_string(&mut result);
result
}
/// Write the contents of this node, including its children, to the given writer.
fn write_node_to_event_writer<W>(self, writer: &mut Writer<W>) -> Result<(), Error>
where
W: Write,
{
// TODO: we convert all strings to utf8, replacing unpaired surrogates by the replacement char.
/// Write the contents of this node, including its children, to the given string.
fn write_node_to_string(self, result: &mut WString) {
// TODO: we convert some strings to utf8, replacing unpaired surrogates by the replacement char.
// It is correct?
let children: Vec<_> = self.children().collect();
let children_len = children.len();
match &*self.0.read() {
XmlNodeData::DocumentRoot { .. } => Ok(()),
XmlNodeData::DocumentRoot { .. } => {}
XmlNodeData::Element {
tag_name,
attributes,
..
} => {
let mut node_name = tag_name.to_utf8_lossy();
if children_len == 0 {
let mut n = node_name.into_owned();
n.push(' ');
node_name = n.into();
}
let mut bs = match node_name {
Cow::Borrowed(name) => BytesStart::borrowed_name(name.as_bytes()),
Cow::Owned(name) => BytesStart::owned_name(name),
};
result.push_byte(b'<');
result.push_str(tag_name);
for (key, value) in attributes {
bs.push_attribute(Attribute::from((
key.to_utf8_lossy().as_ref(),
value.to_utf8_lossy().as_ref(),
)));
result.push_byte(b' ');
result.push_str(key);
result.push_str(WStr::from_units(b"=\""));
let encoded_value = value.to_utf8_lossy();
let escaped_value = escape(encoded_value.as_bytes());
result.push_str(WStr::from_units(&*escaped_value));
result.push_byte(b'"');
}
if children_len > 0 {
writer.write_event(&Event::Start(bs))
if children.is_empty() {
result.push_str(WStr::from_units(b" />"));
} else {
writer.write_event(&Event::Empty(bs))
result.push_byte(b'>');
}
}
XmlNodeData::Text { contents, .. } => {
let encoded = contents.to_utf8_lossy();
let escaped = escape(encoded.as_bytes());
result.push_str(WStr::from_units(&*escaped));
}
}
XmlNodeData::Text { contents, .. } => writer.write_event(&Event::Text(
BytesText::from_plain_str(&contents.to_utf8_lossy()),
)),
}?;
for child in children {
child.write_node_to_event_writer(writer)?;
for child in &children {
child.write_node_to_string(result);
}
match &*self.0.read() {
XmlNodeData::DocumentRoot { .. } => Ok(()),
XmlNodeData::DocumentRoot { .. } => {}
XmlNodeData::Element { tag_name, .. } => {
if children_len > 0 {
let bs = match tag_name.to_utf8_lossy() {
Cow::Borrowed(name) => BytesEnd::borrowed(name.as_bytes()),
Cow::Owned(name) => BytesEnd::owned(name.into()),
};
writer.write_event(&Event::End(bs))
} else {
Ok(())
if !children.is_empty() {
result.push_str(WStr::from_units(b"</"));
result.push_str(tag_name);
result.push_byte(b'>');
}
}
XmlNodeData::Text { .. } => Ok(()),
}?;
Ok(())
XmlNodeData::Text { .. } => {}
}
}
}