diff --git a/Cargo.lock b/Cargo.lock index 543b81d..5748ba7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -244,6 +244,7 @@ dependencies = [ "serde_yaml 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "simple-error 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "xml-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -285,6 +286,11 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "xml-rs" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "yaml-rust" version = "0.4.0" @@ -333,4 +339,5 @@ dependencies = [ "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +"checksum xml-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "541b12c998c5b56aa2b4e6f18f03664eef9a4fd0a246a55594efae6cc2d964b5" "checksum yaml-rust 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "57ab38ee1a4a266ed033496cf9af1828d8d6e6c1cfa5f643a2809effcae4d628" diff --git a/Cargo.toml b/Cargo.toml index f51cb74..2c94ebb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ serde_json = "~1.0.9" simple-error = "0.1" serde_yaml = "~0.8.1" toml = "~0.4.8" +xml-rs = "0.8.0" [dev-dependencies] bencher = "~0.1.5" diff --git a/src/convert/mod.rs b/src/convert/mod.rs index 259f1a8..9b4947f 100644 --- a/src/convert/mod.rs +++ b/src/convert/mod.rs @@ -19,6 +19,7 @@ pub mod flags; pub mod json; pub mod toml; pub mod traits; +pub mod xml; pub mod yaml; use std::collections::HashMap; @@ -49,6 +50,7 @@ impl ConverterRegistry { registry.register("exec", Box::new(exec::ExecConverter::new())); registry.register("yaml", Box::new(yaml::YamlConverter::new())); registry.register("toml", Box::new(toml::TomlConverter::new())); + registry.register("xml", Box::new(xml::XmlConverter {})); registry } diff --git a/src/convert/xml.rs b/src/convert/xml.rs new file mode 100644 index 0000000..aee00a8 --- /dev/null +++ b/src/convert/xml.rs @@ -0,0 +1,208 @@ +use std; +use std::error::Error; +use std::io::Write; +use std::rc::Rc; + +use super::traits::{Converter, Result}; +use crate::ast::{Position, PositionedItem}; +use crate::build::Val; +use crate::error::BuildError; +use crate::error::ErrorType; + +use xml::common::XmlVersion; +use xml::writer::events::XmlEvent; +use xml::writer::EventWriter; +use xml::EmitterConfig; + +pub struct XmlConverter {} + +impl XmlConverter { + fn get_str_val(v: &Val) -> std::result::Result<&str, Box> { + if let Val::Str(ref s) = v { + Ok(s) + } else { + Err(Box::new(BuildError::new( + "Not a String value", + ErrorType::TypeFail, + Position::new(0, 0, 0), + ))) + } + } + + fn get_tuple_val( + v: &Val, + ) -> std::result::Result<&Vec<(PositionedItem, Rc)>, Box> { + if let Val::Tuple(ref fs) = v { + Ok(fs) + } else { + Err(Box::new(BuildError::new( + "Not a tuple value", + ErrorType::TypeFail, + Position::new(0, 0, 0), + ))) + } + } + + fn get_list_val(v: &Val) -> std::result::Result<&Vec>, Box> { + if let Val::List(ref fs) = v { + Ok(fs) + } else { + Err(Box::new(BuildError::new( + "Not a List value", + ErrorType::TypeFail, + Position::new(0, 0, 0), + ))) + } + } + + fn write_node(&self, v: &Val, w: &mut EventWriter) -> Result { + // First we determine if this is a tag or text node + if let Val::Tuple(ref fs) = v { + let mut name: Option<&str> = None; + // TODO let mut namespace: Option<&str> = None; + let mut attrs: Option<&Vec<(PositionedItem, Rc)>> = None; + let mut children: Option<&Vec>> = None; + let mut text: Option<&str> = None; + for (ref field, ref val) in fs.iter() { + if field.val == "name" { + name = Some(Self::get_str_val(val.as_ref())?); + } + //if field.val == "namespace" { + // namespace = Some(Self::get_str_val(val.as_ref())?); + //} + if field.val == "attrs" { + // This should be a tuple. + attrs = Some(Self::get_tuple_val(val.as_ref())?); + } + if field.val == "children" { + // This should be a list of tuples. + children = Some(Self::get_list_val(val.as_ref())?); + } + if field.val == "text" { + text = Some(Self::get_str_val(val.as_ref())?); + } + } + if name.is_some() && text.is_some() { + return Err(Box::new(BuildError::new( + "XML nodes can not have both text and name fields", + ErrorType::TypeFail, + Position::new(0, 0, 0), + ))); + } + if name.is_some() { + let mut start = XmlEvent::start_element(name.unwrap()); + if attrs.is_some() { + for (ref name, ref val) in attrs.unwrap().iter() { + start = start.attr(name.val.as_ref(), Self::get_str_val(val.as_ref())?); + } + } + w.write(start)?; + if children.is_some() { + for child in children.unwrap().iter() { + self.write_node(child.as_ref(), w)?; + } + } + w.write(XmlEvent::end_element())?; + } + if text.is_some() { + w.write(XmlEvent::characters(text.unwrap()))?; + } + } + if let Val::Str(ref s) = v { + w.write(XmlEvent::characters(s.as_ref()))?; + } else { + return Err(Box::new(BuildError::new( + "XML nodes must be a Tuple or a string", + ErrorType::TypeFail, + Position::new(0, 0, 0), + ))); + } + Ok(()) + } + + fn write(&self, v: &Val, w: &mut Write) -> Result { + if let Val::Tuple(ref fs) = v { + let mut version: Option<&str> = None; + let mut encoding: Option<&str> = None; + let mut standalone: Option = None; + let mut root: Option> = None; + for &(ref name, ref val) in fs.iter() { + if name.val == "versin" { + version = Some(Self::get_str_val(val)?); + } + if name.val == "encoding" { + encoding = Some(Self::get_str_val(val)?); + } + if name.val == "standalone" { + standalone = match val.as_ref() { + Val::Boolean(b) => Some(*b), + _ => None, + }; + } + if name.val == "root" { + root = Some(val.clone()); + } + } + match root { + Some(n) => { + let mut writer = EmitterConfig::new() + .perform_indent(true) + .normalize_empty_elements(false) + .create_writer(w); + // first we see if we need to emit a document + // declaration event. + let version = match version { + Some(s) => { + if s == "1.0" { + Some(XmlVersion::Version10) + } else if s == "1.1" { + Some(XmlVersion::Version11) + } else { + // If they specified the wrong version then + // error out. + return Err(Box::new(BuildError::new( + "XML version must be either 1.0 or 1.1", + ErrorType::TypeFail, + Position::new(0, 0, 0), + ))); + } + } + None => None, + }; + writer.write(XmlEvent::StartDocument { + // We default to version 1.1 documents if not specified. + version: version.unwrap_or(XmlVersion::Version10), + encoding: encoding, + standalone: standalone, + })?; + self.write_node(n.as_ref(), &mut writer) + } + None => Err(Box::new(BuildError::new( + "XML doc tuples must have a root field", + ErrorType::TypeFail, + Position::new(0, 0, 0), + ))), + } + } else { + Err(Box::new(BuildError::new( + "XML outputs must be a Tuple", + ErrorType::TypeFail, + Position::new(0, 0, 0), + ))) + } + } +} + +impl Converter for XmlConverter { + fn convert(&self, v: Rc, mut w: &mut Write) -> Result { + self.write(&v, &mut w) + } + + fn file_ext(&self) -> String { + String::from("xml") + } + + fn description(&self) -> String { + String::from("Convert a ucg DSL into xml.") + } +} diff --git a/src/lib.rs b/src/lib.rs index cf84226..2aad197 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,6 +26,7 @@ extern crate serde_json; extern crate serde_yaml; extern crate simple_error; extern crate toml; +extern crate xml; #[macro_use] pub mod ast;