xso: allow FromXml implementations to provide matching hints
That way, callers can put multiple candidate implementations in, for example, a sorted vector and more efficiently select candidates to try when looking at a new element.
This commit is contained in:
parent
d7b62488dc
commit
126588b47d
3 changed files with 110 additions and 22 deletions
|
|
@ -54,6 +54,9 @@ Version NEXT:
|
||||||
`AsXmlText` and `FromXmlText` implementations based on the standard
|
`AsXmlText` and `FromXmlText` implementations based on the standard
|
||||||
library's `Display` and `FromStr` traits.
|
library's `Display` and `FromStr` traits.
|
||||||
- `AsXmlDyn`, a dyn-compatible variant of `AsXml` (!573).
|
- `AsXmlDyn`, a dyn-compatible variant of `AsXml` (!573).
|
||||||
|
- `FromXml::xml_name_matcher()` and related types, which allow `FromXml`
|
||||||
|
implementors to provide cachable hints to callers about which XML
|
||||||
|
elements are valid candidates for parsing (!573).
|
||||||
* Changes
|
* Changes
|
||||||
- Generated AsXml iterator and FromXml builder types are now
|
- Generated AsXml iterator and FromXml builder types are now
|
||||||
doc(hidden), to not clutter hand-written documentation with auto
|
doc(hidden), to not clutter hand-written documentation with auto
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,68 @@ use alloc::boxed::Box;
|
||||||
use crate::error::{Error, FromEventsError};
|
use crate::error::{Error, FromEventsError};
|
||||||
use crate::{FromEventsBuilder, FromXml};
|
use crate::{FromEventsBuilder, FromXml};
|
||||||
|
|
||||||
|
/// Match an XML element qualified name.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub enum XmlNameMatcher<'x> {
|
||||||
|
/// Match any XML element
|
||||||
|
Any,
|
||||||
|
|
||||||
|
/// Match any XML element in the given namespace.
|
||||||
|
InNamespace(&'x str),
|
||||||
|
|
||||||
|
/// Match any XML element with the exact namespace/name combination.
|
||||||
|
Specific(&'x str, &'x str),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'x> XmlNameMatcher<'x> {
|
||||||
|
/// Return the superset of two `XmlNameMatcher` instances.
|
||||||
|
pub const fn superset(self, other: Self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Any => Self::Any,
|
||||||
|
Self::InNamespace(my_namespace) => match other {
|
||||||
|
Self::Any => Self::Any,
|
||||||
|
Self::InNamespace(other_namespace) | Self::Specific(other_namespace, _) => {
|
||||||
|
if crate::util::const_str_eq(my_namespace, other_namespace) {
|
||||||
|
Self::InNamespace(my_namespace)
|
||||||
|
} else {
|
||||||
|
Self::Any
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Self::Specific(my_namespace, my_name) => match other {
|
||||||
|
Self::Any => Self::Any,
|
||||||
|
Self::InNamespace(other_namespace) => {
|
||||||
|
if crate::util::const_str_eq(my_namespace, other_namespace) {
|
||||||
|
Self::InNamespace(my_namespace)
|
||||||
|
} else {
|
||||||
|
Self::Any
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Specific(other_namespace, other_name) => {
|
||||||
|
if crate::util::const_str_eq(my_namespace, other_namespace) {
|
||||||
|
if crate::util::const_str_eq(my_name, other_name) {
|
||||||
|
Self::Specific(my_name, other_name)
|
||||||
|
} else {
|
||||||
|
Self::InNamespace(my_namespace)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Self::Any
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return true if the given `qname` matches this matcher.
|
||||||
|
pub fn matches(&self, qname: &rxml::QName) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Any => true,
|
||||||
|
Self::InNamespace(ns) => qname.0.as_str() == *ns,
|
||||||
|
Self::Specific(ns, name) => qname.0.as_str() == *ns && qname.1.as_str() == *name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// # Parsing context for [`FromEventsBuilder`]
|
/// # Parsing context for [`FromEventsBuilder`]
|
||||||
///
|
///
|
||||||
/// For the most part, [`FromEventsBuilder`] implementations can work with
|
/// For the most part, [`FromEventsBuilder`] implementations can work with
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,34 @@ pub mod minidom_compat;
|
||||||
mod rxml_util;
|
mod rxml_util;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
|
|
||||||
|
// This is a hack to not make `const_str_eq` publicly available, except
|
||||||
|
// through the `exports` module if the `macros` feature is enabled, but have
|
||||||
|
// it available internally in all cases.
|
||||||
|
mod util {
|
||||||
|
/// Compile-time comparison of two strings.
|
||||||
|
///
|
||||||
|
/// Used by macro-generated code.
|
||||||
|
///
|
||||||
|
/// This is necessary because `<str as PartialEq>::eq` is not `const`.
|
||||||
|
pub const fn const_str_eq(a: &str, b: &str) -> bool {
|
||||||
|
let a = a.as_bytes();
|
||||||
|
let b = b.as_bytes();
|
||||||
|
if a.len() != b.len() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut i = 0;
|
||||||
|
while i < a.len() {
|
||||||
|
if a[i] != b[i] {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub mod exports {
|
pub mod exports {
|
||||||
#[cfg(all(feature = "minidom", feature = "macros"))]
|
#[cfg(all(feature = "minidom", feature = "macros"))]
|
||||||
|
|
@ -75,35 +103,15 @@ pub mod exports {
|
||||||
#[cfg(feature = "macros")]
|
#[cfg(feature = "macros")]
|
||||||
pub type CoreU8 = u8;
|
pub type CoreU8 = u8;
|
||||||
|
|
||||||
/// Compile-time comparison of two strings.
|
|
||||||
///
|
|
||||||
/// Used by macro-generated code.
|
|
||||||
///
|
|
||||||
/// This is necessary because `<str as PartialEq>::eq` is not `const`.
|
|
||||||
#[cfg(feature = "macros")]
|
#[cfg(feature = "macros")]
|
||||||
pub const fn const_str_eq(a: &'static str, b: &'static str) -> bool {
|
pub use super::util::const_str_eq;
|
||||||
let a = a.as_bytes();
|
|
||||||
let b = b.as_bytes();
|
|
||||||
if a.len() != b.len() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut i = 0;
|
|
||||||
while i < a.len() {
|
|
||||||
if a[i] != b[i] {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
use alloc::{borrow::Cow, boxed::Box, string::String, vec::Vec};
|
use alloc::{borrow::Cow, boxed::Box, string::String, vec::Vec};
|
||||||
|
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use fromxml::Context;
|
pub use fromxml::Context;
|
||||||
|
use fromxml::XmlNameMatcher;
|
||||||
|
|
||||||
pub use text::TextCodec;
|
pub use text::TextCodec;
|
||||||
|
|
||||||
|
|
@ -235,6 +243,21 @@ pub trait FromXml {
|
||||||
attrs: rxml::AttrMap,
|
attrs: rxml::AttrMap,
|
||||||
ctx: &Context<'_>,
|
ctx: &Context<'_>,
|
||||||
) -> Result<Self::Builder, self::error::FromEventsError>;
|
) -> Result<Self::Builder, self::error::FromEventsError>;
|
||||||
|
|
||||||
|
/// Return a predicate which determines if `Self` *may* be parsed from
|
||||||
|
/// a given XML element.
|
||||||
|
///
|
||||||
|
/// The returned matcher **must** match all elements from which `Self`
|
||||||
|
/// can be parsed, but it may also match elements from which `Self`
|
||||||
|
/// cannot be parsed.
|
||||||
|
///
|
||||||
|
/// This is an optimisation utility for code locations which have to
|
||||||
|
/// disambiguate between many `FromXml` implementations. The provided
|
||||||
|
/// implementation returns a matcher which matches all elements, which is
|
||||||
|
/// correct, but also very inefficient.
|
||||||
|
fn xml_name_matcher() -> XmlNameMatcher<'static> {
|
||||||
|
XmlNameMatcher::Any
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait allowing to convert XML text to a value.
|
/// Trait allowing to convert XML text to a value.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue