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