fastxml 0.11.0

A fast, memory-efficient XML library with XPath and XSD validation support
Documentation
//! Helper functions for XSD parsing.

use std::collections::HashMap;

use crate::error::Result;
use crate::schema::xsd::types::Occurs;

/// XSD namespace URI.
pub const XSD_NAMESPACE: &str = "http://www.w3.org/2001/XMLSchema";

/// Parses attributes from a start element event.
pub(super) fn parse_attributes(attrs: &[(&str, &str)]) -> HashMap<String, String> {
    attrs
        .iter()
        .map(|&(k, v)| (k.to_string(), v.to_string()))
        .collect()
}

/// Parses and validates minOccurs/maxOccurs from attributes.
/// Returns (minOccurs, maxOccurs) or an error if invalid.
pub(super) fn parse_occurs(attrs: &HashMap<String, String>) -> Result<(Occurs, Occurs)> {
    use crate::schema::error::SchemaError;

    let min = if let Some(min_str) = attrs.get("minOccurs") {
        Occurs::parse(min_str).map_err(|e| SchemaError::InvalidOccurs { value: e })?
    } else {
        Occurs::default()
    };

    let max = if let Some(max_str) = attrs.get("maxOccurs") {
        Occurs::parse(max_str).map_err(|e| SchemaError::InvalidOccurs { value: e })?
    } else {
        Occurs::default()
    };

    // Validate minOccurs <= maxOccurs
    match (&min, &max) {
        (Occurs::Count(min_val), Occurs::Count(max_val)) if min_val > max_val => {
            return Err(SchemaError::MinOccursGreaterThanMaxOccurs {
                min: *min_val,
                max: *max_val,
            }
            .into());
        }
        _ => {}
    }

    Ok((min, max))
}

/// Parses a non-negative integer facet value with validation.
pub(super) fn parse_facet_length(name: &str, value: &str) -> Result<u32> {
    use crate::schema::error::SchemaError;

    // Check for negative values
    if value.starts_with('-') {
        return Err(SchemaError::InvalidFacetValue {
            facet: name.to_string(),
            value: value.to_string(),
            reason: "must be non-negative".to_string(),
        }
        .into());
    }
    value.parse::<u32>().map_err(|_| {
        SchemaError::InvalidFacetValue {
            facet: name.to_string(),
            value: value.to_string(),
            reason: "must be a non-negative integer".to_string(),
        }
        .into()
    })
}

/// Splits a quick_xml start tag into borrowed name parts, attributes, and
/// namespace declarations.
#[allow(clippy::type_complexity)]
pub(super) fn split_start_event<'a>(
    e: &'a quick_xml::events::BytesStart<'a>,
) -> Result<(
    &'a str,
    Option<&'a str>,
    Vec<(&'a str, std::borrow::Cow<'a, str>)>,
    Vec<crate::namespace::Namespace>,
)> {
    let full_name = std::str::from_utf8(e.name().into_inner())?;
    let (prefix, name) = crate::namespace::split_qname(full_name);

    let mut namespace_decls = Vec::new();
    let mut attributes = Vec::new();

    for attr_result in e.attributes() {
        let attr = attr_result?;
        let key = std::str::from_utf8(attr.key.into_inner())?;
        let value = attr.unescape_value().map_err(|e| {
            crate::parser::error::ParseError::AttributeDecodeError {
                message: e.to_string(),
            }
        })?;

        if key == "xmlns" {
            namespace_decls.push(crate::namespace::Namespace::default_ns(value.as_ref()));
        } else if let Some(ns_prefix) = key.strip_prefix("xmlns:") {
            namespace_decls.push(crate::namespace::Namespace::new(ns_prefix, value.as_ref()));
        } else {
            attributes.push((key, value));
        }
    }

    Ok((name, prefix, attributes, namespace_decls))
}