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(); let length = o.length(activation).unwrap();
Some(AmfValue::ECMAArray(vec![], values, length as u32)) Some(AmfValue::ECMAArray(vec![], values, length as u32))
} else if let Some(xml_node) = o.as_xml_node() { } else if let Some(xml_node) = o.as_xml_node() {
xml_node Some(AmfValue::XML(
.into_string() xml_node.into_string().to_utf8_lossy().into_owned(),
.map(|xml_string| AmfValue::XML(xml_string, true)) true,
.ok() ))
} else if let Some(date) = o.as_date_object() { } else if let Some(date) = o.as_date_object() {
date.date_time() date.date_time()
.map(|date_time| AmfValue::Date(date_time.timestamp_millis() as f64, None)) .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 { let request_options = if let Some(node) = send_object {
// Send `node` as string. // Send `node` as string.
RequestOptions::post(Some(( 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(), "application/x-www-form-urlencoded".to_string(),
))) )))
} else { } else {

View File

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

View File

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