From 921e0949259ee7099025724a2ffc4ac49a9a52da Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Sun, 4 Sep 2022 23:02:36 +0200 Subject: [PATCH] Use Id type to make namespaces explicit part of node identity --- instant-xml-macros/src/de.rs | 54 ++++++++------- instant-xml/src/de.rs | 117 ++++++++++++++++++++++++--------- instant-xml/src/lib.rs | 12 +++- instant-xml/tests/de-direct.rs | 4 +- instant-xml/tests/de-ns.rs | 6 +- 5 files changed, 130 insertions(+), 63 deletions(-) diff --git a/instant-xml-macros/src/de.rs b/instant-xml-macros/src/de.rs index 158f10a..4ca8523 100644 --- a/instant-xml-macros/src/de.rs +++ b/instant-xml-macros/src/de.rs @@ -156,10 +156,10 @@ impl Deserializer { use ::instant_xml::de::Node; #declare_values - while let Some(( key, _ )) = deserializer.peek_next_attribute() { + while let Some(attr) = deserializer.peek_next_attribute()? { let attr = { #attributes_consts - match *key { + match attr.id { #attributes_names _ => __Attributes::__Ignore } @@ -167,16 +167,17 @@ impl Deserializer { match attr { #attr_type_match - __Attributes::__Ignore => todo!(), + __Attributes::__Ignore => {} } } while let Some(node) = deserializer.peek_next_tag()? { match node { Node::Open { ns, name } => { + let id = ::instant_xml::Id { ns, name }; let element = { #elements_consts - match name { + match id { #elements_names _ => __Elements::__Ignore } @@ -184,7 +185,9 @@ impl Deserializer { match element { #elem_type_match - __Elements::__Ignore => panic!("No such element"), + __Elements::__Ignore => { + deserializer.ignore(id)?; + } } } Node::Close { name } => { @@ -216,7 +219,10 @@ impl Deserializer { )); out.extend(quote!( - const KIND: ::instant_xml::Kind = ::instant_xml::Kind::Element(#name); + const KIND: ::instant_xml::Kind = ::instant_xml::Kind::Element(::instant_xml::Id { + ns: #default_namespace, + name: #name, + }); )); out = quote!( @@ -247,8 +253,24 @@ impl Deserializer { let enum_name = Ident::new(&format!("__Value{index}"), Span::call_site()); tokens.enum_.extend(quote!(#enum_name,)); + let default_ns = match &field_meta.ns.default { + Namespace::Default => &container_meta.ns.default, + _ => &field_meta.ns.default, + }; + + let ns = match default_ns { + Namespace::Default => "", + Namespace::Prefix(prefix) => match container_meta.ns.prefixes.get(prefix) { + Some(ns) => ns, + None => panic!("undefined prefix {prefix} in xml attribute"), + }, + Namespace::Literal(ns) => ns, + }; + tokens.consts.extend(quote!( - const #const_field_var_str: &str = <#no_lifetime_type>::KIND.name(#field_var_str); + const #const_field_var_str: ::instant_xml::Id<'static> = <#no_lifetime_type>::KIND.name( + ::instant_xml::Id { ns: #ns, name: #field_var_str } + ); )); if !field_meta.attribute { @@ -265,20 +287,6 @@ impl Deserializer { let mut #enum_name: Option<#no_lifetime_type> = None; )); - let default_ns = match field_meta.ns.default { - Namespace::Default => &container_meta.ns.default, - _ => &field_meta.ns.default, - }; - - let new_default_ns = match default_ns { - Namespace::Default => quote!(None), - Namespace::Prefix(prefix) => match container_meta.ns.prefixes.get(prefix) { - Some(ns) => quote!(Some(#ns)), - None => panic!("invalid prefix for xml attribute"), - }, - Namespace::Literal(ns) => quote!(Some(#ns)), - }; - if !field_meta.attribute { tokens.match_.extend(quote!( __Elements::#enum_name => { @@ -286,10 +294,6 @@ impl Deserializer { panic!("duplicated value"); } - if Some(ns) != #new_default_ns { - return Err(Error::WrongNamespace); - } - #enum_name = Some(<#no_lifetime_type>::deserialize(deserializer)?); }, )); diff --git a/instant-xml/src/de.rs b/instant-xml/src/de.rs index 81b084e..527258d 100644 --- a/instant-xml/src/de.rs +++ b/instant-xml/src/de.rs @@ -1,16 +1,16 @@ use std::collections::HashMap; use std::iter::Peekable; -use super::Error; +use super::{Error, Id}; use xmlparser::{ElementEnd, Token, Tokenizer}; pub struct Deserializer<'xml> { parser: Peekable>, def_namespaces: HashMap<&'xml str, &'xml str>, - parser_namespaces: HashMap<&'xml str, &'xml str>, + pub parser_namespaces: HashMap<&'xml str, &'xml str>, def_default_namespace: &'xml str, parser_default_namespace: &'xml str, - tag_attributes: Vec<(&'xml str, &'xml str)>, + tag_attributes: Vec>, next_type: EntityType, } @@ -54,26 +54,44 @@ impl<'xml> Deserializer<'xml> { })) } - // Check if defined and gotten namespaces equals for each field - pub fn compare_namespace( - &self, - expected: &Option<&str>, - actual: Option<&str>, - ) -> Result<(), Error> { - match (expected, actual) { - (Some(expected), Some(actual)) => { - match self.parser_namespaces.get(expected) == self.def_namespaces.get(actual) { - true => Ok(()), - false => Err(Error::WrongNamespace), - } - } - (Some(_), None) | (None, Some(_)) => Err(Error::WrongNamespace), - (None, None) => Ok(()), - } + pub fn id(&self, item: &TagData<'xml>) -> Result, Error> { + let ns = match (item.ns, item.prefix) { + (Some(_), Some(_)) => return Err(Error::WrongNamespace), + (Some(ns), None) => ns, + (None, Some(prefix)) => match self.parser_namespaces.get(prefix) { + Some(ns) => ns, + None => return Err(Error::WrongNamespace), + }, + (None, None) => "", + }; + + Ok(Id { + ns, + name: &item.key, + }) } - pub fn peek_next_attribute(&self) -> Option<&(&'xml str, &'xml str)> { - self.tag_attributes.last() + pub fn peek_next_attribute(&self) -> Result>, Error> { + let attr = match self.tag_attributes.last() { + Some(attr) => attr, + None => return Ok(None), + }; + + let ns = match attr.prefix { + Some(key) => match self.parser_namespaces.get(key) { + Some(ns) => ns, + None => return Err(Error::WrongNamespace), + }, + None => self.parser_default_namespace, + }; + + Ok(Some(AttributeNode { + id: Id { + ns, + name: attr.local, + }, + value: attr.value, + })) } pub fn deserialize_struct( @@ -204,10 +222,34 @@ impl<'xml> Deserializer<'xml> { V: Visitor<'xml>, { match self.tag_attributes.pop() { - Some((_, value)) => visitor.visit_str(value), + Some(attr) => visitor.visit_str(attr.value), None => Err(Error::UnexpectedEndOfStream), } } + + pub fn ignore(&mut self, id: Id<'xml>) -> Result<(), Error> { + let mut levels = 0; + while let Some(result) = self.parser.next() { + match result? { + XmlRecord::Open(item) => { + if self.id(&item)? == id { + levels += 1; + } + } + XmlRecord::Close(item) => { + if item == id.name { + levels -= 1; + if levels == 0 { + return Ok(()); + } + } + } + _ => {} + } + } + + Ok(()) + } } pub struct XmlParser<'xml> { @@ -272,12 +314,12 @@ impl<'xml> Iterator for XmlParser<'xml> { let mut attributes = Vec::new(); loop { - let item = match self.iter.next() { + let token = match self.iter.next() { Some(v) => v, None => return None, }; - match item { + match token { Ok(Token::ElementStart { prefix, local, .. }) => { key = Some(local.as_str()); prefix_ret = match prefix.is_empty() { @@ -319,12 +361,13 @@ impl<'xml> Iterator for XmlParser<'xml> { } else if prefix.as_str() == "xmlns" { // Namespaces namespaces.insert(local.as_str(), value.as_str()); - } else if prefix.is_empty() { - // Other attributes - attributes.push((local.as_str(), value.as_str())); } else { - // TODO: Can the attributes have the prefix? - todo!(); + let prefix = (!prefix.is_empty()).then_some(prefix.as_str()); + attributes.push(Attribute { + prefix, + local: local.as_str(), + value: value.as_str(), + }); } } Ok(Token::Text { text }) => { @@ -349,20 +392,34 @@ pub trait Visitor<'xml>: Sized { } } +#[derive(Debug)] pub enum XmlRecord<'xml> { Open(TagData<'xml>), Element(&'xml str), Close(&'xml str), } +#[derive(Debug)] pub struct TagData<'xml> { pub key: &'xml str, - pub attributes: Vec<(&'xml str, &'xml str)>, + pub attributes: Vec>, pub ns: Option<&'xml str>, pub prefixes: HashMap<&'xml str, &'xml str>, pub prefix: Option<&'xml str>, } +pub struct AttributeNode<'xml> { + pub id: Id<'xml>, + pub value: &'xml str, +} + +#[derive(Debug)] +pub struct Attribute<'xml> { + pub prefix: Option<&'xml str>, + pub local: &'xml str, + pub value: &'xml str, +} + pub enum Node<'xml> { Open { ns: &'xml str, name: &'xml str }, Close { name: &'xml str }, diff --git a/instant-xml/src/lib.rs b/instant-xml/src/lib.rs index fd79cdf..5c27b5f 100644 --- a/instant-xml/src/lib.rs +++ b/instant-xml/src/lib.rs @@ -48,18 +48,24 @@ pub trait FromXml<'xml>: Sized { pub enum Kind { Scalar, - Element(&'static str), + Element(Id<'static>), } impl Kind { - pub const fn name(&self, field: &'static str) -> &'static str { + pub const fn name<'a>(&self, field: Id<'static>) -> Id<'static> { match self { Kind::Scalar => field, - Kind::Element(name) => name, + Kind::Element(name) => *name, } } } +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct Id<'a> { + pub ns: &'a str, + pub name: &'a str, +} + pub trait FromXmlOwned: for<'xml> FromXml<'xml> {} #[derive(Clone, Debug, Eq, Error, PartialEq)] diff --git a/instant-xml/tests/de-direct.rs b/instant-xml/tests/de-direct.rs index b952645..95297d6 100644 --- a/instant-xml/tests/de-direct.rs +++ b/instant-xml/tests/de-direct.rs @@ -26,7 +26,7 @@ fn direct_namespaces() { "true" ) .unwrap_err(), - Error::WrongNamespace + Error::MissingValue ); // Wrong direct namespace - missing namespace @@ -35,6 +35,6 @@ fn direct_namespaces() { "true" ) .unwrap_err(), - Error::WrongNamespace + Error::MissingValue ); } diff --git a/instant-xml/tests/de-ns.rs b/instant-xml/tests/de-ns.rs index a0c47ee..9f447d8 100644 --- a/instant-xml/tests/de-ns.rs +++ b/instant-xml/tests/de-ns.rs @@ -75,7 +75,7 @@ fn default_namespaces() { // Wrong child namespace assert_eq!( StructWithWrongNestedNamespace::from_xml("true").unwrap_err(), - Error::WrongNamespace + Error::MissingValue ); } @@ -118,7 +118,7 @@ fn other_namespaces() { "true" ) .unwrap_err(), - Error::WrongNamespace + Error::MissingValue ); // Other namespace not-nested - missing parser prefix @@ -127,7 +127,7 @@ fn other_namespaces() { "true" ) .unwrap_err(), - Error::WrongNamespace + Error::MissingValue ); // Correct child other namespace