xsd_parser/quick_xml/
mod.rs

1//! The `quick_xml` module contains helper types for serializing and deserializing
2//! generated code using the [`quick_xml`] crate.
3
4pub mod reader;
5
6mod attributes;
7mod deserialize;
8mod error;
9mod serialize;
10
11use std::sync::LazyLock;
12
13pub use std::io::Write as XmlWrite;
14
15pub use quick_xml::{
16    events::{BytesCData, BytesDecl, BytesEnd, BytesPI, BytesStart, BytesText, Event},
17    name::{LocalName, Namespace, QName, ResolveResult},
18    Writer,
19};
20use regex::Regex;
21
22pub use crate::models::RawByteStr;
23
24pub use self::attributes::{filter_xmlns_attributes, write_attrib, write_attrib_opt};
25pub use self::deserialize::{
26    ContentDeserializer, DeserializeBytes, DeserializeBytesFromStr, DeserializeReader,
27    DeserializeStrError, DeserializeSync, Deserializer, DeserializerArtifact, DeserializerEvent,
28    DeserializerOutput, DeserializerResult, ElementHandlerOutput, WithDeserializer,
29};
30pub use self::error::{Error, Kind as ErrorKind, UnionError, ValidateError};
31pub use self::reader::{ErrorReader, IoReader, SliceReader, XmlReader, XmlReaderSync};
32pub use self::serialize::{
33    BoxedSerializer, ContentSerializer, IterSerializer, SerializeBytes, SerializeBytesToString,
34    SerializeSync, Serializer, WithBoxedSerializer, WithSerializer,
35};
36
37#[cfg(feature = "async")]
38pub use tokio::io::AsyncWrite as XmlWriteAsync;
39
40#[cfg(feature = "async")]
41pub use self::serialize::SerializeAsync;
42
43#[cfg(feature = "async")]
44pub use self::deserialize::DeserializeAsync;
45
46#[cfg(feature = "async")]
47pub use self::reader::XmlReaderAsync;
48
49/// Helper method to replace whitespaces in a string value.
50#[must_use]
51pub fn whitespace_replace(s: &str) -> String {
52    s.replace(['\t', '\n', '\r'], " ")
53}
54
55/// Helper method to collapse whitespaces in a string value.
56pub fn whitespace_collapse(s: &str) -> String {
57    RX_WHITESPACE_COLLAPSE.replace_all(s, " ").trim().to_owned()
58}
59
60/// Helper method the get the total number of digits for a decimal string value.
61///
62/// # Errors
63///
64/// Returns [`ValidateError::InvalidDecimalValue`] if the passed string is
65/// not a valid decimal value, or [`ValidateError::TotalDigits`] if it exceeds
66/// the `expected` amount or total digits.
67pub fn total_digits(s: &str, expected: usize) -> Result<(), ValidateError> {
68    let m = RX_DECIMAL
69        .captures(s)
70        .ok_or(ValidateError::InvalidDecimalValue)?;
71
72    let actual = match (m.get(1), m.get(2)) {
73        (None, _) => unreachable!("Capture group 1 should be always present"),
74        (Some(a), None) => a.len(),
75        (Some(a), Some(b)) => a.len() + b.len(),
76    };
77
78    if actual <= expected {
79        Ok(())
80    } else {
81        Err(ValidateError::TotalDigits(expected))
82    }
83}
84
85/// Helper method the get the number of fraction digits for a decimal string value.
86///
87/// # Errors
88///
89/// Returns [`ValidateError::InvalidDecimalValue`] if the passed string is
90/// not a valid decimal value, or [`ValidateError::FractionDigits`] if it exceeds
91/// the `expected` amount or fraction digits.
92pub fn fraction_digits(s: &str, expected: usize) -> Result<(), ValidateError> {
93    let m = RX_DECIMAL
94        .captures(s)
95        .ok_or(ValidateError::InvalidDecimalValue)?;
96
97    let actual = m.get(2).map(|s| s.len()).unwrap_or_default();
98
99    if actual <= expected {
100        Ok(())
101    } else {
102        Err(ValidateError::FractionDigits(expected))
103    }
104}
105
106static RX_DECIMAL: LazyLock<Regex> =
107    LazyLock::new(|| Regex::new(r"^-?([0-9]+)(?:\.([0-9]*))?$").unwrap());
108static RX_WHITESPACE_COLLAPSE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\s+").unwrap());