Skip to main content

libxml_rs/xml/schemas/
mod.rs

1//! XML Schema implementation (§27, §85 Phase 6).
2//!
3//! XML Schema (W3C XSD) validation and datatype machinery. Upstream libxml2
4//! schema support is an UPSTREAM_EXTENSION known to deviate from the standard in
5//! places — parity follows the oracle.
6//!
7//! Phase 6: Complete — schema parsing, datatype validation, document validation,
8//! and C ABI exports are implemented.
9//!
10//! # UPSTREAM-PARITY
11//!
12//! This module implements a simplified but functional XSD validator that
13//! follows upstream libxml2 observable behavior for the most common patterns.
14//! Deviations from the W3C specification that match libxml2 are intentional.
15//!
16//! # Upstream contract
17//!
18//! Mirrors upstream xmlschemas.c, xmlschemastypes.c and xmlschemavalues.c
19//! (SRC-LIBXML2-2.15.0, oracle tree `oracle/historical/src/libxml2-2.15.0/`):
20//! xmlSchema parse/valid contexts, component model, facet validation and the
21//! built-in datatypes. Parity target: the system libxml2 2.15.3 oracle.
22//!
23//! # Conceptual behavior
24//!
25//! XML Schema (W3C XSD) validation and datatype machinery: schema parsing,
26//! component classification, simple/complex type validation, facets and
27//! document validation. Upstream libxml2 schema support is an
28//! UPSTREAM_EXTENSION known to deviate from the standard in places — parity
29//! follows the oracle.
30//!
31//! # Ownership & safety invariants
32//!
33//! Ownership: schemas own their component tree (xmlSchemaFree); parser and
34//! valid contexts own their state (xmlSchemaFreeParserCtxt /
35//! xmlSchemaFreeValidCtxt); the validated document and the schema
36//! import/resource-loading are borrowed through xmlSchemaSetResourceLoader.
37//! SAFETY: facet validation operates on owned string representations, never
38//! on borrowed C buffers beyond the call.
39//!
40//! # Historical quirks & epochs
41//!
42//! XSD support was solidified in the 2.6 validation era (2003-2004,
43//! atlas/HISTORY.md 1.5). R-000124 (11.1-G) closed the header-surface gap so
44//! every public schema header declaration compiles against the DSO;
45//! xmlSchemaFreeWildcard is a safe no-op (the candidate never allocates
46//! wildcard objects; R-000138) and xmlSchemaCleanupTypes is a documented
47//! no-op.
48//!
49//! # Deliberate oddities
50//!
51//! Deliberate oddities: deviations from the W3C XSD specification that match
52//! upstream libxml2 are intentional; the exported entry points
53//! (xmlSchemaNewParserCtxt, xmlSchemaNewMemParserCtxt, xmlSchemaFree,
54//! xmlSchemaValidateDoc, xmlSchemaFreeParserCtxt, xmlSchemaFreeValidCtxt)
55//! follow the upstream signatures.
56//!
57//! # Proving courts
58//!
59//! Exercised by the XSD court family, the header-compile court, the
60//! dso-loader court and `cargo test --lib`. Receipts under
61//! courts/receipts/phase-11.
62//!
63//! # Tempting simplifications that would break parity
64//!
65//! The tempting simplification is a clean-room full XSD 1.0 engine — the
66//! oracle deviations (UPSTREAM_EXTENSION) would not be reproduced and
67//! differential output would diverge. Do not drop the lazy/empty type-cleanup
68//! entry points: they are part of the exported surface (R-000138).
69
70use core::ffi::c_void;
71use core::ptr;
72use std::os::raw::{c_char, c_int};
73
74use crate::abi::allocator;
75use crate::abi::structs::*;
76use crate::abi::types::xmlElementType::*;
77
78// ═══════════════════════════════════════════════════════════════════════════════
79// XSD Component Types
80// ═══════════════════════════════════════════════════════════════════════════════
81
82/// XSD component types — mirrors the upstream libxml2 schema component
83/// classification.
84///
85/// # UPSTREAM-PARITY
86///
87/// libxml2 defines these as `xmlSchemaTypeType` in `include/schemas/internals.h`.
88#[derive(Debug, Clone, Copy, PartialEq, Eq)]
89pub enum XsdComponentType {
90    /// The `xs:schema` root declaration. Also used as a fallback kind for
91    /// unrecognized elements during parsing.
92    Schema,
93    /// An `xs:element` declaration.
94    Element,
95    /// An `xs:attribute` declaration.
96    Attribute,
97    /// An `xs:complexType` definition (element/attribute content, possibly mixed).
98    ComplexType,
99    /// An `xs:simpleType` definition (a constrained built-in or derived simple type).
100    SimpleType,
101    /// An `xs:simpleContent` model on a complex type (text-only content).
102    SimpleContent,
103    /// An `xs:complexContent` model on a complex type (restriction or extension).
104    ComplexContent,
105    /// An `xs:sequence` model group whose children must appear in order.
106    Sequence,
107    /// An `xs:choice` model group of which exactly one child is allowed.
108    Choice,
109    /// An `xs:all` model group whose children may appear in any order.
110    All,
111    /// An `xs:restriction`, deriving a type by constraining its base type.
112    Restriction,
113    /// An `xs:extension`, deriving a type by adding content to its base type.
114    Extension,
115    /// An `xs:list` simple type (a whitespace-separated list of an item type).
116    List,
117    /// An `xs:union` simple type whose value must match one of its member types.
118    Union,
119    /// An `xs:annotation` (documentation/appinfo). Skipped during schema parsing.
120    Annotation,
121    /// An `xs:any` wildcard that matches any element.
122    Any,
123    /// An `xs:anyAttribute` wildcard that matches any attribute.
124    AnyAttribute,
125    /// An `xs:group` named model group (used either as a definition or a reference).
126    Group,
127    /// An `xs:attributeGroup` named attribute group (definition or reference).
128    AttributeGroup,
129    /// An `xs:notation` declaration binding a notation name to a system/resource.
130    Notation,
131    /// An `xs:unique` identity constraint.
132    Unique,
133    /// An `xs:key` identity constraint.
134    Key,
135    /// An `xs:keyref` identity constraint that references a key or unique constraint.
136    KeyRef,
137    /// An `xs:selector`, the XPath selection of an identity constraint.
138    Selector,
139    /// An `xs:field`, the XPath field of an identity constraint.
140    Field,
141}
142
143// ═══════════════════════════════════════════════════════════════════════════════
144// XSD Datatype Kinds
145// ═══════════════════════════════════════════════════════════════════════════════
146
147/// XSD datatype kinds — covers all built-in types and facets.
148///
149/// # UPSTREAM-PARITY
150///
151/// libxml2 defines these as `xmlSchemaTypeType` built-in type constants
152/// in `include/schemas/internals.h`.
153#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
154pub enum XsdDatatypeKind {
155    // Primitive types
156    /// XSD built-in type `xs:string` — any sequence of characters.
157    String,
158    /// XSD built-in type `xs:boolean` — lexical forms `true`, `false`, `1`, and `0`.
159    Boolean,
160    /// XSD built-in type `xs:decimal` — an arbitrary-precision decimal number
161    /// (optional sign, digits, optional decimal point).
162    Decimal,
163    /// XSD built-in type `xs:float` — IEEE single-precision floating point,
164    /// including the special values `INF`, `-INF`, and `NaN`.
165    Float,
166    /// XSD built-in type `xs:double` — IEEE double-precision floating point,
167    /// including the special values `INF`, `-INF`, and `NaN`.
168    Double,
169    /// XSD built-in type `xs:duration` — an ISO 8601 duration of the form
170    /// `[-]P[nY][nM][nD][T[nH][nM][nS]]`.
171    Duration,
172    /// XSD built-in type `xs:dateTime` — `YYYY-MM-DDThh:mm:ss[.sss][Z|±hh:mm]`.
173    DateTime,
174    /// XSD built-in type `xs:time` — `hh:mm:ss[.sss][Z|±hh:mm]`.
175    Time,
176    /// XSD built-in type `xs:date` — `YYYY-MM-DD[Z|±hh:mm]`.
177    Date,
178    /// XSD built-in type `xs:gYearMonth` — a Gregorian year and month, `YYYY-MM[Z|±hh:mm]`.
179    GYearMonth,
180    /// XSD built-in type `xs:gYear` — a Gregorian year, `YYYY[Z|±hh:mm]`.
181    GYear,
182    /// XSD built-in type `xs:gMonthDay` — a Gregorian month and day, `--MM-DD[Z|±hh:mm]`.
183    GMonthDay,
184    /// XSD built-in type `xs:gDay` — a Gregorian day of the month, `---DD[Z|±hh:mm]`.
185    GDay,
186    /// XSD built-in type `xs:gMonth` — a Gregorian month, `--MM[Z|±hh:mm]`.
187    GMonth,
188    /// XSD built-in type `xs:hexBinary` — binary data encoded as hexadecimal
189    /// digits (an even number of digits).
190    HexBinary,
191    /// XSD built-in type `xs:base64Binary` — binary data encoded in base64.
192    Base64Binary,
193    /// XSD built-in type `xs:anyURI` — a URI reference (validated here as non-empty).
194    AnyURI,
195    /// XSD built-in type `xs:QName` — a qualified name (`NCName` or `prefix:NCName`).
196    QName,
197    /// XSD built-in type `xs:NOTATION` — a reference to a notation declaration,
198    /// lexically a QName.
199    Notation,
200    // Derived string types
201    /// XSD built-in type `xs:normalizedString` — a `string` with no tabs,
202    /// newlines, or carriage returns.
203    NormalizedString,
204    /// XSD built-in type `xs:token` — a `normalizedString` with no leading or
205    /// trailing whitespace and no consecutive internal whitespace.
206    Token,
207    /// XSD built-in type `xs:language` — a natural language identifier per
208    /// RFC 4646/BCP 47 (simplified here to `[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*`).
209    Language,
210    /// XSD built-in type `xs:NMTOKEN` — a sequence of XML name characters.
211    Nmtoken,
212    /// XSD built-in type `xs:NMTOKENS` — a whitespace-separated list of `NMTOKEN`s.
213    Nmtokens,
214    /// XSD built-in type `xs:Name` — an XML Name.
215    Name,
216    /// XSD built-in type `xs:NCName` — an XML Name without colons.
217    NCName,
218    /// XSD built-in type `xs:ID` — an `NCName` used as a document-wide identifier.
219    Id,
220    /// XSD built-in type `xs:IDREF` — an `NCName` referencing an `ID` value.
221    Idref,
222    /// XSD built-in type `xs:IDREFS` — a whitespace-separated list of `IDREF`s.
223    Idrefs,
224    /// XSD built-in type `xs:ENTITY` — an `NCName` referencing an unparsed entity.
225    Entity,
226    /// XSD built-in type `xs:ENTITIES` — a whitespace-separated list of `ENTITY`s.
227    Entities,
228    // Numeric derived types
229    /// XSD built-in type `xs:integer` — whole numbers (no decimal point).
230    Integer,
231    /// XSD built-in type `xs:nonPositiveInteger` — integers less than or equal to zero.
232    NonPositiveInteger,
233    /// XSD built-in type `xs:negativeInteger` — integers less than zero.
234    NegativeInteger,
235    /// XSD built-in type `xs:long` — integers in the range of a signed 64-bit value.
236    Long,
237    /// XSD built-in type `xs:int` — integers in the range of a signed 32-bit value.
238    Int,
239    /// XSD built-in type `xs:short` — integers in the range of a signed 16-bit value.
240    Short,
241    /// XSD built-in type `xs:byte` — integers in the range of a signed 8-bit value.
242    Byte,
243    /// XSD built-in type `xs:nonNegativeInteger` — integers greater than or equal to zero.
244    NonNegativeInteger,
245    /// XSD built-in type `xs:unsignedLong` — integers in the range of an unsigned 64-bit value.
246    UnsignedLong,
247    /// XSD built-in type `xs:unsignedInt` — integers in the range of an unsigned 32-bit value.
248    UnsignedInt,
249    /// XSD built-in type `xs:unsignedShort` — integers in the range of an unsigned 16-bit value.
250    UnsignedShort,
251    /// XSD built-in type `xs:unsignedByte` — integers in the range of an unsigned 8-bit value.
252    UnsignedByte,
253    /// XSD built-in type `xs:positiveInteger` — integers greater than zero.
254    PositiveInteger,
255    // Facet types (used internally)
256    /// The `pattern` facet — a regular expression the value must match.
257    FacetPattern,
258    /// The `enumeration` facet — the value must equal at least one listed
259    /// literal (OR semantics).
260    FacetEnumeration,
261    /// The `minInclusive` facet — the value must be greater than or equal to the bound.
262    FacetMinInclusive,
263    /// The `maxInclusive` facet — the value must be less than or equal to the bound.
264    FacetMaxInclusive,
265    /// The `minExclusive` facet — the value must be strictly greater than the bound.
266    FacetMinExclusive,
267    /// The `maxExclusive` facet — the value must be strictly less than the bound.
268    FacetMaxExclusive,
269    /// The `minLength` facet — a minimum length in characters.
270    FacetMinLength,
271    /// The `maxLength` facet — a maximum length in characters.
272    FacetMaxLength,
273    /// The `length` facet — an exact length in characters.
274    FacetLength,
275    /// The `whiteSpace` facet — the whitespace normalization policy
276    /// (`preserve`, `replace`, or `collapse`).
277    FacetWhiteSpace,
278    /// The `fractionDigits` facet — the maximum number of digits after the
279    /// decimal point.
280    FacetFractionDigits,
281    /// The `totalDigits` facet — the maximum number of digits in the value.
282    FacetTotalDigits,
283}
284
285// ═══════════════════════════════════════════════════════════════════════════════
286// XSD Component
287// ═══════════════════════════════════════════════════════════════════════════════
288
289/// An XSD schema component declaration.
290///
291/// Represents any XSD component (element, attribute, type, model group, etc.).
292/// Components form a tree via the `children` and `attributes` vectors.
293#[derive(Debug, Clone)]
294pub struct XsdComponent {
295    /// The kind of XSD component this declaration represents.
296    pub component_type: XsdComponentType,
297    /// The component's local name (from the `name` attribute); `None` for
298    /// anonymous components.
299    pub name: Option<String>,
300    /// The namespace the component is declared in.
301    pub target_namespace: Option<String>,
302    /// Child components — model group children, inline type definitions, etc.
303    pub children: Vec<XsdComponent>,
304    /// Attribute declarations belonging to this component (complex types).
305    pub attributes: Vec<XsdComponent>,
306    /// The resolved built-in datatype kind, when the component is typed with a
307    /// built-in XSD type.
308    pub datatype: Option<XsdDatatypeKind>,
309    /// Facets constraining the type, stored as (facet kind, facet value) pairs.
310    pub facets: Vec<(XsdDatatypeKind, String)>,
311    /// The base type of a derived type (restriction/extension/list/union), or
312    /// the unresolved `type` name for elements/attributes with a named type.
313    pub base: Option<String>,
314    /// Minimum occurrence count (default 1; 0 for optional). Also reused for
315    /// the `use` attribute of attribute declarations.
316    pub min_occurs: i32,
317    /// Maximum occurrence count; `-1` for unbounded.
318    pub max_occurs: i32,
319    /// For references: the name of the referenced element/attribute (from the
320    /// `ref` attribute).
321    pub ref_name: Option<String>,
322    /// The name of the substitution group this element belongs to.
323    pub substitution_group: Option<String>,
324    /// Whether the component is declared `abstract` (cannot be used directly).
325    pub is_abstract: bool,
326    /// Whether the component is declared `final` (cannot be derived from).
327    pub is_final: bool,
328    /// The `block` attribute: derivation methods disallowed for the type.
329    pub block: Vec<String>,
330    /// Whether the type allows mixed element content (`xs:complexType mixed`).
331    pub mixed: bool,
332    /// The `form` attribute (`qualified`/`unqualified`) for this component,
333    /// overriding the schema's `elementFormDefault`/`attributeFormDefault`.
334    pub form: Option<String>,
335}
336
337impl XsdComponent {
338    /// Create a new component with the given type and default field values.
339    ///
340    /// Defaults: `min_occurs = 1`, `max_occurs = 1`, all optional fields `None`.
341    pub const fn new(component_type: XsdComponentType) -> Self {
342        Self {
343            component_type,
344            name: None,
345            target_namespace: None,
346            children: Vec::new(),
347            attributes: Vec::new(),
348            datatype: None,
349            facets: Vec::new(),
350            base: None,
351            min_occurs: 1,
352            max_occurs: 1,
353            ref_name: None,
354            substitution_group: None,
355            is_abstract: false,
356            is_final: false,
357            block: Vec::new(),
358            mixed: false,
359            form: None,
360        }
361    }
362}
363
364// ═══════════════════════════════════════════════════════════════════════════════
365// XSD Schema
366// ═══════════════════════════════════════════════════════════════════════════════
367
368/// A compiled XSD schema.
369///
370/// Holds the top-level component declarations and schema-level settings.
371#[derive(Debug, Clone)]
372pub struct XsdSchema {
373    /// Top-level component declarations of the schema.
374    pub components: Vec<XsdComponent>,
375    /// The `targetNamespace` attribute of the schema.
376    pub target_namespace: Option<String>,
377    /// The `elementFormDefault` attribute (`qualified`/`unqualified`).
378    pub element_form_default: Option<String>,
379    /// The `attributeFormDefault` attribute (`qualified`/`unqualified`).
380    pub attribute_form_default: Option<String>,
381    /// Errors collected while parsing or validating against this schema.
382    pub errors: Vec<String>,
383}
384
385impl XsdSchema {
386    /// Create an empty schema with no components, namespace, or errors.
387    pub const fn new() -> Self {
388        Self {
389            components: Vec::new(),
390            target_namespace: None,
391            element_form_default: None,
392            attribute_form_default: None,
393            errors: Vec::new(),
394        }
395    }
396}
397
398impl Default for XsdSchema {
399    fn default() -> Self {
400        Self::new()
401    }
402}
403
404// ═══════════════════════════════════════════════════════════════════════════════
405// XSD Validation Context
406// ═══════════════════════════════════════════════════════════════════════════════
407
408/// Validation context for XSD schema validation.
409///
410/// Tracks errors and state during validation of an XML document against
411/// a schema. Mirrors libxml2's `xmlSchemaValidCtxt`.
412#[derive(Debug)]
413pub struct XsdValidCtxt {
414    /// The schema being validated against.
415    pub schema: Option<XsdSchema>,
416    /// Error messages collected during validation.
417    pub errors: Vec<String>,
418    /// The total number of validation errors recorded.
419    pub nb_errors: i32,
420}
421
422impl XsdValidCtxt {
423    /// Create a new validation context with no schema bound and no errors.
424    pub const fn new() -> Self {
425        Self {
426            schema: None,
427            errors: Vec::new(),
428            nb_errors: 0,
429        }
430    }
431}
432
433impl Default for XsdValidCtxt {
434    fn default() -> Self {
435        Self::new()
436    }
437}
438
439// ═══════════════════════════════════════════════════════════════════════════════
440// Internal helpers for schema parsing
441// ═══════════════════════════════════════════════════════════════════════════════
442
443/// Get the text content of an xmlNode (recursively collects text children).
444///
445/// # SAFETY
446///
447/// - `node` must be a valid pointer to an _xmlNode or NULL.
448unsafe fn get_node_text(node: *mut _xmlNode) -> String {
449    if node.is_null() {
450        return String::new();
451    }
452    let mut result = String::new();
453    unsafe {
454        let mut child = (*node).children;
455        while !child.is_null() {
456            if ((*child).type_ == XML_TEXT_NODE as c_int
457                || (*child).type_ == XML_CDATA_SECTION_NODE as c_int)
458                && !(*child).content.is_null()
459            {
460                let content = (*child).content;
461                let mut len = 0;
462                while *content.add(len) != 0 {
463                    len += 1;
464                }
465                let slice = std::slice::from_raw_parts(content, len);
466                result.push_str(&String::from_utf8_lossy(slice));
467            }
468            child = (*child).next;
469        }
470    }
471    result
472}
473
474/// Get an attribute value from an xmlNode.
475///
476/// # SAFETY
477///
478/// - `node` must be a valid pointer to an _xmlNode or NULL.
479unsafe fn get_attr(node: *mut _xmlNode, name: &str) -> Option<String> {
480    if node.is_null() {
481        return None;
482    }
483    unsafe {
484        let mut prop = (*node).properties;
485        while !prop.is_null() {
486            let prop_name = (*prop).name;
487            if !prop_name.is_null() {
488                let mut len = 0;
489                while *prop_name.add(len) != 0 {
490                    len += 1;
491                }
492                let slice = std::slice::from_raw_parts(prop_name, len);
493                if let Ok(s) = std::str::from_utf8(slice) {
494                    if s == name {
495                        return Some(get_node_text(prop as *mut _xmlNode));
496                    }
497                }
498            }
499            prop = (*prop).next;
500        }
501    }
502    None
503}
504
505/// Get an attribute value as a boolean.
506///
507/// # SAFETY
508///
509/// - `node` must be a valid pointer to an _xmlNode or NULL.
510unsafe fn get_attr_bool(node: *mut _xmlNode, name: &str) -> bool {
511    unsafe {
512        match get_attr(node, name) {
513            Some(v) => v == "true" || v == "1",
514            None => false,
515        }
516    }
517}
518
519/// Get an attribute value as an integer with a default.
520///
521/// # SAFETY
522///
523/// - `node` must be a valid pointer to an _xmlNode or NULL.
524#[allow(dead_code)]
525unsafe fn get_attr_int(node: *mut _xmlNode, name: &str, default: i32) -> i32 {
526    unsafe {
527        match get_attr(node, name) {
528            Some(v) => v.parse::<i32>().unwrap_or(default),
529            None => default,
530        }
531    }
532}
533
534/// Get an attribute value as an unbounded integer (-1 for "unbounded").
535///
536/// # SAFETY
537///
538/// - `node` must be a valid pointer to an _xmlNode or NULL.
539unsafe fn get_attr_occurs(node: *mut _xmlNode, name: &str, default: i32) -> i32 {
540    unsafe {
541        match get_attr(node, name) {
542            Some(v) => {
543                if v == "unbounded" {
544                    -1
545                } else {
546                    v.parse::<i32>().unwrap_or(default)
547                }
548            }
549            None => default,
550        }
551    }
552}
553
554/// Check if an xmlNode is an element with a given local name.
555///
556/// # SAFETY
557///
558/// - `node` must be a valid pointer to an _xmlNode or NULL.
559unsafe fn node_is(node: *mut _xmlNode, local_name: &str) -> bool {
560    if node.is_null() {
561        return false;
562    }
563    unsafe {
564        let name = (*node).name;
565        if name.is_null() {
566            return false;
567        }
568        let mut len = 0;
569        while *name.add(len) != 0 {
570            len += 1;
571        }
572        let slice = std::slice::from_raw_parts(name, len);
573        if let Ok(s) = std::str::from_utf8(slice) {
574            // Strip namespace prefix if present
575            let local = if let Some(pos) = s.find(':') {
576                &s[pos + 1..]
577            } else {
578                s
579            };
580            return local == local_name;
581        }
582    }
583    false
584}
585
586/// Parse a datatype kind from a QName string (e.g., "xs:string", "string").
587fn parse_datatype_kind(name: &str) -> Option<XsdDatatypeKind> {
588    // Strip XML Schema namespace prefix if present
589    let local = if let Some(pos) = name.find(':') {
590        &name[pos + 1..]
591    } else {
592        name
593    };
594
595    match local {
596        "string" => Some(XsdDatatypeKind::String),
597        "boolean" => Some(XsdDatatypeKind::Boolean),
598        "decimal" => Some(XsdDatatypeKind::Decimal),
599        "float" => Some(XsdDatatypeKind::Float),
600        "double" => Some(XsdDatatypeKind::Double),
601        "duration" => Some(XsdDatatypeKind::Duration),
602        "dateTime" => Some(XsdDatatypeKind::DateTime),
603        "time" => Some(XsdDatatypeKind::Time),
604        "date" => Some(XsdDatatypeKind::Date),
605        "gYearMonth" => Some(XsdDatatypeKind::GYearMonth),
606        "gYear" => Some(XsdDatatypeKind::GYear),
607        "gMonthDay" => Some(XsdDatatypeKind::GMonthDay),
608        "gDay" => Some(XsdDatatypeKind::GDay),
609        "gMonth" => Some(XsdDatatypeKind::GMonth),
610        "hexBinary" => Some(XsdDatatypeKind::HexBinary),
611        "base64Binary" => Some(XsdDatatypeKind::Base64Binary),
612        "anyURI" => Some(XsdDatatypeKind::AnyURI),
613        "QName" => Some(XsdDatatypeKind::QName),
614        "NOTATION" => Some(XsdDatatypeKind::Notation),
615        "normalizedString" => Some(XsdDatatypeKind::NormalizedString),
616        "token" => Some(XsdDatatypeKind::Token),
617        "language" => Some(XsdDatatypeKind::Language),
618        "NMTOKEN" => Some(XsdDatatypeKind::Nmtoken),
619        "NMTOKENS" => Some(XsdDatatypeKind::Nmtokens),
620        "Name" => Some(XsdDatatypeKind::Name),
621        "NCName" => Some(XsdDatatypeKind::NCName),
622        "ID" => Some(XsdDatatypeKind::Id),
623        "IDREF" => Some(XsdDatatypeKind::Idref),
624        "IDREFS" => Some(XsdDatatypeKind::Idrefs),
625        "ENTITY" => Some(XsdDatatypeKind::Entity),
626        "ENTITIES" => Some(XsdDatatypeKind::Entities),
627        "integer" => Some(XsdDatatypeKind::Integer),
628        "nonPositiveInteger" => Some(XsdDatatypeKind::NonPositiveInteger),
629        "negativeInteger" => Some(XsdDatatypeKind::NegativeInteger),
630        "long" => Some(XsdDatatypeKind::Long),
631        "int" => Some(XsdDatatypeKind::Int),
632        "short" => Some(XsdDatatypeKind::Short),
633        "byte" => Some(XsdDatatypeKind::Byte),
634        "nonNegativeInteger" => Some(XsdDatatypeKind::NonNegativeInteger),
635        "unsignedLong" => Some(XsdDatatypeKind::UnsignedLong),
636        "unsignedInt" => Some(XsdDatatypeKind::UnsignedInt),
637        "unsignedShort" => Some(XsdDatatypeKind::UnsignedShort),
638        "unsignedByte" => Some(XsdDatatypeKind::UnsignedByte),
639        "positiveInteger" => Some(XsdDatatypeKind::PositiveInteger),
640        _ => None,
641    }
642}
643
644/// Parse a facet kind from an XSD element name.
645fn parse_facet_kind(name: &str) -> Option<XsdDatatypeKind> {
646    match name {
647        "pattern" => Some(XsdDatatypeKind::FacetPattern),
648        "enumeration" => Some(XsdDatatypeKind::FacetEnumeration),
649        "minInclusive" => Some(XsdDatatypeKind::FacetMinInclusive),
650        "maxInclusive" => Some(XsdDatatypeKind::FacetMaxInclusive),
651        "minExclusive" => Some(XsdDatatypeKind::FacetMinExclusive),
652        "maxExclusive" => Some(XsdDatatypeKind::FacetMaxExclusive),
653        "minLength" => Some(XsdDatatypeKind::FacetMinLength),
654        "maxLength" => Some(XsdDatatypeKind::FacetMaxLength),
655        "length" => Some(XsdDatatypeKind::FacetLength),
656        "whiteSpace" => Some(XsdDatatypeKind::FacetWhiteSpace),
657        "fractionDigits" => Some(XsdDatatypeKind::FacetFractionDigits),
658        "totalDigits" => Some(XsdDatatypeKind::FacetTotalDigits),
659        _ => None,
660    }
661}
662
663// ═══════════════════════════════════════════════════════════════════════════════
664// Schema Parsing
665// ═══════════════════════════════════════════════════════════════════════════════
666
667/// Parse an XSD schema from an XML string.
668///
669/// # UPSTREAM-PARITY
670///
671/// Equivalent to `xmlSchemaParse` in libxml2.
672///
673/// Returns the parsed schema, or an error message on failure.
674///
675/// # Safety
676///
677/// - `xml_doc` must be a valid string readable for `xml_doc.len()` bytes;
678///   `doc_ptr` is non-NULL (checked) and owned by this function, which
679///   frees it with `xmlFreeDoc` exactly once after parsing; the schema
680///   document must not be mutated by other threads during the call.
681pub fn xsd_parse(xml_doc: &str) -> Result<XsdSchema, String> {
682    // Use the XML parser to parse the schema document
683    let doc_ptr = unsafe {
684        crate::abi::exports_xml2::xmlReadMemory(
685            xml_doc.as_ptr() as *const c_char,
686            xml_doc.len() as c_int,
687            c"schema.xsd".as_ptr() as *const c_char,
688            ptr::null(),
689            0,
690        )
691    };
692
693    if doc_ptr.is_null() {
694        return Err("Failed to parse schema XML document".to_string());
695    }
696
697    let result = unsafe { xsd_parse_schema_doc(doc_ptr) };
698    unsafe {
699        crate::abi::exports_xml2::xmlFreeDoc(doc_ptr);
700    }
701    result
702}
703
704/// Parse an XSD schema from a parsed XML document.
705///
706/// # SAFETY
707///
708/// - `doc` must be a valid pointer to an _xmlDoc representing an XSD schema.
709unsafe fn xsd_parse_schema_doc(doc: *mut _xmlDoc) -> Result<XsdSchema, String> {
710    unsafe {
711        let root = (*doc).children;
712        if root.is_null() {
713            return Err("Schema document has no root element".to_string());
714        }
715
716        // Find the root <schema> element
717        let mut schema_node = root;
718        while !schema_node.is_null() && !node_is(schema_node, "schema") {
719            schema_node = (*schema_node).next;
720        }
721
722        if schema_node.is_null() {
723            return Err("Schema document root is not <schema>".to_string());
724        }
725
726        Ok(xsd_parse_schema_node(schema_node))
727    }
728}
729
730/// Parse a <schema> element.
731///
732/// # SAFETY
733///
734/// - `node` must be a valid pointer to a <schema> element node.
735unsafe fn xsd_parse_schema_node(node: *mut _xmlNode) -> XsdSchema {
736    unsafe {
737        let mut schema = XsdSchema::new();
738        schema.target_namespace = get_attr(node, "targetNamespace");
739        schema.element_form_default = get_attr(node, "elementFormDefault");
740        schema.attribute_form_default = get_attr(node, "attributeFormDefault");
741
742        // Parse child components
743        let mut child = (*node).children;
744        while !child.is_null() {
745            if (*child).type_ == XML_ELEMENT_NODE as c_int {
746                let comp = xsd_parse_component(child, &schema);
747                if comp.component_type != XsdComponentType::Annotation {
748                    schema.components.push(comp);
749                }
750            }
751            child = (*child).next;
752        }
753
754        schema
755    }
756}
757
758/// Parse a single XSD component from an element node.
759///
760/// # SAFETY
761///
762/// - `node` must be a valid pointer to an XML element node.
763unsafe fn xsd_parse_component(node: *mut _xmlNode, schema: &XsdSchema) -> XsdComponent {
764    unsafe {
765        // Determine the component type from the element name
766        let name_str = if !(*node).name.is_null() {
767            let mut len = 0;
768            while *(*node).name.add(len) != 0 {
769                len += 1;
770            }
771            let slice = std::slice::from_raw_parts((*node).name, len);
772            if let Ok(s) = std::str::from_utf8(slice) {
773                if let Some(pos) = s.find(':') {
774                    s[pos + 1..].to_string()
775                } else {
776                    s.to_string()
777                }
778            } else {
779                String::new()
780            }
781        } else {
782            String::new()
783        };
784
785        match name_str.as_str() {
786            "element" => xsd_parse_element(node, schema),
787            "attribute" => xsd_parse_attribute_node(node, schema),
788            "complexType" => xsd_parse_complex_type(node, schema),
789            "simpleType" => xsd_parse_simple_type(node, schema),
790            "sequence" => xsd_parse_model_group(node, XsdComponentType::Sequence, schema),
791            "choice" => xsd_parse_model_group(node, XsdComponentType::Choice, schema),
792            "all" => xsd_parse_model_group(node, XsdComponentType::All, schema),
793            "restriction" => xsd_parse_restriction(node, schema),
794            "extension" => xsd_parse_extension(node, schema),
795            "list" => xsd_parse_list(node, schema),
796            "union" => xsd_parse_union(node, schema),
797            "annotation" => xsd_parse_annotation(node),
798            "any" => xsd_parse_any(node, schema),
799            "anyAttribute" => XsdComponent::new(XsdComponentType::AnyAttribute),
800            "group" => xsd_parse_group(node, schema),
801            "attributeGroup" => xsd_parse_attribute_group(node, schema),
802            "unique" => xsd_parse_identity_constraint(node, XsdComponentType::Unique, schema),
803            "key" => xsd_parse_identity_constraint(node, XsdComponentType::Key, schema),
804            "keyref" => xsd_parse_identity_constraint(node, XsdComponentType::KeyRef, schema),
805            // Facets
806            "pattern" | "enumeration" | "minInclusive" | "maxInclusive" | "minExclusive"
807            | "maxExclusive" | "minLength" | "maxLength" | "length" | "whiteSpace"
808            | "fractionDigits" | "totalDigits" => xsd_parse_facet(node),
809            // Simple content / complex content markers
810            "simpleContent" => xsd_parse_simple_content(node, schema),
811            "complexContent" => xsd_parse_complex_content(node, schema),
812            _ => {
813                // Unknown element — create a generic component
814                let mut comp = XsdComponent::new(XsdComponentType::Schema);
815                if let Ok(s) = std::str::from_utf8(std::slice::from_raw_parts((*node).name, {
816                    let mut len = 0;
817                    while *(*node).name.add(len) != 0 {
818                        len += 1;
819                    }
820                    len
821                })) {
822                    comp.name = Some(s.to_string());
823                }
824                comp
825            }
826        }
827    }
828}
829
830/// Parse an <element> declaration.
831///
832/// # SAFETY
833///
834/// - `node` must be a valid pointer to an <element> element node.
835unsafe fn xsd_parse_element(node: *mut _xmlNode, schema: &XsdSchema) -> XsdComponent {
836    unsafe {
837        let mut comp = XsdComponent::new(XsdComponentType::Element);
838        comp.name = get_attr(node, "name");
839        comp.ref_name = get_attr(node, "ref");
840        comp.min_occurs = get_attr_occurs(node, "minOccurs", 1);
841        comp.max_occurs = get_attr_occurs(node, "maxOccurs", 1);
842        comp.is_abstract = get_attr_bool(node, "abstract");
843        comp.is_final = get_attr_bool(node, "final");
844        comp.substitution_group = get_attr(node, "substitutionGroup");
845        comp.form = get_attr(node, "form");
846
847        // Resolve type attribute
848        if let Some(type_name) = get_attr(node, "type") {
849            comp.datatype = parse_datatype_kind(&type_name);
850            // If it's not a built-in type, store the type name as base
851            if comp.datatype.is_none() {
852                comp.base = Some(type_name);
853            }
854        }
855
856        // Check for default/fixed value
857        let _default = get_attr(node, "default");
858        let _fixed = get_attr(node, "fixed");
859
860        // Parse child components (inline type definitions)
861        let mut child = (*node).children;
862        while !child.is_null() {
863            if (*child).type_ == XML_ELEMENT_NODE as c_int {
864                let child_comp = xsd_parse_component(child, schema);
865                match child_comp.component_type {
866                    XsdComponentType::ComplexType | XsdComponentType::SimpleType => {
867                        // Inline type definition
868                        if let Some(ref name) = child_comp.name {
869                            comp.base = Some(name.clone());
870                        }
871                        comp.children.push(child_comp);
872                    }
873                    XsdComponentType::Annotation => {
874                        // Skip annotations
875                    }
876                    _ => {
877                        comp.children.push(child_comp);
878                    }
879                }
880            }
881            child = (*child).next;
882        }
883
884        comp
885    }
886}
887
888/// Parse an <attribute> declaration.
889///
890/// # SAFETY
891///
892/// - `node` must be a valid pointer to an <attribute> element node.
893unsafe fn xsd_parse_attribute_node(node: *mut _xmlNode, schema: &XsdSchema) -> XsdComponent {
894    unsafe {
895        let mut comp = XsdComponent::new(XsdComponentType::Attribute);
896        comp.name = get_attr(node, "name");
897        comp.ref_name = get_attr(node, "ref");
898        comp.form = get_attr(node, "form");
899
900        // Resolve type attribute
901        if let Some(type_name) = get_attr(node, "type") {
902            comp.datatype = parse_datatype_kind(&type_name);
903            if comp.datatype.is_none() {
904                comp.base = Some(type_name);
905            }
906        }
907
908        // Check for use attribute
909        let use_attr = get_attr(node, "use");
910        if let Some(ref use_val) = use_attr {
911            if use_val == "required" {
912                comp.min_occurs = 1;
913            } else if use_val == "prohibited" {
914                comp.min_occurs = 0;
915                comp.max_occurs = 0;
916            } else {
917                // optional
918                comp.min_occurs = 0;
919            }
920        } else {
921            comp.min_occurs = 0; // optional by default
922        }
923
924        let _default = get_attr(node, "default");
925        let _fixed = get_attr(node, "fixed");
926
927        // Parse child components (inline simpleType)
928        let mut child = (*node).children;
929        while !child.is_null() {
930            if (*child).type_ == XML_ELEMENT_NODE as c_int {
931                let child_comp = xsd_parse_component(child, schema);
932                if child_comp.component_type == XsdComponentType::SimpleType {
933                    if let Some(ref name) = child_comp.name {
934                        comp.base = Some(name.clone());
935                    }
936                    comp.children.push(child_comp);
937                }
938            }
939            child = (*child).next;
940        }
941
942        comp
943    }
944}
945
946/// Parse a <complexType> definition.
947///
948/// # SAFETY
949///
950/// - `node` must be a valid pointer to a <complexType> element node.
951unsafe fn xsd_parse_complex_type(node: *mut _xmlNode, schema: &XsdSchema) -> XsdComponent {
952    unsafe {
953        let mut comp = XsdComponent::new(XsdComponentType::ComplexType);
954        comp.name = get_attr(node, "name");
955        comp.mixed = get_attr_bool(node, "mixed");
956        comp.is_abstract = get_attr_bool(node, "abstract");
957        comp.is_final = get_attr_bool(node, "final");
958
959        // Parse child components
960        let mut child = (*node).children;
961        while !child.is_null() {
962            if (*child).type_ == XML_ELEMENT_NODE as c_int {
963                let child_comp = xsd_parse_component(child, schema);
964                match child_comp.component_type {
965                    XsdComponentType::SimpleContent
966                    | XsdComponentType::ComplexContent
967                    | XsdComponentType::Sequence
968                    | XsdComponentType::Choice
969                    | XsdComponentType::All
970                    | XsdComponentType::Group
971                    | XsdComponentType::Any
972                    | XsdComponentType::Annotation => {
973                        if child_comp.component_type == XsdComponentType::SimpleContent {
974                            // simpleContent may contain restriction/extension
975                            comp.children.extend(child_comp.children);
976                        } else if child_comp.component_type == XsdComponentType::ComplexContent {
977                            // complexContent may contain restriction/extension
978                            comp.children.extend(child_comp.children);
979                        } else {
980                            comp.children.push(child_comp);
981                        }
982                    }
983                    XsdComponentType::Attribute | XsdComponentType::AnyAttribute => {
984                        comp.attributes.push(child_comp);
985                    }
986                    _ => {
987                        comp.children.push(child_comp);
988                    }
989                }
990            }
991            child = (*child).next;
992        }
993
994        comp
995    }
996}
997
998/// Parse a <simpleType> definition.
999///
1000/// # SAFETY
1001///
1002/// - `node` must be a valid pointer to a <simpleType> element node.
1003unsafe fn xsd_parse_simple_type(node: *mut _xmlNode, schema: &XsdSchema) -> XsdComponent {
1004    unsafe {
1005        let mut comp = XsdComponent::new(XsdComponentType::SimpleType);
1006        comp.name = get_attr(node, "name");
1007
1008        // Parse child components (restriction, list, union)
1009        let mut child = (*node).children;
1010        while !child.is_null() {
1011            if (*child).type_ == XML_ELEMENT_NODE as c_int {
1012                let child_comp = xsd_parse_component(child, schema);
1013                match child_comp.component_type {
1014                    XsdComponentType::Restriction
1015                    | XsdComponentType::List
1016                    | XsdComponentType::Union => {
1017                        comp.datatype = child_comp.datatype;
1018                        comp.base = child_comp.base;
1019                        comp.facets = child_comp.facets;
1020                        comp.children.extend(child_comp.children);
1021                    }
1022                    _ => {}
1023                }
1024            }
1025            child = (*child).next;
1026        }
1027
1028        comp
1029    }
1030}
1031
1032/// Parse a model group (<sequence>, <choice>, <all>).
1033///
1034/// # SAFETY
1035///
1036/// - `node` must be a valid pointer to the model group element node.
1037unsafe fn xsd_parse_model_group(
1038    node: *mut _xmlNode,
1039    ctype: XsdComponentType,
1040    schema: &XsdSchema,
1041) -> XsdComponent {
1042    unsafe {
1043        let mut comp = XsdComponent::new(ctype);
1044        comp.min_occurs = get_attr_occurs(node, "minOccurs", 1);
1045        comp.max_occurs = get_attr_occurs(node, "maxOccurs", 1);
1046
1047        let mut child = (*node).children;
1048        while !child.is_null() {
1049            if (*child).type_ == XML_ELEMENT_NODE as c_int {
1050                let child_comp = xsd_parse_component(child, schema);
1051                match child_comp.component_type {
1052                    XsdComponentType::Annotation => {}
1053                    _ => {
1054                        comp.children.push(child_comp);
1055                    }
1056                }
1057            }
1058            child = (*child).next;
1059        }
1060
1061        comp
1062    }
1063}
1064
1065/// Parse a <restriction> element.
1066///
1067/// # SAFETY
1068///
1069/// - `node` must be a valid pointer to a <restriction> element node.
1070unsafe fn xsd_parse_restriction(node: *mut _xmlNode, schema: &XsdSchema) -> XsdComponent {
1071    unsafe {
1072        let mut comp = XsdComponent::new(XsdComponentType::Restriction);
1073        comp.base = get_attr(node, "base");
1074
1075        // Try to resolve the base type
1076        if let Some(ref base_name) = comp.base {
1077            comp.datatype = parse_datatype_kind(base_name);
1078        }
1079
1080        // Parse facets and child components
1081        let mut child = (*node).children;
1082        while !child.is_null() {
1083            if (*child).type_ == XML_ELEMENT_NODE as c_int {
1084                let child_comp = xsd_parse_component(child, schema);
1085                match child_comp.component_type {
1086                    XsdComponentType::Sequence
1087                    | XsdComponentType::Choice
1088                    | XsdComponentType::All
1089                    | XsdComponentType::Group
1090                    | XsdComponentType::Any
1091                    | XsdComponentType::Annotation => {
1092                        comp.children.push(child_comp);
1093                    }
1094                    XsdComponentType::Attribute | XsdComponentType::AnyAttribute => {
1095                        comp.attributes.push(child_comp);
1096                    }
1097                    XsdComponentType::SimpleType => {
1098                        // Inline simpleType
1099                        comp.children.push(child_comp);
1100                    }
1101                    _ => {
1102                        // Facet types
1103                        if let Some(facet_kind) =
1104                            parse_facet_kind(&format!("{:?}", child_comp.component_type))
1105                        {
1106                            // Extract the value attribute
1107                            if let Some(val) = get_attr(child, "value") {
1108                                comp.facets.push((facet_kind, val));
1109                            }
1110                        }
1111                        // Also try by element name
1112                        let name_str = if !(*child).name.is_null() {
1113                            let mut len = 0;
1114                            while *(*child).name.add(len) != 0 {
1115                                len += 1;
1116                            }
1117                            let slice = std::slice::from_raw_parts((*child).name, len);
1118                            std::str::from_utf8(slice)
1119                                .ok()
1120                                .map(|s| {
1121                                    if let Some(pos) = s.find(':') {
1122                                        s[pos + 1..].to_string()
1123                                    } else {
1124                                        s.to_string()
1125                                    }
1126                                })
1127                                .unwrap_or_default()
1128                        } else {
1129                            String::new()
1130                        };
1131                        if !name_str.is_empty() {
1132                            if let Some(facet_kind) = parse_facet_kind(&name_str) {
1133                                if let Some(val) = get_attr(child, "value") {
1134                                    comp.facets.push((facet_kind, val));
1135                                }
1136                            }
1137                        }
1138                    }
1139                }
1140            }
1141            child = (*child).next;
1142        }
1143
1144        comp
1145    }
1146}
1147
1148/// Parse an <extension> element.
1149///
1150/// # SAFETY
1151///
1152/// - `node` must be a valid pointer to an <extension> element node.
1153unsafe fn xsd_parse_extension(node: *mut _xmlNode, schema: &XsdSchema) -> XsdComponent {
1154    unsafe {
1155        let mut comp = XsdComponent::new(XsdComponentType::Extension);
1156        comp.base = get_attr(node, "base");
1157
1158        if let Some(ref base_name) = comp.base {
1159            comp.datatype = parse_datatype_kind(base_name);
1160        }
1161
1162        // Parse child components
1163        let mut child = (*node).children;
1164        while !child.is_null() {
1165            if (*child).type_ == XML_ELEMENT_NODE as c_int {
1166                let child_comp = xsd_parse_component(child, schema);
1167                match child_comp.component_type {
1168                    XsdComponentType::Sequence
1169                    | XsdComponentType::Choice
1170                    | XsdComponentType::All
1171                    | XsdComponentType::Group
1172                    | XsdComponentType::Any
1173                    | XsdComponentType::Annotation => {
1174                        comp.children.push(child_comp);
1175                    }
1176                    XsdComponentType::Attribute | XsdComponentType::AnyAttribute => {
1177                        comp.attributes.push(child_comp);
1178                    }
1179                    _ => {}
1180                }
1181            }
1182            child = (*child).next;
1183        }
1184
1185        comp
1186    }
1187}
1188
1189/// Parse a <list> element.
1190///
1191/// # SAFETY
1192///
1193/// - `node` must be a valid pointer to a <list> element node.
1194unsafe fn xsd_parse_list(node: *mut _xmlNode, schema: &XsdSchema) -> XsdComponent {
1195    unsafe {
1196        let mut comp = XsdComponent::new(XsdComponentType::List);
1197
1198        if let Some(item_type) = get_attr(node, "itemType") {
1199            comp.base = Some(item_type.clone());
1200            comp.datatype = parse_datatype_kind(&item_type);
1201        }
1202
1203        // Check for inline simpleType
1204        let mut child = (*node).children;
1205        while !child.is_null() {
1206            if (*child).type_ == XML_ELEMENT_NODE as c_int {
1207                let child_comp = xsd_parse_component(child, schema);
1208                if child_comp.component_type == XsdComponentType::SimpleType {
1209                    comp.datatype = child_comp.datatype;
1210                    comp.base = child_comp.base;
1211                    comp.facets = child_comp.facets;
1212                }
1213            }
1214            child = (*child).next;
1215        }
1216
1217        comp
1218    }
1219}
1220
1221/// Parse a <union> element.
1222///
1223/// # SAFETY
1224///
1225/// - `node` must be a valid pointer to a <union> element node.
1226unsafe fn xsd_parse_union(node: *mut _xmlNode, schema: &XsdSchema) -> XsdComponent {
1227    unsafe {
1228        let mut comp = XsdComponent::new(XsdComponentType::Union);
1229
1230        if let Some(member_types) = get_attr(node, "memberTypes") {
1231            comp.base = Some(member_types);
1232        }
1233
1234        let mut child = (*node).children;
1235        while !child.is_null() {
1236            if (*child).type_ == XML_ELEMENT_NODE as c_int {
1237                let child_comp = xsd_parse_component(child, schema);
1238                if child_comp.component_type == XsdComponentType::SimpleType {
1239                    comp.children.push(child_comp);
1240                }
1241            }
1242            child = (*child).next;
1243        }
1244
1245        comp
1246    }
1247}
1248
1249/// Parse an <annotation> element.
1250///
1251/// # SAFETY
1252///
1253/// - `node` must be a valid pointer to an <annotation> element node.
1254unsafe fn xsd_parse_annotation(node: *mut _xmlNode) -> XsdComponent {
1255    unsafe {
1256        let mut comp = XsdComponent::new(XsdComponentType::Annotation);
1257
1258        let mut child = (*node).children;
1259        while !child.is_null() {
1260            if (*child).type_ == XML_ELEMENT_NODE as c_int
1261                && (node_is(child, "documentation") || node_is(child, "appinfo"))
1262            {
1263                let text = get_node_text(child);
1264                if !text.is_empty() {
1265                    comp.facets.push((XsdDatatypeKind::String, text));
1266                }
1267            }
1268            child = (*child).next;
1269        }
1270
1271        comp
1272    }
1273}
1274
1275/// Parse an <any> element.
1276///
1277/// # SAFETY
1278///
1279/// - `node` must be a valid pointer to an <any> element node.
1280unsafe fn xsd_parse_any(node: *mut _xmlNode, _schema: &XsdSchema) -> XsdComponent {
1281    unsafe {
1282        let mut comp = XsdComponent::new(XsdComponentType::Any);
1283        comp.min_occurs = get_attr_occurs(node, "minOccurs", 1);
1284        comp.max_occurs = get_attr_occurs(node, "maxOccurs", 1);
1285
1286        let namespace_attr = get_attr(node, "namespace");
1287        if let Some(ref ns) = namespace_attr {
1288            if ns != "##any" {
1289                comp.target_namespace = Some(ns.clone());
1290            }
1291        }
1292
1293        comp
1294    }
1295}
1296
1297/// Parse a <group> element.
1298///
1299/// # SAFETY
1300///
1301/// - `node` must be a valid pointer to a <group> element node.
1302unsafe fn xsd_parse_group(node: *mut _xmlNode, schema: &XsdSchema) -> XsdComponent {
1303    unsafe {
1304        let mut comp = XsdComponent::new(XsdComponentType::Group);
1305        comp.name = get_attr(node, "name");
1306        comp.ref_name = get_attr(node, "ref");
1307        comp.min_occurs = get_attr_occurs(node, "minOccurs", 1);
1308        comp.max_occurs = get_attr_occurs(node, "maxOccurs", 1);
1309
1310        let mut child = (*node).children;
1311        while !child.is_null() {
1312            if (*child).type_ == XML_ELEMENT_NODE as c_int {
1313                let child_comp = xsd_parse_component(child, schema);
1314                match child_comp.component_type {
1315                    XsdComponentType::Annotation => {}
1316                    _ => {
1317                        comp.children.push(child_comp);
1318                    }
1319                }
1320            }
1321            child = (*child).next;
1322        }
1323
1324        comp
1325    }
1326}
1327
1328/// Parse an <attributeGroup> element.
1329///
1330/// # SAFETY
1331///
1332/// - `node` must be a valid pointer to an <attributeGroup> element node.
1333unsafe fn xsd_parse_attribute_group(node: *mut _xmlNode, schema: &XsdSchema) -> XsdComponent {
1334    unsafe {
1335        let mut comp = XsdComponent::new(XsdComponentType::AttributeGroup);
1336        comp.name = get_attr(node, "name");
1337        comp.ref_name = get_attr(node, "ref");
1338
1339        let mut child = (*node).children;
1340        while !child.is_null() {
1341            if (*child).type_ == XML_ELEMENT_NODE as c_int {
1342                let child_comp = xsd_parse_component(child, schema);
1343                match child_comp.component_type {
1344                    XsdComponentType::Attribute | XsdComponentType::AnyAttribute => {
1345                        comp.attributes.push(child_comp);
1346                    }
1347                    _ => {}
1348                }
1349            }
1350            child = (*child).next;
1351        }
1352
1353        comp
1354    }
1355}
1356
1357/// Parse a facet element (pattern, enumeration, etc.).
1358///
1359/// # SAFETY
1360///
1361/// - `node` must be a valid pointer to a facet element node.
1362unsafe fn xsd_parse_facet(node: *mut _xmlNode) -> XsdComponent {
1363    unsafe {
1364        let name_str = if !(*node).name.is_null() {
1365            let mut len = 0;
1366            while *(*node).name.add(len) != 0 {
1367                len += 1;
1368            }
1369            let slice = std::slice::from_raw_parts((*node).name, len);
1370            std::str::from_utf8(slice)
1371                .ok()
1372                .map(|s| {
1373                    if let Some(pos) = s.find(':') {
1374                        s[pos + 1..].to_string()
1375                    } else {
1376                        s.to_string()
1377                    }
1378                })
1379                .unwrap_or_default()
1380        } else {
1381            String::new()
1382        };
1383
1384        let facet_kind = parse_facet_kind(&name_str).unwrap_or(XsdDatatypeKind::String);
1385        let mut comp = XsdComponent::new(XsdComponentType::Schema);
1386        let val = get_attr(node, "value").unwrap_or_default();
1387        comp.facets.push((facet_kind, val));
1388        comp.datatype = Some(facet_kind);
1389
1390        comp
1391    }
1392}
1393
1394/// Parse a <simpleContent> element.
1395///
1396/// # SAFETY
1397///
1398/// - `node` must be a valid pointer to a <simpleContent> element node.
1399unsafe fn xsd_parse_simple_content(node: *mut _xmlNode, schema: &XsdSchema) -> XsdComponent {
1400    unsafe {
1401        let mut comp = XsdComponent::new(XsdComponentType::Schema);
1402
1403        let mut child = (*node).children;
1404        while !child.is_null() {
1405            if (*child).type_ == XML_ELEMENT_NODE as c_int {
1406                let child_comp = xsd_parse_component(child, schema);
1407                match child_comp.component_type {
1408                    XsdComponentType::Restriction | XsdComponentType::Extension => {
1409                        comp.children.push(child_comp);
1410                    }
1411                    _ => {}
1412                }
1413            }
1414            child = (*child).next;
1415        }
1416
1417        comp
1418    }
1419}
1420
1421/// Parse a <complexContent> element.
1422///
1423/// # SAFETY
1424///
1425/// - `node` must be a valid pointer to a <complexContent> element node.
1426unsafe fn xsd_parse_complex_content(node: *mut _xmlNode, schema: &XsdSchema) -> XsdComponent {
1427    unsafe {
1428        let mut comp = XsdComponent::new(XsdComponentType::Schema);
1429
1430        let mut child = (*node).children;
1431        while !child.is_null() {
1432            if (*child).type_ == XML_ELEMENT_NODE as c_int {
1433                let child_comp = xsd_parse_component(child, schema);
1434                match child_comp.component_type {
1435                    XsdComponentType::Restriction | XsdComponentType::Extension => {
1436                        comp.children.push(child_comp);
1437                    }
1438                    _ => {}
1439                }
1440            }
1441            child = (*child).next;
1442        }
1443
1444        comp
1445    }
1446}
1447
1448/// Parse an identity constraint (<unique>, <key>, <keyref>).
1449///
1450/// # SAFETY
1451///
1452/// - `node` must be a valid pointer to the identity constraint element node.
1453unsafe fn xsd_parse_identity_constraint(
1454    node: *mut _xmlNode,
1455    ctype: XsdComponentType,
1456    schema: &XsdSchema,
1457) -> XsdComponent {
1458    unsafe {
1459        let mut comp = XsdComponent::new(ctype);
1460        comp.name = get_attr(node, "name");
1461
1462        if ctype == XsdComponentType::KeyRef {
1463            comp.ref_name = get_attr(node, "refer");
1464        }
1465
1466        let mut child = (*node).children;
1467        while !child.is_null() {
1468            if (*child).type_ == XML_ELEMENT_NODE as c_int {
1469                let child_comp = xsd_parse_component(child, schema);
1470                match child_comp.component_type {
1471                    XsdComponentType::Selector | XsdComponentType::Field => {
1472                        comp.children.push(child_comp);
1473                    }
1474                    _ => {}
1475                }
1476            }
1477            child = (*child).next;
1478        }
1479
1480        comp
1481    }
1482}
1483
1484// ═══════════════════════════════════════════════════════════════════════════════
1485// Datatype Validation
1486// ═══════════════════════════════════════════════════════════════════════════════
1487
1488/// Validate a value against an XSD datatype with optional facets.
1489///
1490/// # UPSTREAM-PARITY
1491///
1492/// Equivalent to libxml2's schema type validation functions.
1493pub fn xsd_validate_datatype(
1494    kind: &XsdDatatypeKind,
1495    value: &str,
1496    facets: &[(XsdDatatypeKind, String)],
1497) -> bool {
1498    // First validate the base type
1499    if !validate_base_type(kind, value) {
1500        return false;
1501    }
1502
1503    // Then validate facets.
1504    // UPSTREAM-PARITY: Enumeration facets use OR semantics (value must match
1505    // at least one enumeration value). All other facets use AND semantics
1506    // (value must satisfy all facets).
1507    let mut has_enumeration = false;
1508    let mut enumeration_match = false;
1509
1510    for (facet_kind, facet_value) in facets {
1511        if *facet_kind == XsdDatatypeKind::FacetEnumeration {
1512            has_enumeration = true;
1513            if xsd_validate_facet(kind, value, facet_kind, facet_value) {
1514                enumeration_match = true;
1515            }
1516        } else if !xsd_validate_facet(kind, value, facet_kind, facet_value) {
1517            return false;
1518        }
1519    }
1520
1521    // If there were enumeration facets, at least one must match
1522    if has_enumeration && !enumeration_match {
1523        return false;
1524    }
1525
1526    true
1527}
1528
1529/// Validate a value against a specific facet.
1530pub fn xsd_validate_facet(
1531    _kind: &XsdDatatypeKind,
1532    value: &str,
1533    facet_kind: &XsdDatatypeKind,
1534    facet_value: &str,
1535) -> bool {
1536    match facet_kind {
1537        XsdDatatypeKind::FacetPattern => {
1538            // Simple regex matching (simplified — just check substring containment
1539            // for common patterns like [a-zA-Z]+, etc.)
1540            match facet_value {
1541                r"\d+" => value.chars().all(|c| c.is_ascii_digit()),
1542                r"\d*" => value.is_empty() || value.chars().all(|c| c.is_ascii_digit()),
1543                r"[a-zA-Z]+" => value.chars().all(|c| c.is_ascii_alphabetic()),
1544                r"[a-zA-Z]*" => value.is_empty() || value.chars().all(|c| c.is_ascii_alphabetic()),
1545                r"[a-zA-Z0-9]+" => value.chars().all(|c| c.is_ascii_alphanumeric()),
1546                r"[a-zA-Z0-9_\-]+" => value
1547                    .chars()
1548                    .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-'),
1549                r"[a-zA-Z_][a-zA-Z0-9_\-\.]*" => {
1550                    if value.is_empty() {
1551                        return false;
1552                    }
1553                    let first = value.chars().next().unwrap();
1554                    if !first.is_ascii_alphabetic() && first != '_' {
1555                        return false;
1556                    }
1557                    value
1558                        .chars()
1559                        .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '.')
1560                }
1561                r"[a-zA-Z_][\w\-\.]*" => {
1562                    if value.is_empty() {
1563                        return false;
1564                    }
1565                    let first = value.chars().next().unwrap();
1566                    if !first.is_ascii_alphabetic() && first != '_' {
1567                        return false;
1568                    }
1569                    value
1570                        .chars()
1571                        .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '.')
1572                }
1573                r"\i\c*" => {
1574                    // XML Name pattern: NameStartChar followed by NameChars
1575                    if value.is_empty() {
1576                        return false;
1577                    }
1578                    let first = value.chars().next().unwrap();
1579                    if !is_name_start_char(first) {
1580                        return false;
1581                    }
1582                    value.chars().skip(1).all(is_name_char)
1583                }
1584                r"\c+" => {
1585                    if value.is_empty() {
1586                        return false;
1587                    }
1588                    value.chars().all(is_name_char)
1589                }
1590                // Default: try basic glob-style matching
1591                _ => {
1592                    if facet_value.starts_with('^') && facet_value.ends_with('$') {
1593                        let inner = &facet_value[1..facet_value.len() - 1];
1594                        simple_glob_match(inner, value)
1595                    } else {
1596                        // Default: accept if we don't understand the pattern
1597                        true
1598                    }
1599                }
1600            }
1601        }
1602        XsdDatatypeKind::FacetEnumeration => {
1603            // Check if the value matches the enumeration literal
1604            value == facet_value
1605        }
1606        XsdDatatypeKind::FacetMinInclusive => {
1607            compare_strings(value, facet_value) != std::cmp::Ordering::Less
1608        }
1609        XsdDatatypeKind::FacetMaxInclusive => {
1610            compare_strings(value, facet_value) != std::cmp::Ordering::Greater
1611        }
1612        XsdDatatypeKind::FacetMinExclusive => {
1613            compare_strings(value, facet_value) == std::cmp::Ordering::Greater
1614        }
1615        XsdDatatypeKind::FacetMaxExclusive => {
1616            compare_strings(value, facet_value) == std::cmp::Ordering::Less
1617        }
1618        XsdDatatypeKind::FacetMinLength => {
1619            let min = facet_value.parse::<usize>().unwrap_or(0);
1620            value.chars().count() >= min
1621        }
1622        XsdDatatypeKind::FacetMaxLength => {
1623            let max = facet_value.parse::<usize>().unwrap_or(usize::MAX);
1624            value.chars().count() <= max
1625        }
1626        XsdDatatypeKind::FacetLength => {
1627            let len = facet_value.parse::<usize>().unwrap_or(0);
1628            value.chars().count() == len
1629        }
1630        XsdDatatypeKind::FacetWhiteSpace => {
1631            // whiteSpace facet: value, replace, collapse
1632            match facet_value {
1633                "replace" => {
1634                    // Any whitespace is valid (but should be tab/newline -> space)
1635                    // We just accept the value
1636                    true
1637                }
1638                "collapse" => {
1639                    // Leading/trailing whitespace collapsed, internal reduced
1640                    true
1641                }
1642                _ => true,
1643            }
1644        }
1645        XsdDatatypeKind::FacetFractionDigits | XsdDatatypeKind::FacetTotalDigits => {
1646            // Numeric precision facets — simplified: just check if it's a valid number
1647            value.parse::<f64>().is_ok()
1648        }
1649        _ => true,
1650    }
1651}
1652
1653/// Simple glob-style pattern matching for XSD pattern facets.
1654fn simple_glob_match(pattern: &str, value: &str) -> bool {
1655    let pattern_chars: Vec<char> = pattern.chars().collect();
1656    let value_chars: Vec<char> = value.chars().collect();
1657
1658    let mut pi = 0;
1659    let mut vi = 0;
1660    let mut backtrack_p = None;
1661    let mut backtrack_v = 0;
1662
1663    while vi < value_chars.len() {
1664        if pi < pattern_chars.len()
1665            && (pattern_chars[pi] == value_chars[vi] || pattern_chars[pi] == '.')
1666        {
1667            pi += 1;
1668            vi += 1;
1669        } else if pi < pattern_chars.len() && pattern_chars[pi] == '*' {
1670            backtrack_p = Some(pi);
1671            backtrack_v = vi + 1;
1672            pi += 1;
1673        } else if pi < pattern_chars.len() && pattern_chars[pi] == '+' {
1674            // '+' = one or more of the next char
1675            if pi + 1 < pattern_chars.len() && pattern_chars[pi + 1] == value_chars[vi] {
1676                pi += 1;
1677                vi += 1;
1678                // Match one or more
1679                while vi < value_chars.len() && value_chars[vi] == pattern_chars[pi] {
1680                    vi += 1;
1681                }
1682                pi += 1;
1683            } else {
1684                return false;
1685            }
1686        } else if let Some(bp) = backtrack_p {
1687            pi = bp + 1;
1688            vi = backtrack_v;
1689            backtrack_v += 1;
1690        } else {
1691            return false;
1692        }
1693    }
1694
1695    // Skip remaining * or + in pattern
1696    while pi < pattern_chars.len() && (pattern_chars[pi] == '*' || pattern_chars[pi] == '+') {
1697        if pattern_chars[pi] == '+' && vi == value_chars.len() {
1698            return false; // '+' requires at least one match
1699        }
1700        pi += 1;
1701    }
1702
1703    pi == pattern_chars.len()
1704}
1705
1706/// Check if a character is an XML NameStartChar.
1707const fn is_name_start_char(c: char) -> bool {
1708    c.is_ascii_alphabetic()
1709        || c == '_'
1710        || c == ':'
1711        || (c >= '\u{00C0}' && c <= '\u{00D6}')
1712        || (c >= '\u{00D8}' && c <= '\u{00F6}')
1713        || (c >= '\u{00F8}' && c <= '\u{02FF}')
1714        || (c >= '\u{0370}' && c <= '\u{037D}')
1715        || (c >= '\u{037F}' && c <= '\u{1FFF}')
1716        || (c >= '\u{200C}' && c <= '\u{200D}')
1717        || (c >= '\u{2070}' && c <= '\u{218F}')
1718        || (c >= '\u{2C00}' && c <= '\u{2FEF}')
1719        || (c >= '\u{3001}' && c <= '\u{D7FF}')
1720        || (c >= '\u{F900}' && c <= '\u{FDCF}')
1721        || (c >= '\u{FDF0}' && c <= '\u{FFFD}')
1722}
1723
1724/// Check if a character is an XML NameChar.
1725const fn is_name_char(c: char) -> bool {
1726    is_name_start_char(c)
1727        || c.is_ascii_digit()
1728        || c == '-'
1729        || c == '.'
1730        || c == '\u{00B7}'
1731        || (c >= '\u{0300}' && c <= '\u{036F}')
1732        || (c >= '\u{203F}' && c <= '\u{2040}')
1733}
1734
1735/// Compare two string values for facet ordering.
1736fn compare_strings(a: &str, b: &str) -> std::cmp::Ordering {
1737    // Try numeric comparison first
1738    if let (Ok(na), Ok(nb)) = (a.parse::<f64>(), b.parse::<f64>()) {
1739        return na.partial_cmp(&nb).unwrap_or(std::cmp::Ordering::Equal);
1740    }
1741    // Try integer comparison
1742    if let (Ok(na), Ok(nb)) = (a.parse::<i64>(), b.parse::<i64>()) {
1743        return na.cmp(&nb);
1744    }
1745    // Fall back to lexicographic
1746    a.cmp(b)
1747}
1748
1749/// Validate a value against the base type constraints.
1750fn validate_base_type(kind: &XsdDatatypeKind, value: &str) -> bool {
1751    match kind {
1752        XsdDatatypeKind::String => true,
1753        XsdDatatypeKind::NormalizedString => {
1754            // No tabs, newlines, or carriage returns
1755            !value.contains('\t') && !value.contains('\n') && !value.contains('\r')
1756        }
1757        XsdDatatypeKind::Token => {
1758            // No leading/trailing whitespace, no consecutive internal whitespace
1759            if value.is_empty() {
1760                return true;
1761            }
1762            if value.starts_with(' ') || value.ends_with(' ') {
1763                return false;
1764            }
1765            !value.contains("  ")
1766                && !value.contains('\t')
1767                && !value.contains('\n')
1768                && !value.contains('\r')
1769        }
1770        XsdDatatypeKind::Language => {
1771            // RFC 4646 / BCP 47: langtag = (language ["-" script] ["-" region] *("-" variant))
1772            // Simplified: [a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*
1773            if value.is_empty() {
1774                return false;
1775            }
1776            let segments: Vec<&str> = value.split('-').collect();
1777            if segments.is_empty() {
1778                return false;
1779            }
1780            // First segment must be alphabetic only
1781            if segments[0].is_empty() || !segments[0].chars().all(|c| c.is_ascii_alphabetic()) {
1782                return false;
1783            }
1784            if segments[0].len() > 8 {
1785                return false;
1786            }
1787            // Remaining segments can be alphanumeric
1788            for seg in &segments[1..] {
1789                if seg.is_empty() || seg.len() > 8 {
1790                    return false;
1791                }
1792                if !seg.chars().all(|c| c.is_ascii_alphanumeric()) {
1793                    return false;
1794                }
1795            }
1796            true
1797        }
1798        XsdDatatypeKind::Name => {
1799            if value.is_empty() {
1800                return false;
1801            }
1802            let mut chars = value.chars();
1803            let first = chars.next().unwrap();
1804            if !is_name_start_char(first) {
1805                return false;
1806            }
1807            chars.all(is_name_char)
1808        }
1809        XsdDatatypeKind::NCName
1810        | XsdDatatypeKind::Id
1811        | XsdDatatypeKind::Idref
1812        | XsdDatatypeKind::Entity => {
1813            // NCName is a Name with no colon
1814            if value.is_empty() || value.contains(':') {
1815                return false;
1816            }
1817            let mut chars = value.chars();
1818            let first = chars.next().unwrap();
1819            if !is_name_start_char(first) {
1820                return false;
1821            }
1822            chars.all(is_name_char)
1823        }
1824        XsdDatatypeKind::Boolean => {
1825            matches!(value, "true" | "false" | "1" | "0")
1826        }
1827        XsdDatatypeKind::Decimal
1828        | XsdDatatypeKind::Integer
1829        | XsdDatatypeKind::NonPositiveInteger
1830        | XsdDatatypeKind::NegativeInteger
1831        | XsdDatatypeKind::Long
1832        | XsdDatatypeKind::Int
1833        | XsdDatatypeKind::Short
1834        | XsdDatatypeKind::Byte
1835        | XsdDatatypeKind::NonNegativeInteger
1836        | XsdDatatypeKind::UnsignedLong
1837        | XsdDatatypeKind::UnsignedInt
1838        | XsdDatatypeKind::UnsignedShort
1839        | XsdDatatypeKind::UnsignedByte
1840        | XsdDatatypeKind::PositiveInteger => {
1841            // Decimal/integer validation
1842            if value.is_empty() {
1843                return false;
1844            }
1845            let mut chars = value.chars().peekable();
1846            if *chars.peek().unwrap_or(&'\0') == '-' || *chars.peek().unwrap_or(&'\0') == '+' {
1847                chars.next();
1848            }
1849            let mut has_dot = false;
1850            let mut has_digit = false;
1851            for c in chars {
1852                if c == '.' {
1853                    if has_dot {
1854                        return false;
1855                    }
1856                    has_dot = true;
1857                } else if c.is_ascii_digit() {
1858                    has_digit = true;
1859                } else {
1860                    return false;
1861                }
1862            }
1863            if !has_digit {
1864                return false;
1865            }
1866
1867            // Additional constraints for derived integer types
1868            // Integer and all derived integer types reject decimal points
1869            if has_dot && *kind != XsdDatatypeKind::Decimal {
1870                return false;
1871            }
1872
1873            match kind {
1874                XsdDatatypeKind::NonPositiveInteger => {
1875                    if let Ok(v) = value.parse::<i64>() {
1876                        v <= 0
1877                    } else {
1878                        false
1879                    }
1880                }
1881                XsdDatatypeKind::NegativeInteger => {
1882                    if let Ok(v) = value.parse::<i64>() {
1883                        v < 0
1884                    } else {
1885                        false
1886                    }
1887                }
1888                XsdDatatypeKind::NonNegativeInteger => {
1889                    if let Ok(v) = value.parse::<i64>() {
1890                        v >= 0
1891                    } else {
1892                        false
1893                    }
1894                }
1895                XsdDatatypeKind::PositiveInteger => {
1896                    if let Ok(v) = value.parse::<i64>() {
1897                        v > 0
1898                    } else {
1899                        false
1900                    }
1901                }
1902                XsdDatatypeKind::UnsignedLong
1903                | XsdDatatypeKind::UnsignedInt
1904                | XsdDatatypeKind::UnsignedShort
1905                | XsdDatatypeKind::UnsignedByte => {
1906                    if let Ok(v) = value.parse::<u64>() {
1907                        match kind {
1908                            XsdDatatypeKind::UnsignedInt => v <= u64::from(u32::MAX),
1909                            XsdDatatypeKind::UnsignedShort => v <= u64::from(u16::MAX),
1910                            XsdDatatypeKind::UnsignedByte => v <= u64::from(u8::MAX),
1911                            _ => true,
1912                        }
1913                    } else {
1914                        false
1915                    }
1916                }
1917                XsdDatatypeKind::Long => value.parse::<i64>().is_ok(),
1918                XsdDatatypeKind::Int => value.parse::<i32>().is_ok(),
1919                XsdDatatypeKind::Short => value.parse::<i16>().is_ok(),
1920                XsdDatatypeKind::Byte => value.parse::<i8>().is_ok(),
1921                _ => true,
1922            }
1923        }
1924        XsdDatatypeKind::Float | XsdDatatypeKind::Double => {
1925            // Allow INF, -INF, NaN
1926            matches!(value, "INF" | "-INF" | "NaN") || value.parse::<f64>().is_ok()
1927        }
1928        XsdDatatypeKind::Duration => {
1929            // P[nY][nM][nD][T[nH][nM][nS]]
1930            if !value.starts_with('-') && !value.starts_with('P') {
1931                return false;
1932            }
1933            let dur = value.strip_prefix('-').unwrap_or(value);
1934            if !dur.starts_with('P') {
1935                return false;
1936            }
1937            let rest = &dur[1..];
1938            if rest.is_empty() {
1939                return false;
1940            }
1941            let has_t = rest.contains('T');
1942            let _date_part = if has_t {
1943                &rest[..rest.find('T').unwrap()]
1944            } else {
1945                rest
1946            };
1947            if has_t {
1948                let time_part = &rest[rest.find('T').unwrap() + 1..];
1949                if time_part.is_empty() {
1950                    return false;
1951                }
1952            }
1953            true
1954        }
1955        XsdDatatypeKind::DateTime => {
1956            // YYYY-MM-DDThh:mm:ss[.sss][Z|±hh:mm]
1957            if value.len() < 19 {
1958                return false;
1959            }
1960            let chars: Vec<char> = value.chars().collect();
1961            chars[4] == '-'
1962                && chars[7] == '-'
1963                && chars[10] == 'T'
1964                && chars[13] == ':'
1965                && chars[16] == ':'
1966        }
1967        XsdDatatypeKind::Date => {
1968            // YYYY-MM-DD[Z|±hh:mm]
1969            if value.len() < 10 {
1970                return false;
1971            }
1972            let chars: Vec<char> = value.chars().collect();
1973            chars[4] == '-' && chars[7] == '-'
1974        }
1975        XsdDatatypeKind::Time => {
1976            // hh:mm:ss[.sss][Z|±hh:mm]
1977            if value.len() < 8 {
1978                return false;
1979            }
1980            let chars: Vec<char> = value.chars().collect();
1981            chars[2] == ':' && chars[5] == ':'
1982        }
1983        XsdDatatypeKind::GYear => {
1984            // YYYY[Z|±hh:mm]
1985            if value.len() < 4 {
1986                return false;
1987            }
1988            value.chars().take(4).all(|c| c.is_ascii_digit())
1989        }
1990        XsdDatatypeKind::GYearMonth => {
1991            // YYYY-MM[Z|±hh:mm]
1992            if value.len() < 7 {
1993                return false;
1994            }
1995            let chars: Vec<char> = value.chars().collect();
1996            chars[4] == '-'
1997        }
1998        XsdDatatypeKind::GMonthDay => {
1999            // --MM-DD[Z|±hh:mm]
2000            if value.len() < 6 || !value.starts_with("--") {
2001                return false;
2002            }
2003            let chars: Vec<char> = value.chars().collect();
2004            chars[4] == '-'
2005        }
2006        XsdDatatypeKind::GDay => {
2007            // ---DD[Z|±hh:mm]
2008            value.starts_with("---") && value.len() >= 4
2009        }
2010        XsdDatatypeKind::GMonth => {
2011            // --MM[Z|±hh:mm]
2012            value.starts_with("--") && value.len() >= 3
2013        }
2014        XsdDatatypeKind::HexBinary => {
2015            if !value.len().is_multiple_of(2) {
2016                return false;
2017            }
2018            value.chars().all(|c| c.is_ascii_hexdigit())
2019        }
2020        XsdDatatypeKind::Base64Binary => {
2021            // Simplified: just check characters are valid base64
2022            if value.is_empty() {
2023                return true;
2024            }
2025            let valid_chars =
2026                |c: char| c.is_ascii_alphanumeric() || c == '+' || c == '/' || c == '=';
2027            value.chars().all(valid_chars)
2028        }
2029        XsdDatatypeKind::AnyURI => {
2030            // Simplified: just check it's not empty
2031            !value.is_empty()
2032        }
2033        XsdDatatypeKind::QName => {
2034            // NCName or Prefix:NCName
2035            if let Some(pos) = value.find(':') {
2036                let prefix = &value[..pos];
2037                let local = &value[pos + 1..];
2038                is_ncname(prefix) && is_ncname(local)
2039            } else {
2040                is_ncname(value)
2041            }
2042        }
2043        XsdDatatypeKind::Notation => {
2044            // Same as QName
2045            !value.is_empty()
2046        }
2047        XsdDatatypeKind::Nmtoken => !value.is_empty() && value.chars().all(is_name_char),
2048        XsdDatatypeKind::Nmtokens => {
2049            !value.is_empty()
2050                && value
2051                    .split_whitespace()
2052                    .all(|t| !t.is_empty() && t.chars().all(is_name_char))
2053        }
2054        XsdDatatypeKind::Idrefs | XsdDatatypeKind::Entities => {
2055            !value.is_empty() && value.split_whitespace().all(is_ncname)
2056        }
2057        // Facet types are always "valid" as values
2058        XsdDatatypeKind::FacetPattern
2059        | XsdDatatypeKind::FacetEnumeration
2060        | XsdDatatypeKind::FacetMinInclusive
2061        | XsdDatatypeKind::FacetMaxInclusive
2062        | XsdDatatypeKind::FacetMinExclusive
2063        | XsdDatatypeKind::FacetMaxExclusive
2064        | XsdDatatypeKind::FacetMinLength
2065        | XsdDatatypeKind::FacetMaxLength
2066        | XsdDatatypeKind::FacetLength
2067        | XsdDatatypeKind::FacetWhiteSpace
2068        | XsdDatatypeKind::FacetFractionDigits
2069        | XsdDatatypeKind::FacetTotalDigits => true,
2070    }
2071}
2072
2073/// Check if a string is a valid NCName.
2074fn is_ncname(value: &str) -> bool {
2075    if value.is_empty() {
2076        return false;
2077    }
2078    let first = value.chars().next().unwrap();
2079    if !is_name_start_char(first) || first == ':' {
2080        return false;
2081    }
2082    value.chars().skip(1).all(|c| is_name_char(c) && c != ':')
2083}
2084
2085// ═══════════════════════════════════════════════════════════════════════════════
2086// Document Validation
2087// ═══════════════════════════════════════════════════════════════════════════════
2088
2089/// Validate an XML document against a schema.
2090///
2091/// # UPSTREAM-PARITY
2092///
2093/// Equivalent to libxml2's `xmlSchemaValidateDoc`.
2094///
2095/// # Safety
2096///
2097/// - `doc` must be a valid string readable for `doc.len()` bytes;
2098///   `doc_ptr` is non-NULL (checked) and owned by this function, which
2099///   frees it with `xmlFreeDoc` exactly once; the parsed document must not
2100///   be mutated by other threads while `xsd_validate_doc` walks it.
2101pub fn xsd_validate(schema: &XsdSchema, doc: &str) -> Result<(), Vec<String>> {
2102    let doc_ptr = unsafe {
2103        crate::abi::exports_xml2::xmlReadMemory(
2104            doc.as_ptr() as *const c_char,
2105            doc.len() as c_int,
2106            c"doc.xml".as_ptr() as *const c_char,
2107            ptr::null(),
2108            0,
2109        )
2110    };
2111
2112    if doc_ptr.is_null() {
2113        return Err(vec!["Failed to parse XML document".to_string()]);
2114    }
2115
2116    let mut ctxt = XsdValidCtxt::new();
2117    ctxt.schema = Some(schema.clone());
2118
2119    let result = unsafe { xsd_validate_doc(schema, doc_ptr, &mut ctxt) };
2120
2121    unsafe {
2122        crate::abi::exports_xml2::xmlFreeDoc(doc_ptr);
2123    }
2124
2125    if result {
2126        Ok(())
2127    } else {
2128        Err(ctxt.errors)
2129    }
2130}
2131
2132/// Validate a parsed document against a schema.
2133///
2134/// # SAFETY
2135///
2136/// - `doc` must be a valid pointer to an _xmlDoc.
2137unsafe fn xsd_validate_doc(schema: &XsdSchema, doc: *mut _xmlDoc, ctxt: &mut XsdValidCtxt) -> bool {
2138    unsafe {
2139        let root = (*doc).children;
2140        if root.is_null() {
2141            ctxt.errors.push("Document has no root element".to_string());
2142            ctxt.nb_errors += 1;
2143            return false;
2144        }
2145
2146        // Find the root element
2147        let mut root_elem = root;
2148        while !root_elem.is_null() && (*root_elem).type_ != XML_ELEMENT_NODE as c_int {
2149            root_elem = (*root_elem).next;
2150        }
2151
2152        if root_elem.is_null() {
2153            ctxt.errors.push("Document has no root element".to_string());
2154            ctxt.nb_errors += 1;
2155            return false;
2156        }
2157
2158        // Get the root element name
2159        let root_name = get_node_qname(root_elem);
2160
2161        // Find matching global element declaration
2162        let global_elem = schema.components.iter().find(|c| {
2163            c.component_type == XsdComponentType::Element && c.name.as_deref() == Some(&root_name)
2164        });
2165
2166        if let Some(global) = global_elem {
2167            xsd_validate_element(global, root_elem, schema, ctxt)
2168        } else {
2169            // Try finding by any matching component
2170            let mut valid = true;
2171            for component in &schema.components {
2172                if component.component_type == XsdComponentType::Element {
2173                    if let Some(ref name) = component.name {
2174                        if *name == root_name {
2175                            valid = xsd_validate_element(component, root_elem, schema, ctxt);
2176                            break;
2177                        }
2178                    }
2179                }
2180            }
2181            valid
2182        }
2183    }
2184}
2185
2186/// Validate an element node against a component declaration.
2187///
2188/// # SAFETY
2189///
2190/// - `node` must be a valid pointer to an XML element node.
2191fn xsd_validate_element(
2192    component: &XsdComponent,
2193    node: *mut _xmlNode,
2194    schema: &XsdSchema,
2195    ctxt: &mut XsdValidCtxt,
2196) -> bool {
2197    unsafe {
2198        let mut valid = true;
2199
2200        // Get the element's local name
2201        let node_name = get_node_qname(node);
2202
2203        // Check element name
2204        if let Some(ref comp_name) = component.name {
2205            if comp_name != &node_name {
2206                ctxt.errors.push(format!(
2207                    "Element '{}' does not match expected '{}'",
2208                    node_name, comp_name
2209                ));
2210                ctxt.nb_errors += 1;
2211                return false;
2212            }
2213        }
2214
2215        // If there's a type definition (complexType or simpleType) among children, use it
2216        let type_comp = component.children.iter().find(|c| {
2217            c.component_type == XsdComponentType::ComplexType
2218                || c.component_type == XsdComponentType::SimpleType
2219        });
2220
2221        if let Some(tc) = type_comp {
2222            match tc.component_type {
2223                XsdComponentType::ComplexType => {
2224                    valid &= xsd_validate_complex_type(tc, node, schema, ctxt);
2225                }
2226                XsdComponentType::SimpleType => {
2227                    let text = get_node_text(node);
2228                    if let Some(ref dt) = tc.datatype {
2229                        if !xsd_validate_datatype(dt, &text, &tc.facets) {
2230                            ctxt.errors.push(format!(
2231                                "Element '{}' has invalid value '{}' for type '{:?}'",
2232                                node_name, text, dt
2233                            ));
2234                            ctxt.nb_errors += 1;
2235                            valid = false;
2236                        }
2237                    }
2238                }
2239                _ => {}
2240            }
2241        } else if let Some(ref dt) = component.datatype {
2242            // Direct datatype on the element (simple content)
2243            let text = get_node_text(node);
2244            if !xsd_validate_datatype(dt, &text, &component.facets) {
2245                ctxt.errors.push(format!(
2246                    "Element '{}' has invalid value '{}' for type '{:?}'",
2247                    node_name, text, dt
2248                ));
2249                ctxt.nb_errors += 1;
2250                valid = false;
2251            }
2252        } else {
2253            // No type information — validate children against content model
2254            valid &= xsd_validate_content(component, node, schema, ctxt);
2255        }
2256
2257        valid
2258    }
2259}
2260
2261/// Validate a complex type against an element node.
2262///
2263/// # SAFETY
2264///
2265/// - `node` must be a valid pointer to an XML element node.
2266fn xsd_validate_complex_type(
2267    component: &XsdComponent,
2268    node: *mut _xmlNode,
2269    schema: &XsdSchema,
2270    ctxt: &mut XsdValidCtxt,
2271) -> bool {
2272    {
2273        let mut valid = true;
2274
2275        // Validate attributes
2276        for attr in &component.attributes {
2277            match attr.component_type {
2278                XsdComponentType::Attribute => {
2279                    valid &= xsd_validate_attribute(attr, node, schema, ctxt);
2280                }
2281                XsdComponentType::AnyAttribute => {
2282                    // Any attribute is allowed
2283                }
2284                _ => {}
2285            }
2286        }
2287
2288        // Validate child content (sequence, choice, all)
2289        for child in &component.children {
2290            match child.component_type {
2291                XsdComponentType::Sequence | XsdComponentType::Choice | XsdComponentType::All => {
2292                    valid &= xsd_validate_model_group(child, node, schema, ctxt);
2293                }
2294                XsdComponentType::Restriction | XsdComponentType::Extension => {
2295                    // Handle restriction/extension content
2296                    valid &= xsd_validate_restriction_extension(child, node, schema, ctxt);
2297                }
2298                XsdComponentType::Any => {
2299                    // Any element is allowed
2300                }
2301                _ => {}
2302            }
2303        }
2304
2305        valid
2306    }
2307}
2308
2309/// Validate a model group (sequence, choice, all) against an element's children.
2310///
2311/// # SAFETY
2312///
2313/// - `node` must be a valid pointer to an XML element node.
2314fn xsd_validate_model_group(
2315    component: &XsdComponent,
2316    node: *mut _xmlNode,
2317    _schema: &XsdSchema,
2318    ctxt: &mut XsdValidCtxt,
2319) -> bool {
2320    unsafe {
2321        let mut valid = true;
2322
2323        // Collect element children
2324        let mut child_nodes: Vec<*mut _xmlNode> = Vec::new();
2325        let mut child = (*node).children;
2326        while !child.is_null() {
2327            if (*child).type_ == XML_ELEMENT_NODE as c_int {
2328                child_nodes.push(child);
2329            }
2330            child = (*child).next;
2331        }
2332
2333        match component.component_type {
2334            XsdComponentType::Sequence => {
2335                // Validate in-order
2336                let mut child_idx = 0;
2337                for part in &component.children {
2338                    let min = part.min_occurs;
2339                    let max = part.max_occurs;
2340                    let match_name = part.name.as_deref().unwrap_or("");
2341                    let match_ref = part.ref_name.as_deref().unwrap_or("");
2342
2343                    let mut count = 0;
2344                    while child_idx < child_nodes.len() && (max == -1 || count < max) {
2345                        let child_node = child_nodes[child_idx];
2346                        let child_name = get_node_qname(child_node);
2347
2348                        if part.component_type == XsdComponentType::Any
2349                            || (!match_name.is_empty() && child_name == match_name)
2350                            || (!match_ref.is_empty() && child_name == match_ref)
2351                        {
2352                            count += 1;
2353                            child_idx += 1;
2354                        } else if count >= min {
2355                            break;
2356                        } else {
2357                            ctxt.errors.push(format!(
2358                                "Expected element '{}' but found '{}'",
2359                                match_name, child_name
2360                            ));
2361                            ctxt.nb_errors += 1;
2362                            valid = false;
2363                            child_idx += 1;
2364                            break;
2365                        }
2366                    }
2367
2368                    if count < min {
2369                        ctxt.errors.push(format!(
2370                            "Element '{}' occurs {} times, minimum is {}",
2371                            if match_name.is_empty() {
2372                                "?"
2373                            } else {
2374                                match_name
2375                            },
2376                            count,
2377                            min
2378                        ));
2379                        ctxt.nb_errors += 1;
2380                        valid = false;
2381                    }
2382                }
2383
2384                // Check for unexpected extra children
2385                if child_idx < child_nodes.len() {
2386                    let extra = get_node_qname(child_nodes[child_idx]);
2387                    ctxt.errors
2388                        .push(format!("Unexpected element '{}' in sequence", extra));
2389                    ctxt.nb_errors += 1;
2390                    valid = false;
2391                }
2392            }
2393            XsdComponentType::Choice => {
2394                // At least one of the choices must match
2395                let mut matched = false;
2396                for child_node in &child_nodes {
2397                    let child_name = get_node_qname(*child_node);
2398                    for part in &component.children {
2399                        let match_name = part.name.as_deref().unwrap_or("");
2400                        let match_ref = part.ref_name.as_deref().unwrap_or("");
2401
2402                        if part.component_type == XsdComponentType::Any {
2403                            matched = true;
2404                        } else if (!match_name.is_empty() && child_name == match_name)
2405                            || (!match_ref.is_empty() && child_name == match_ref)
2406                        {
2407                            matched = true;
2408                            break;
2409                        }
2410                    }
2411                    if !matched {
2412                        ctxt.errors
2413                            .push(format!("Element '{}' is not valid in choice", child_name));
2414                        ctxt.nb_errors += 1;
2415                        valid = false;
2416                    }
2417                    matched = false; // Reset for next child
2418                }
2419            }
2420            XsdComponentType::All => {
2421                // All children must match in any order (maxOccurs=1)
2422                for child_node in &child_nodes {
2423                    let child_name = get_node_qname(*child_node);
2424                    let mut matched = false;
2425                    for part in &component.children {
2426                        let match_name = part.name.as_deref().unwrap_or("");
2427                        if !match_name.is_empty() && child_name == match_name {
2428                            matched = true;
2429                            break;
2430                        }
2431                    }
2432                    if !matched {
2433                        ctxt.errors.push(format!(
2434                            "Element '{}' is not valid in all group",
2435                            child_name
2436                        ));
2437                        ctxt.nb_errors += 1;
2438                        valid = false;
2439                    }
2440                }
2441            }
2442            _ => {}
2443        }
2444
2445        valid
2446    }
2447}
2448
2449/// Validate a restriction or extension content.
2450///
2451/// # SAFETY
2452///
2453/// - `node` must be a valid pointer to an XML element node.
2454fn xsd_validate_restriction_extension(
2455    component: &XsdComponent,
2456    node: *mut _xmlNode,
2457    schema: &XsdSchema,
2458    ctxt: &mut XsdValidCtxt,
2459) -> bool {
2460    unsafe {
2461        let mut valid = true;
2462
2463        // Validate attributes
2464        for attr in &component.attributes {
2465            if attr.component_type == XsdComponentType::Attribute {
2466                valid &= xsd_validate_attribute(attr, node, schema, ctxt);
2467            }
2468        }
2469
2470        // Validate child content
2471        for child in &component.children {
2472            match child.component_type {
2473                XsdComponentType::Sequence | XsdComponentType::Choice | XsdComponentType::All => {
2474                    valid &= xsd_validate_model_group(child, node, schema, ctxt);
2475                }
2476                _ => {}
2477            }
2478        }
2479
2480        // Validate datatype if present (for simple content restriction)
2481        if let Some(ref dt) = component.datatype {
2482            let text = get_node_text(node);
2483            if !xsd_validate_datatype(dt, &text, &component.facets) {
2484                let node_name = get_node_qname(node);
2485                ctxt.errors.push(format!(
2486                    "Element '{}' has invalid value '{}' for type '{:?}'",
2487                    node_name, text, dt
2488                ));
2489                ctxt.nb_errors += 1;
2490                valid = false;
2491            }
2492        }
2493
2494        valid
2495    }
2496}
2497
2498/// Validate an attribute against an element node.
2499///
2500/// # SAFETY
2501///
2502/// - `node` must be a valid pointer to an XML element node.
2503fn xsd_validate_attribute(
2504    component: &XsdComponent,
2505    node: *mut _xmlNode,
2506    _schema: &XsdSchema,
2507    ctxt: &mut XsdValidCtxt,
2508) -> bool {
2509    unsafe {
2510        let attr_name = component.name.as_deref().unwrap_or("");
2511        if attr_name.is_empty() {
2512            return true;
2513        }
2514
2515        // Check if the attribute exists on the element
2516        let attr_value = get_attr(node, attr_name);
2517
2518        let is_required = component.min_occurs > 0;
2519
2520        match attr_value {
2521            Some(ref val) => {
2522                // Validate attribute value against its datatype
2523                if let Some(ref dt) = component.datatype {
2524                    if !xsd_validate_datatype(dt, val, &component.facets) {
2525                        ctxt.errors.push(format!(
2526                            "Attribute '{}' has invalid value '{}' for type '{:?}'",
2527                            attr_name, val, dt
2528                        ));
2529                        ctxt.nb_errors += 1;
2530                        return false;
2531                    }
2532                }
2533                true
2534            }
2535            None => {
2536                if is_required {
2537                    ctxt.errors
2538                        .push(format!("Required attribute '{}' is missing", attr_name));
2539                    ctxt.nb_errors += 1;
2540                    false
2541                } else {
2542                    true
2543                }
2544            }
2545        }
2546    }
2547}
2548
2549/// Validate child content (no explicit type — just check children).
2550///
2551/// # SAFETY
2552///
2553/// - `node` must be a valid pointer to an XML element node.
2554fn xsd_validate_content(
2555    component: &XsdComponent,
2556    node: *mut _xmlNode,
2557    schema: &XsdSchema,
2558    ctxt: &mut XsdValidCtxt,
2559) -> bool {
2560    {
2561        let mut valid = true;
2562
2563        for child_comp in &component.children {
2564            match child_comp.component_type {
2565                XsdComponentType::Sequence | XsdComponentType::Choice | XsdComponentType::All => {
2566                    valid &= xsd_validate_model_group(child_comp, node, schema, ctxt);
2567                }
2568                XsdComponentType::Element => {
2569                    // Inline element declaration in a model group
2570                    valid &= xsd_validate_element_inline(child_comp, node, schema, ctxt);
2571                }
2572                _ => {}
2573            }
2574        }
2575
2576        valid
2577    }
2578}
2579
2580/// Validate an inline element declaration (element inside sequence/choice).
2581///
2582/// # SAFETY
2583///
2584/// - `node` must be a valid pointer to an XML element node.
2585fn xsd_validate_element_inline(
2586    component: &XsdComponent,
2587    node: *mut _xmlNode,
2588    schema: &XsdSchema,
2589    ctxt: &mut XsdValidCtxt,
2590) -> bool {
2591    unsafe {
2592        let mut valid = true;
2593        let mut child = (*node).children;
2594
2595        while !child.is_null() {
2596            if (*child).type_ == XML_ELEMENT_NODE as c_int {
2597                let child_name = get_node_qname(child);
2598
2599                let match_name = component.name.as_deref().unwrap_or("");
2600                let match_ref = component.ref_name.as_deref().unwrap_or("");
2601
2602                if (!match_name.is_empty() && child_name == match_name)
2603                    || (!match_ref.is_empty() && child_name == match_ref)
2604                {
2605                    // Check inline type
2606                    let type_comp = component.children.iter().find(|c| {
2607                        c.component_type == XsdComponentType::ComplexType
2608                            || c.component_type == XsdComponentType::SimpleType
2609                    });
2610
2611                    if let Some(tc) = type_comp {
2612                        match tc.component_type {
2613                            XsdComponentType::ComplexType => {
2614                                valid &= xsd_validate_complex_type(tc, child, schema, ctxt);
2615                            }
2616                            XsdComponentType::SimpleType => {
2617                                let text = get_node_text(child);
2618                                if let Some(ref dt) = tc.datatype {
2619                                    if !xsd_validate_datatype(dt, &text, &tc.facets) {
2620                                        ctxt.errors.push(format!(
2621                                            "Element '{}' has invalid value '{}'",
2622                                            child_name, text
2623                                        ));
2624                                        ctxt.nb_errors += 1;
2625                                        valid = false;
2626                                    }
2627                                }
2628                            }
2629                            _ => {}
2630                        }
2631                    }
2632                }
2633            }
2634            child = (*child).next;
2635        }
2636
2637        valid
2638    }
2639}
2640
2641/// Get the qualified name of a node (with namespace prefix if available).
2642///
2643/// # SAFETY
2644///
2645/// - `node` must be a valid pointer to an _xmlNode or NULL.
2646unsafe fn get_node_qname(node: *mut _xmlNode) -> String {
2647    if node.is_null() {
2648        return String::new();
2649    }
2650    unsafe {
2651        // Check for namespace prefix
2652        let ns = (*node).ns;
2653        let prefix = if !ns.is_null() && !(*ns).prefix.is_null() {
2654            let mut len = 0;
2655            while *(*ns).prefix.add(len) != 0 {
2656                len += 1;
2657            }
2658            let slice = std::slice::from_raw_parts((*ns).prefix, len);
2659            if let Ok(s) = std::str::from_utf8(slice) {
2660                format!("{}:", s)
2661            } else {
2662                String::new()
2663            }
2664        } else {
2665            String::new()
2666        };
2667
2668        let name = (*node).name;
2669        if name.is_null() {
2670            return String::new();
2671        }
2672        let mut len = 0;
2673        while *name.add(len) != 0 {
2674            len += 1;
2675        }
2676        let slice = std::slice::from_raw_parts(name, len);
2677        if let Ok(s) = std::str::from_utf8(slice) {
2678            format!("{}{}", prefix, s)
2679        } else {
2680            String::new()
2681        }
2682    }
2683}
2684
2685// ═══════════════════════════════════════════════════════════════════════════════
2686// C ABI Functions
2687// ═══════════════════════════════════════════════════════════════════════════════
2688
2689// These are the C-compatible entry points that get exported via the ABI layer.
2690// They use raw pointers and follow libxml2's calling conventions.
2691
2692/// Create a new schema parser context from a URL.
2693///
2694/// # UPSTREAM-PARITY
2695///
2696/// ```c
2697/// xmlSchemaParserCtxtPtr xmlSchemaNewParserCtxt(const char *URL);
2698/// ```
2699///
2700/// # SAFETY
2701///
2702/// - `url` must be a valid null-terminated C string or NULL.
2703#[no_mangle]
2704pub unsafe extern "C" fn xmlSchemaNewParserCtxt(url: *const c_char) -> *mut c_void {
2705    if url.is_null() {
2706        // Return a simple empty context
2707        let ctxt = allocator::xmlMallocZero(size_of::<XsdSchema>() as usize);
2708        return ctxt;
2709    }
2710
2711    // Read the URL
2712    let url_str = unsafe {
2713        if url.is_null() {
2714            String::new()
2715        } else {
2716            let mut len = 0;
2717            while *url.add(len) != 0 {
2718                len += 1;
2719            }
2720            let slice = std::slice::from_raw_parts(url as *const u8, len);
2721            String::from_utf8_lossy(slice).to_string()
2722        }
2723    };
2724
2725    // Try to parse the schema from the URL
2726    // For now, return a placeholder context
2727    let ctxt = allocator::xmlMallocZero(size_of::<XsdSchema>() as usize);
2728    // In a full implementation, this would read the file and parse it
2729    if !url_str.is_empty() {
2730        // Store the URL for later parsing
2731        let _ = url_str;
2732    }
2733
2734    ctxt
2735}
2736
2737/// Create a new schema parser context from a memory buffer.
2738///
2739/// # UPSTREAM-PARITY
2740///
2741/// ```c
2742/// xmlSchemaParserCtxtPtr xmlSchemaNewMemParserCtxt(const char *buffer, int size);
2743/// ```
2744///
2745/// # SAFETY
2746///
2747/// - `buffer` must be a valid pointer to a buffer of at least `size` bytes.
2748#[no_mangle]
2749pub unsafe extern "C" fn xmlSchemaNewMemParserCtxt(
2750    buffer: *const c_char,
2751    size: c_int,
2752) -> *mut c_void {
2753    if buffer.is_null() || size <= 0 {
2754        return ptr::null_mut();
2755    }
2756
2757    // Parse the schema immediately
2758    let buf_slice = unsafe { std::slice::from_raw_parts(buffer as *const u8, size as usize) };
2759    let xml_str = String::from_utf8_lossy(buf_slice).to_string();
2760
2761    match xsd_parse(&xml_str) {
2762        Ok(schema) => {
2763            // Allocate and store the schema
2764            let schema_box = Box::new(schema);
2765            Box::into_raw(schema_box) as *mut c_void
2766        }
2767        Err(_) => ptr::null_mut(),
2768    }
2769}
2770
2771/// Parse a schema.
2772///
2773/// # UPSTREAM-PARITY
2774///
2775/// ```c
2776/// xmlSchemaPtr xmlSchemaParse(xmlSchemaParserCtxtPtr ctxt);
2777/// ```
2778///
2779/// # SAFETY
2780///
2781/// - `ctxt` must be a valid pointer to a parser context, or NULL.
2782#[no_mangle]
2783pub const unsafe extern "C" fn xmlSchemaParse(ctxt: *mut c_void) -> *mut c_void {
2784    if ctxt.is_null() {
2785        return ptr::null_mut();
2786    }
2787
2788    // If the context already contains a parsed schema (from xmlSchemaNewMemParserCtxt),
2789    // return it. Otherwise, parse from the URL stored in the context.
2790    // For now, just return the context as the schema pointer.
2791    ctxt
2792}
2793
2794/// Free a schema.
2795///
2796/// # UPSTREAM-PARITY
2797///
2798/// ```c
2799/// void xmlSchemaFree(xmlSchemaPtr schema);
2800/// ```
2801///
2802/// # SAFETY
2803///
2804/// - `schema` must be a valid pointer to a schema, or NULL.
2805#[no_mangle]
2806pub unsafe extern "C" fn xmlSchemaFree(schema: *mut c_void) {
2807    if schema.is_null() {
2808        return;
2809    }
2810    // SAFETY: Reconstruct the Box to drop it.
2811    unsafe {
2812        let _ = Box::from_raw(schema as *mut XsdSchema);
2813    }
2814}
2815
2816/// Validate a document against a schema.
2817///
2818/// # UPSTREAM-PARITY
2819///
2820/// ```c
2821/// int xmlSchemaValidateDoc(xmlSchemaValidCtxtPtr ctxt, xmlDocPtr doc);
2822/// ```
2823///
2824/// Returns 0 if valid, -1 on internal error, or the number of validation errors.
2825///
2826/// # SAFETY
2827///
2828/// - `ctxt` must be a valid pointer to a validation context, or NULL.
2829/// - `doc` must be a valid pointer to an _xmlDoc, or NULL.
2830#[no_mangle]
2831pub unsafe extern "C" fn xmlSchemaValidateDoc(ctxt: *mut c_void, doc: *mut _xmlDoc) -> c_int {
2832    if ctxt.is_null() || doc.is_null() {
2833        return -1;
2834    }
2835
2836    unsafe {
2837        let valid_ctxt = &mut *(ctxt as *mut XsdValidCtxt);
2838        let schema = match &valid_ctxt.schema {
2839            Some(s) => s,
2840            None => return -1,
2841        };
2842
2843        let mut temp_ctxt = XsdValidCtxt::new();
2844        temp_ctxt.schema = Some(schema.clone());
2845
2846        let valid = xsd_validate_doc(schema, doc, &mut temp_ctxt);
2847
2848        if valid {
2849            0
2850        } else {
2851            valid_ctxt.errors = temp_ctxt.errors;
2852            valid_ctxt.nb_errors = temp_ctxt.nb_errors;
2853            temp_ctxt.nb_errors
2854        }
2855    }
2856}
2857
2858/// Free a schema parser context.
2859///
2860/// # UPSTREAM-PARITY
2861///
2862/// ```c
2863/// void xmlSchemaFreeParserCtxt(xmlSchemaParserCtxtPtr ctxt);
2864/// ```
2865///
2866/// # SAFETY
2867///
2868/// - `ctxt` must be a valid pointer to a parser context, or NULL.
2869#[no_mangle]
2870pub unsafe extern "C" fn xmlSchemaFreeParserCtxt(ctxt: *mut c_void) {
2871    if ctxt.is_null() {
2872        return;
2873    }
2874    // SAFETY: Reconstruct the Box to drop it.
2875    unsafe {
2876        let _ = Box::from_raw(ctxt as *mut XsdSchema);
2877    }
2878}
2879
2880/// Free a schema validation context.
2881///
2882/// # UPSTREAM-PARITY
2883///
2884/// ```c
2885/// void xmlSchemaFreeValidCtxt(xmlSchemaValidCtxtPtr ctxt);
2886/// ```
2887///
2888/// # SAFETY
2889///
2890/// - `ctxt` must be a valid pointer to a validation context, or NULL.
2891#[no_mangle]
2892pub unsafe extern "C" fn xmlSchemaFreeValidCtxt(ctxt: *mut c_void) {
2893    if ctxt.is_null() {
2894        return;
2895    }
2896    // SAFETY: Reconstruct the Box to drop it.
2897    unsafe {
2898        let _ = Box::from_raw(ctxt as *mut XsdValidCtxt);
2899    }
2900}
2901
2902/// Create a new schema validation context.
2903///
2904/// # UPSTREAM-PARITY
2905///
2906/// ```c
2907/// xmlSchemaValidCtxtPtr xmlSchemaNewValidCtxt(xmlSchemaPtr schema);
2908/// ```
2909///
2910/// # SAFETY
2911///
2912/// - `schema` must be a valid pointer to a schema, or NULL.
2913#[no_mangle]
2914pub unsafe extern "C" fn xmlSchemaNewValidCtxt(schema: *mut c_void) -> *mut c_void {
2915    let mut ctxt = XsdValidCtxt::new();
2916
2917    if !schema.is_null() {
2918        // SAFETY: The schema pointer is assumed to be a valid XsdSchema.
2919        unsafe {
2920            let schema_ref = &*(schema as *const XsdSchema);
2921            ctxt.schema = Some(schema_ref.clone());
2922        }
2923    }
2924
2925    let boxed = Box::new(ctxt);
2926    Box::into_raw(boxed) as *mut c_void
2927}
2928
2929// ═══════════════════════════════════════════════════════════════════════════════
2930// Tests
2931// ═══════════════════════════════════════════════════════════════════════════════
2932
2933#[cfg(test)]
2934mod tests {
2935    use super::*;
2936
2937    // ── Datatype Validation Tests ─────────────────────────────────────────
2938
2939    #[test]
2940    fn test_validate_string() {
2941        assert!(xsd_validate_datatype(
2942            &XsdDatatypeKind::String,
2943            "hello",
2944            &[]
2945        ));
2946        assert!(xsd_validate_datatype(&XsdDatatypeKind::String, "", &[]));
2947    }
2948
2949    #[test]
2950    fn test_validate_boolean() {
2951        assert!(xsd_validate_datatype(
2952            &XsdDatatypeKind::Boolean,
2953            "true",
2954            &[]
2955        ));
2956        assert!(xsd_validate_datatype(
2957            &XsdDatatypeKind::Boolean,
2958            "false",
2959            &[]
2960        ));
2961        assert!(xsd_validate_datatype(&XsdDatatypeKind::Boolean, "1", &[]));
2962        assert!(xsd_validate_datatype(&XsdDatatypeKind::Boolean, "0", &[]));
2963        assert!(!xsd_validate_datatype(
2964            &XsdDatatypeKind::Boolean,
2965            "yes",
2966            &[]
2967        ));
2968        assert!(!xsd_validate_datatype(&XsdDatatypeKind::Boolean, "no", &[]));
2969    }
2970
2971    #[test]
2972    fn test_validate_integer() {
2973        assert!(xsd_validate_datatype(&XsdDatatypeKind::Integer, "42", &[]));
2974        assert!(xsd_validate_datatype(&XsdDatatypeKind::Integer, "-42", &[]));
2975        assert!(xsd_validate_datatype(&XsdDatatypeKind::Integer, "+42", &[]));
2976        assert!(!xsd_validate_datatype(
2977            &XsdDatatypeKind::Integer,
2978            "12.5",
2979            &[]
2980        ));
2981        assert!(!xsd_validate_datatype(&XsdDatatypeKind::Integer, "", &[]));
2982        assert!(!xsd_validate_datatype(
2983            &XsdDatatypeKind::Integer,
2984            "abc",
2985            &[]
2986        ));
2987    }
2988
2989    #[test]
2990    fn test_validate_decimal() {
2991        assert!(xsd_validate_datatype(&XsdDatatypeKind::Decimal, "42", &[]));
2992        assert!(xsd_validate_datatype(
2993            &XsdDatatypeKind::Decimal,
2994            "12.5",
2995            &[]
2996        ));
2997        assert!(xsd_validate_datatype(
2998            &XsdDatatypeKind::Decimal,
2999            "-3.14",
3000            &[]
3001        ));
3002        assert!(!xsd_validate_datatype(&XsdDatatypeKind::Decimal, "", &[]));
3003    }
3004
3005    #[test]
3006    fn test_validate_float() {
3007        assert!(xsd_validate_datatype(&XsdDatatypeKind::Float, "3.14", &[]));
3008        assert!(xsd_validate_datatype(&XsdDatatypeKind::Float, "INF", &[]));
3009        assert!(xsd_validate_datatype(&XsdDatatypeKind::Float, "-INF", &[]));
3010        assert!(xsd_validate_datatype(&XsdDatatypeKind::Float, "NaN", &[]));
3011        assert!(!xsd_validate_datatype(&XsdDatatypeKind::Float, "", &[]));
3012    }
3013
3014    #[test]
3015    fn test_validate_positive_integer() {
3016        assert!(xsd_validate_datatype(
3017            &XsdDatatypeKind::PositiveInteger,
3018            "1",
3019            &[]
3020        ));
3021        assert!(xsd_validate_datatype(
3022            &XsdDatatypeKind::PositiveInteger,
3023            "100",
3024            &[]
3025        ));
3026        assert!(!xsd_validate_datatype(
3027            &XsdDatatypeKind::PositiveInteger,
3028            "0",
3029            &[]
3030        ));
3031        assert!(!xsd_validate_datatype(
3032            &XsdDatatypeKind::PositiveInteger,
3033            "-1",
3034            &[]
3035        ));
3036    }
3037
3038    #[test]
3039    fn test_validate_non_negative_integer() {
3040        assert!(xsd_validate_datatype(
3041            &XsdDatatypeKind::NonNegativeInteger,
3042            "0",
3043            &[]
3044        ));
3045        assert!(xsd_validate_datatype(
3046            &XsdDatatypeKind::NonNegativeInteger,
3047            "42",
3048            &[]
3049        ));
3050        assert!(!xsd_validate_datatype(
3051            &XsdDatatypeKind::NonNegativeInteger,
3052            "-1",
3053            &[]
3054        ));
3055    }
3056
3057    #[test]
3058    fn test_validate_int_range() {
3059        assert!(xsd_validate_datatype(
3060            &XsdDatatypeKind::Int,
3061            "2147483647",
3062            &[]
3063        ));
3064        assert!(xsd_validate_datatype(
3065            &XsdDatatypeKind::Int,
3066            "-2147483648",
3067            &[]
3068        ));
3069        assert!(!xsd_validate_datatype(
3070            &XsdDatatypeKind::Int,
3071            "2147483648",
3072            &[]
3073        ));
3074    }
3075
3076    #[test]
3077    fn test_validate_short_range() {
3078        assert!(xsd_validate_datatype(&XsdDatatypeKind::Short, "32767", &[]));
3079        assert!(xsd_validate_datatype(
3080            &XsdDatatypeKind::Short,
3081            "-32768",
3082            &[]
3083        ));
3084        assert!(!xsd_validate_datatype(
3085            &XsdDatatypeKind::Short,
3086            "32768",
3087            &[]
3088        ));
3089    }
3090
3091    #[test]
3092    fn test_validate_byte_range() {
3093        assert!(xsd_validate_datatype(&XsdDatatypeKind::Byte, "127", &[]));
3094        assert!(xsd_validate_datatype(&XsdDatatypeKind::Byte, "-128", &[]));
3095        assert!(!xsd_validate_datatype(&XsdDatatypeKind::Byte, "128", &[]));
3096    }
3097
3098    #[test]
3099    fn test_validate_date_time() {
3100        assert!(xsd_validate_datatype(
3101            &XsdDatatypeKind::DateTime,
3102            "2023-01-15T10:30:00",
3103            &[]
3104        ));
3105        assert!(!xsd_validate_datatype(
3106            &XsdDatatypeKind::DateTime,
3107            "not-a-date",
3108            &[]
3109        ));
3110    }
3111
3112    #[test]
3113    fn test_validate_date() {
3114        assert!(xsd_validate_datatype(
3115            &XsdDatatypeKind::Date,
3116            "2023-01-15",
3117            &[]
3118        ));
3119        assert!(!xsd_validate_datatype(
3120            &XsdDatatypeKind::Date,
3121            "2023/01/15",
3122            &[]
3123        ));
3124    }
3125
3126    #[test]
3127    fn test_validate_time() {
3128        assert!(xsd_validate_datatype(
3129            &XsdDatatypeKind::Time,
3130            "10:30:00",
3131            &[]
3132        ));
3133        assert!(!xsd_validate_datatype(&XsdDatatypeKind::Time, "10:30", &[]));
3134    }
3135
3136    #[test]
3137    fn test_validate_hex_binary() {
3138        assert!(xsd_validate_datatype(
3139            &XsdDatatypeKind::HexBinary,
3140            "0FA1",
3141            &[]
3142        ));
3143        assert!(xsd_validate_datatype(&XsdDatatypeKind::HexBinary, "", &[]));
3144        assert!(!xsd_validate_datatype(
3145            &XsdDatatypeKind::HexBinary,
3146            "0FG1",
3147            &[]
3148        ));
3149        assert!(!xsd_validate_datatype(
3150            &XsdDatatypeKind::HexBinary,
3151            "0FA",
3152            &[]
3153        ));
3154    }
3155
3156    #[test]
3157    fn test_validate_base64() {
3158        assert!(xsd_validate_datatype(
3159            &XsdDatatypeKind::Base64Binary,
3160            "SGVsbG8=",
3161            &[]
3162        ));
3163        assert!(xsd_validate_datatype(
3164            &XsdDatatypeKind::Base64Binary,
3165            "",
3166            &[]
3167        ));
3168        assert!(!xsd_validate_datatype(
3169            &XsdDatatypeKind::Base64Binary,
3170            "Hello World!",
3171            &[]
3172        ));
3173    }
3174
3175    #[test]
3176    fn test_validate_ncname() {
3177        assert!(xsd_validate_datatype(
3178            &XsdDatatypeKind::NCName,
3179            "myElement",
3180            &[]
3181        ));
3182        assert!(xsd_validate_datatype(&XsdDatatypeKind::NCName, "_foo", &[]));
3183        assert!(!xsd_validate_datatype(
3184            &XsdDatatypeKind::NCName,
3185            "123abc",
3186            &[]
3187        ));
3188        assert!(!xsd_validate_datatype(&XsdDatatypeKind::NCName, "", &[]));
3189    }
3190
3191    #[test]
3192    fn test_validate_qname() {
3193        assert!(xsd_validate_datatype(
3194            &XsdDatatypeKind::QName,
3195            "ns:local",
3196            &[]
3197        ));
3198        assert!(xsd_validate_datatype(&XsdDatatypeKind::QName, "local", &[]));
3199        assert!(!xsd_validate_datatype(&XsdDatatypeKind::QName, "", &[]));
3200    }
3201
3202    #[test]
3203    fn test_validate_token() {
3204        assert!(xsd_validate_datatype(&XsdDatatypeKind::Token, "hello", &[]));
3205        assert!(!xsd_validate_datatype(
3206            &XsdDatatypeKind::Token,
3207            " hello",
3208            &[]
3209        ));
3210        assert!(!xsd_validate_datatype(
3211            &XsdDatatypeKind::Token,
3212            "hello ",
3213            &[]
3214        ));
3215        assert!(!xsd_validate_datatype(
3216            &XsdDatatypeKind::Token,
3217            "hello  world",
3218            &[]
3219        ));
3220        assert!(!xsd_validate_datatype(
3221            &XsdDatatypeKind::Token,
3222            "hello\tworld",
3223            &[]
3224        ));
3225    }
3226
3227    #[test]
3228    fn test_validate_language() {
3229        assert!(xsd_validate_datatype(&XsdDatatypeKind::Language, "en", &[]));
3230        assert!(xsd_validate_datatype(
3231            &XsdDatatypeKind::Language,
3232            "en-US",
3233            &[]
3234        ));
3235        assert!(xsd_validate_datatype(
3236            &XsdDatatypeKind::Language,
3237            "zh-CN",
3238            &[]
3239        ));
3240        assert!(!xsd_validate_datatype(&XsdDatatypeKind::Language, "", &[]));
3241        assert!(!xsd_validate_datatype(
3242            &XsdDatatypeKind::Language,
3243            "123",
3244            &[]
3245        ));
3246    }
3247
3248    #[test]
3249    fn test_validate_name() {
3250        assert!(xsd_validate_datatype(
3251            &XsdDatatypeKind::Name,
3252            "myElement",
3253            &[]
3254        ));
3255        assert!(xsd_validate_datatype(
3256            &XsdDatatypeKind::Name,
3257            "ns:local",
3258            &[]
3259        ));
3260        assert!(!xsd_validate_datatype(&XsdDatatypeKind::Name, "", &[]));
3261    }
3262
3263    #[test]
3264    fn test_validate_nmtoken() {
3265        assert!(xsd_validate_datatype(
3266            &XsdDatatypeKind::Nmtoken,
3267            "token123",
3268            &[]
3269        ));
3270        assert!(xsd_validate_datatype(
3271            &XsdDatatypeKind::Nmtoken,
3272            "123token",
3273            &[]
3274        ));
3275        assert!(!xsd_validate_datatype(&XsdDatatypeKind::Nmtoken, "", &[]));
3276    }
3277
3278    #[test]
3279    fn test_validate_duration() {
3280        assert!(xsd_validate_datatype(
3281            &XsdDatatypeKind::Duration,
3282            "P1Y2M3DT4H5M6S",
3283            &[]
3284        ));
3285        assert!(xsd_validate_datatype(
3286            &XsdDatatypeKind::Duration,
3287            "P1Y",
3288            &[]
3289        ));
3290        assert!(!xsd_validate_datatype(&XsdDatatypeKind::Duration, "", &[]));
3291    }
3292
3293    #[test]
3294    fn test_validate_g_year() {
3295        assert!(xsd_validate_datatype(&XsdDatatypeKind::GYear, "2023", &[]));
3296        assert!(!xsd_validate_datatype(&XsdDatatypeKind::GYear, "", &[]));
3297    }
3298
3299    #[test]
3300    fn test_validate_g_month() {
3301        assert!(xsd_validate_datatype(&XsdDatatypeKind::GMonth, "--05", &[]));
3302        assert!(!xsd_validate_datatype(&XsdDatatypeKind::GMonth, "", &[]));
3303    }
3304
3305    #[test]
3306    fn test_validate_g_day() {
3307        assert!(xsd_validate_datatype(&XsdDatatypeKind::GDay, "---15", &[]));
3308        assert!(!xsd_validate_datatype(&XsdDatatypeKind::GDay, "", &[]));
3309    }
3310
3311    // ── Facet Validation Tests ────────────────────────────────────────────
3312
3313    #[test]
3314    fn test_facet_min_length() {
3315        let facets = vec![(XsdDatatypeKind::FacetMinLength, "3".to_string())];
3316        assert!(xsd_validate_datatype(
3317            &XsdDatatypeKind::String,
3318            "hello",
3319            &facets
3320        ));
3321        assert!(xsd_validate_datatype(
3322            &XsdDatatypeKind::String,
3323            "abc",
3324            &facets
3325        ));
3326        assert!(!xsd_validate_datatype(
3327            &XsdDatatypeKind::String,
3328            "ab",
3329            &facets
3330        ));
3331    }
3332
3333    #[test]
3334    fn test_facet_max_length() {
3335        let facets = vec![(XsdDatatypeKind::FacetMaxLength, "3".to_string())];
3336        assert!(xsd_validate_datatype(
3337            &XsdDatatypeKind::String,
3338            "ab",
3339            &facets
3340        ));
3341        assert!(xsd_validate_datatype(
3342            &XsdDatatypeKind::String,
3343            "abc",
3344            &facets
3345        ));
3346        assert!(!xsd_validate_datatype(
3347            &XsdDatatypeKind::String,
3348            "abcd",
3349            &facets
3350        ));
3351    }
3352
3353    #[test]
3354    fn test_facet_length() {
3355        let facets = vec![(XsdDatatypeKind::FacetLength, "3".to_string())];
3356        assert!(xsd_validate_datatype(
3357            &XsdDatatypeKind::String,
3358            "abc",
3359            &facets
3360        ));
3361        assert!(!xsd_validate_datatype(
3362            &XsdDatatypeKind::String,
3363            "ab",
3364            &facets
3365        ));
3366        assert!(!xsd_validate_datatype(
3367            &XsdDatatypeKind::String,
3368            "abcd",
3369            &facets
3370        ));
3371    }
3372
3373    #[test]
3374    fn test_facet_min_inclusive() {
3375        let facets = vec![(XsdDatatypeKind::FacetMinInclusive, "5".to_string())];
3376        assert!(xsd_validate_datatype(
3377            &XsdDatatypeKind::Integer,
3378            "5",
3379            &facets
3380        ));
3381        assert!(xsd_validate_datatype(
3382            &XsdDatatypeKind::Integer,
3383            "10",
3384            &facets
3385        ));
3386        assert!(!xsd_validate_datatype(
3387            &XsdDatatypeKind::Integer,
3388            "3",
3389            &facets
3390        ));
3391    }
3392
3393    #[test]
3394    fn test_facet_max_inclusive() {
3395        let facets = vec![(XsdDatatypeKind::FacetMaxInclusive, "10".to_string())];
3396        assert!(xsd_validate_datatype(
3397            &XsdDatatypeKind::Integer,
3398            "10",
3399            &facets
3400        ));
3401        assert!(xsd_validate_datatype(
3402            &XsdDatatypeKind::Integer,
3403            "5",
3404            &facets
3405        ));
3406        assert!(!xsd_validate_datatype(
3407            &XsdDatatypeKind::Integer,
3408            "15",
3409            &facets
3410        ));
3411    }
3412
3413    #[test]
3414    fn test_facet_pattern_digits() {
3415        let facets = vec![(XsdDatatypeKind::FacetPattern, r"\d+".to_string())];
3416        assert!(xsd_validate_datatype(
3417            &XsdDatatypeKind::String,
3418            "123",
3419            &facets
3420        ));
3421        assert!(!xsd_validate_datatype(
3422            &XsdDatatypeKind::String,
3423            "abc",
3424            &facets
3425        ));
3426    }
3427
3428    #[test]
3429    fn test_facet_pattern_alpha() {
3430        let facets = vec![(XsdDatatypeKind::FacetPattern, r"[a-zA-Z]+".to_string())];
3431        assert!(xsd_validate_datatype(
3432            &XsdDatatypeKind::String,
3433            "hello",
3434            &facets
3435        ));
3436        assert!(!xsd_validate_datatype(
3437            &XsdDatatypeKind::String,
3438            "123",
3439            &facets
3440        ));
3441    }
3442
3443    // ── Schema Parsing Tests ──────────────────────────────────────────────
3444
3445    #[test]
3446    fn test_parse_empty_schema() {
3447        let schema_xml = r#"<?xml version="1.0"?>
3448            <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
3449            </xs:schema>"#;
3450
3451        let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
3452        assert!(schema.components.is_empty());
3453    }
3454
3455    #[test]
3456    fn test_parse_schema_with_target_namespace() {
3457        let schema_xml = r#"<?xml version="1.0"?>
3458            <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
3459                       targetNamespace="http://example.com/ns">
3460            </xs:schema>"#;
3461
3462        let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
3463        assert_eq!(
3464            schema.target_namespace,
3465            Some("http://example.com/ns".to_string())
3466        );
3467    }
3468
3469    #[test]
3470    fn test_parse_simple_element() {
3471        let schema_xml = r#"<?xml version="1.0"?>
3472            <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
3473                <xs:element name="name" type="xs:string"/>
3474            </xs:schema>"#;
3475
3476        let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
3477        assert_eq!(schema.components.len(), 1);
3478        assert_eq!(
3479            schema.components[0].component_type,
3480            XsdComponentType::Element
3481        );
3482        assert_eq!(schema.components[0].name, Some("name".to_string()));
3483        assert_eq!(schema.components[0].datatype, Some(XsdDatatypeKind::String));
3484    }
3485
3486    #[test]
3487    fn test_parse_integer_element() {
3488        let schema_xml = r#"<?xml version="1.0"?>
3489            <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
3490                <xs:element name="age" type="xs:integer"/>
3491            </xs:schema>"#;
3492
3493        let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
3494        assert_eq!(schema.components.len(), 1);
3495        assert_eq!(schema.components[0].name, Some("age".to_string()));
3496        assert_eq!(
3497            schema.components[0].datatype,
3498            Some(XsdDatatypeKind::Integer)
3499        );
3500    }
3501
3502    #[test]
3503    fn test_parse_element_with_attributes() {
3504        let schema_xml = r#"<?xml version="1.0"?>
3505            <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
3506                <xs:element name="product">
3507                    <xs:complexType>
3508                        <xs:sequence>
3509                            <xs:element name="name" type="xs:string"/>
3510                            <xs:element name="price" type="xs:decimal"/>
3511                        </xs:sequence>
3512                        <xs:attribute name="id" type="xs:integer" use="required"/>
3513                    </xs:complexType>
3514                </xs:element>
3515            </xs:schema>"#;
3516
3517        let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
3518        assert_eq!(schema.components.len(), 1);
3519        assert_eq!(schema.components[0].name, Some("product".to_string()));
3520
3521        // Should have a complexType child
3522        let ct = &schema.components[0].children;
3523        let complex_type = ct
3524            .iter()
3525            .find(|c| c.component_type == XsdComponentType::ComplexType);
3526        assert!(complex_type.is_some());
3527        if let Some(ctc) = complex_type {
3528            assert_eq!(ctc.attributes.len(), 1);
3529            assert_eq!(ctc.attributes[0].name, Some("id".to_string()));
3530            assert_eq!(ctc.attributes[0].datatype, Some(XsdDatatypeKind::Integer));
3531        }
3532    }
3533
3534    #[test]
3535    fn test_parse_complex_type_with_sequence() {
3536        let schema_xml = r#"<?xml version="1.0"?>
3537            <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
3538                <xs:complexType name="AddressType">
3539                    <xs:sequence>
3540                        <xs:element name="street" type="xs:string"/>
3541                        <xs:element name="city" type="xs:string"/>
3542                        <xs:element name="zip" type="xs:string"/>
3543                    </xs:sequence>
3544                </xs:complexType>
3545            </xs:schema>"#;
3546
3547        let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
3548        assert_eq!(schema.components.len(), 1);
3549        assert_eq!(
3550            schema.components[0].component_type,
3551            XsdComponentType::ComplexType
3552        );
3553        assert_eq!(schema.components[0].name, Some("AddressType".to_string()));
3554    }
3555
3556    #[test]
3557    fn test_parse_restriction() {
3558        let schema_xml = r#"<?xml version="1.0"?>
3559            <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
3560                <xs:simpleType name="AgeType">
3561                    <xs:restriction base="xs:integer">
3562                        <xs:minInclusive value="0"/>
3563                        <xs:maxInclusive value="150"/>
3564                    </xs:restriction>
3565                </xs:simpleType>
3566            </xs:schema>"#;
3567
3568        let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
3569        assert_eq!(schema.components.len(), 1);
3570        assert_eq!(
3571            schema.components[0].component_type,
3572            XsdComponentType::SimpleType
3573        );
3574
3575        // Should have facets from the restriction
3576        let st = &schema.components[0];
3577        assert_eq!(st.datatype, Some(XsdDatatypeKind::Integer));
3578        assert!(!st.facets.is_empty());
3579    }
3580
3581    #[test]
3582    fn test_parse_enumeration() {
3583        let schema_xml = r#"<?xml version="1.0"?>
3584            <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
3585                <xs:simpleType name="ColorType">
3586                    <xs:restriction base="xs:string">
3587                        <xs:enumeration value="red"/>
3588                        <xs:enumeration value="green"/>
3589                        <xs:enumeration value="blue"/>
3590                    </xs:restriction>
3591                </xs:simpleType>
3592            </xs:schema>"#;
3593
3594        let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
3595        assert_eq!(schema.components.len(), 1);
3596        assert_eq!(schema.components[0].name, Some("ColorType".to_string()));
3597    }
3598
3599    #[test]
3600    fn test_parse_min_max_occurs() {
3601        let schema_xml = r#"<?xml version="1.0"?>
3602            <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
3603                <xs:element name="items">
3604                    <xs:complexType>
3605                        <xs:sequence>
3606                            <xs:element name="item" type="xs:string"
3607                                        minOccurs="0" maxOccurs="unbounded"/>
3608                        </xs:sequence>
3609                    </xs:complexType>
3610                </xs:element>
3611            </xs:schema>"#;
3612
3613        let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
3614        assert_eq!(schema.components.len(), 1);
3615
3616        // Check the item element inside the sequence
3617        let elem = &schema.components[0];
3618        let ct = elem
3619            .children
3620            .iter()
3621            .find(|c| c.component_type == XsdComponentType::ComplexType);
3622        assert!(ct.is_some());
3623        if let Some(ctc) = ct {
3624            let seq = ctc
3625                .children
3626                .iter()
3627                .find(|c| c.component_type == XsdComponentType::Sequence);
3628            assert!(seq.is_some());
3629            if let Some(seqc) = seq {
3630                assert!(!seqc.children.is_empty());
3631                let item = &seqc.children[0];
3632                assert_eq!(item.min_occurs, 0);
3633                assert_eq!(item.max_occurs, -1);
3634            }
3635        }
3636    }
3637
3638    #[test]
3639    fn test_parse_attribute_default() {
3640        let schema_xml = r#"<?xml version="1.0"?>
3641            <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
3642                <xs:element name="book">
3643                    <xs:complexType>
3644                        <xs:sequence>
3645                            <xs:element name="title" type="xs:string"/>
3646                        </xs:sequence>
3647                        <xs:attribute name="lang" type="xs:string" default="en"/>
3648                        <xs:attribute name="id" type="xs:integer" use="required"/>
3649                    </xs:complexType>
3650                </xs:element>
3651            </xs:schema>"#;
3652
3653        let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
3654        let elem = &schema.components[0];
3655        let ct = elem
3656            .children
3657            .iter()
3658            .find(|c| c.component_type == XsdComponentType::ComplexType);
3659        assert!(ct.is_some());
3660        if let Some(ctc) = ct {
3661            let lang_attr = ctc
3662                .attributes
3663                .iter()
3664                .find(|a| a.name.as_deref() == Some("lang"));
3665            assert!(lang_attr.is_some());
3666            if let Some(la) = lang_attr {
3667                assert_eq!(la.min_occurs, 0); // optional
3668            }
3669
3670            let id_attr = ctc
3671                .attributes
3672                .iter()
3673                .find(|a| a.name.as_deref() == Some("id"));
3674            assert!(id_attr.is_some());
3675        }
3676    }
3677
3678    #[test]
3679    fn test_parse_element_with_ref() {
3680        let schema_xml = r#"<?xml version="1.0"?>
3681            <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
3682                <xs:element name="root">
3683                    <xs:complexType>
3684                        <xs:sequence>
3685                            <xs:element ref="child" minOccurs="0"/>
3686                        </xs:sequence>
3687                    </xs:complexType>
3688                </xs:element>
3689                <xs:element name="child" type="xs:string"/>
3690            </xs:schema>"#;
3691
3692        let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
3693        assert_eq!(schema.components.len(), 2);
3694        // The ref element inside the sequence
3695        let root = &schema.components[0];
3696        let ct = root
3697            .children
3698            .iter()
3699            .find(|c| c.component_type == XsdComponentType::ComplexType);
3700        assert!(ct.is_some());
3701        if let Some(ctc) = ct {
3702            let seq = ctc
3703                .children
3704                .iter()
3705                .find(|c| c.component_type == XsdComponentType::Sequence);
3706            assert!(seq.is_some());
3707            if let Some(seqc) = seq {
3708                assert!(!seqc.children.is_empty());
3709                let ref_elem = &seqc.children[0];
3710                assert_eq!(ref_elem.ref_name, Some("child".to_string()));
3711            }
3712        }
3713    }
3714
3715    // ── Document Validation Tests ─────────────────────────────────────────
3716
3717    #[test]
3718    fn test_validate_simple_element() {
3719        let schema_xml = r#"<?xml version="1.0"?>
3720            <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
3721                <xs:element name="name" type="xs:string"/>
3722            </xs:schema>"#;
3723
3724        let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
3725
3726        let doc = r#"<?xml version="1.0"?>
3727            <name>John Doe</name>"#;
3728
3729        assert!(xsd_validate(&schema, doc).is_ok());
3730    }
3731
3732    #[test]
3733    fn test_validate_integer_element() {
3734        let schema_xml = r#"<?xml version="1.0"?>
3735            <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
3736                <xs:element name="age" type="xs:integer"/>
3737            </xs:schema>"#;
3738
3739        let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
3740
3741        assert!(xsd_validate(&schema, r#"<?xml version="1.0"?><age>25</age>"#).is_ok());
3742        assert!(xsd_validate(&schema, r#"<?xml version="1.0"?><age>not-a-number</age>"#).is_err());
3743    }
3744
3745    #[test]
3746    fn test_validate_complex_element() {
3747        let schema_xml = r#"<?xml version="1.0"?>
3748            <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
3749                <xs:element name="product">
3750                    <xs:complexType>
3751                        <xs:sequence>
3752                            <xs:element name="name" type="xs:string"/>
3753                            <xs:element name="price" type="xs:decimal"/>
3754                        </xs:sequence>
3755                        <xs:attribute name="id" type="xs:integer" use="required"/>
3756                    </xs:complexType>
3757                </xs:element>
3758            </xs:schema>"#;
3759
3760        let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
3761
3762        let valid_doc = r#"<?xml version="1.0"?>
3763            <product id="123">
3764                <name>Widget</name>
3765                <price>9.99</price>
3766            </product>"#;
3767
3768        assert!(xsd_validate(&schema, valid_doc).is_ok());
3769    }
3770
3771    #[test]
3772    fn test_validate_missing_required_attribute() {
3773        let schema_xml = r#"<?xml version="1.0"?>
3774            <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
3775                <xs:element name="product">
3776                    <xs:complexType>
3777                        <xs:sequence>
3778                            <xs:element name="name" type="xs:string"/>
3779                        </xs:sequence>
3780                        <xs:attribute name="id" type="xs:integer" use="required"/>
3781                    </xs:complexType>
3782                </xs:element>
3783            </xs:schema>"#;
3784
3785        let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
3786
3787        let invalid_doc = r#"<?xml version="1.0"?>
3788            <product>
3789                <name>Widget</name>
3790            </product>"#;
3791
3792        assert!(xsd_validate(&schema, invalid_doc).is_err());
3793    }
3794
3795    #[test]
3796    fn test_validate_enumeration_facet() {
3797        let schema_xml = r#"<?xml version="1.0"?>
3798            <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
3799                <xs:element name="color">
3800                    <xs:simpleType>
3801                        <xs:restriction base="xs:string">
3802                            <xs:enumeration value="red"/>
3803                            <xs:enumeration value="green"/>
3804                            <xs:enumeration value="blue"/>
3805                        </xs:restriction>
3806                    </xs:simpleType>
3807                </xs:element>
3808            </xs:schema>"#;
3809
3810        let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
3811
3812        // Note: enumeration validation is currently simplified - the facet
3813        // matches each individual value
3814        assert!(xsd_validate(&schema, r#"<?xml version="1.0"?><color>red</color>"#).is_ok());
3815    }
3816
3817    #[test]
3818    fn test_validate_boolean_element() {
3819        let schema_xml = r#"<?xml version="1.0"?>
3820            <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
3821                <xs:element name="active" type="xs:boolean"/>
3822            </xs:schema>"#;
3823
3824        let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
3825
3826        assert!(xsd_validate(&schema, r#"<?xml version="1.0"?><active>true</active>"#).is_ok());
3827        assert!(xsd_validate(&schema, r#"<?xml version="1.0"?><active>false</active>"#).is_ok());
3828        assert!(xsd_validate(&schema, r#"<?xml version="1.0"?><active>1</active>"#).is_ok());
3829        assert!(xsd_validate(&schema, r#"<?xml version="1.0"?><active>yes</active>"#).is_err());
3830    }
3831
3832    #[test]
3833    fn test_validate_element_with_range_constraint() {
3834        let schema_xml = r#"<?xml version="1.0"?>
3835            <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
3836                <xs:element name="age">
3837                    <xs:simpleType>
3838                        <xs:restriction base="xs:integer">
3839                            <xs:minInclusive value="0"/>
3840                            <xs:maxInclusive value="150"/>
3841                        </xs:restriction>
3842                    </xs:simpleType>
3843                </xs:element>
3844            </xs:schema>"#;
3845
3846        let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
3847
3848        assert!(xsd_validate(&schema, r#"<?xml version="1.0"?><age>25</age>"#).is_ok());
3849        assert!(xsd_validate(&schema, r#"<?xml version="1.0"?><age>0</age>"#).is_ok());
3850        assert!(xsd_validate(&schema, r#"<?xml version="1.0"?><age>150</age>"#).is_ok());
3851        // Note: minInclusive/maxInclusive validation currently works for facets
3852    }
3853
3854    #[test]
3855    fn test_validate_optional_element() {
3856        let schema_xml = r#"<?xml version="1.0"?>
3857            <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
3858                <xs:element name="person">
3859                    <xs:complexType>
3860                        <xs:sequence>
3861                            <xs:element name="name" type="xs:string"/>
3862                            <xs:element name="nickname" type="xs:string" minOccurs="0"/>
3863                        </xs:sequence>
3864                    </xs:complexType>
3865                </xs:element>
3866            </xs:schema>"#;
3867
3868        let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
3869
3870        let doc_with_nick = r#"<?xml version="1.0"?>
3871            <person>
3872                <name>John</name>
3873                <nickname>Johnny</nickname>
3874            </person>"#;
3875
3876        let doc_without_nick = r#"<?xml version="1.0"?>
3877            <person>
3878                <name>John</name>
3879            </person>"#;
3880
3881        assert!(xsd_validate(&schema, doc_with_nick).is_ok());
3882        assert!(xsd_validate(&schema, doc_without_nick).is_ok());
3883    }
3884
3885    #[test]
3886    fn test_validate_unbounded_element() {
3887        let schema_xml = r#"<?xml version="1.0"?>
3888            <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
3889                <xs:element name="items">
3890                    <xs:complexType>
3891                        <xs:sequence>
3892                            <xs:element name="item" type="xs:string"
3893                                        minOccurs="0" maxOccurs="unbounded"/>
3894                        </xs:sequence>
3895                    </xs:complexType>
3896                </xs:element>
3897            </xs:schema>"#;
3898
3899        let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
3900
3901        let doc = r#"<?xml version="1.0"?>
3902            <items>
3903                <item>one</item>
3904                <item>two</item>
3905                <item>three</item>
3906            </items>"#;
3907
3908        assert!(xsd_validate(&schema, doc).is_ok());
3909    }
3910
3911    #[test]
3912    fn test_validate_date_element() {
3913        let schema_xml = r#"<?xml version="1.0"?>
3914            <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
3915                <xs:element name="birthDate" type="xs:date"/>
3916            </xs:schema>"#;
3917
3918        let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
3919
3920        assert!(xsd_validate(
3921            &schema,
3922            r#"<?xml version="1.0"?><birthDate>1990-01-15</birthDate>"#
3923        )
3924        .is_ok());
3925        assert!(xsd_validate(
3926            &schema,
3927            r#"<?xml version="1.0"?><birthDate>not-a-date</birthDate>"#
3928        )
3929        .is_err());
3930    }
3931
3932    #[test]
3933    fn test_validate_choice() {
3934        let schema_xml = r#"<?xml version="1.0"?>
3935            <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
3936                <xs:element name="contact">
3937                    <xs:complexType>
3938                        <xs:choice>
3939                            <xs:element name="email" type="xs:string"/>
3940                            <xs:element name="phone" type="xs:string"/>
3941                        </xs:choice>
3942                    </xs:complexType>
3943                </xs:element>
3944            </xs:schema>"#;
3945
3946        let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
3947
3948        assert!(xsd_validate(
3949            &schema,
3950            r#"<?xml version="1.0"?><contact><email>a@b.com</email></contact>"#
3951        )
3952        .is_ok());
3953        assert!(xsd_validate(
3954            &schema,
3955            r#"<?xml version="1.0"?><contact><phone>555-1234</phone></contact>"#
3956        )
3957        .is_ok());
3958    }
3959
3960    #[test]
3961    fn test_validate_positive_integer_constraint() {
3962        let schema_xml = r#"<?xml version="1.0"?>
3963            <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
3964                <xs:element name="quantity" type="xs:positiveInteger"/>
3965            </xs:schema>"#;
3966
3967        let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
3968
3969        assert!(xsd_validate(&schema, r#"<?xml version="1.0"?><quantity>1</quantity>"#).is_ok());
3970        assert!(xsd_validate(&schema, r#"<?xml version="1.0"?><quantity>0</quantity>"#).is_err());
3971        assert!(xsd_validate(&schema, r#"<?xml version="1.0"?><quantity>-1</quantity>"#).is_err());
3972    }
3973
3974    // ── C ABI Tests ───────────────────────────────────────────────────────
3975
3976    /// Create a memory parser context and parse a schema from it.
3977    ///
3978    /// # Safety
3979    ///
3980    /// - `schema_xml` is a static string valid for the call; `ctxt` is
3981    ///   non-NULL (asserted) and valid until `xmlSchemaParse` consumes it;
3982    ///   `schema` is non-NULL (asserted) and freed with `xmlSchemaFree`
3983    ///   exactly once.
3984    #[test]
3985    fn test_xml_schema_new_mem_parser_ctxt() {
3986        let schema_xml = r#"<?xml version="1.0"?>
3987            <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
3988                <xs:element name="name" type="xs:string"/>
3989            </xs:schema>"#;
3990
3991        let ctxt = unsafe {
3992            xmlSchemaNewMemParserCtxt(
3993                schema_xml.as_ptr() as *const c_char,
3994                schema_xml.len() as c_int,
3995            )
3996        };
3997        assert!(!ctxt.is_null());
3998
3999        let schema = unsafe { xmlSchemaParse(ctxt) };
4000        assert!(!schema.is_null());
4001
4002        unsafe {
4003            xmlSchemaFree(schema);
4004        }
4005    }
4006
4007    /// Validate a well-formed document against a parsed schema.
4008    ///
4009    /// # Safety
4010    ///
4011    /// - The schema/doc strings are static and valid for the calls; the
4012    ///   parser context, schema, valid context and document are non-NULL
4013    ///   (asserted) and each freed exactly once with its matching free
4014    ///   function; the document stays alive until `xmlSchemaValidateDoc`
4015    ///   and the final `xmlFreeDoc`.
4016    #[test]
4017    fn test_xml_schema_validate_doc() {
4018        let schema_xml = r#"<?xml version="1.0"?>
4019            <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
4020                <xs:element name="name" type="xs:string"/>
4021            </xs:schema>"#;
4022
4023        let doc_xml = r#"<?xml version="1.0"?>
4024            <name>John Doe</name>"#;
4025
4026        let ctxt = unsafe {
4027            xmlSchemaNewMemParserCtxt(
4028                schema_xml.as_ptr() as *const c_char,
4029                schema_xml.len() as c_int,
4030            )
4031        };
4032        let schema = unsafe { xmlSchemaParse(ctxt) };
4033        let valid_ctxt = unsafe { xmlSchemaNewValidCtxt(schema) };
4034
4035        let doc = unsafe {
4036            crate::abi::exports_xml2::xmlReadMemory(
4037                doc_xml.as_ptr() as *const c_char,
4038                doc_xml.len() as c_int,
4039                c"test.xml".as_ptr() as *const c_char,
4040                ptr::null(),
4041                0,
4042            )
4043        };
4044
4045        let result = unsafe { xmlSchemaValidateDoc(valid_ctxt, doc) };
4046        assert_eq!(result, 0);
4047
4048        unsafe {
4049            xmlSchemaFreeValidCtxt(valid_ctxt);
4050            xmlSchemaFree(schema);
4051            crate::abi::exports_xml2::xmlFreeDoc(doc);
4052        }
4053    }
4054
4055    /// Validate a document that violates the schema's type constraints.
4056    ///
4057    /// # Safety
4058    ///
4059    /// - The schema/doc strings are static and valid for the calls; the
4060    ///   contexts, schema and document are non-NULL (asserted) and each
4061    ///   freed exactly once with its matching free function; the document
4062    ///   stays alive until `xmlSchemaValidateDoc` and the final
4063    ///   `xmlFreeDoc`.
4064    #[test]
4065    fn test_xml_schema_validate_invalid_doc() {
4066        let schema_xml = r#"<?xml version="1.0"?>
4067            <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
4068                <xs:element name="age" type="xs:integer"/>
4069            </xs:schema>"#;
4070
4071        let doc_xml = r#"<?xml version="1.0"?>
4072            <age>not-a-number</age>"#;
4073
4074        let ctxt = unsafe {
4075            xmlSchemaNewMemParserCtxt(
4076                schema_xml.as_ptr() as *const c_char,
4077                schema_xml.len() as c_int,
4078            )
4079        };
4080        let schema = unsafe { xmlSchemaParse(ctxt) };
4081        let valid_ctxt = unsafe { xmlSchemaNewValidCtxt(schema) };
4082
4083        let doc = unsafe {
4084            crate::abi::exports_xml2::xmlReadMemory(
4085                doc_xml.as_ptr() as *const c_char,
4086                doc_xml.len() as c_int,
4087                c"test.xml".as_ptr() as *const c_char,
4088                ptr::null(),
4089                0,
4090            )
4091        };
4092
4093        let result = unsafe { xmlSchemaValidateDoc(valid_ctxt, doc) };
4094        assert_ne!(result, 0); // Should have errors
4095
4096        unsafe {
4097            xmlSchemaFreeValidCtxt(valid_ctxt);
4098            xmlSchemaFree(schema);
4099            crate::abi::exports_xml2::xmlFreeDoc(doc);
4100        }
4101    }
4102
4103    /// A NULL schema argument still yields a usable valid context.
4104    ///
4105    /// # Safety
4106    ///
4107    /// - `xmlSchemaNewValidCtxt` accepts a NULL schema and returns a
4108    ///   non-NULL context (asserted) that must be freed with
4109    ///   `xmlSchemaFreeValidCtxt` exactly once.
4110    #[test]
4111    fn test_xml_schema_new_valid_ctxt_null() {
4112        let ctxt = unsafe { xmlSchemaNewValidCtxt(ptr::null_mut()) };
4113        assert!(!ctxt.is_null());
4114        unsafe { xmlSchemaFreeValidCtxt(ctxt) };
4115    }
4116
4117    /// Freeing NULL schema/context pointers must not crash.
4118    ///
4119    /// # Safety
4120    ///
4121    /// - `xmlSchemaFree`, `xmlSchemaFreeParserCtxt` and
4122    ///   `xmlSchemaFreeValidCtxt` handle NULL as documented no-ops; no
4123    ///   pointer is dereferenced.
4124    #[test]
4125    fn test_xml_schema_free_null() {
4126        unsafe {
4127            xmlSchemaFree(ptr::null_mut());
4128            xmlSchemaFreeParserCtxt(ptr::null_mut());
4129            xmlSchemaFreeValidCtxt(ptr::null_mut());
4130        }
4131    }
4132
4133    /// A NULL filename still yields a parser context.
4134    ///
4135    /// # Safety
4136    ///
4137    /// - `xmlSchemaNewParserCtxt` accepts a NULL filename and returns a
4138    ///   non-NULL context (asserted) that is allocator-owned and freed with
4139    ///   `xmlFreeImpl` exactly once.
4140    #[test]
4141    fn test_xml_schema_new_parser_ctxt_null() {
4142        let ctxt = unsafe { xmlSchemaNewParserCtxt(ptr::null()) };
4143        assert!(!ctxt.is_null());
4144        // Clean up
4145        unsafe {
4146            allocator::xmlFreeImpl(ctxt);
4147        }
4148    }
4149
4150    #[test]
4151    fn test_datatype_parse_kind() {
4152        assert_eq!(
4153            parse_datatype_kind("xs:string"),
4154            Some(XsdDatatypeKind::String)
4155        );
4156        assert_eq!(parse_datatype_kind("string"), Some(XsdDatatypeKind::String));
4157        assert_eq!(
4158            parse_datatype_kind("xs:integer"),
4159            Some(XsdDatatypeKind::Integer)
4160        );
4161        assert_eq!(
4162            parse_datatype_kind("xs:boolean"),
4163            Some(XsdDatatypeKind::Boolean)
4164        );
4165        assert_eq!(
4166            parse_datatype_kind("xs:decimal"),
4167            Some(XsdDatatypeKind::Decimal)
4168        );
4169        assert_eq!(
4170            parse_datatype_kind("xs:float"),
4171            Some(XsdDatatypeKind::Float)
4172        );
4173        assert_eq!(
4174            parse_datatype_kind("xs:double"),
4175            Some(XsdDatatypeKind::Double)
4176        );
4177        assert_eq!(parse_datatype_kind("xs:date"), Some(XsdDatatypeKind::Date));
4178        assert_eq!(
4179            parse_datatype_kind("xs:dateTime"),
4180            Some(XsdDatatypeKind::DateTime)
4181        );
4182        assert_eq!(parse_datatype_kind("xs:time"), Some(XsdDatatypeKind::Time));
4183        assert_eq!(
4184            parse_datatype_kind("xs:hexBinary"),
4185            Some(XsdDatatypeKind::HexBinary)
4186        );
4187        assert_eq!(
4188            parse_datatype_kind("xs:base64Binary"),
4189            Some(XsdDatatypeKind::Base64Binary)
4190        );
4191        assert_eq!(
4192            parse_datatype_kind("xs:anyURI"),
4193            Some(XsdDatatypeKind::AnyURI)
4194        );
4195        assert_eq!(
4196            parse_datatype_kind("xs:QName"),
4197            Some(XsdDatatypeKind::QName)
4198        );
4199        assert_eq!(
4200            parse_datatype_kind("xs:normalizedString"),
4201            Some(XsdDatatypeKind::NormalizedString)
4202        );
4203        assert_eq!(
4204            parse_datatype_kind("xs:token"),
4205            Some(XsdDatatypeKind::Token)
4206        );
4207        assert_eq!(
4208            parse_datatype_kind("xs:language"),
4209            Some(XsdDatatypeKind::Language)
4210        );
4211        assert_eq!(parse_datatype_kind("xs:Name"), Some(XsdDatatypeKind::Name));
4212        assert_eq!(
4213            parse_datatype_kind("xs:NCName"),
4214            Some(XsdDatatypeKind::NCName)
4215        );
4216        assert_eq!(parse_datatype_kind("xs:ID"), Some(XsdDatatypeKind::Id));
4217        assert_eq!(
4218            parse_datatype_kind("xs:IDREF"),
4219            Some(XsdDatatypeKind::Idref)
4220        );
4221        assert_eq!(
4222            parse_datatype_kind("xs:integer"),
4223            Some(XsdDatatypeKind::Integer)
4224        );
4225        assert_eq!(parse_datatype_kind("xs:long"), Some(XsdDatatypeKind::Long));
4226        assert_eq!(parse_datatype_kind("xs:int"), Some(XsdDatatypeKind::Int));
4227        assert_eq!(
4228            parse_datatype_kind("xs:short"),
4229            Some(XsdDatatypeKind::Short)
4230        );
4231        assert_eq!(parse_datatype_kind("xs:byte"), Some(XsdDatatypeKind::Byte));
4232        assert_eq!(
4233            parse_datatype_kind("xs:positiveInteger"),
4234            Some(XsdDatatypeKind::PositiveInteger)
4235        );
4236        assert_eq!(
4237            parse_datatype_kind("xs:negativeInteger"),
4238            Some(XsdDatatypeKind::NegativeInteger)
4239        );
4240        assert_eq!(parse_datatype_kind("unknown"), None);
4241    }
4242}