#![warn(missing_docs)]
use core::{fmt, str};
use fmt::Display;
use std::{borrow::Borrow, ops::Deref, str::FromStr};
pub mod de;
pub use de::{DeserializationGroup, Deserialize, DeserializeOwned, Deserializer};
pub mod ser;
pub use ser::{AttributeSerializer, SerializationGroup, Serialize, SerializeAttribute, Serializer};
mod macros;
pub mod types;
pub mod value;
pub use value::XmlValue;
mod noop;
pub use noop::NoopDeSerializer;
#[cfg(feature = "derive")]
extern crate xmlity_derive;
#[cfg(feature = "derive")]
pub use xmlity_derive::{
DeserializationGroup, Deserialize, SerializationGroup, Serialize, SerializeAttribute,
};
mod name_tokens {
const fn is_name_start_char(c: char) -> bool {
matches!(
c, 'A'..='Z' | '_' | 'a'..='z' | '\u{00C0}'..='\u{00D6}' | '\u{00D8}'..='\u{00F6}' | '\u{00F8}'..='\u{02FF}' | '\u{0370}'..='\u{037D}' | '\u{037F}'..='\u{1FFF}' | '\u{200C}'..='\u{200D}' | '\u{2070}'..='\u{218F}' | '\u{2C00}'..='\u{2FEF}' | '\u{3001}'..='\u{D7FF}' | '\u{F900}'..='\u{FDCF}' | '\u{FDF0}'..='\u{FFFD}' | '\u{10000}'..='\u{EFFFF}'
)
}
const fn is_name_char(c: char) -> bool {
is_name_start_char(c)
|| matches!(c, '-' | '.' | '0'..='9' | '\u{00B7}' | '\u{0300}'..='\u{036F}' | '\u{203F}'..='\u{2040}')
}
#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum InvalidXmlNameError {
#[error("Invalid start character")]
InvalidStartChar,
#[error("Invalid character at index {index}")]
InvalidChar {
index: usize,
character: char,
},
#[error("Empty name")]
Empty,
}
pub fn is_valid_name(name: &str) -> Result<(), InvalidXmlNameError> {
let mut chars = name.chars();
if let Some(c) = chars.next() {
if !is_name_start_char(c) {
return Err(InvalidXmlNameError::InvalidStartChar);
}
} else {
return Err(InvalidXmlNameError::Empty);
}
for (index, character) in chars.enumerate().map(|(i, c)| (i + 1, c)) {
if !is_name_char(character) {
return Err(InvalidXmlNameError::InvalidChar { index, character });
}
}
Ok(())
}
}
pub use name_tokens::InvalidXmlNameError;
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct ExpandedName<'a> {
local_name: &'a LocalName,
namespace: Option<&'a XmlNamespace>,
}
impl<'a> ExpandedName<'a> {
pub fn new(local_name: &'a LocalName, namespace: Option<&'a XmlNamespace>) -> Self {
Self {
local_name,
namespace,
}
}
pub fn into_owned(self) -> ExpandedNameBuf {
ExpandedNameBuf::new(
self.local_name.to_owned(),
self.namespace.map(|n| n.to_owned()),
)
}
pub fn into_parts(self) -> (&'a LocalName, Option<&'a XmlNamespace>) {
(self.local_name, self.namespace)
}
pub fn local_name(&self) -> &'a LocalName {
self.local_name
}
pub fn namespace(&self) -> &Option<&'a XmlNamespace> {
&self.namespace
}
pub fn to_q_name(self, resolved_prefix: Option<&'a Prefix>) -> QName<'a> {
QName::new(resolved_prefix, self.local_name)
}
}
impl Display for ExpandedName<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.local_name.fmt(f)
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct ExpandedNameBuf {
local_name: LocalNameBuf,
namespace: Option<XmlNamespaceBuf>,
}
impl ExpandedNameBuf {
pub fn new(local_name: LocalNameBuf, namespace: Option<XmlNamespaceBuf>) -> Self {
Self {
local_name,
namespace,
}
}
pub fn as_ref(&self) -> ExpandedName<'_> {
ExpandedName::new(
self.local_name.borrow(),
self.namespace.as_ref().map(|n| n.borrow()),
)
}
pub fn into_parts(self) -> (LocalNameBuf, Option<XmlNamespaceBuf>) {
(self.local_name, self.namespace)
}
pub fn namespace(&self) -> Option<&XmlNamespace> {
self.namespace.as_deref()
}
pub fn local_name(&self) -> &LocalName {
&self.local_name
}
}
impl From<ExpandedName<'_>> for ExpandedNameBuf {
fn from(value: ExpandedName<'_>) -> Self {
value.into_owned()
}
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct QName<'a> {
prefix: Option<&'a Prefix>,
local_name: &'a LocalName,
}
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum QNameParseError {
#[error("Invalid prefix: {0}")]
InvalidPrefix(#[from] PrefixParseError),
#[error("Invalid local name: {0}")]
InvalidLocalName(#[from] LocalNameParseError),
}
impl<'a> QName<'a> {
pub fn new(prefix: Option<&'a Prefix>, local_name: &'a LocalName) -> Self {
QName { prefix, local_name }
}
pub fn into_parts(self) -> (Option<&'a Prefix>, &'a LocalName) {
(self.prefix, self.local_name)
}
pub fn prefix(&self) -> &Option<&'a Prefix> {
&self.prefix
}
pub fn local_name(&self) -> &LocalName {
self.local_name
}
pub fn into_owned(self) -> QNameBuf {
QNameBuf::new(
self.prefix.map(|p| p.to_owned()),
self.local_name.to_owned(),
)
}
}
impl Display for QName<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(prefix) = &self.prefix.as_ref().filter(|prefix| !prefix.is_default()) {
write!(f, "{}:{}", prefix, self.local_name)
} else {
write!(f, "{}", self.local_name)
}
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct QNameBuf {
prefix: Option<PrefixBuf>,
local_name: LocalNameBuf,
}
impl QNameBuf {
pub fn new(prefix: Option<PrefixBuf>, local_name: LocalNameBuf) -> Self {
Self { prefix, local_name }
}
pub fn as_ref(&self) -> QName<'_> {
QName::new(
self.prefix.as_ref().map(|p| p.borrow()),
self.local_name.borrow(),
)
}
}
impl FromStr for QNameBuf {
type Err = QNameParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (prefix, local_name) = s.split_once(':').unwrap_or(("", s));
let prefix = if prefix.is_empty() {
None
} else {
Some(PrefixBuf::from_str(prefix)?)
};
let local_name = LocalNameBuf::from_str(local_name)?;
Ok(QNameBuf::new(prefix, local_name))
}
}
impl Display for QNameBuf {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_ref().fmt(f)
}
}
impl From<QName<'_>> for QNameBuf {
fn from(value: QName<'_>) -> Self {
value.into_owned()
}
}
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct XmlNamespace(str);
#[derive(Debug, thiserror::Error)]
pub enum XmlNamespaceParseError {}
impl XmlNamespace {
pub const unsafe fn new_unchecked(value: &str) -> &Self {
unsafe { &*(value as *const str as *const XmlNamespace) }
}
pub fn new(value: &str) -> Result<&Self, XmlNamespaceParseError> {
Ok(unsafe { Self::new_unchecked(value) })
}
pub fn as_str(&self) -> &str {
&self.0
}
pub const XMLNS: &'static XmlNamespace =
unsafe { XmlNamespace::new_unchecked("http://www.w3.org/2000/xmlns/") };
pub const XML: &'static XmlNamespace =
unsafe { XmlNamespace::new_unchecked("http://www.w3.org/XML/1998/namespace") };
pub const XHTML: &'static XmlNamespace =
unsafe { XmlNamespace::new_unchecked("http://www.w3.org/1999/xhtml") };
pub const XS: &'static XmlNamespace =
unsafe { XmlNamespace::new_unchecked("http://www.w3.org/2001/XMLSchema") };
pub const XSI: &'static XmlNamespace =
unsafe { XmlNamespace::new_unchecked("http://www.w3.org/2001/XMLSchema-instance") };
}
impl Display for XmlNamespace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl Serialize for XmlNamespace {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_text(&self.0)
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct XmlNamespaceBuf(String);
impl Borrow<XmlNamespace> for XmlNamespaceBuf {
fn borrow(&self) -> &XmlNamespace {
unsafe { XmlNamespace::new_unchecked(&self.0) }
}
}
impl Deref for XmlNamespaceBuf {
type Target = XmlNamespace;
fn deref(&self) -> &Self::Target {
self.borrow()
}
}
impl ToOwned for XmlNamespace {
type Owned = XmlNamespaceBuf;
fn to_owned(&self) -> Self::Owned {
XmlNamespaceBuf(self.0.to_owned())
}
}
impl FromStr for XmlNamespaceBuf {
type Err = XmlNamespaceParseError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
XmlNamespace::new(value).map(ToOwned::to_owned)
}
}
impl<'de> Deserialize<'de> for XmlNamespaceBuf {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(types::string::FromStrVisitor::default())
}
}
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Prefix(str);
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum PrefixParseError {
#[error("Prefix has an invalid XML name: {0}")]
InvalidXmlName(#[from] name_tokens::InvalidXmlNameError),
}
impl Prefix {
pub const unsafe fn new_unchecked(value: &str) -> &Self {
unsafe { &*(value as *const str as *const Prefix) }
}
pub fn new(value: &str) -> Result<&Self, PrefixParseError> {
name_tokens::is_valid_name(value)?;
Ok(unsafe { Self::new_unchecked(value) })
}
pub fn into_owned(&self) -> PrefixBuf {
unsafe { PrefixBuf::new_unchecked(self.0.to_owned()) }
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn xmlns(&self) -> QName<'_> {
QName::new(Some(Prefix::XMLNS), <&LocalName>::from(self))
}
pub fn is_default(&self) -> bool {
self.0.is_empty()
}
pub const BLANK: &'static Prefix =
unsafe { Prefix::new_unchecked("") };
pub const XMLNS: &'static Prefix =
unsafe { Prefix::new_unchecked("xmlns") };
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct PrefixBuf(String);
impl Default for PrefixBuf {
fn default() -> Self {
Prefix::BLANK.to_owned()
}
}
impl PrefixBuf {
pub const unsafe fn new_unchecked(value: String) -> Self {
Self(value)
}
pub fn new(value: String) -> Result<Self, PrefixParseError> {
Prefix::new(&value)?;
Ok(unsafe { Self::new_unchecked(value) })
}
}
impl Borrow<Prefix> for PrefixBuf {
fn borrow(&self) -> &Prefix {
unsafe { Prefix::new_unchecked(&self.0) }
}
}
impl Deref for PrefixBuf {
type Target = Prefix;
fn deref(&self) -> &Self::Target {
self.borrow()
}
}
impl ToOwned for Prefix {
type Owned = PrefixBuf;
fn to_owned(&self) -> Self::Owned {
PrefixBuf(self.0.to_owned())
}
}
impl FromStr for PrefixBuf {
type Err = PrefixParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Prefix::new(s).map(ToOwned::to_owned)
}
}
impl<'a> From<&'a Prefix> for &'a LocalName {
fn from(value: &'a Prefix) -> Self {
unsafe { LocalName::new_unchecked(value.as_str()) }
}
}
impl<'a> From<Option<&'a Prefix>> for &'a Prefix {
fn from(value: Option<&'a Prefix>) -> Self {
value.unwrap_or(Prefix::BLANK)
}
}
impl Display for Prefix {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl Serialize for Prefix {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_text(&self.0)
}
}
impl<'de> Deserialize<'de> for PrefixBuf {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(types::string::FromStrVisitor::default())
}
}
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct LocalName(str);
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum LocalNameParseError {
#[error("Local name has an invalid XML name: {0}")]
InvalidXmlName(#[from] name_tokens::InvalidXmlNameError),
}
impl LocalName {
pub const unsafe fn new_unchecked(value: &str) -> &Self {
unsafe { &*(value as *const str as *const LocalName) }
}
pub fn new(value: &str) -> Result<&Self, LocalNameParseError> {
name_tokens::is_valid_name(value)?;
Ok(unsafe { Self::new_unchecked(value) })
}
pub fn as_str(&self) -> &str {
&self.0
}
pub const XMLNS: &'static LocalName =
unsafe { LocalName::new_unchecked("xmlns") };
}
impl Display for LocalName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl Serialize for LocalName {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_text(&self.0)
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct LocalNameBuf(String);
impl LocalNameBuf {
pub const unsafe fn new_unchecked(value: String) -> Self {
Self(value)
}
pub fn new(value: String) -> Result<Self, LocalNameParseError> {
LocalName::new(&value)?;
Ok(unsafe { Self::new_unchecked(value) })
}
}
impl Borrow<LocalName> for LocalNameBuf {
fn borrow(&self) -> &LocalName {
unsafe { LocalName::new_unchecked(&self.0) }
}
}
impl Deref for LocalNameBuf {
type Target = LocalName;
fn deref(&self) -> &Self::Target {
self.borrow()
}
}
impl ToOwned for LocalName {
type Owned = LocalNameBuf;
fn to_owned(&self) -> Self::Owned {
LocalNameBuf(self.0.to_owned())
}
}
impl FromStr for LocalNameBuf {
type Err = LocalNameParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
LocalName::new(s).map(ToOwned::to_owned)
}
}
impl Serialize for LocalNameBuf {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.deref().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for LocalNameBuf {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(types::string::FromStrVisitor::default())
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
use std::str::FromStr;
#[rstest]
#[case::basic("prefix")]
fn test_prefix(#[case] prefix_text: &str) {
let prefix = Prefix::new(prefix_text).unwrap();
assert_eq!(prefix.to_string(), prefix_text);
assert_eq!(prefix.as_str().to_string(), prefix_text);
}
#[rstest]
#[case::empty("", PrefixParseError::InvalidXmlName(InvalidXmlNameError::Empty))]
#[case::space("invalid prefix", PrefixParseError::InvalidXmlName(InvalidXmlNameError::InvalidChar { index: 7, character: ' ' }))]
fn invalid_prefix_invalid_characters(
#[case] prefix: &str,
#[case] expected_error: PrefixParseError,
) {
let error = Prefix::new(prefix).unwrap_err();
assert_eq!(error, expected_error);
}
#[rstest]
#[case::basic("localName")]
fn test_local_name(#[case] local_name_text: &str) {
let local_name = LocalNameBuf::from_str(local_name_text).unwrap();
assert_eq!(local_name.to_string(), local_name_text);
}
#[rstest]
#[case::empty("", LocalNameParseError::InvalidXmlName(InvalidXmlNameError::Empty))]
#[case::space("invalid localName", LocalNameParseError::InvalidXmlName(InvalidXmlNameError::InvalidChar { index: 7, character: ' ' }))]
fn invalid_local_name_invalid_characters(
#[case] local_name: &str,
#[case] expected_error: LocalNameParseError,
) {
let error = LocalNameBuf::from_str(local_name).unwrap_err();
assert_eq!(error, expected_error);
}
#[rstest]
#[case::basic("localName", None)]
#[case::with_namespace("localName", Some(XmlNamespace::new("http://example.com").unwrap()))]
fn test_expanded_name(#[case] local_name_text: &str, #[case] namespace: Option<&XmlNamespace>) {
let local_name = LocalName::new(local_name_text).unwrap();
let expanded_name = ExpandedName::new(local_name, namespace.clone());
assert_eq!(expanded_name.local_name(), local_name);
assert_eq!(expanded_name.namespace(), &namespace);
assert_eq!(expanded_name.to_string(), local_name_text);
assert_eq!(expanded_name.local_name.as_str(), local_name_text);
}
#[rstest]
#[case::basic("prefix:localName")]
fn test_qname(#[case] qname_text: &str) {
let qname = QNameBuf::from_str(qname_text).unwrap();
assert_eq!(qname.to_string(), qname_text);
}
#[rstest]
#[case::invalid_local_name("prefix:invalid localName", QNameParseError::InvalidLocalName(LocalNameParseError::InvalidXmlName(InvalidXmlNameError::InvalidChar { index: 7, character: ' ' })))]
fn invalid_qname_invalid_characters(
#[case] qname: &str,
#[case] expected_error: QNameParseError,
) {
let error = QNameBuf::from_str(qname).unwrap_err();
assert_eq!(error, expected_error);
}
}