mirror of
https://gitlab.com/xmpp-rs/xmpp-rs.git
synced 2024-07-12 22:21:53 +00:00
xso-proc: add support for built-in prefixes in attribute names
This simplifies the use of built-in XML attributes such as xml:lang.
This commit is contained in:
parent
84de7fc248
commit
d4d520e1f6
3 changed files with 104 additions and 9 deletions
|
@ -282,3 +282,20 @@ fn namespaced_attribute_roundtrip_b() {
|
||||||
xmlns:tns1='urn:example:ns2' tns1:foo='a2'/>",
|
xmlns:tns1='urn:example:ns2' tns1:foo='a2'/>",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
|
||||||
|
#[xml(namespace = NS1, name = "attr")]
|
||||||
|
struct PrefixedAttribute {
|
||||||
|
#[xml(attribute = "xml:lang")]
|
||||||
|
lang: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn prefixed_attribute_roundtrip() {
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use std::{
|
||||||
|
option::Option::{None, Some},
|
||||||
|
result::Result::{Err, Ok},
|
||||||
|
};
|
||||||
|
roundtrip_full::<PrefixedAttribute>("<attr xmlns='urn:example:ns1' xml:lang='foo'/>");
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,11 @@ use syn::{meta::ParseNestedMeta, spanned::Spanned, *};
|
||||||
|
|
||||||
use rxml_validation::NcName;
|
use rxml_validation::NcName;
|
||||||
|
|
||||||
|
/// XML core namespace URI (for the `xml:` prefix)
|
||||||
|
pub const XMLNS_XML: &'static str = "http://www.w3.org/XML/1998/namespace";
|
||||||
|
/// XML namespace URI (for the `xmlns:` prefix)
|
||||||
|
pub const XMLNS_XMLNS: &'static str = "http://www.w3.org/2000/xmlns/";
|
||||||
|
|
||||||
/// Value for the `#[xml(namespace = ..)]` attribute.
|
/// Value for the `#[xml(namespace = ..)]` attribute.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) enum NamespaceRef {
|
pub(crate) enum NamespaceRef {
|
||||||
|
@ -25,6 +30,12 @@ pub(crate) enum NamespaceRef {
|
||||||
Path(Path),
|
Path(Path),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl NamespaceRef {
|
||||||
|
fn fudge(value: &str, span: Span) -> Self {
|
||||||
|
Self::LitStr(LitStr::new(value, span))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl syn::parse::Parse for NamespaceRef {
|
impl syn::parse::Parse for NamespaceRef {
|
||||||
fn parse(input: syn::parse::ParseStream<'_>) -> Result<Self> {
|
fn parse(input: syn::parse::ParseStream<'_>) -> Result<Self> {
|
||||||
if input.peek(syn::LitStr) {
|
if input.peek(syn::LitStr) {
|
||||||
|
@ -67,10 +78,7 @@ impl syn::parse::Parse for NameRef {
|
||||||
let span = s.span();
|
let span = s.span();
|
||||||
match NcName::try_from(s.value()) {
|
match NcName::try_from(s.value()) {
|
||||||
Ok(value) => Ok(Self::Literal { value, span }),
|
Ok(value) => Ok(Self::Literal { value, span }),
|
||||||
Err(e) => Err(Error::new(
|
Err(e) => Err(Error::new(span, format!("not a valid XML name: {}", e))),
|
||||||
span,
|
|
||||||
format!("not a valid XML element name: {}", e),
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let p: Path = input.parse()?;
|
let p: Path = input.parse()?;
|
||||||
|
@ -195,6 +203,44 @@ impl XmlCompoundMeta {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse an XML name while resolving built-in namespace prefixes.
|
||||||
|
fn parse_prefixed_name(
|
||||||
|
value: syn::parse::ParseStream<'_>,
|
||||||
|
) -> Result<(Option<NamespaceRef>, NameRef)> {
|
||||||
|
let name: LitStr = value.parse()?;
|
||||||
|
let name_span = name.span();
|
||||||
|
let (prefix, name) = match name
|
||||||
|
.value()
|
||||||
|
.try_into()
|
||||||
|
.and_then(|name: rxml_validation::Name| name.split_name())
|
||||||
|
{
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
return Err(Error::new(
|
||||||
|
name_span,
|
||||||
|
format!("not a valid XML name: {}", e),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let name = NameRef::Literal {
|
||||||
|
value: name,
|
||||||
|
span: name_span,
|
||||||
|
};
|
||||||
|
if let Some(prefix) = prefix {
|
||||||
|
let namespace_uri = match prefix.as_str() {
|
||||||
|
"xml" => XMLNS_XML,
|
||||||
|
"xmlns" => XMLNS_XMLNS,
|
||||||
|
other => return Err(Error::new(
|
||||||
|
name_span,
|
||||||
|
format!("prefix `{}` is not a built-in prefix and cannot be used. specify the desired namespace using the `namespace` key instead.", other)
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
Ok((Some(NamespaceRef::fudge(namespace_uri, name_span)), name))
|
||||||
|
} else {
|
||||||
|
Ok((None, name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Contents of an `#[xml(..)]` attribute on a struct or enum variant member.
|
/// Contents of an `#[xml(..)]` attribute on a struct or enum variant member.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) enum XmlFieldMeta {
|
pub(crate) enum XmlFieldMeta {
|
||||||
|
@ -222,10 +268,11 @@ impl XmlFieldMeta {
|
||||||
fn attribute_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
|
fn attribute_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
|
||||||
if meta.input.peek(Token![=]) {
|
if meta.input.peek(Token![=]) {
|
||||||
// shorthand syntax
|
// shorthand syntax
|
||||||
|
let (namespace, name) = parse_prefixed_name(meta.value()?)?;
|
||||||
Ok(Self::Attribute {
|
Ok(Self::Attribute {
|
||||||
span: meta.path.span(),
|
span: meta.path.span(),
|
||||||
name: Some(meta.value()?.parse()?),
|
name: Some(name),
|
||||||
namespace: None,
|
namespace,
|
||||||
})
|
})
|
||||||
} else if meta.input.peek(syn::token::Paren) {
|
} else if meta.input.peek(syn::token::Paren) {
|
||||||
// full syntax
|
// full syntax
|
||||||
|
@ -236,11 +283,31 @@ impl XmlFieldMeta {
|
||||||
if name.is_some() {
|
if name.is_some() {
|
||||||
return Err(Error::new_spanned(meta.path, "duplicate `name` key"));
|
return Err(Error::new_spanned(meta.path, "duplicate `name` key"));
|
||||||
}
|
}
|
||||||
name = Some(meta.value()?.parse()?);
|
let value = meta.value()?;
|
||||||
|
name = if value.peek(LitStr) {
|
||||||
|
let name_span = value.span();
|
||||||
|
let (new_namespace, name) = parse_prefixed_name(value)?;
|
||||||
|
if let Some(new_namespace) = new_namespace {
|
||||||
|
if namespace.is_some() {
|
||||||
|
return Err(Error::new(
|
||||||
|
name_span,
|
||||||
|
"cannot combine `namespace` key with prefixed `name`",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
namespace = Some(new_namespace);
|
||||||
|
}
|
||||||
|
Some(name)
|
||||||
|
} else {
|
||||||
|
// just use the normal parser
|
||||||
|
Some(value.parse()?)
|
||||||
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
} else if meta.path.is_ident("namespace") {
|
} else if meta.path.is_ident("namespace") {
|
||||||
if namespace.is_some() {
|
if namespace.is_some() {
|
||||||
return Err(Error::new_spanned(meta.path, "duplicate `namespace` key"));
|
return Err(Error::new_spanned(
|
||||||
|
meta.path,
|
||||||
|
"duplicate `namespace` key or `name` key has prefix",
|
||||||
|
));
|
||||||
}
|
}
|
||||||
namespace = Some(meta.value()?.parse()?);
|
namespace = Some(meta.value()?.parse()?);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -72,9 +72,16 @@ The following keys can be used inside the `#[xml(attribute(..))]` meta:
|
||||||
| `namespace` | *string literal* or *path* | The optional namespace of the XML attribute to match. If it is a *path*, it must point at a `&'static str`. Note that attributes, unlike elements, are unnamespaced by default. |
|
| `namespace` | *string literal* or *path* | The optional namespace of the XML attribute to match. If it is a *path*, it must point at a `&'static str`. Note that attributes, unlike elements, are unnamespaced by default. |
|
||||||
| `name` | *string literal* or *path* | The name of the XML attribute to match. If it is a *path*, it must point at a `&'static NcNameStr`. |
|
| `name` | *string literal* or *path* | The name of the XML attribute to match. If it is a *path*, it must point at a `&'static NcNameStr`. |
|
||||||
|
|
||||||
|
If the `name` key contains a namespace prefix, it must be one of the prefixes
|
||||||
|
defined as built-in in the XML specifications. That prefix will then be
|
||||||
|
expanded to the corresponding namespace URI and the value for the `namespace`
|
||||||
|
key is implied. Mixing a prefixed name with an explicit `namespace` key is
|
||||||
|
not allowed.
|
||||||
|
|
||||||
The `attribute` meta also supports a shorthand syntax,
|
The `attribute` meta also supports a shorthand syntax,
|
||||||
`#[xml(attribute = ..)]`, where the value is treated as the value for the
|
`#[xml(attribute = ..)]`, where the value is treated as the value for the
|
||||||
`name` key and the `namespace` is unset.
|
`name` key (with optional prefix as described above, and unnamespaced
|
||||||
|
otherwise).
|
||||||
|
|
||||||
##### Example
|
##### Example
|
||||||
|
|
||||||
|
@ -91,17 +98,21 @@ struct Foo {
|
||||||
c: String,
|
c: String,
|
||||||
#[xml(attribute(namespace = "urn:example", name = "fnord"))]
|
#[xml(attribute(namespace = "urn:example", name = "fnord"))]
|
||||||
d: String,
|
d: String,
|
||||||
|
#[xml(attribute = "xml:lang")]
|
||||||
|
e: String,
|
||||||
};
|
};
|
||||||
|
|
||||||
let foo: Foo = xso::from_bytes(b"<foo
|
let foo: Foo = xso::from_bytes(b"<foo
|
||||||
xmlns='urn:example'
|
xmlns='urn:example'
|
||||||
a='1' bar='2' baz='3'
|
a='1' bar='2' baz='3'
|
||||||
xmlns:tns0='urn:example' tns0:fnord='4'
|
xmlns:tns0='urn:example' tns0:fnord='4'
|
||||||
|
xml:lang='5'
|
||||||
/>").unwrap();
|
/>").unwrap();
|
||||||
assert_eq!(foo, Foo {
|
assert_eq!(foo, Foo {
|
||||||
a: "1".to_string(),
|
a: "1".to_string(),
|
||||||
b: "2".to_string(),
|
b: "2".to_string(),
|
||||||
c: "3".to_string(),
|
c: "3".to_string(),
|
||||||
d: "4".to_string(),
|
d: "4".to_string(),
|
||||||
|
e: "5".to_string(),
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
Loading…
Reference in a new issue