diff --git a/src/lib.rs b/src/lib.rs index 8d8b801..e4cd485 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,12 +30,14 @@ mod reader; mod sixbit; mod text_reader; mod to_element; +mod to_text_xml; mod value; mod writer; use node::NodeDefinition; use node_types::StandardType; use text_reader::TextXmlReader; +use to_text_xml::TextXmlWriter; // Public exports pub use compression::Compression; @@ -46,6 +48,7 @@ pub use error::{KbinError, KbinErrorKind, Result}; pub use node::{Node, NodeCollection}; pub use options::Options; pub use to_element::ToElement; +pub use to_text_xml::ToTextXml; pub use value::Value; pub use writer::{Writer, Writeable}; @@ -197,3 +200,10 @@ pub fn to_binary_with_options(options: Options, input: &T) -> Result> let mut writer = Writer::with_options(options); writer.to_binary(input) } + +pub fn to_text_xml(input: &T) -> Result> + where T: ToTextXml +{ + let writer = TextXmlWriter::new(); + writer.to_text_xml(input) +} diff --git a/src/to_text_xml/mod.rs b/src/to_text_xml/mod.rs new file mode 100644 index 0000000..3c3d506 --- /dev/null +++ b/src/to_text_xml/mod.rs @@ -0,0 +1,35 @@ +use std::io::{Cursor, Write}; + +use quick_xml::Writer; + +use error::KbinError; + +mod node; +mod node_collection; + +pub trait ToTextXml { + fn write(&self, writer: &mut Writer) -> Result<(), KbinError>; +} + +pub struct TextXmlWriter { + xml_writer: Writer>>, +} + +impl TextXmlWriter { + pub fn new() -> Self { + let inner = Cursor::new(Vec::new()); + let xml_writer = Writer::new_with_indent(inner, b' ', 2); + + Self { + xml_writer, + } + } + + pub fn to_text_xml(mut self, value: &T) -> Result, KbinError> + where T: ToTextXml + { + value.write(&mut self.xml_writer)?; + + Ok(self.xml_writer.into_inner().into_inner()) + } +} diff --git a/src/to_text_xml/node.rs b/src/to_text_xml/node.rs new file mode 100644 index 0000000..0066902 --- /dev/null +++ b/src/to_text_xml/node.rs @@ -0,0 +1,98 @@ +use std::borrow::Cow; +use std::io::Write; + +use quick_xml::Writer; +use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event}; +use quick_xml::events::attributes::Attribute; + +use error::KbinError; +use node::Node; +use node_types::StandardType; +use to_text_xml::ToTextXml; +use value::Value; + +impl ToTextXml for Node { + fn write(&self, writer: &mut Writer) -> Result<(), KbinError> { + let key = self.key(); + let mut elem = BytesStart::borrowed(key.as_bytes(), key.as_bytes().len()); + + // Write the attributes for the value, but not the value contents. + if let Some(value) = self.value() { + let node_type = value.standard_type(); + + match value { + Value::Binary(ref data) => { + elem.push_attribute(Attribute { + key: b"__size", + value: Cow::Owned(data.len().to_string().into_bytes()), + }); + }, + Value::Array(_, ref values) => { + elem.push_attribute(Attribute { + key: b"__count", + value: Cow::Owned(values.len().to_string().into_bytes()), + }); + }, + _ => {}, + }; + + // Only add a `__type` attribute if this is not a `NodeStart` node + if node_type != StandardType::NodeStart { + elem.push_attribute(Attribute { + key: b"__type", + value: Cow::Borrowed(node_type.name.as_bytes()), + }); + } + } + + if let Some(attributes) = self.attributes() { + for (key, value) in attributes { + elem.push_attribute(Attribute { + key: key.as_bytes(), + value: Cow::Borrowed(value.as_bytes()), + }); + } + } + + // Now write the value contents. + let start_elem = if let Some(value) = self.value() { + writer.write_event(Event::Start(elem))?; + + let value = value.to_string(); + let elem = BytesText::from_plain_str(&value); + writer.write_event(Event::Text(elem))?; + + None + } else { + Some(elem) + }; + + let has_value = start_elem.is_none(); + let has_children = match self.children() { + Some(children) => !children.is_empty(), + None => false, + }; + + // A `Some` value here means the start element was not written + if let Some(start_elem) = start_elem { + if !has_children { + writer.write_event(Event::Empty(start_elem))?; + } else { + writer.write_event(Event::Start(start_elem))?; + } + } + + if let Some(children) = self.children() { + for child in children { + child.write(writer)?; + } + } + + if has_value || has_children { + let end_elem = BytesEnd::borrowed(key.as_bytes()); + writer.write_event(Event::End(end_elem))?; + } + + Ok(()) + } +} diff --git a/src/to_text_xml/node_collection.rs b/src/to_text_xml/node_collection.rs new file mode 100644 index 0000000..afca831 --- /dev/null +++ b/src/to_text_xml/node_collection.rs @@ -0,0 +1,101 @@ +use std::borrow::Cow; +use std::io::Write; + +use quick_xml::Writer; +use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event}; +use quick_xml::events::attributes::Attribute; + +use error::{KbinError, KbinErrorKind}; +use node::NodeCollection; +use node_types::StandardType; +use to_text_xml::ToTextXml; + +impl ToTextXml for NodeCollection { + fn write(&self, writer: &mut Writer) -> Result<(), KbinError> { + let base = self.base(); + let key = base.key()?.ok_or(KbinErrorKind::InvalidState)?; + let value = match base.value() { + Ok(value) => Some(value), + Err(e) => { + match e.get_context() { + KbinErrorKind::InvalidNodeType(_) => None, + _ => return Err(e), + } + }, + }; + + let mut elem = BytesStart::borrowed(key.as_bytes(), key.as_bytes().len()); + + if base.is_array { + let values = value.as_ref().ok_or(KbinErrorKind::InvalidState)?.as_array()?; + + elem.push_attribute(Attribute { + key: b"__count", + value: Cow::Owned(values.len().to_string().into_bytes()), + }); + } + + if base.node_type == StandardType::Binary { + let value = value.as_ref().ok_or(KbinErrorKind::InvalidState)?.as_slice()?; + + elem.push_attribute(Attribute { + key: b"__size", + value: Cow::Owned(value.len().to_string().into_bytes()), + }); + } + + // Only add a `__type` attribute if this is not a `NodeStart` node + if base.node_type != StandardType::NodeStart { + elem.push_attribute(Attribute { + key: b"__type", + value: Cow::Borrowed(base.node_type.name.as_bytes()), + }); + } + + for attribute in self.attributes() { + let key = attribute.key()?.ok_or(KbinErrorKind::InvalidState)?.into_bytes(); + let value = attribute.value()?.to_string().into_bytes(); + + elem.push_attribute(Attribute { + key: &key, + value: Cow::Owned(value), + }); + } + + let start_elem = match value { + Some(value) => { + writer.write_event(Event::Start(elem))?; + + let value = value.to_string(); + let elem = BytesText::from_plain_str(&value); + writer.write_event(Event::Text(elem))?; + + None + }, + None => Some(elem), + }; + + let has_value = start_elem.is_none(); + let has_children = !self.children().is_empty(); + + // A `Some` value here means the start element was not written + if let Some(start_elem) = start_elem { + if !has_children { + writer.write_event(Event::Empty(start_elem))?; + } else { + writer.write_event(Event::Start(start_elem))?; + } + } + + for child in self.children() { + child.write(writer)?; + } + + if has_value || has_children { + let end_elem = BytesEnd::borrowed(key.as_bytes()); + writer.write_event(Event::End(end_elem))?; + } + + Ok(()) + } +}