libxml_rs/abi/exports_schema.rs
1//! exports_schema — C ABI exports for the XML Schema family.
2//!
3//! Implements the public ABI of the upstream headers `xmlschemas.h`,
4//! `xmlschemastypes.h` and `schematron.h` (family closure 11.1-I).
5//!
6//! The internal XSD engine lives in `src/xml/schemas` (`XsdSchema`,
7//! `xsd_parse`, `xsd_validate`, `xsd_validate_datatype`). It powers
8//! `xmllint --schema` and is oracle-verified, so every function here that
9//! needs real schema behavior is wrapped around that engine. Datatype-value
10//! machinery (`xmlSchemaVal`, `xmlSchemaFacet`, built-in `xmlSchemaType`
11//! descriptors) does not exist in the internal engine and is provided here
12//! as small `repr(C)` structs allocated through the crate allocator.
13//!
14//! # UPSTREAM-PARITY
15//!
16//! All 57 functions below follow the exact oracle signatures. Behavior notes:
17//!
18//! - Parser/validation context *state* (error callbacks, options, filename,
19//! locator) is stored in side registries keyed by context address, because
20//! the internal engine's `xmlSchemaNewParserCtxt`/`xmlSchemaNewValidCtxt`
21//! (defined in `src/xml/schemas/mod.rs`) box the schema/validation structs
22//! directly and cannot be extended without breaking that module's layout.
23//! - `xmlSchemaNewDocParserCtxt` boxes a parsed `XsdSchema`, matching the
24//! internal engine convention that `xmlSchemaParse` returns its context
25//! as the schema pointer (`schema == pctxt`).
26//! - Functions requiring streaming/SAX interception the internal DOM engine
27//! cannot provide (`xmlSchemaSAXPlug`, `xmlSchemaValidateStream` without a
28//! readable input buffer) are simplified and documented inline.
29//!
30//! # Upstream contract
31//!
32//! Parity target is upstream `xmlschemas.c`, `xmlschemastypes.c` and
33//! `schematron.c` (libxml2 2.15.3) with the `xmlschemas.h`/
34//! `xmlschemastypes.h`/`schematron.h` signatures; R-000165 closed the xsd
35//! export gaps and R-000168 (11.1-U) recorded the platform-conditional
36//! surface (c_ulong-vs-u64 width bugs in `xmlSchemaValidateFacetWhtsp` were
37//! fixed by that residual).
38//!
39//! # Conceptual behavior
40//!
41//! This module implements the XML Schema ABI: schema parse/validate contexts,
42//! datatype value/facet machinery (`xmlSchemaVal`, `xmlSchemaFacet`, built-in
43//! `xmlSchemaType` descriptors), the white-space normalization helpers and the
44//! SAX-plug streaming entry points. Real schema behavior wraps the internal
45//! `src/xml/schemas` engine (which powers `xmllint --schema`); the datatype
46//! descriptors are provided here as small `repr(C)` structs.
47//!
48//! # Ownership & safety invariants
49//!
50//! Schema/validation contexts are caller-owned (freed with `xmlSchemaFree`/
51//! `xmlSchemaFreeParserCtxt`/`xmlSchemaFreeValidCtxt`); datatype values and
52//! facet objects are xml-allocator objects freed with `xmlSchemaFreeValue`/
53//! `xmlSchemaFreeFacet`; the side registries for context state live as long
54//! as the owning context. R-000168 fixed word-size-dependent typing so the
55//! mirror is correct on 32-bit/arm64.
56//!
57//! # Historical quirks & epochs
58//!
59//! XML Schema support was added in the 2.6 `validation_era` (HISTORY.md) and
60//! the schema ABI has been stable since; R-000165 (11.1-O) added the missing
61//! schema symbols (e.g. `xmlSchemaSetResourceLoader`) and R-000168 (11.1-U)
62//! audited the platform-conditioned families.
63//!
64//! # Deliberate oddities
65//!
66//! The streaming/SAX entry points that the internal DOM engine cannot provide
67//! (`xmlSchemaSAXPlug`, `xmlSchemaValidateStream`) are simplified with the
68//! divergence documented inline per function — deliberate, not silent.
69//!
70//! # Proving courts
71//!
72//! The CLI-XMLLINT XSD cases, the data-ABI schema probes and the
73//! DSO-LOADER/HEADER-COMPILE courts cover this module; the schemas unit tests
74//! run under cargo test.
75//!
76//! # Tempting simplifications that would break parity
77//!
78//! A tempting simplification is to type the datatype machinery with platform
79//! `c_ulong` everywhere — R-000168 proved that produces word-size-dependent
80//! bugs (the facet-whitespace probe caught the width error); the mirror must
81//! stay fixed-width. Another shortcut, dropping the built-in type descriptors,
82//! would break `xmlSchemaGetBuiltInType` consumers that enumerate the
83//! predefined datatypes.
84
85#![allow(
86 missing_docs,
87 non_snake_case,
88 non_camel_case_types,
89 non_upper_case_globals
90)]
91#![allow(clippy::missing_safety_doc)]
92#![allow(clippy::not_unsafe_ptr_arg_deref)]
93#![allow(missing_debug_implementations)]
94
95// SAFETY-SCOPE: EXPORT-SCHEMA-MECHANICAL-001
96// (11.1-Z.3 proof scope, classified-generated) — this module is the
97// mechanical extern-"C" export surface: every `unsafe` block in it is
98// the documented indirection/registry-access pattern whose validity
99// rests on the upstream C contract, and the exported signatures are
100// machine-measured by the ABI-FUNCTION-SIGNATURE and DSO-LOADER
101// courts and the C-API differential probes. The safety contract of
102// each export is stated in its own doc comment; this scope covers the
103// mechanical wrappers' unsafe blocks.
104
105use core::ffi::c_void;
106use core::ptr;
107use once_cell::sync::Lazy;
108use parking_lot::Mutex;
109use std::collections::HashMap;
110use std::ffi::{CStr, CString};
111use std::os::raw::{c_char, c_int, c_ulong};
112
113use crate::abi::allocator::{xmlFreeImpl, xmlMemStrdupImpl};
114use crate::abi::callbacks::{xmlStructuredErrorFunc, xmlValidityErrorFunc, xmlValidityWarningFunc};
115use crate::abi::structs::{_xmlDoc, _xmlError, _xmlNode, _xmlParserInputBuffer, _xmlSAXHandler};
116use crate::abi::types::{
117 xmlChar, xmlCharEncoding, xmlErrorLevel, XML_FROM_SCHEMASP, XML_FROM_SCHEMASV,
118};
119use crate::xml::schemas::{
120 xsd_parse, xsd_validate, xsd_validate_datatype, xsd_validate_facet, XsdDatatypeKind, XsdSchema,
121 XsdValidCtxt,
122};
123use crate::xml::schematron::schematron_parse;
124
125// ═══════════════════════════════════════════════════════════════════════════════
126// Opaque upstream types
127// ═══════════════════════════════════════════════════════════════════════════════
128
129// These mirror the forward-declared opaque structs in the upstream headers
130// (`typedef struct _xmlSchema xmlSchema;` etc.). They are only ever used
131// through pointers, so uninhabited enums are ABI-identical to the opaque
132// C structs.
133
134pub enum xmlSchema {}
135pub enum xmlSchemaParserCtxt {}
136pub enum xmlSchemaValidCtxt {}
137pub enum xmlSchemaVal {}
138pub enum xmlSchemaFacet {}
139pub enum xmlSchemaType {}
140pub enum xmlSchemaWildcard {}
141pub enum xmlSchemaSAXPlugStruct {}
142pub enum xmlSchematronParserCtxt {}
143pub enum xmlSchematronValidCtxt {}
144
145/// `int (*xmlSchemaValidityLocatorFunc)(void *ctx, const char **file, unsigned long *line);`
146/// (xmlschemas.h)
147pub type xmlSchemaValidityLocatorFunc =
148 unsafe extern "C" fn(ctx: *mut c_void, file: *mut *const c_char, line: *mut c_ulong) -> c_int;
149
150// ═══════════════════════════════════════════════════════════════════════════════
151// Internal representations (allocated with Box / the crate allocator)
152// ═══════════════════════════════════════════════════════════════════════════════
153
154/// An `xmlSchemaVal` — a typed simple value.
155///
156/// `value`/`ns` are NUL-terminated heap strings (xmlMalloc'd). List values
157/// (NMTOKENS, IDREFS, ENTITIES) are chained through `next`.
158#[repr(C)]
159struct XsdVal {
160 val_type: c_int, // xmlSchemaValType
161 value: *mut xmlChar, // canonical/lexical value
162 ns: *mut xmlChar, // namespace URI for QName/NOTATION
163 next: *mut XsdVal, // next item for list values
164}
165
166/// An `xmlSchemaFacet` — a facet declaration.
167#[repr(C)]
168struct XsdFacet {
169 facet_type: c_int, // xmlSchemaFacetType (1000..1011)
170 value: *mut xmlChar,
171 next: *mut XsdFacet,
172}
173
174/// A built-in `xmlSchemaType` descriptor (static, registry-owned).
175#[repr(C)]
176struct XsdType {
177 val_type: c_int, // xmlSchemaValType
178 item_type: *mut XsdType, // item type for the built-in list types
179 name: *const c_char, // static type name ("string", "int", ...)
180}
181
182/// State kept for a parser context (side registry).
183#[derive(Default, Clone, Copy)]
184struct ParserState {
185 err: Option<xmlValidityErrorFunc>,
186 warn: Option<xmlValidityWarningFunc>,
187 ctx: usize,
188 serror: Option<xmlStructuredErrorFunc>,
189 sctx: usize,
190 resource_loader: Option<crate::abi::callbacks::xmlResourceLoader>,
191 resource_ctxt: usize,
192}
193
194/// State kept for a validation context (side registry).
195#[derive(Default, Clone, Copy)]
196struct ValidState {
197 err: Option<xmlValidityErrorFunc>,
198 warn: Option<xmlValidityWarningFunc>,
199 ctx: usize,
200 serror: Option<xmlStructuredErrorFunc>,
201 sctx: usize,
202 options: c_int,
203 filename: usize, // raw const char* (upstream stores the pointer, not a copy)
204 locator: Option<xmlSchemaValidityLocatorFunc>,
205 locator_ctx: usize,
206}
207
208/// State kept for a Schematron validation context (side registry).
209#[derive(Default, Clone, Copy)]
210struct SchematronValidState {
211 serror: Option<xmlStructuredErrorFunc>,
212 sctx: usize,
213}
214
215/// The SAX plug allocated by `xmlSchemaSAXPlug`.
216#[repr(C)]
217struct XsdSaxPlug {
218 sax: *const _xmlSAXHandler,
219 user_data: *mut c_void,
220}
221
222static PARSER_STATES: Lazy<Mutex<HashMap<usize, ParserState>>> =
223 Lazy::new(|| Mutex::new(HashMap::new()));
224static VALID_STATES: Lazy<Mutex<HashMap<usize, ValidState>>> =
225 Lazy::new(|| Mutex::new(HashMap::new()));
226static SCHEMATRON_VALID_STATES: Lazy<Mutex<HashMap<usize, SchematronValidState>>> =
227 Lazy::new(|| Mutex::new(HashMap::new()));
228static TYPE_REGISTRY: Lazy<Mutex<HashMap<c_int, usize>>> = Lazy::new(|| Mutex::new(HashMap::new()));
229
230/// Read the validation options (xmlSchemaSetValidOptions) registered for a
231/// validation context in the side registry. The schema engine consults them
232/// when deciding whether to create default/fixed attributes on the instance
233/// (XML_SCHEMA_VAL_VC_I_CREATE).
234pub(crate) fn valid_ctxt_options(ctxt_addr: usize) -> c_int {
235 let guard = VALID_STATES.lock();
236 guard.get(&ctxt_addr).map(|s| s.options).unwrap_or(0)
237}
238
239// ═══════════════════════════════════════════════════════════════════════════════
240// Constants (upstream values)
241// ═══════════════════════════════════════════════════════════════════════════════
242
243// xmlSchemaValType (schemasInternals.h)
244const VAL_UNKNOWN: c_int = 0;
245const VAL_STRING: c_int = 1;
246const VAL_NORMSTRING: c_int = 2;
247const VAL_DECIMAL: c_int = 3;
248const VAL_TIME: c_int = 4;
249const VAL_GDAY: c_int = 5;
250const VAL_GMONTH: c_int = 6;
251const VAL_GMONTHDAY: c_int = 7;
252const VAL_GYEAR: c_int = 8;
253const VAL_GYEARMONTH: c_int = 9;
254const VAL_DATE: c_int = 10;
255const VAL_DATETIME: c_int = 11;
256const VAL_DURATION: c_int = 12;
257const VAL_FLOAT: c_int = 13;
258const VAL_DOUBLE: c_int = 14;
259const VAL_BOOLEAN: c_int = 15;
260const VAL_TOKEN: c_int = 16;
261const VAL_LANGUAGE: c_int = 17;
262const VAL_NMTOKEN: c_int = 18;
263const VAL_NMTOKENS: c_int = 19;
264const VAL_NAME: c_int = 20;
265const VAL_QNAME: c_int = 21;
266const VAL_NCNAME: c_int = 22;
267const VAL_ID: c_int = 23;
268const VAL_IDREF: c_int = 24;
269const VAL_IDREFS: c_int = 25;
270const VAL_ENTITY: c_int = 26;
271const VAL_ENTITIES: c_int = 27;
272const VAL_NOTATION: c_int = 28;
273const VAL_ANYURI: c_int = 29;
274const VAL_INTEGER: c_int = 30;
275const VAL_NPINTEGER: c_int = 31;
276const VAL_NINTEGER: c_int = 32;
277const VAL_NNINTEGER: c_int = 33;
278const VAL_PINTEGER: c_int = 34;
279const VAL_INT: c_int = 35;
280const VAL_UINT: c_int = 36;
281const VAL_LONG: c_int = 37;
282const VAL_ULONG: c_int = 38;
283const VAL_SHORT: c_int = 39;
284const VAL_USHORT: c_int = 40;
285const VAL_BYTE: c_int = 41;
286const VAL_UBYTE: c_int = 42;
287const VAL_HEXBINARY: c_int = 43;
288const VAL_BASE64BINARY: c_int = 44;
289const VAL_ANYTYPE: c_int = 45;
290const VAL_ANYSIMPLETYPE: c_int = 46;
291
292// xmlSchemaFacetType (schemasInternals.h)
293const FACET_MININCLUSIVE: c_int = 1000;
294const FACET_MINEXCLUSIVE: c_int = 1001;
295const FACET_MAXINCLUSIVE: c_int = 1002;
296const FACET_MAXEXCLUSIVE: c_int = 1003;
297const FACET_TOTALDIGITS: c_int = 1004;
298const FACET_FRACTIONDIGITS: c_int = 1005;
299const FACET_PATTERN: c_int = 1006;
300const FACET_ENUMERATION: c_int = 1007;
301const FACET_WHITESPACE: c_int = 1008;
302const FACET_LENGTH: c_int = 1009;
303const FACET_MAXLENGTH: c_int = 1010;
304const FACET_MINLENGTH: c_int = 1011;
305
306// xmlSchemaWhitespaceValueType (xmlschemastypes.h)
307const WS_PRESERVE: c_int = 1;
308const WS_REPLACE: c_int = 2;
309const WS_COLLAPSE: c_int = 3;
310
311// xmlSchemaValidOptions (xmlschemas.h)
312const VAL_VC_I_CREATE: c_int = 1 << 0;
313const VAL_XSI_ASSEMBLE: c_int = 1 << 1;
314const VAL_OPTIONS_MASK: c_int = VAL_VC_I_CREATE | VAL_XSI_ASSEMBLE;
315
316/// The W3C XML Schema namespace.
317const XSD_NS: &[u8] = b"http://www.w3.org/2001/XMLSchema";
318
319// ═══════════════════════════════════════════════════════════════════════════════
320// Small helpers
321// ═══════════════════════════════════════════════════════════════════════════════
322
323/// Read a NUL-terminated C string as UTF-8 lossy.
324///
325/// # SAFETY
326///
327/// - `s` must be a valid NUL-terminated C string or NULL.
328unsafe fn cstr_to_str(s: *const c_char) -> Option<String> {
329 if s.is_null() {
330 return None;
331 }
332 // SAFETY: Caller guarantees a valid NUL-terminated C string.
333 let c = unsafe { CStr::from_ptr(s) };
334 Some(String::from_utf8_lossy(c.to_bytes()).to_string())
335}
336
337/// Compare a C string against a byte slice (no trailing NUL in the slice).
338///
339/// # SAFETY
340///
341/// - `s` must be a valid NUL-terminated C string or NULL.
342const unsafe fn cstr_eq(s: *const c_char, bytes: &[u8]) -> bool {
343 if s.is_null() {
344 return false;
345 }
346 // SAFETY: Caller guarantees a valid NUL-terminated C string.
347 unsafe {
348 let mut i = 0usize;
349 while i < bytes.len() {
350 if *s.add(i) as u8 != bytes[i] {
351 return false;
352 }
353 i += 1;
354 }
355 *s.add(i) == 0
356 }
357}
358
359/// Duplicate a C string into heap memory owned by the caller.
360///
361/// Returns NULL when `s` is NULL.
362unsafe fn dup_cstr(s: *const c_char) -> *mut c_char {
363 if s.is_null() {
364 return ptr::null_mut();
365 }
366 // SAFETY: xmlMemStrdup requires a valid C string; caller guarantees it.
367 unsafe { xmlMemStrdupImpl(s) as *mut c_char }
368}
369
370const fn is_whitespace(c: char) -> bool {
371 c == ' ' || c == '\t' || c == '\n' || c == '\r'
372}
373
374/// Replace whitespace characters with spaces (upstream `xmlSchemaWhiteSpaceReplace`).
375fn whitespace_replace(s: &str) -> String {
376 s.chars()
377 .map(|c| if is_whitespace(c) { ' ' } else { c })
378 .collect()
379}
380
381/// Collapse whitespace: trim and replace internal runs with single spaces
382/// (upstream `xmlSchemaCollapseString`).
383fn whitespace_collapse(s: &str) -> String {
384 let mut out = String::with_capacity(s.len());
385 let mut in_run = false;
386 for c in s.chars() {
387 if is_whitespace(c) {
388 in_run = true;
389 } else {
390 if in_run && !out.is_empty() {
391 out.push(' ');
392 }
393 in_run = false;
394 out.push(c);
395 }
396 }
397 out
398}
399
400/// Apply one of the `xmlSchemaWhitespaceValueType` transformations.
401fn apply_ws(s: &str, ws: c_int) -> String {
402 match ws {
403 WS_REPLACE => whitespace_replace(s),
404 WS_COLLAPSE => whitespace_collapse(s),
405 _ => s.to_string(),
406 }
407}
408
409/// Map an `xmlSchemaValType` to the internal `XsdDatatypeKind`.
410const fn val_type_to_kind(val_type: c_int) -> Option<XsdDatatypeKind> {
411 Some(match val_type {
412 VAL_STRING | VAL_ANYTYPE | VAL_ANYSIMPLETYPE => XsdDatatypeKind::String,
413 VAL_NORMSTRING => XsdDatatypeKind::NormalizedString,
414 VAL_DECIMAL => XsdDatatypeKind::Decimal,
415 VAL_TIME => XsdDatatypeKind::Time,
416 VAL_GDAY => XsdDatatypeKind::GDay,
417 VAL_GMONTH => XsdDatatypeKind::GMonth,
418 VAL_GMONTHDAY => XsdDatatypeKind::GMonthDay,
419 VAL_GYEAR => XsdDatatypeKind::GYear,
420 VAL_GYEARMONTH => XsdDatatypeKind::GYearMonth,
421 VAL_DATE => XsdDatatypeKind::Date,
422 VAL_DATETIME => XsdDatatypeKind::DateTime,
423 VAL_DURATION => XsdDatatypeKind::Duration,
424 VAL_FLOAT => XsdDatatypeKind::Float,
425 VAL_DOUBLE => XsdDatatypeKind::Double,
426 VAL_BOOLEAN => XsdDatatypeKind::Boolean,
427 VAL_TOKEN => XsdDatatypeKind::Token,
428 VAL_LANGUAGE => XsdDatatypeKind::Language,
429 VAL_NMTOKEN => XsdDatatypeKind::Nmtoken,
430 VAL_NMTOKENS => XsdDatatypeKind::Nmtokens,
431 VAL_NAME => XsdDatatypeKind::Name,
432 VAL_QNAME => XsdDatatypeKind::QName,
433 VAL_NCNAME => XsdDatatypeKind::NCName,
434 VAL_ID => XsdDatatypeKind::Id,
435 VAL_IDREF => XsdDatatypeKind::Idref,
436 VAL_IDREFS => XsdDatatypeKind::Idrefs,
437 VAL_ENTITY => XsdDatatypeKind::Entity,
438 VAL_ENTITIES => XsdDatatypeKind::Entities,
439 VAL_NOTATION => XsdDatatypeKind::Notation,
440 VAL_ANYURI => XsdDatatypeKind::AnyURI,
441 VAL_INTEGER => XsdDatatypeKind::Integer,
442 VAL_NPINTEGER => XsdDatatypeKind::NonPositiveInteger,
443 VAL_NINTEGER => XsdDatatypeKind::NegativeInteger,
444 VAL_NNINTEGER => XsdDatatypeKind::NonNegativeInteger,
445 VAL_PINTEGER => XsdDatatypeKind::PositiveInteger,
446 VAL_INT => XsdDatatypeKind::Int,
447 VAL_UINT => XsdDatatypeKind::UnsignedInt,
448 VAL_LONG => XsdDatatypeKind::Long,
449 VAL_ULONG => XsdDatatypeKind::UnsignedLong,
450 VAL_SHORT => XsdDatatypeKind::Short,
451 VAL_USHORT => XsdDatatypeKind::UnsignedShort,
452 VAL_BYTE => XsdDatatypeKind::Byte,
453 VAL_UBYTE => XsdDatatypeKind::UnsignedByte,
454 VAL_HEXBINARY => XsdDatatypeKind::HexBinary,
455 VAL_BASE64BINARY => XsdDatatypeKind::Base64Binary,
456 _ => return None,
457 })
458}
459
460/// Map an `xmlSchemaFacetType` to the internal facet kind.
461const fn facet_type_to_kind(facet_type: c_int) -> Option<XsdDatatypeKind> {
462 Some(match facet_type {
463 FACET_MININCLUSIVE => XsdDatatypeKind::FacetMinInclusive,
464 FACET_MINEXCLUSIVE => XsdDatatypeKind::FacetMinExclusive,
465 FACET_MAXINCLUSIVE => XsdDatatypeKind::FacetMaxInclusive,
466 FACET_MAXEXCLUSIVE => XsdDatatypeKind::FacetMaxExclusive,
467 FACET_TOTALDIGITS => XsdDatatypeKind::FacetTotalDigits,
468 FACET_FRACTIONDIGITS => XsdDatatypeKind::FacetFractionDigits,
469 FACET_PATTERN => XsdDatatypeKind::FacetPattern,
470 FACET_ENUMERATION => XsdDatatypeKind::FacetEnumeration,
471 FACET_WHITESPACE => XsdDatatypeKind::FacetWhiteSpace,
472 FACET_LENGTH => XsdDatatypeKind::FacetLength,
473 FACET_MAXLENGTH => XsdDatatypeKind::FacetMaxLength,
474 FACET_MINLENGTH => XsdDatatypeKind::FacetMinLength,
475 _ => return None,
476 })
477}
478
479/// NUL-terminated static name for a built-in `xmlSchemaValType`.
480const fn type_name(val_type: c_int) -> Option<&'static [u8]> {
481 Some(match val_type {
482 VAL_STRING => b"string\0",
483 VAL_NORMSTRING => b"normalizedString\0",
484 VAL_DECIMAL => b"decimal\0",
485 VAL_TIME => b"time\0",
486 VAL_GDAY => b"gDay\0",
487 VAL_GMONTH => b"gMonth\0",
488 VAL_GMONTHDAY => b"gMonthDay\0",
489 VAL_GYEAR => b"gYear\0",
490 VAL_GYEARMONTH => b"gYearMonth\0",
491 VAL_DATE => b"date\0",
492 VAL_DATETIME => b"dateTime\0",
493 VAL_DURATION => b"duration\0",
494 VAL_FLOAT => b"float\0",
495 VAL_DOUBLE => b"double\0",
496 VAL_BOOLEAN => b"boolean\0",
497 VAL_TOKEN => b"token\0",
498 VAL_LANGUAGE => b"language\0",
499 VAL_NMTOKEN => b"NMTOKEN\0",
500 VAL_NMTOKENS => b"NMTOKENS\0",
501 VAL_NAME => b"Name\0",
502 VAL_QNAME => b"QName\0",
503 VAL_NCNAME => b"NCName\0",
504 VAL_ID => b"ID\0",
505 VAL_IDREF => b"IDREF\0",
506 VAL_IDREFS => b"IDREFS\0",
507 VAL_ENTITY => b"ENTITY\0",
508 VAL_ENTITIES => b"ENTITIES\0",
509 VAL_NOTATION => b"NOTATION\0",
510 VAL_ANYURI => b"anyURI\0",
511 VAL_INTEGER => b"integer\0",
512 VAL_NPINTEGER => b"nonPositiveInteger\0",
513 VAL_NINTEGER => b"negativeInteger\0",
514 VAL_NNINTEGER => b"nonNegativeInteger\0",
515 VAL_PINTEGER => b"positiveInteger\0",
516 VAL_INT => b"int\0",
517 VAL_UINT => b"unsignedInt\0",
518 VAL_LONG => b"long\0",
519 VAL_ULONG => b"unsignedLong\0",
520 VAL_SHORT => b"short\0",
521 VAL_USHORT => b"unsignedShort\0",
522 VAL_BYTE => b"byte\0",
523 VAL_UBYTE => b"unsignedByte\0",
524 VAL_HEXBINARY => b"hexBinary\0",
525 VAL_BASE64BINARY => b"base64Binary\0",
526 VAL_ANYTYPE => b"anyType\0",
527 VAL_ANYSIMPLETYPE => b"anySimpleType\0",
528 _ => return None,
529 })
530}
531
532/// Whether an `xmlSchemaValType` is numeric (decimal / integer family / float / double).
533const fn is_numeric_type(val_type: c_int) -> bool {
534 matches!(
535 val_type,
536 VAL_DECIMAL
537 | VAL_FLOAT
538 | VAL_DOUBLE
539 | VAL_INTEGER
540 | VAL_NPINTEGER
541 | VAL_NINTEGER
542 | VAL_NNINTEGER
543 | VAL_PINTEGER
544 | VAL_INT
545 | VAL_UINT
546 | VAL_LONG
547 | VAL_ULONG
548 | VAL_SHORT
549 | VAL_USHORT
550 | VAL_BYTE
551 | VAL_UBYTE
552 )
553}
554
555/// Whether an `xmlSchemaValType` is a built-in list type.
556const fn is_list_type(val_type: c_int) -> bool {
557 matches!(val_type, VAL_NMTOKENS | VAL_IDREFS | VAL_ENTITIES)
558}
559
560/// The item type of a built-in list type (itself otherwise).
561const fn list_item_type(val_type: c_int) -> c_int {
562 match val_type {
563 VAL_NMTOKENS => VAL_NMTOKEN,
564 VAL_IDREFS => VAL_IDREF,
565 VAL_ENTITIES => VAL_ENTITY,
566 other => other,
567 }
568}
569
570/// Canonicalize a decimal string (no leading/trailing zeros, "-" only for
571/// negative non-zero values).
572fn canonical_decimal(s: &str) -> String {
573 let mut s = s.trim();
574 let neg = s.starts_with('-');
575 if s.starts_with('+') || s.starts_with('-') {
576 s = &s[1..];
577 }
578 let (int_part, frac_part) = match s.find('.') {
579 Some(i) => (&s[..i], &s[i + 1..]),
580 None => (s, ""),
581 };
582 let int_trimmed = int_part.trim_start_matches('0');
583 let int_trimmed = if int_trimmed.is_empty() {
584 "0"
585 } else {
586 int_trimmed
587 };
588 let frac_trimmed = frac_part.trim_end_matches('0');
589 let mut out = String::new();
590 if neg && !(int_trimmed == "0" && frac_trimmed.is_empty()) {
591 out.push('-');
592 }
593 out.push_str(int_trimmed);
594 if !frac_trimmed.is_empty() {
595 out.push('.');
596 out.push_str(frac_trimmed);
597 }
598 out
599}
600
601// ═══════════════════════════════════════════════════════════════════════════════
602// Value allocation / ownership helpers
603// ═══════════════════════════════════════════════════════════════════════════════
604
605/// Create a value node. `value`/`ns` are duplicated into heap memory.
606unsafe fn new_val(val_type: c_int, value: *const c_char, ns: *const c_char) -> *mut XsdVal {
607 // SAFETY: dup_cstr requires valid C strings or NULL; caller guarantees it.
608 unsafe {
609 Box::into_raw(Box::new(XsdVal {
610 val_type,
611 value: dup_cstr(value) as *mut xmlChar,
612 ns: dup_cstr(ns) as *mut xmlChar,
613 next: ptr::null_mut(),
614 }))
615 }
616}
617
618/// Free a value chain (values + their strings).
619unsafe fn free_val_chain(mut cur: *mut XsdVal) {
620 while !cur.is_null() {
621 let next = unsafe { (*cur).next };
622 // SAFETY: value/ns were xmlMalloc'd by new_val; cur is a Box we own.
623 unsafe {
624 if !(*cur).value.is_null() {
625 xmlFreeImpl((*cur).value as *mut c_void);
626 }
627 if !(*cur).ns.is_null() {
628 xmlFreeImpl((*cur).ns as *mut c_void);
629 }
630 drop(Box::from_raw(cur));
631 }
632 cur = next;
633 }
634}
635
636/// Create a value from a Rust string, duplicating into heap memory.
637fn val_from_str(val_type: c_int, value: &str) -> *mut XsdVal {
638 if let Ok(c) = CString::new(value) {
639 // SAFETY: c is a valid C string for the duration of the call.
640 unsafe { new_val(val_type, c.as_ptr(), ptr::null()) }
641 } else {
642 ptr::null_mut()
643 }
644}
645
646/// Create a (possibly list) value from a C string. List types are split on
647/// whitespace into an item chain, matching upstream's list-value layout.
648unsafe fn new_string_value(val_type: c_int, value: *const c_char) -> *mut XsdVal {
649 if value.is_null() {
650 return ptr::null_mut();
651 }
652 let s = unsafe { cstr_to_str(value).unwrap_or_default() };
653 if is_list_type(val_type) {
654 let item_type = list_item_type(val_type);
655 let tokens: Vec<&str> = s.split_whitespace().collect();
656 if tokens.is_empty() {
657 return ptr::null_mut();
658 }
659 let mut head: *mut XsdVal = ptr::null_mut();
660 let mut tail: *mut XsdVal = ptr::null_mut();
661 for tok in tokens {
662 let item = val_from_str(item_type, tok);
663 if item.is_null() {
664 continue;
665 }
666 if head.is_null() {
667 head = item;
668 } else {
669 // SAFETY: tail is a valid node from this loop.
670 unsafe { (*tail).next = item };
671 }
672 tail = item;
673 }
674 head
675 } else {
676 val_from_str(val_type, &s)
677 }
678}
679
680/// Build the built-in type descriptor for `val_type` (registry-owned, leaked).
681fn builtin_type(val_type: c_int) -> *mut XsdType {
682 if val_type <= VAL_UNKNOWN || val_type > VAL_ANYSIMPLETYPE {
683 return ptr::null_mut();
684 }
685 {
686 let reg = TYPE_REGISTRY.lock();
687 if let Some(addr) = reg.get(&val_type) {
688 return *addr as *mut XsdType;
689 }
690 }
691 // Resolve the item type outside the lock to avoid re-entrancy.
692 let item = if is_list_type(val_type) {
693 builtin_type(list_item_type(val_type))
694 } else {
695 ptr::null_mut()
696 };
697 let name = type_name(val_type)
698 .map(|b| b.as_ptr() as *const c_char)
699 .unwrap_or(ptr::null());
700 let t = Box::into_raw(Box::new(XsdType {
701 val_type,
702 item_type: item,
703 name,
704 }));
705 let mut reg = TYPE_REGISTRY.lock();
706 if let Some(existing) = reg.get(&val_type) {
707 // Another thread won the race; drop our duplicate.
708 // SAFETY: t is a Box we own and nobody else can see yet.
709 unsafe { drop(Box::from_raw(t)) };
710 return *existing as *mut XsdType;
711 }
712 reg.insert(val_type, t as usize);
713 t
714}
715
716// ═══════════════════════════════════════════════════════════════════════════════
717// Error dispatch helpers
718// ═══════════════════════════════════════════════════════════════════════════════
719
720/// Dispatch accumulated validation errors to the callbacks registered on a
721/// validation context (both the plain and the structured handler).
722///
723/// # SAFETY
724///
725/// - `ctxt_addr` must be the address of an `XsdValidCtxt` that a caller
726/// registered state for; otherwise this is a no-op.
727pub(crate) unsafe fn dispatch_valid_errors(ctxt_addr: usize, errors: &[String]) {
728 let state = {
729 let guard = VALID_STATES.lock();
730 guard.get(&ctxt_addr).copied()
731 };
732 let Some(state) = state else { return };
733 if state.err.is_none() && state.serror.is_none() {
734 return;
735 }
736 for msg in errors {
737 // UPSTREAM-PARITY: upstream schema validity messages end with a
738 // newline (xmlSchemaErr -> xmlSchemaVErr with "\n") — PHP's libxml
739 // error handler only RAISES messages that carry a trailing newline
740 // (php_libxml_internal_error_handler_ex sets output only when it
741 // strips one), so a newline-less message would be swallowed.
742 let text = if msg.ends_with('\n') {
743 msg.clone()
744 } else {
745 format!("{}\n", msg)
746 };
747 let Ok(cmsg) = CString::new(text.as_str()) else {
748 continue;
749 };
750 if let Some(err) = state.err {
751 // SAFETY: The caller supplied this callback in xmlSchemaSetValidErrors.
752 unsafe { err(state.ctx as *mut c_void, cmsg.as_ptr()) };
753 }
754 if let Some(serror) = state.serror {
755 // SAFETY: The caller supplied this callback in
756 // xmlSchemaSetValidStructuredErrors.
757 let mut e: _xmlError = unsafe { std::mem::zeroed() };
758 e.domain = XML_FROM_SCHEMASV;
759 e.code = 0;
760 e.message = cmsg.as_ptr() as *mut c_char;
761 e.level = xmlErrorLevel::XML_ERR_ERROR as c_int;
762 e.file = state.filename as *mut c_char;
763 e.line = 0;
764 unsafe { serror(state.sctx as *mut c_void, &e) };
765 }
766 }
767}
768
769/// Dispatch a single error message through a parser context's callbacks.
770///
771/// The message is NUL-terminated for the C callback. UPSTREAM-PARITY: schema
772/// parser diagnostics end with a newline; PHP's libxml error handler only
773/// RAISES messages that carry a trailing newline
774/// (php_libxml_internal_error_handler_ex), so a newline is appended when
775/// missing.
776///
777/// # SAFETY
778///
779/// - `ctxt_addr` must be the address of a parser context that a caller
780/// registered state for; otherwise this is a no-op.
781pub(crate) unsafe fn dispatch_parser_error(ctxt_addr: usize, msg: &str) {
782 let state = {
783 let guard = PARSER_STATES.lock();
784 guard.get(&ctxt_addr).copied()
785 };
786 let Some(state) = state else { return };
787 if state.err.is_none() && state.serror.is_none() {
788 return;
789 }
790 let text = if msg.ends_with('\n') {
791 msg.to_string()
792 } else {
793 format!("{}\n", msg)
794 };
795 let Ok(cmsg) = CString::new(text) else { return };
796 if let Some(err) = state.err {
797 // SAFETY: Caller-supplied callback from xmlSchemaSetParserErrors.
798 unsafe { err(state.ctx as *mut c_void, cmsg.as_ptr()) };
799 }
800 if let Some(serror) = state.serror {
801 // SAFETY: Caller-supplied callback from xmlSchemaSetParserStructuredErrors.
802 let mut e: _xmlError = unsafe { std::mem::zeroed() };
803 e.domain = XML_FROM_SCHEMASP;
804 e.code = 0;
805 e.message = cmsg.as_ptr() as *mut c_char;
806 e.level = xmlErrorLevel::XML_ERR_ERROR as c_int;
807 unsafe { serror(state.sctx as *mut c_void, &e) };
808 }
809}
810
811// ═══════════════════════════════════════════════════════════════════════════════
812// Document serialization helpers
813// ═══════════════════════════════════════════════════════════════════════════════
814
815/// Serialize a document to a Rust string.
816///
817/// # SAFETY
818///
819/// - `doc` must be a valid `_xmlDoc` pointer.
820unsafe fn doc_to_string(doc: *mut _xmlDoc) -> Option<String> {
821 if doc.is_null() {
822 return None;
823 }
824 let mut mem: *mut xmlChar = ptr::null_mut();
825 let mut size: c_int = 0;
826 // SAFETY: doc is valid; mem/size are writable locals.
827 unsafe { crate::xml::tree::xmlDocDumpFormatMemory(doc, &mut mem, &mut size, 0) };
828 if mem.is_null() {
829 return None;
830 }
831 // SAFETY: mem/size describe the dumped buffer.
832 let slice = unsafe { std::slice::from_raw_parts(mem as *const u8, size as usize) };
833 let out = String::from_utf8_lossy(slice).to_string();
834 // SAFETY: mem was allocated by the dumper; xmlFree is the matching free.
835 unsafe { xmlFreeImpl(mem as *mut c_void) };
836 Some(out)
837}
838
839/// Serialize a single element (subtree) to a Rust string.
840///
841/// # SAFETY
842///
843/// - `node` must be a valid `_xmlNode` pointer.
844unsafe fn node_to_string(node: *mut _xmlNode) -> Option<String> {
845 if node.is_null() {
846 return None;
847 }
848 // xmlBufferCreate returns a valid buffer or NULL.
849 let buf = crate::abi::exports_xml2::xmlBufferCreate();
850 if buf.is_null() {
851 return None;
852 }
853 // SAFETY: node and buf are valid; node->doc may be NULL, which the dumper tolerates.
854 unsafe { crate::xml::tree::xmlNodeDump(buf, (*node).doc, node, 0, 0) };
855 // buf is valid; content/length are readable fields.
856 let content = crate::abi::exports_xml2::xmlBufferContent(buf);
857 let len = crate::abi::exports_xml2::xmlBufferLength(buf);
858 if content.is_null() || len <= 0 {
859 // buf was created by xmlBufferCreate; xmlBufferFree matches.
860 crate::abi::exports_xml2::xmlBufferFree(buf);
861 return None;
862 }
863 // SAFETY: content/len describe the dumped element bytes.
864 let slice = unsafe { std::slice::from_raw_parts(content as *const u8, len as usize) };
865 let out = String::from_utf8_lossy(slice).to_string();
866 // buf was created by xmlBufferCreate; xmlBufferFree matches.
867 crate::abi::exports_xml2::xmlBufferFree(buf);
868 Some(out)
869}
870
871/// Reset the accumulated error state of a validation context.
872///
873/// # SAFETY
874///
875/// - `ctxt` must be a valid `XsdValidCtxt` pointer.
876unsafe fn reset_valid_ctxt(ctxt: *mut xmlSchemaValidCtxt) {
877 // SAFETY: ctxt is the internal XsdValidCtxt box.
878 let vc = unsafe { &mut *(ctxt as *mut XsdValidCtxt) };
879 vc.errors.clear();
880 vc.nb_errors = 0;
881}
882
883/// Parse an XML string into a doc and validate it with the internal engine,
884/// dispatching any errors through the context's callbacks.
885///
886/// Returns the number of validation errors, or -1 on internal error.
887///
888/// # SAFETY
889///
890/// - `ctxt` must be a valid `XsdValidCtxt` (as created by the internal
891/// `xmlSchemaNewValidCtxt`).
892unsafe fn validate_doc_string(ctxt: *mut xmlSchemaValidCtxt, xml: &str) -> c_int {
893 if ctxt.is_null() {
894 return -1;
895 }
896 // Reset so xmlSchemaIsValid reflects this run even when the internal
897 // engine only records errors on failure.
898 unsafe { reset_valid_ctxt(ctxt) };
899 // SAFETY: xmlReadMemory reads `xml` for `len` bytes; the pointer is valid.
900 let doc = unsafe {
901 crate::abi::exports_xml2::xmlReadMemory(
902 xml.as_ptr() as *const c_char,
903 xml.len() as c_int,
904 c"doc.xml".as_ptr() as *const c_char,
905 ptr::null(),
906 0,
907 )
908 };
909 if doc.is_null() {
910 // Mirror upstream xmlSchemaValidateFile: report and bail out with -1.
911 unsafe {
912 dispatch_valid_errors(ctxt as usize, &["Document is not well-formed".to_string()]);
913 }
914 return -1;
915 }
916 // SAFETY: ctxt is a valid XsdValidCtxt; doc is a valid _xmlDoc.
917 let ret = unsafe { crate::xml::schemas::xmlSchemaValidateDoc(ctxt as *mut c_void, doc) };
918 // SAFETY: doc was created by xmlReadMemory; xmlFreeDoc matches.
919 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
920 if ret != 0 {
921 // SAFETY: ctxt is the internal XsdValidCtxt whose errors were filled
922 // by xmlSchemaValidateDoc on failure.
923 let errors = unsafe { (*(ctxt as *mut XsdValidCtxt)).errors.clone() };
924 unsafe { dispatch_valid_errors(ctxt as usize, &errors) };
925 }
926 ret
927}
928
929// ═══════════════════════════════════════════════════════════════════════════════
930// xmlschemastypes.h — initialization / built-in types
931// ═══════════════════════════════════════════════════════════════════════════════
932
933/// Initialize the XML Schema datatype machinery.
934///
935/// # UPSTREAM-PARITY
936///
937/// ```c
938/// int xmlSchemaInitTypes(void);
939/// ```
940///
941/// Returns 0 on success. The internal engine initializes lazily; this
942/// eagerly creates the built-in type descriptors so subsequent
943/// `xmlSchemaGetBuiltInType` calls never fail.
944#[no_mangle]
945pub extern "C" fn xmlSchemaInitTypes() -> c_int {
946 // Force creation of the full built-in type table.
947 for t in VAL_STRING..=VAL_ANYSIMPLETYPE {
948 builtin_type(t);
949 }
950 0
951}
952
953/// Clean up the XML Schema datatype machinery.
954///
955/// # UPSTREAM-PARITY
956///
957/// ```c
958/// void xmlSchemaCleanupTypes(void);
959/// ```
960///
961/// Upstream frees its static tables at shutdown. The built-in descriptors
962/// here are intentionally leaked statics (their addresses are handed out to
963/// callers and must remain valid), so this is a no-op for ABI compatibility.
964#[no_mangle]
965pub const extern "C" fn xmlSchemaCleanupTypes() {
966 // No-op: built-in type descriptors are process-lifetime statics.
967}
968
969/// Look up a predefined (built-in) type by name and namespace.
970///
971/// # UPSTREAM-PARITY
972///
973/// ```c
974/// xmlSchemaType * xmlSchemaGetPredefinedType(const xmlChar *name, const xmlChar *ns);
975/// ```
976///
977/// Matches upstream: both `name` and `ns` must be non-NULL and match the
978/// built-in type's name and the XML Schema namespace
979/// (`http://www.w3.org/2001/XMLSchema`).
980///
981/// # SAFETY
982///
983/// - `name`/`ns` must be valid NUL-terminated C strings or NULL.
984#[no_mangle]
985pub unsafe extern "C" fn xmlSchemaGetPredefinedType(
986 name: *const xmlChar,
987 ns: *const xmlChar,
988) -> *mut xmlSchemaType {
989 if name.is_null() || ns.is_null() {
990 return ptr::null_mut();
991 }
992 // SAFETY: Caller guarantees valid C strings.
993 let ns_str = unsafe { cstr_to_str(ns as *const c_char) }.unwrap_or_default();
994 if ns_str.as_bytes() != XSD_NS {
995 return ptr::null_mut();
996 }
997 for val_type in VAL_STRING..=VAL_ANYSIMPLETYPE {
998 // SAFETY: type_name returns static NUL-terminated bytes.
999 if let Some(bytes) = type_name(val_type) {
1000 // SAFETY: caller guarantees a valid C string.
1001 if unsafe { cstr_eq(name as *const c_char, &bytes[..bytes.len() - 1]) } {
1002 return builtin_type(val_type) as *mut xmlSchemaType;
1003 }
1004 }
1005 }
1006 ptr::null_mut()
1007}
1008
1009/// Get the built-in type descriptor for an `xmlSchemaValType`.
1010///
1011/// # UPSTREAM-PARITY
1012///
1013/// ```c
1014/// xmlSchemaType * xmlSchemaGetBuiltInType(xmlSchemaValType type);
1015/// ```
1016///
1017/// Returns NULL for `XML_SCHEMAS_UNKNOWN` and out-of-range values.
1018#[no_mangle]
1019pub extern "C" fn xmlSchemaGetBuiltInType(type_: c_int) -> *mut xmlSchemaType {
1020 builtin_type(type_) as *mut xmlSchemaType
1021}
1022
1023/// For a built-in list type, return its item type.
1024///
1025/// # UPSTREAM-PARITY
1026///
1027/// ```c
1028/// xmlSchemaType * xmlSchemaGetBuiltInListSimpleTypeItemType(xmlSchemaType *type);
1029/// ```
1030///
1031/// Returns NULL for non-list types. Implemented via the built-in descriptor
1032/// table (NMTOKENS→NMTOKEN, IDREFS→IDREF, ENTITIES→ENTITY).
1033///
1034/// # SAFETY
1035///
1036/// - `type_` must be a descriptor returned by `xmlSchemaGetBuiltInType`/
1037/// `xmlSchemaGetPredefinedType`, or NULL.
1038#[no_mangle]
1039pub unsafe extern "C" fn xmlSchemaGetBuiltInListSimpleTypeItemType(
1040 type_: *mut xmlSchemaType,
1041) -> *mut xmlSchemaType {
1042 if type_.is_null() {
1043 return ptr::null_mut();
1044 }
1045 // SAFETY: type_ is one of our descriptor pointers.
1046 unsafe { (*(type_ as *mut XsdType)).item_type as *mut xmlSchemaType }
1047}
1048
1049/// Whether `facetType` is a valid facet for the built-in type `type_`.
1050///
1051/// # UPSTREAM-PARITY
1052///
1053/// ```c
1054/// int xmlSchemaIsBuiltInTypeFacet(xmlSchemaType *type, int facetType);
1055/// ```
1056///
1057/// Returns 1 if valid, 0 otherwise. The check is coarse compared to
1058/// upstream's per-type tables: string-family types accept the length/pattern/
1059/// enumeration/whitespace facets, numeric types accept the bound/digit facets,
1060/// and all types accept pattern/enumeration/whitespace.
1061///
1062/// # SAFETY
1063///
1064/// - `type_` must be a descriptor returned by `xmlSchemaGetBuiltInType`/
1065/// `xmlSchemaGetPredefinedType`, or NULL.
1066#[no_mangle]
1067pub unsafe extern "C" fn xmlSchemaIsBuiltInTypeFacet(
1068 type_: *mut xmlSchemaType,
1069 facetType: c_int,
1070) -> c_int {
1071 if type_.is_null() {
1072 return 0;
1073 }
1074 if !(FACET_MININCLUSIVE..=FACET_MINLENGTH).contains(&facetType) {
1075 return 0;
1076 }
1077 // SAFETY: type_ is one of our descriptor pointers.
1078 let val_type = unsafe { (*(type_ as *mut XsdType)).val_type };
1079 let length_facets = matches!(
1080 facetType,
1081 FACET_LENGTH
1082 | FACET_MINLENGTH
1083 | FACET_MAXLENGTH
1084 | FACET_PATTERN
1085 | FACET_ENUMERATION
1086 | FACET_WHITESPACE
1087 );
1088 let numeric_facets = matches!(
1089 facetType,
1090 FACET_MININCLUSIVE
1091 | FACET_MINEXCLUSIVE
1092 | FACET_MAXINCLUSIVE
1093 | FACET_MAXEXCLUSIVE
1094 | FACET_TOTALDIGITS
1095 | FACET_FRACTIONDIGITS
1096 | FACET_PATTERN
1097 | FACET_ENUMERATION
1098 | FACET_WHITESPACE
1099 );
1100 let universal = matches!(
1101 facetType,
1102 FACET_PATTERN | FACET_ENUMERATION | FACET_WHITESPACE
1103 );
1104 if is_numeric_type(val_type) {
1105 if numeric_facets {
1106 1
1107 } else {
1108 0
1109 }
1110 } else if matches!(val_type, VAL_STRING | VAL_NORMSTRING | VAL_TOKEN) || is_list_type(val_type)
1111 {
1112 if length_facets {
1113 1
1114 } else {
1115 0
1116 }
1117 } else if universal {
1118 1
1119 } else {
1120 0
1121 }
1122}
1123
1124// ═══════════════════════════════════════════════════════════════════════════════
1125// xmlschemastypes.h — string whitespace helpers
1126// ═══════════════════════════════════════════════════════════════════════════════
1127
1128/// Replace whitespace characters with spaces and return a new string.
1129///
1130/// # UPSTREAM-PARITY
1131///
1132/// ```c
1133/// xmlChar * xmlSchemaWhiteSpaceReplace(const xmlChar *value);
1134/// ```
1135///
1136/// The result is xmlMalloc'd; the caller must free it with `xmlFree`.
1137///
1138/// # SAFETY
1139///
1140/// - `value` must be a valid NUL-terminated C string or NULL.
1141#[no_mangle]
1142pub unsafe extern "C" fn xmlSchemaWhiteSpaceReplace(value: *const xmlChar) -> *mut xmlChar {
1143 if value.is_null() {
1144 return ptr::null_mut();
1145 }
1146 // SAFETY: Caller guarantees a valid C string.
1147 let s = unsafe { cstr_to_str(value as *const c_char) }.unwrap_or_default();
1148 let out = whitespace_replace(&s);
1149 if let Ok(c) = CString::new(out) {
1150 // SAFETY: dup_cstr copies the string into xmlMalloc'd memory.
1151 unsafe { dup_cstr(c.as_ptr()) as *mut xmlChar }
1152 } else {
1153 ptr::null_mut()
1154 }
1155}
1156
1157/// Collapse whitespace and return a new string.
1158///
1159/// # UPSTREAM-PARITY
1160///
1161/// ```c
1162/// xmlChar * xmlSchemaCollapseString(const xmlChar *value);
1163/// ```
1164///
1165/// The result is xmlMalloc'd; the caller must free it with `xmlFree`.
1166///
1167/// # SAFETY
1168///
1169/// - `value` must be a valid NUL-terminated C string or NULL.
1170#[no_mangle]
1171pub unsafe extern "C" fn xmlSchemaCollapseString(value: *const xmlChar) -> *mut xmlChar {
1172 if value.is_null() {
1173 return ptr::null_mut();
1174 }
1175 // SAFETY: Caller guarantees a valid C string.
1176 let s = unsafe { cstr_to_str(value as *const c_char) }.unwrap_or_default();
1177 let out = whitespace_collapse(&s);
1178 if let Ok(c) = CString::new(out) {
1179 // SAFETY: dup_cstr copies the string into xmlMalloc'd memory.
1180 unsafe { dup_cstr(c.as_ptr()) as *mut xmlChar }
1181 } else {
1182 ptr::null_mut()
1183 }
1184}
1185
1186// ═══════════════════════════════════════════════════════════════════════════════
1187// xmlschemastypes.h — value objects
1188// ═══════════════════════════════════════════════════════════════════════════════
1189
1190/// Create a simple typed value.
1191///
1192/// # UPSTREAM-PARITY
1193///
1194/// ```c
1195/// xmlSchemaVal * xmlSchemaNewStringValue(xmlSchemaValType type, const xmlChar *value);
1196/// ```
1197///
1198/// For the built-in list types (NMTOKENS, IDREFS, ENTITIES) the value is
1199/// split on whitespace into an item chain, matching upstream's layout.
1200///
1201/// # SAFETY
1202///
1203/// - `value` must be a valid NUL-terminated C string or NULL.
1204#[no_mangle]
1205pub unsafe extern "C" fn xmlSchemaNewStringValue(
1206 type_: c_int,
1207 value: *const xmlChar,
1208) -> *mut xmlSchemaVal {
1209 // SAFETY: Delegates to new_string_value with the same contract.
1210 unsafe { new_string_value(type_, value as *const c_char) as *mut xmlSchemaVal }
1211}
1212
1213/// Create a NOTATION value.
1214///
1215/// # UPSTREAM-PARITY
1216///
1217/// ```c
1218/// xmlSchemaVal * xmlSchemaNewNOTATIONValue(const xmlChar *name, const xmlChar *ns);
1219/// ```
1220///
1221/// # SAFETY
1222///
1223/// - `name`/`ns` must be valid NUL-terminated C strings or NULL.
1224#[no_mangle]
1225pub unsafe extern "C" fn xmlSchemaNewNOTATIONValue(
1226 name: *const xmlChar,
1227 ns: *const xmlChar,
1228) -> *mut xmlSchemaVal {
1229 // SAFETY: Delegates to new_val with the same contract.
1230 unsafe {
1231 new_val(VAL_NOTATION, name as *const c_char, ns as *const c_char) as *mut xmlSchemaVal
1232 }
1233}
1234
1235/// Create a QName value.
1236///
1237/// # UPSTREAM-PARITY
1238///
1239/// ```c
1240/// xmlSchemaVal * xmlSchemaNewQNameValue(const xmlChar *namespaceName, const xmlChar *localName);
1241/// ```
1242///
1243/// # SAFETY
1244///
1245/// - `namespaceName`/`localName` must be valid NUL-terminated C strings or NULL.
1246#[no_mangle]
1247pub unsafe extern "C" fn xmlSchemaNewQNameValue(
1248 namespaceName: *const xmlChar,
1249 localName: *const xmlChar,
1250) -> *mut xmlSchemaVal {
1251 // SAFETY: Delegates to new_val with the same contract.
1252 unsafe {
1253 new_val(
1254 VAL_QNAME,
1255 localName as *const c_char,
1256 namespaceName as *const c_char,
1257 ) as *mut xmlSchemaVal
1258 }
1259}
1260
1261/// Deep-copy a value (including list chains).
1262///
1263/// # UPSTREAM-PARITY
1264///
1265/// ```c
1266/// xmlSchemaVal * xmlSchemaCopyValue(xmlSchemaVal *val);
1267/// ```
1268///
1269/// # SAFETY
1270///
1271/// - `val` must be a value created by this module or NULL.
1272#[no_mangle]
1273pub unsafe extern "C" fn xmlSchemaCopyValue(val: *mut xmlSchemaVal) -> *mut xmlSchemaVal {
1274 if val.is_null() {
1275 return ptr::null_mut();
1276 }
1277 // SAFETY: val is one of our XsdVal nodes.
1278 let mut src = val as *mut XsdVal;
1279 let mut head: *mut XsdVal = ptr::null_mut();
1280 let mut tail: *mut XsdVal = ptr::null_mut();
1281 while !src.is_null() {
1282 // SAFETY: each node is a valid XsdVal owned by this module.
1283 let (vtype, vvalue, vns) = unsafe { ((*src).val_type, (*src).value, (*src).ns) };
1284 let node = unsafe { new_val(vtype, vvalue as *const c_char, vns as *const c_char) };
1285 if node.is_null() {
1286 // Out of memory: free the partial chain.
1287 unsafe { free_val_chain(head) };
1288 return ptr::null_mut();
1289 }
1290 if head.is_null() {
1291 head = node;
1292 } else {
1293 // SAFETY: tail is a valid node from this loop.
1294 unsafe { (*tail).next = node };
1295 }
1296 tail = node;
1297 // SAFETY: src is a valid node from the chain.
1298 src = unsafe { (*src).next };
1299 }
1300 head as *mut xmlSchemaVal
1301}
1302
1303/// Free a value (and any list items).
1304///
1305/// # UPSTREAM-PARITY
1306///
1307/// ```c
1308/// void xmlSchemaFreeValue(xmlSchemaVal *val);
1309/// ```
1310///
1311/// # SAFETY
1312///
1313/// - `val` must be a value created by this module (or NULL).
1314#[no_mangle]
1315pub unsafe extern "C" fn xmlSchemaFreeValue(val: *mut xmlSchemaVal) {
1316 if val.is_null() {
1317 return;
1318 }
1319 // SAFETY: val is one of our XsdVal chains.
1320 unsafe { free_val_chain(val as *mut XsdVal) };
1321}
1322
1323/// Get the `xmlSchemaValType` of a value.
1324///
1325/// # UPSTREAM-PARITY
1326///
1327/// ```c
1328/// xmlSchemaValType xmlSchemaGetValType(xmlSchemaVal *val);
1329/// ```
1330///
1331/// # SAFETY
1332///
1333/// - `val` must be a value created by this module or NULL (returns 0).
1334#[no_mangle]
1335pub unsafe extern "C" fn xmlSchemaGetValType(val: *mut xmlSchemaVal) -> c_int {
1336 if val.is_null() {
1337 return VAL_UNKNOWN;
1338 }
1339 // SAFETY: val is one of our XsdVal nodes.
1340 unsafe { (*(val as *mut XsdVal)).val_type }
1341}
1342
1343/// Return the next item of a list value.
1344///
1345/// # UPSTREAM-PARITY
1346///
1347/// ```c
1348/// xmlSchemaVal * xmlSchemaValueGetNext(xmlSchemaVal *cur);
1349/// ```
1350///
1351/// # SAFETY
1352///
1353/// - `cur` must be a value created by this module or NULL.
1354#[no_mangle]
1355pub unsafe extern "C" fn xmlSchemaValueGetNext(cur: *mut xmlSchemaVal) -> *mut xmlSchemaVal {
1356 if cur.is_null() {
1357 return ptr::null_mut();
1358 }
1359 // SAFETY: cur is one of our XsdVal nodes.
1360 unsafe { (*(cur as *mut XsdVal)).next as *mut xmlSchemaVal }
1361}
1362
1363/// Return the string representation of a value.
1364///
1365/// # UPSTREAM-PARITY
1366///
1367/// ```c
1368/// const xmlChar * xmlSchemaValueGetAsString(xmlSchemaVal *val);
1369/// ```
1370///
1371/// The returned pointer is owned by the value and stays valid until the
1372/// value is freed. Returns NULL for a NULL argument.
1373///
1374/// # SAFETY
1375///
1376/// - `val` must be a value created by this module or NULL.
1377#[no_mangle]
1378pub unsafe extern "C" fn xmlSchemaValueGetAsString(val: *mut xmlSchemaVal) -> *const xmlChar {
1379 if val.is_null() {
1380 return ptr::null_mut();
1381 }
1382 // SAFETY: val is one of our XsdVal nodes; value is a valid C string or NULL.
1383 unsafe { (*(val as *mut XsdVal)).value as *const xmlChar }
1384}
1385
1386/// Return a boolean value as 1/0; -1 if the value is not boolean.
1387///
1388/// # UPSTREAM-PARITY
1389///
1390/// ```c
1391/// int xmlSchemaValueGetAsBoolean(xmlSchemaVal *val);
1392/// ```
1393///
1394/// # SAFETY
1395///
1396/// - `val` must be a value created by this module or NULL.
1397#[no_mangle]
1398pub unsafe extern "C" fn xmlSchemaValueGetAsBoolean(val: *mut xmlSchemaVal) -> c_int {
1399 if val.is_null() {
1400 return -1;
1401 }
1402 // SAFETY: val is one of our XsdVal nodes.
1403 let v = unsafe { &*(val as *mut XsdVal) };
1404 if v.val_type != VAL_BOOLEAN {
1405 return -1;
1406 }
1407 // SAFETY: v.value is a valid C string or NULL.
1408 let s = unsafe { cstr_to_str(v.value as *const c_char) }.unwrap_or_default();
1409 match s.as_str() {
1410 "true" | "1" => 1,
1411 "false" | "0" => 0,
1412 _ => -1,
1413 }
1414}
1415
1416/// Append `cur` to the list value `prev` (which may itself be a chain).
1417///
1418/// # UPSTREAM-PARITY
1419///
1420/// ```c
1421/// int xmlSchemaValueAppend(xmlSchemaVal *prev, xmlSchemaVal *cur);
1422/// ```
1423///
1424/// Returns 0 on success, -1 on error (NULL argument).
1425///
1426/// # SAFETY
1427///
1428/// - `prev`/`cur` must be values created by this module.
1429#[no_mangle]
1430pub unsafe extern "C" fn xmlSchemaValueAppend(
1431 prev: *mut xmlSchemaVal,
1432 cur: *mut xmlSchemaVal,
1433) -> c_int {
1434 if prev.is_null() || cur.is_null() {
1435 return -1;
1436 }
1437 // SAFETY: prev is one of our XsdVal chains.
1438 let mut tail = prev as *mut XsdVal;
1439 // SAFETY: walking the chain; all nodes are ours.
1440 while !unsafe { (*tail).next }.is_null() {
1441 tail = unsafe { (*tail).next };
1442 }
1443 // SAFETY: tail is the last node of our chain; cur is ours.
1444 unsafe { (*tail).next = cur as *mut XsdVal };
1445 0
1446}
1447
1448// ═══════════════════════════════════════════════════════════════════════════════
1449// xmlschemastypes.h — canonical values
1450// ═══════════════════════════════════════════════════════════════════════════════
1451
1452/// Compute the canonical representation of a value.
1453///
1454/// # UPSTREAM-PARITY
1455///
1456/// ```c
1457/// int xmlSchemaGetCanonValue(xmlSchemaVal *val, const xmlChar **retValue);
1458/// ```
1459///
1460/// Returns 0 on success and stores an xmlMalloc'd string in `*retValue`
1461/// (caller frees with `xmlFree`); -1 on error.
1462///
1463/// # SAFETY
1464///
1465/// - `val` must be a value created by this module; `retValue` must be a
1466/// writable non-NULL pointer.
1467#[no_mangle]
1468pub unsafe extern "C" fn xmlSchemaGetCanonValue(
1469 val: *mut xmlSchemaVal,
1470 retValue: *mut *const xmlChar,
1471) -> c_int {
1472 // SAFETY: Delegates with the same contract.
1473 unsafe { xmlSchemaGetCanonValueWhtsp(val, retValue, WS_COLLAPSE) }
1474}
1475
1476/// Compute the canonical representation of a value, applying a whitespace
1477/// transformation first.
1478///
1479/// # UPSTREAM-PARITY
1480///
1481/// ```c
1482/// int xmlSchemaGetCanonValueWhtsp(xmlSchemaVal *val, const xmlChar **retValue,
1483/// xmlSchemaWhitespaceValueType ws);
1484/// ```
1485///
1486/// Returns 0 on success and stores an xmlMalloc'd string in `*retValue`
1487/// (caller frees with `xmlFree`); -1 on error.
1488///
1489/// # SAFETY
1490///
1491/// - `val` must be a value created by this module; `retValue` must be a
1492/// writable non-NULL pointer.
1493#[no_mangle]
1494pub unsafe extern "C" fn xmlSchemaGetCanonValueWhtsp(
1495 val: *mut xmlSchemaVal,
1496 retValue: *mut *const xmlChar,
1497 ws: c_int,
1498) -> c_int {
1499 if val.is_null() || retValue.is_null() {
1500 return -1;
1501 }
1502 // SAFETY: val is one of our XsdVal nodes.
1503 let v = unsafe { &*(val as *mut XsdVal) };
1504 // List values canonicalize to their items joined by single spaces.
1505 if v.next.is_null() && !is_list_type(v.val_type) {
1506 let s = unsafe { cstr_to_str(v.value as *const c_char) }.unwrap_or_default();
1507 let norm = apply_ws(&s, ws);
1508 let canon = match v.val_type {
1509 t if is_numeric_type(t) => {
1510 if matches!(t, VAL_FLOAT | VAL_DOUBLE) {
1511 match norm.as_str() {
1512 "NaN" => "NaN".to_string(),
1513 "INF" => "INF".to_string(),
1514 "-INF" => "-INF".to_string(),
1515 _ => match norm.parse::<f64>() {
1516 Ok(f) if f.is_nan() => "NaN".to_string(),
1517 Ok(f) if f.is_infinite() && f.is_sign_negative() => "-INF".to_string(),
1518 Ok(f) if f.is_infinite() => "INF".to_string(),
1519 Ok(f) => f.to_string(),
1520 Err(_) => canonical_decimal(&norm),
1521 },
1522 }
1523 } else if matches!(t, VAL_DECIMAL) {
1524 canonical_decimal(&norm)
1525 } else {
1526 // Integer family: parse as i128 when possible.
1527 match norm.parse::<i128>() {
1528 Ok(i) => i.to_string(),
1529 Err(_) => canonical_decimal(&norm),
1530 }
1531 }
1532 }
1533 VAL_BOOLEAN => match norm.as_str() {
1534 "true" | "1" => "true".to_string(),
1535 "false" | "0" => "false".to_string(),
1536 _ => return -1,
1537 },
1538 _ => norm,
1539 };
1540 let Ok(c) = CString::new(canon) else {
1541 return -1;
1542 };
1543 // SAFETY: dup_cstr copies into xmlMalloc'd memory; caller frees.
1544 let out = unsafe { dup_cstr(c.as_ptr()) } as *const xmlChar;
1545 if out.is_null() {
1546 return -1;
1547 }
1548 // SAFETY: retValue is caller-guaranteed writable.
1549 unsafe { *retValue = out };
1550 0
1551 } else {
1552 // List value: canonicalize each item and join with single spaces.
1553 let mut parts: Vec<String> = Vec::new();
1554 let mut cur = val as *mut XsdVal;
1555 while !cur.is_null() {
1556 // SAFETY: cur is a node of our chain.
1557 let s = unsafe { cstr_to_str((*cur).value as *const c_char) }.unwrap_or_default();
1558 parts.push(apply_ws(&s, ws));
1559 cur = unsafe { (*cur).next };
1560 }
1561 let Ok(c) = CString::new(parts.join(" ")) else {
1562 return -1;
1563 };
1564 // SAFETY: dup_cstr copies into xmlMalloc'd memory; caller frees.
1565 let out = unsafe { dup_cstr(c.as_ptr()) } as *const xmlChar;
1566 if out.is_null() {
1567 return -1;
1568 }
1569 // SAFETY: retValue is caller-guaranteed writable.
1570 unsafe { *retValue = out };
1571 0
1572 }
1573}
1574
1575// ═══════════════════════════════════════════════════════════════════════════════
1576// xmlschemastypes.h — facets
1577// ═══════════════════════════════════════════════════════════════════════════════
1578
1579/// Create an empty facet.
1580///
1581/// # UPSTREAM-PARITY
1582///
1583/// ```c
1584/// xmlSchemaFacet * xmlSchemaNewFacet(void);
1585/// ```
1586///
1587/// The facet starts with type 0 and a NULL value; the value can be set
1588/// through `xmlSchemaGetFacetValueAsULong` consumers via the internal
1589/// representation. Returns NULL on allocation failure.
1590#[no_mangle]
1591pub extern "C" fn xmlSchemaNewFacet() -> *mut xmlSchemaFacet {
1592 // SAFETY: Box allocation is infallible modulo OOM abort.
1593 Box::into_raw(Box::new(XsdFacet {
1594 facet_type: 0,
1595 value: ptr::null_mut(),
1596 next: ptr::null_mut(),
1597 })) as *mut xmlSchemaFacet
1598}
1599
1600/// Free a facet.
1601///
1602/// # UPSTREAM-PARITY
1603///
1604/// ```c
1605/// void xmlSchemaFreeFacet(xmlSchemaFacet *facet);
1606/// ```
1607///
1608/// # SAFETY
1609///
1610/// - `facet` must be a facet created by this module or NULL.
1611#[no_mangle]
1612pub unsafe extern "C" fn xmlSchemaFreeFacet(facet: *mut xmlSchemaFacet) {
1613 if facet.is_null() {
1614 return;
1615 }
1616 // SAFETY: facet is one of our XsdFacet boxes.
1617 let f = unsafe { Box::from_raw(facet as *mut XsdFacet) };
1618 if !f.value.is_null() {
1619 // SAFETY: value was xmlMalloc'd by this module.
1620 unsafe { xmlFreeImpl(f.value as *mut c_void) };
1621 }
1622 drop(f);
1623}
1624
1625/// Parse a facet's value as an unsigned long.
1626///
1627/// # UPSTREAM-PARITY
1628///
1629/// ```c
1630/// unsigned long xmlSchemaGetFacetValueAsULong(xmlSchemaFacet *facet);
1631/// ```
1632///
1633/// Returns 0 when the facet is NULL or the value is not numeric.
1634///
1635/// # SAFETY
1636///
1637/// - `facet` must be a facet created by this module or NULL.
1638#[no_mangle]
1639pub unsafe extern "C" fn xmlSchemaGetFacetValueAsULong(facet: *mut xmlSchemaFacet) -> c_ulong {
1640 if facet.is_null() {
1641 return 0;
1642 }
1643 // SAFETY: facet is one of our XsdFacet boxes; value is a C string or NULL.
1644 let s = unsafe { cstr_to_str((*(facet as *mut XsdFacet)).value as *const c_char) }
1645 .unwrap_or_default();
1646 s.trim().parse::<u64>().unwrap_or(0) as c_ulong
1647}
1648
1649/// Check a facet for validity against a type.
1650///
1651/// # UPSTREAM-PARITY
1652///
1653/// ```c
1654/// int xmlSchemaCheckFacet(xmlSchemaFacet *facet, xmlSchemaType *typeDecl,
1655/// xmlSchemaParserCtxt *ctxt, const xmlChar *name);
1656/// ```
1657///
1658/// Returns 0 if the facet is acceptable, -1 otherwise (reporting through the
1659/// parser context's error callbacks). Simplified: verifies the facet type is
1660/// known and valid for `typeDecl`; deeper value-level checks (upstream's
1661/// full facet construction) are handled by `xmlSchemaValidateFacet` at
1662/// validation time.
1663///
1664/// # SAFETY
1665///
1666/// - `facet`/`typeDecl`/`ctxt` must be objects created by this crate or NULL;
1667/// `name` must be a valid C string or NULL.
1668#[no_mangle]
1669pub unsafe extern "C" fn xmlSchemaCheckFacet(
1670 facet: *mut xmlSchemaFacet,
1671 typeDecl: *mut xmlSchemaType,
1672 ctxt: *mut xmlSchemaParserCtxt,
1673 name: *const xmlChar,
1674) -> c_int {
1675 if facet.is_null() {
1676 return -1;
1677 }
1678 // SAFETY: facet is one of our XsdFacet boxes.
1679 let facet_type = unsafe { (*(facet as *mut XsdFacet)).facet_type };
1680 if !(FACET_MININCLUSIVE..=FACET_MINLENGTH).contains(&facet_type) {
1681 unsafe {
1682 dispatch_parser_error(ctxt as usize, "Invalid facet type");
1683 }
1684 return -1;
1685 }
1686 if !typeDecl.is_null() {
1687 let valid = xmlSchemaIsBuiltInTypeFacet(typeDecl, facet_type);
1688 if valid == 0 {
1689 let label = unsafe { cstr_to_str(name as *const c_char) }
1690 .unwrap_or_else(|| "facet".to_string());
1691 unsafe {
1692 dispatch_parser_error(
1693 ctxt as usize,
1694 &format!("Facet '{}' is not valid for this type", label),
1695 );
1696 }
1697 return -1;
1698 }
1699 }
1700 0
1701}
1702
1703/// Validate a value against a facet of a base type.
1704///
1705/// # UPSTREAM-PARITY
1706///
1707/// ```c
1708/// int xmlSchemaValidateFacet(xmlSchemaType *base, xmlSchemaFacet *facet,
1709/// const xmlChar *value, xmlSchemaVal *val);
1710/// ```
1711///
1712/// Returns 0 if the value satisfies the facet, -1 otherwise. Implemented via
1713/// the internal engine's `xsd_validate_facet`.
1714///
1715/// # SAFETY
1716///
1717/// - `base`/`facet`/`val` must be objects created by this crate or NULL;
1718/// `value` must be a valid C string or NULL.
1719#[no_mangle]
1720pub unsafe extern "C" fn xmlSchemaValidateFacet(
1721 base: *mut xmlSchemaType,
1722 facet: *mut xmlSchemaFacet,
1723 value: *const xmlChar,
1724 val: *mut xmlSchemaVal,
1725) -> c_int {
1726 if facet.is_null() {
1727 return -1;
1728 }
1729 // SAFETY: facet is one of our boxes.
1730 let facet_type = unsafe { (*(facet as *mut XsdFacet)).facet_type };
1731 let Some(facet_kind) = facet_type_to_kind(facet_type) else {
1732 return -1;
1733 };
1734 // SAFETY: facet value is a C string or NULL.
1735 let facet_value = unsafe { cstr_to_str((*(facet as *mut XsdFacet)).value as *const c_char) }
1736 .unwrap_or_default();
1737 // Determine the base kind: from `base`, falling back to `val`, then string.
1738 let base_type = if !base.is_null() {
1739 // SAFETY: base is one of our descriptors.
1740 unsafe { (*(base as *mut XsdType)).val_type }
1741 } else if !val.is_null() {
1742 // SAFETY: val is one of our nodes.
1743 unsafe { (*(val as *mut XsdVal)).val_type }
1744 } else {
1745 VAL_STRING
1746 };
1747 let Some(kind) = val_type_to_kind(base_type) else {
1748 return -1;
1749 };
1750 // SAFETY: value is a caller-guaranteed C string or NULL.
1751 let raw = unsafe { cstr_to_str(value as *const c_char) }.unwrap_or_default();
1752 let norm = whitespace_collapse(&raw);
1753 if xsd_validate_facet(&kind, &norm, &facet_kind, &facet_value) {
1754 0
1755 } else {
1756 -1
1757 }
1758}
1759
1760/// Validate a value against a facet with explicit whitespace handling.
1761///
1762/// # UPSTREAM-PARITY
1763///
1764/// ```c
1765/// int xmlSchemaValidateFacetWhtsp(xmlSchemaFacet *facet,
1766/// xmlSchemaWhitespaceValueType fws,
1767/// xmlSchemaValType valType,
1768/// const xmlChar *value, xmlSchemaVal *val,
1769/// xmlSchemaWhitespaceValueType ws);
1770/// ```
1771///
1772/// Returns 0 if the value satisfies the facet, -1 otherwise.
1773///
1774/// # SAFETY
1775///
1776/// - `facet`/`val` must be objects created by this crate or NULL; `value`
1777/// must be a valid C string or NULL.
1778#[no_mangle]
1779pub unsafe extern "C" fn xmlSchemaValidateFacetWhtsp(
1780 facet: *mut xmlSchemaFacet,
1781 _fws: c_int,
1782 valType: c_int,
1783 value: *const xmlChar,
1784 val: *mut xmlSchemaVal,
1785 ws: c_int,
1786) -> c_int {
1787 if facet.is_null() {
1788 return -1;
1789 }
1790 // SAFETY: facet is one of our boxes.
1791 let facet_type = unsafe { (*(facet as *mut XsdFacet)).facet_type };
1792 let Some(facet_kind) = facet_type_to_kind(facet_type) else {
1793 return -1;
1794 };
1795 // SAFETY: facet value is a C string or NULL.
1796 let facet_value = unsafe { cstr_to_str((*(facet as *mut XsdFacet)).value as *const c_char) }
1797 .unwrap_or_default();
1798 let kind = if val.is_null() {
1799 val_type_to_kind(valType)
1800 } else {
1801 // SAFETY: val is one of our nodes.
1802 val_type_to_kind(unsafe { (*(val as *mut XsdVal)).val_type })
1803 };
1804 let Some(kind) = kind else {
1805 return -1;
1806 };
1807 // SAFETY: value is a caller-guaranteed C string or NULL.
1808 let raw = unsafe { cstr_to_str(value as *const c_char) }.unwrap_or_default();
1809 let norm = apply_ws(&raw, ws);
1810 if xsd_validate_facet(&kind, &norm, &facet_kind, &facet_value) {
1811 0
1812 } else {
1813 -1
1814 }
1815}
1816
1817/// Validate a length facet against a value, reporting the value's length.
1818///
1819/// # UPSTREAM-PARITY
1820///
1821/// ```c
1822/// int xmlSchemaValidateLengthFacet(xmlSchemaType *type, xmlSchemaFacet *facet,
1823/// const xmlChar *value, xmlSchemaVal *val,
1824/// unsigned long *length);
1825/// ```
1826///
1827/// Returns 0 if the facet holds, -1 otherwise. `*length` receives the
1828/// value's length (characters for string types, items for list types).
1829///
1830/// # SAFETY
1831///
1832/// - `type`/`facet`/`val` must be objects created by this crate or NULL;
1833/// `value` must be a valid C string or NULL; `length` must be writable.
1834#[no_mangle]
1835pub unsafe extern "C" fn xmlSchemaValidateLengthFacet(
1836 type_: *mut xmlSchemaType,
1837 facet: *mut xmlSchemaFacet,
1838 value: *const xmlChar,
1839 val: *mut xmlSchemaVal,
1840 length: *mut c_ulong,
1841) -> c_int {
1842 let val_type = if !type_.is_null() {
1843 // SAFETY: type_ is one of our descriptors.
1844 unsafe { (*(type_ as *mut XsdType)).val_type }
1845 } else if !val.is_null() {
1846 // SAFETY: val is one of our nodes.
1847 unsafe { (*(val as *mut XsdVal)).val_type }
1848 } else {
1849 VAL_STRING
1850 };
1851 // SAFETY: Delegates with the same contract.
1852 unsafe { xmlSchemaValidateLengthFacetWhtsp(facet, val_type, value, val, length, WS_PRESERVE) }
1853}
1854
1855/// Validate a length facet with explicit whitespace handling.
1856///
1857/// # UPSTREAM-PARITY
1858///
1859/// ```c
1860/// int xmlSchemaValidateLengthFacetWhtsp(xmlSchemaFacet *facet,
1861/// xmlSchemaValType valType,
1862/// const xmlChar *value, xmlSchemaVal *val,
1863/// unsigned long *length,
1864/// xmlSchemaWhitespaceValueType ws);
1865/// ```
1866///
1867/// Returns 0 if the facet holds, -1 otherwise. `*length` receives the
1868/// value's length (characters for string types, items for list types).
1869///
1870/// # SAFETY
1871///
1872/// - `facet`/`val` must be objects created by this crate or NULL; `value`
1873/// must be a valid C string or NULL; `length` must be writable.
1874#[no_mangle]
1875pub unsafe extern "C" fn xmlSchemaValidateLengthFacetWhtsp(
1876 facet: *mut xmlSchemaFacet,
1877 valType: c_int,
1878 value: *const xmlChar,
1879 val: *mut xmlSchemaVal,
1880 length: *mut c_ulong,
1881 ws: c_int,
1882) -> c_int {
1883 if facet.is_null() {
1884 return -1;
1885 }
1886 // SAFETY: facet is one of our boxes.
1887 let facet_type = unsafe { (*(facet as *mut XsdFacet)).facet_type };
1888 let facet_ulong = xmlSchemaGetFacetValueAsULong(facet);
1889 let len: c_ulong = if is_list_type(valType) {
1890 if val.is_null() {
1891 0
1892 } else {
1893 // Count items in the chain.
1894 let mut count: c_ulong = 0;
1895 let mut cur = val as *mut XsdVal;
1896 while !cur.is_null() {
1897 count += 1;
1898 // SAFETY: cur is a node of our chain.
1899 cur = unsafe { (*cur).next };
1900 }
1901 count
1902 }
1903 } else {
1904 // SAFETY: value is a caller-guaranteed C string or NULL.
1905 let raw = unsafe { cstr_to_str(value as *const c_char) }.unwrap_or_default();
1906 let norm = apply_ws(&raw, ws);
1907 norm.chars().count() as c_ulong
1908 };
1909 if !length.is_null() {
1910 // SAFETY: caller-guaranteed writable.
1911 unsafe { *length = len as c_ulong };
1912 }
1913 let ok = match facet_type {
1914 FACET_LENGTH => len == facet_ulong,
1915 FACET_MINLENGTH => len >= facet_ulong,
1916 FACET_MAXLENGTH => len <= facet_ulong,
1917 _ => {
1918 // Not a length facet: upstream reports "not a length facet".
1919 return -1;
1920 }
1921 };
1922 if ok {
1923 0
1924 } else {
1925 -1
1926 }
1927}
1928
1929/// Validate a list-type facet given the actual number of items.
1930///
1931/// # UPSTREAM-PARITY
1932///
1933/// ```c
1934/// int xmlSchemaValidateListSimpleTypeFacet(xmlSchemaFacet *facet,
1935/// const xmlChar *value,
1936/// unsigned long actualLen,
1937/// unsigned long *expectedLen);
1938/// ```
1939///
1940/// For length facets, checks `actualLen` against the facet and stores the
1941/// facet's value in `*expectedLen`. Returns 0 if valid, -1 otherwise.
1942///
1943/// # SAFETY
1944///
1945/// - `facet` must be a facet created by this module or NULL; `value` must be
1946/// a valid C string or NULL; `expectedLen` must be writable.
1947#[no_mangle]
1948pub unsafe extern "C" fn xmlSchemaValidateListSimpleTypeFacet(
1949 facet: *mut xmlSchemaFacet,
1950 _value: *const xmlChar,
1951 actualLen: c_ulong,
1952 expectedLen: *mut c_ulong,
1953) -> c_int {
1954 if facet.is_null() {
1955 return -1;
1956 }
1957 // SAFETY: facet is one of our boxes.
1958 let facet_type = unsafe { (*(facet as *mut XsdFacet)).facet_type };
1959 let facet_ulong = xmlSchemaGetFacetValueAsULong(facet);
1960 if !expectedLen.is_null() {
1961 // SAFETY: caller-guaranteed writable.
1962 unsafe { *expectedLen = facet_ulong };
1963 }
1964 let ok = match facet_type {
1965 FACET_LENGTH => actualLen == facet_ulong,
1966 FACET_MINLENGTH => actualLen >= facet_ulong,
1967 FACET_MAXLENGTH => actualLen <= facet_ulong,
1968 _ => return -1,
1969 };
1970 if ok {
1971 0
1972 } else {
1973 -1
1974 }
1975}
1976
1977// ═══════════════════════════════════════════════════════════════════════════════
1978// xmlschemastypes.h — predefined-type validation
1979// ═══════════════════════════════════════════════════════════════════════════════
1980
1981/// Validate a value against a predefined (built-in) type.
1982///
1983/// # UPSTREAM-PARITY
1984///
1985/// ```c
1986/// int xmlSchemaValidatePredefinedType(xmlSchemaType *type, const xmlChar *value,
1987/// xmlSchemaVal **val);
1988/// ```
1989///
1990/// Returns 0 if the value is valid, -1 otherwise. If `val` is non-NULL it
1991/// receives a newly created value on success (caller frees it).
1992///
1993/// # SAFETY
1994///
1995/// - `type` must be a built-in descriptor from this crate; `value` must be a
1996/// valid C string; `val` must be writable or NULL.
1997#[no_mangle]
1998pub unsafe extern "C" fn xmlSchemaValidatePredefinedType(
1999 type_: *mut xmlSchemaType,
2000 value: *const xmlChar,
2001 val: *mut *mut xmlSchemaVal,
2002) -> c_int {
2003 // SAFETY: Delegates with the same contract (node is unused here).
2004 unsafe { xmlSchemaValPredefTypeNode(type_, value, val, ptr::null_mut()) }
2005}
2006
2007/// Validate a value against a predefined type, with whitespace normalization.
2008///
2009/// # UPSTREAM-PARITY
2010///
2011/// ```c
2012/// int xmlSchemaValPredefTypeNode(xmlSchemaType *type, const xmlChar *value,
2013/// xmlSchemaVal **val, xmlNode *node);
2014/// ```
2015///
2016/// Returns 0 if the value is valid, -1 otherwise. If `val` is non-NULL it
2017/// receives a newly created value on success (caller frees it). `node` is
2018/// accepted for signature parity and used for error reporting (ignored here;
2019/// the internal engine validates strings).
2020///
2021/// # SAFETY
2022///
2023/// - `type` must be a built-in descriptor from this crate; `value` must be a
2024/// valid C string; `val` must be writable or NULL; `node` may be NULL.
2025#[no_mangle]
2026pub unsafe extern "C" fn xmlSchemaValPredefTypeNode(
2027 type_: *mut xmlSchemaType,
2028 value: *const xmlChar,
2029 val: *mut *mut xmlSchemaVal,
2030 _node: *mut _xmlNode,
2031) -> c_int {
2032 if type_.is_null() || value.is_null() {
2033 return -1;
2034 }
2035 // SAFETY: type_ is one of our descriptors.
2036 let val_type = unsafe { (*(type_ as *mut XsdType)).val_type };
2037 let Some(kind) = val_type_to_kind(val_type) else {
2038 return -1;
2039 };
2040 // SAFETY: value is a caller-guaranteed C string.
2041 let raw = unsafe { cstr_to_str(value as *const c_char) }.unwrap_or_default();
2042 // Whitespace normalization, as upstream does before validating.
2043 let norm = whitespace_collapse(&raw);
2044 if !xsd_validate_datatype(&kind, &norm, &[]) {
2045 return -1;
2046 }
2047 if !val.is_null() {
2048 // SAFETY: val is caller-guaranteed writable; new_string_value creates
2049 // a value with the same contract as xmlSchemaNewStringValue.
2050 *val = unsafe { new_string_value(val_type, value as *const c_char) } as *mut xmlSchemaVal;
2051 }
2052 0
2053}
2054
2055/// Validate a value against a predefined type, without normalization.
2056///
2057/// # UPSTREAM-PARITY
2058///
2059/// ```c
2060/// int xmlSchemaValPredefTypeNodeNoNorm(xmlSchemaType *type, const xmlChar *value,
2061/// xmlSchemaVal **val, xmlNode *node);
2062/// ```
2063///
2064/// Same as `xmlSchemaValPredefTypeNode` but the value is validated as-is
2065/// (no whitespace collapse). Returns 0 if valid, -1 otherwise.
2066///
2067/// # SAFETY
2068///
2069/// - `type` must be a built-in descriptor from this crate; `value` must be a
2070/// valid C string; `val` must be writable or NULL; `node` may be NULL.
2071#[no_mangle]
2072pub unsafe extern "C" fn xmlSchemaValPredefTypeNodeNoNorm(
2073 type_: *mut xmlSchemaType,
2074 value: *const xmlChar,
2075 val: *mut *mut xmlSchemaVal,
2076 _node: *mut _xmlNode,
2077) -> c_int {
2078 if type_.is_null() || value.is_null() {
2079 return -1;
2080 }
2081 // SAFETY: type_ is one of our descriptors.
2082 let val_type = unsafe { (*(type_ as *mut XsdType)).val_type };
2083 let Some(kind) = val_type_to_kind(val_type) else {
2084 return -1;
2085 };
2086 // SAFETY: value is a caller-guaranteed C string.
2087 let raw = unsafe { cstr_to_str(value as *const c_char) }.unwrap_or_default();
2088 if !xsd_validate_datatype(&kind, &raw, &[]) {
2089 return -1;
2090 }
2091 if !val.is_null() {
2092 // SAFETY: val is caller-guaranteed writable.
2093 *val = unsafe { new_string_value(val_type, value as *const c_char) } as *mut xmlSchemaVal;
2094 }
2095 0
2096}
2097
2098// ═══════════════════════════════════════════════════════════════════════════════
2099// xmlschemastypes.h — value comparison
2100// ═══════════════════════════════════════════════════════════════════════════════
2101
2102/// Compare two schema values.
2103///
2104/// # UPSTREAM-PARITY
2105///
2106/// ```c
2107/// int xmlSchemaCompareValues(xmlSchemaVal *x, xmlSchemaVal *y);
2108/// ```
2109///
2110/// Returns -2 on error, -1 if x < y, 0 if equal, 1 if x > y. Numeric values
2111/// are compared numerically, booleans as false < true, everything else as
2112/// (collapsed) strings.
2113///
2114/// # SAFETY
2115///
2116/// - `x`/`y` must be values created by this module or NULL.
2117#[no_mangle]
2118pub unsafe extern "C" fn xmlSchemaCompareValues(
2119 x: *mut xmlSchemaVal,
2120 y: *mut xmlSchemaVal,
2121) -> c_int {
2122 // SAFETY: Delegates with the same contract.
2123 unsafe { xmlSchemaCompareValuesWhtsp(x, WS_COLLAPSE, y, WS_COLLAPSE) }
2124}
2125
2126/// Compare two schema values with explicit whitespace handling.
2127///
2128/// # UPSTREAM-PARITY
2129///
2130/// ```c
2131/// int xmlSchemaCompareValuesWhtsp(xmlSchemaVal *x,
2132/// xmlSchemaWhitespaceValueType xws,
2133/// xmlSchemaVal *y,
2134/// xmlSchemaWhitespaceValueType yws);
2135/// ```
2136///
2137/// Returns -2 on error, -1 if x < y, 0 if equal, 1 if x > y.
2138///
2139/// # SAFETY
2140///
2141/// - `x`/`y` must be values created by this module or NULL.
2142#[no_mangle]
2143pub unsafe extern "C" fn xmlSchemaCompareValuesWhtsp(
2144 x: *mut xmlSchemaVal,
2145 xws: c_int,
2146 y: *mut xmlSchemaVal,
2147 yws: c_int,
2148) -> c_int {
2149 if x.is_null() || y.is_null() {
2150 return -2;
2151 }
2152 // SAFETY: x/y are our nodes.
2153 let (xt, xv, yt, yv) = unsafe {
2154 (
2155 (*(x as *mut XsdVal)).val_type,
2156 cstr_to_str((*(x as *mut XsdVal)).value as *const c_char).unwrap_or_default(),
2157 (*(y as *mut XsdVal)).val_type,
2158 cstr_to_str((*(y as *mut XsdVal)).value as *const c_char).unwrap_or_default(),
2159 )
2160 };
2161 let xs = apply_ws(&xv, xws);
2162 let ys = apply_ws(&yv, yws);
2163 if is_numeric_type(xt) && is_numeric_type(yt) {
2164 // Numeric comparison with INF/NaN handling.
2165 let to_f64 = |s: &str| -> Option<f64> {
2166 match s {
2167 "INF" => Some(f64::INFINITY),
2168 "-INF" => Some(f64::NEG_INFINITY),
2169 "NaN" => Some(f64::NAN),
2170 _ => s.parse::<f64>().ok(),
2171 }
2172 };
2173 match (to_f64(&xs), to_f64(&ys)) {
2174 (Some(a), Some(b)) => {
2175 if a.is_nan() || b.is_nan() {
2176 return -2;
2177 }
2178 if a < b {
2179 -1
2180 } else if a > b {
2181 1
2182 } else {
2183 0
2184 }
2185 }
2186 _ => -2,
2187 }
2188 } else if xt == VAL_BOOLEAN && yt == VAL_BOOLEAN {
2189 let b = |s: &str| -> Option<i32> {
2190 match s {
2191 "true" | "1" => Some(1),
2192 "false" | "0" => Some(0),
2193 _ => None,
2194 }
2195 };
2196 match (b(&xs), b(&ys)) {
2197 (Some(a), Some(c)) => a.cmp(&c) as c_int,
2198 _ => -2,
2199 }
2200 } else {
2201 match xs.cmp(&ys) {
2202 std::cmp::Ordering::Less => -1,
2203 std::cmp::Ordering::Equal => 0,
2204 std::cmp::Ordering::Greater => 1,
2205 }
2206 }
2207}
2208
2209// ═══════════════════════════════════════════════════════════════════════════════
2210// xmlschemastypes.h — free functions for types/wildcards
2211// ═══════════════════════════════════════════════════════════════════════════════
2212
2213/// Free a schema type.
2214///
2215/// # UPSTREAM-PARITY
2216///
2217/// ```c
2218/// void xmlSchemaFreeType(xmlSchemaType *type);
2219/// ```
2220///
2221/// Built-in type descriptors are process-lifetime statics owned by the
2222/// registry, so freeing one is a no-op. Foreign (non-registry) pointers are
2223/// dropped as this crate's descriptor boxes.
2224///
2225/// # SAFETY
2226///
2227/// - `type_` must be a pointer returned by this crate (or NULL).
2228#[no_mangle]
2229pub unsafe extern "C" fn xmlSchemaFreeType(type_: *mut xmlSchemaType) {
2230 if type_.is_null() {
2231 return;
2232 }
2233 let addr = type_ as usize;
2234 let registered = {
2235 let reg = TYPE_REGISTRY.lock();
2236 reg.values().any(|&v| v == addr)
2237 };
2238 if registered {
2239 // Static descriptor: nothing to free.
2240 return;
2241 }
2242 // SAFETY: The pointer was not registry-owned, so it must be a Box we
2243 // created (defensive; callers should only pass our descriptors).
2244 unsafe { drop(Box::from_raw(type_ as *mut XsdType)) };
2245}
2246
2247/// Free a schema wildcard.
2248///
2249/// # UPSTREAM-PARITY
2250///
2251/// ```c
2252/// void xmlSchemaFreeWildcard(xmlSchemaWildcard *wildcard);
2253/// ```
2254///
2255/// This crate never allocates wildcard objects (the internal engine has no
2256/// wildcard representation), so there is nothing owned to free; kept as a
2257/// no-op for ABI compatibility.
2258///
2259/// # SAFETY
2260///
2261/// - `wildcard` must be NULL or a pointer from this crate.
2262#[no_mangle]
2263pub const unsafe extern "C" fn xmlSchemaFreeWildcard(_wildcard: *mut xmlSchemaWildcard) {
2264 // No-op: the internal engine does not allocate wildcard objects.
2265}
2266
2267// ═══════════════════════════════════════════════════════════════════════════════
2268// xmlschemas.h — parser context
2269// ═══════════════════════════════════════════════════════════════════════════════
2270
2271/// Create a schema parser context from an already-parsed document.
2272///
2273/// # UPSTREAM-PARITY
2274///
2275/// ```c
2276/// xmlSchemaParserCtxt * xmlSchemaNewDocParserCtxt(xmlDoc *doc);
2277/// ```
2278///
2279/// The document is serialized and compiled through the internal engine
2280/// (`xsd_parse`); the compiled schema is stored in the parser context and
2281/// `xmlSchemaParse` hands out a separate schema object (Phase 14: the
2282/// context and the schema have independent lifetimes — lxml frees the
2283/// context right after `xmlSchemaParse`). Returns NULL if `doc` is NULL or
2284/// the schema fails to compile.
2285///
2286/// # SAFETY
2287///
2288/// - `doc` must be a valid `_xmlDoc` pointer.
2289#[no_mangle]
2290pub unsafe extern "C" fn xmlSchemaNewDocParserCtxt(doc: *mut _xmlDoc) -> *mut xmlSchemaParserCtxt {
2291 // SAFETY: doc_to_string requires a valid doc.
2292 let Some(xml) = (unsafe { doc_to_string(doc) }) else {
2293 return ptr::null_mut();
2294 };
2295 let schema = xsd_parse(&xml).ok();
2296 let ctxt = crate::xml::schemas::XsdParserCtxt {
2297 schema,
2298 fail: None,
2299 url: None,
2300 mem: false,
2301 };
2302 Box::into_raw(Box::new(ctxt)) as *mut xmlSchemaParserCtxt
2303}
2304
2305/// Set the error/warning callbacks of a parser context.
2306///
2307/// # UPSTREAM-PARITY
2308///
2309/// ```c
2310/// void xmlSchemaSetParserErrors(xmlSchemaParserCtxt *ctxt,
2311/// xmlSchemaValidityErrorFunc err,
2312/// xmlSchemaValidityWarningFunc warn, void *ctx);
2313/// ```
2314///
2315/// The callbacks are stored in a side registry keyed by the context address.
2316///
2317/// # SAFETY
2318///
2319/// - `ctxt` must be a parser context created by this crate; `err`/`warn`
2320/// must be valid callbacks or NULL; `ctx` may be NULL.
2321#[no_mangle]
2322pub unsafe extern "C" fn xmlSchemaSetParserErrors(
2323 ctxt: *mut xmlSchemaParserCtxt,
2324 err: Option<xmlValidityErrorFunc>,
2325 warn: Option<xmlValidityWarningFunc>,
2326 ctx: *mut c_void,
2327) {
2328 if ctxt.is_null() {
2329 return;
2330 }
2331 let mut guard = PARSER_STATES.lock();
2332 let e = guard.entry(ctxt as usize).or_default();
2333 e.err = err;
2334 e.warn = warn;
2335 e.ctx = ctx as usize;
2336}
2337
2338/// Set the structured error callback of a parser context.
2339///
2340/// # UPSTREAM-PARITY
2341///
2342/// ```c
2343/// void xmlSchemaSetParserStructuredErrors(xmlSchemaParserCtxt *ctxt,
2344/// xmlStructuredErrorFunc serror, void *ctx);
2345/// ```
2346///
2347/// # SAFETY
2348///
2349/// - `ctxt` must be a parser context created by this crate; `serror` must be
2350/// a valid callback or NULL; `ctx` may be NULL.
2351#[no_mangle]
2352pub unsafe extern "C" fn xmlSchemaSetParserStructuredErrors(
2353 ctxt: *mut xmlSchemaParserCtxt,
2354 serror: Option<xmlStructuredErrorFunc>,
2355 ctx: *mut c_void,
2356) {
2357 if ctxt.is_null() {
2358 return;
2359 }
2360 let mut guard = PARSER_STATES.lock();
2361 let e = guard.entry(ctxt as usize).or_default();
2362 e.serror = serror;
2363 e.sctx = ctx as usize;
2364}
2365
2366/// Retrieve the error/warning callbacks of a parser context.
2367///
2368/// # UPSTREAM-PARITY
2369///
2370/// ```c
2371/// int xmlSchemaGetParserErrors(xmlSchemaParserCtxt *ctxt,
2372/// xmlSchemaValidityErrorFunc *err,
2373/// xmlSchemaValidityWarningFunc *warn, void **ctx);
2374/// ```
2375///
2376/// Returns 0 on success, -1 if `ctxt` is NULL. Output parameters may be NULL.
2377///
2378/// # SAFETY
2379///
2380/// - `ctxt` must be a parser context created by this crate; `err`/`warn`/
2381/// `ctx` must be writable or NULL.
2382#[no_mangle]
2383pub unsafe extern "C" fn xmlSchemaGetParserErrors(
2384 ctxt: *mut xmlSchemaParserCtxt,
2385 err: *mut Option<xmlValidityErrorFunc>,
2386 warn: *mut Option<xmlValidityWarningFunc>,
2387 ctx: *mut *mut c_void,
2388) -> c_int {
2389 if ctxt.is_null() {
2390 return -1;
2391 }
2392 let state = {
2393 let guard = PARSER_STATES.lock();
2394 guard.get(&(ctxt as usize)).copied().unwrap_or_default()
2395 };
2396 // SAFETY: output pointers are caller-guaranteed writable when non-NULL.
2397 unsafe {
2398 if !err.is_null() {
2399 *err = state.err;
2400 }
2401 if !warn.is_null() {
2402 *warn = state.warn;
2403 }
2404 if !ctx.is_null() {
2405 *ctx = state.ctx as *mut c_void;
2406 }
2407 }
2408 0
2409}
2410
2411// ═══════════════════════════════════════════════════════════════════════════════
2412// xmlschemas.h — validation context
2413// ═══════════════════════════════════════════════════════════════════════════════
2414
2415/// Set the error/warning callbacks of a validation context.
2416///
2417/// # UPSTREAM-PARITY
2418///
2419/// ```c
2420/// void xmlSchemaSetValidErrors(xmlSchemaValidCtxt *ctxt,
2421/// xmlSchemaValidityErrorFunc err,
2422/// xmlSchemaValidityWarningFunc warn, void *ctx);
2423/// ```
2424///
2425/// # SAFETY
2426///
2427/// - `ctxt` must be a validation context created by this crate; `err`/`warn`
2428/// must be valid callbacks or NULL; `ctx` may be NULL.
2429#[no_mangle]
2430pub unsafe extern "C" fn xmlSchemaSetValidErrors(
2431 ctxt: *mut xmlSchemaValidCtxt,
2432 err: Option<xmlValidityErrorFunc>,
2433 warn: Option<xmlValidityWarningFunc>,
2434 ctx: *mut c_void,
2435) {
2436 if ctxt.is_null() {
2437 return;
2438 }
2439 let mut guard = VALID_STATES.lock();
2440 let e = guard.entry(ctxt as usize).or_default();
2441 e.err = err;
2442 e.warn = warn;
2443 e.ctx = ctx as usize;
2444}
2445
2446/// Retrieve the error/warning callbacks of a validation context.
2447///
2448/// # UPSTREAM-PARITY
2449///
2450/// ```c
2451/// int xmlSchemaGetValidErrors(xmlSchemaValidCtxt *ctxt,
2452/// xmlSchemaValidityErrorFunc *err,
2453/// xmlSchemaValidityWarningFunc *warn, void **ctx);
2454/// ```
2455///
2456/// Returns 0 on success, -1 if `ctxt` is NULL. Output parameters may be NULL.
2457///
2458/// # SAFETY
2459///
2460/// - `ctxt` must be a validation context created by this crate; `err`/`warn`/
2461/// `ctx` must be writable or NULL.
2462#[no_mangle]
2463pub unsafe extern "C" fn xmlSchemaGetValidErrors(
2464 ctxt: *mut xmlSchemaValidCtxt,
2465 err: *mut Option<xmlValidityErrorFunc>,
2466 warn: *mut Option<xmlValidityWarningFunc>,
2467 ctx: *mut *mut c_void,
2468) -> c_int {
2469 if ctxt.is_null() {
2470 return -1;
2471 }
2472 let state = {
2473 let guard = VALID_STATES.lock();
2474 guard.get(&(ctxt as usize)).copied().unwrap_or_default()
2475 };
2476 // SAFETY: output pointers are caller-guaranteed writable when non-NULL.
2477 unsafe {
2478 if !err.is_null() {
2479 *err = state.err;
2480 }
2481 if !warn.is_null() {
2482 *warn = state.warn;
2483 }
2484 if !ctx.is_null() {
2485 *ctx = state.ctx as *mut c_void;
2486 }
2487 }
2488 0
2489}
2490
2491/// Set the structured error callback of a validation context.
2492///
2493/// # UPSTREAM-PARITY
2494///
2495/// ```c
2496/// void xmlSchemaSetValidStructuredErrors(xmlSchemaValidCtxt *ctxt,
2497/// xmlStructuredErrorFunc serror, void *ctx);
2498/// ```
2499///
2500/// # SAFETY
2501///
2502/// - `ctxt` must be a validation context created by this crate; `serror`
2503/// must be a valid callback or NULL; `ctx` may be NULL.
2504#[no_mangle]
2505pub unsafe extern "C" fn xmlSchemaSetValidStructuredErrors(
2506 ctxt: *mut xmlSchemaValidCtxt,
2507 serror: Option<xmlStructuredErrorFunc>,
2508 ctx: *mut c_void,
2509) {
2510 if ctxt.is_null() {
2511 return;
2512 }
2513 let mut guard = VALID_STATES.lock();
2514 let e = guard.entry(ctxt as usize).or_default();
2515 e.serror = serror;
2516 e.sctx = ctx as usize;
2517}
2518
2519/// Set validation options.
2520///
2521/// # UPSTREAM-PARITY
2522///
2523/// ```c
2524/// int xmlSchemaSetValidOptions(xmlSchemaValidCtxt *ctxt, int options);
2525/// ```
2526///
2527/// Accepts `XML_SCHEMA_VAL_VC_I_CREATE` and `XML_SCHEMA_VAL_XSI_ASSEMBLE`
2528/// (stored for the context; the internal engine does not branch on them).
2529/// Returns 0 on success, -1 if `ctxt` is NULL or the options are invalid.
2530///
2531/// # SAFETY
2532///
2533/// - `ctxt` must be a validation context created by this crate.
2534#[no_mangle]
2535pub unsafe extern "C" fn xmlSchemaSetValidOptions(
2536 ctxt: *mut xmlSchemaValidCtxt,
2537 options: c_int,
2538) -> c_int {
2539 if ctxt.is_null() {
2540 return -1;
2541 }
2542 if options & !VAL_OPTIONS_MASK != 0 {
2543 return -1;
2544 }
2545 VALID_STATES
2546 .lock()
2547 .entry(ctxt as usize)
2548 .or_default()
2549 .options = options;
2550 0
2551}
2552
2553/// Get the validation options of a context.
2554///
2555/// # UPSTREAM-PARITY
2556///
2557/// ```c
2558/// int xmlSchemaValidCtxtGetOptions(xmlSchemaValidCtxt *ctxt);
2559/// ```
2560///
2561/// # SAFETY
2562///
2563/// - `ctxt` must be a validation context created by this crate or NULL.
2564#[no_mangle]
2565pub unsafe extern "C" fn xmlSchemaValidCtxtGetOptions(ctxt: *mut xmlSchemaValidCtxt) -> c_int {
2566 if ctxt.is_null() {
2567 return 0;
2568 }
2569 let guard = VALID_STATES.lock();
2570 guard.get(&(ctxt as usize)).map(|s| s.options).unwrap_or(0)
2571}
2572
2573/// Get the parser context associated with a validation context.
2574///
2575/// # UPSTREAM-PARITY
2576///
2577/// ```c
2578/// xmlParserCtxt * xmlSchemaValidCtxtGetParserCtxt(xmlSchemaValidCtxt *ctxt);
2579/// ```
2580///
2581/// Upstream returns the parser context the validator was created from during
2582/// streaming (SAX) validation. The internal engine performs DOM-based
2583/// validation and never creates an associated parser context, so NULL is
2584/// returned (upstream also returns NULL when none is associated).
2585///
2586/// # SAFETY
2587///
2588/// - `ctxt` may be NULL or a validation context created by this crate.
2589#[no_mangle]
2590pub const unsafe extern "C" fn xmlSchemaValidCtxtGetParserCtxt(
2591 _ctxt: *mut xmlSchemaValidCtxt,
2592) -> *mut c_void {
2593 ptr::null_mut()
2594}
2595
2596/// Set the filename reported with validation errors.
2597///
2598/// # UPSTREAM-PARITY
2599///
2600/// ```c
2601/// void xmlSchemaValidateSetFilename(xmlSchemaValidCtxt *vctxt, const char *filename);
2602/// ```
2603///
2604/// The pointer is stored as-is (upstream does not copy it) and is used as
2605/// the `file` field of structured errors.
2606///
2607/// # SAFETY
2608///
2609/// - `vctxt` must be a validation context created by this crate; `filename`
2610/// must be a valid C string or NULL and stay alive while `vctxt` is used.
2611#[no_mangle]
2612pub unsafe extern "C" fn xmlSchemaValidateSetFilename(
2613 vctxt: *mut xmlSchemaValidCtxt,
2614 filename: *const c_char,
2615) {
2616 if vctxt.is_null() {
2617 return;
2618 }
2619 VALID_STATES
2620 .lock()
2621 .entry(vctxt as usize)
2622 .or_default()
2623 .filename = filename as usize;
2624}
2625
2626/// Set a validity locator callback.
2627///
2628/// # UPSTREAM-PARITY
2629///
2630/// ```c
2631/// void xmlSchemaValidateSetLocator(xmlSchemaValidCtxt *vctxt,
2632/// xmlSchemaValidityLocatorFunc f, void *ctxt);
2633/// ```
2634///
2635/// The locator is stored; the internal engine reports errors without line
2636/// information, so the locator is not consulted.
2637///
2638/// # SAFETY
2639///
2640/// - `vctxt` must be a validation context created by this crate; `f` must be
2641/// a valid callback or NULL; `ctxt` may be NULL.
2642#[no_mangle]
2643pub unsafe extern "C" fn xmlSchemaValidateSetLocator(
2644 vctxt: *mut xmlSchemaValidCtxt,
2645 f: Option<xmlSchemaValidityLocatorFunc>,
2646 ctxt: *mut c_void,
2647) {
2648 if vctxt.is_null() {
2649 return;
2650 }
2651 let mut guard = VALID_STATES.lock();
2652 let e = guard.entry(vctxt as usize).or_default();
2653 e.locator = f;
2654 e.locator_ctx = ctxt as usize;
2655}
2656
2657/// Report whether the last validation had no errors.
2658///
2659/// # UPSTREAM-PARITY
2660///
2661/// ```c
2662/// int xmlSchemaIsValid(xmlSchemaValidCtxt *ctxt);
2663/// ```
2664///
2665/// Returns 1 if the last validation passed, 0 otherwise (and for NULL).
2666///
2667/// # SAFETY
2668///
2669/// - `ctxt` must be a validation context created by this crate or NULL.
2670#[no_mangle]
2671pub unsafe extern "C" fn xmlSchemaIsValid(ctxt: *mut xmlSchemaValidCtxt) -> c_int {
2672 if ctxt.is_null() {
2673 return 0;
2674 }
2675 // SAFETY: ctxt is the internal XsdValidCtxt whose nb_errors the internal
2676 // xmlSchemaValidateDoc updates.
2677 let nb = unsafe { (*(ctxt as *mut XsdValidCtxt)).nb_errors };
2678 if nb == 0 {
2679 1
2680 } else {
2681 0
2682 }
2683}
2684
2685// ═══════════════════════════════════════════════════════════════════════════════
2686// xmlschemas.h — validation entry points
2687// ═══════════════════════════════════════════════════════════════════════════════
2688
2689/// Validate an XML file against the context's schema.
2690///
2691/// # UPSTREAM-PARITY
2692///
2693/// ```c
2694/// int xmlSchemaValidateFile(xmlSchemaValidCtxt *ctxt, const char *filename, int options);
2695/// ```
2696///
2697/// Returns the number of validation errors (0 = valid), or -1 on internal
2698/// error (unreadable/unparseable file). Errors are reported through the
2699/// context's callbacks. Wraps the internal engine's `xmlSchemaValidateDoc`.
2700///
2701/// # SAFETY
2702///
2703/// - `ctxt` must be a validation context created by this crate; `filename`
2704/// must be a valid C string.
2705#[no_mangle]
2706pub unsafe extern "C" fn xmlSchemaValidateFile(
2707 ctxt: *mut xmlSchemaValidCtxt,
2708 filename: *const c_char,
2709 options: c_int,
2710) -> c_int {
2711 if ctxt.is_null() || filename.is_null() {
2712 return -1;
2713 }
2714 // Reset so xmlSchemaIsValid reflects this run even when the internal
2715 // engine only records errors on failure.
2716 unsafe { reset_valid_ctxt(ctxt) };
2717 // SAFETY: xmlReadFile requires a valid C string; options is forwarded.
2718 let doc = unsafe { crate::abi::exports_xml2::xmlReadFile(filename, ptr::null(), options) };
2719 if doc.is_null() {
2720 unsafe {
2721 dispatch_valid_errors(ctxt as usize, &["Failed to parse document".to_string()]);
2722 }
2723 return -1;
2724 }
2725 // SAFETY: ctxt is a valid XsdValidCtxt; doc is a valid _xmlDoc.
2726 let ret = unsafe { crate::xml::schemas::xmlSchemaValidateDoc(ctxt as *mut c_void, doc) };
2727 // SAFETY: doc was created by xmlReadFile; xmlFreeDoc matches.
2728 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2729 if ret != 0 {
2730 // SAFETY: ctxt is the internal XsdValidCtxt whose errors were filled
2731 // by xmlSchemaValidateDoc on failure.
2732 let errors = unsafe { (*(ctxt as *mut XsdValidCtxt)).errors.clone() };
2733 unsafe { dispatch_valid_errors(ctxt as usize, &errors) };
2734 }
2735 ret
2736}
2737
2738/// Validate a single element against the context's schema.
2739///
2740/// # UPSTREAM-PARITY
2741///
2742/// ```c
2743/// int xmlSchemaValidateOneElement(xmlSchemaValidCtxt *ctxt, xmlNode *elem);
2744/// ```
2745///
2746/// The element subtree is serialized and validated through the internal
2747/// engine (`xsd_validate`), which matches it against the global element
2748/// declarations of the schema. Returns the number of validation errors
2749/// (0 = valid), or -1 on internal error.
2750///
2751/// # SAFETY
2752///
2753/// - `ctxt` must be a validation context created by this crate; `elem` must
2754/// be a valid `_xmlNode` pointer.
2755#[no_mangle]
2756pub unsafe extern "C" fn xmlSchemaValidateOneElement(
2757 ctxt: *mut xmlSchemaValidCtxt,
2758 elem: *mut _xmlNode,
2759) -> c_int {
2760 if ctxt.is_null() || elem.is_null() {
2761 return -1;
2762 }
2763 // Reset so xmlSchemaIsValid reflects this run even when the internal
2764 // engine only records errors on failure.
2765 unsafe { reset_valid_ctxt(ctxt) };
2766 // SAFETY: ctxt is the internal XsdValidCtxt.
2767 let valid_ctxt = unsafe { &mut *(ctxt as *mut XsdValidCtxt) };
2768 let Some(schema) = valid_ctxt.schema.clone() else {
2769 return -1;
2770 };
2771 // SAFETY: node_to_string requires a valid node.
2772 let Some(xml) = (unsafe { node_to_string(elem) }) else {
2773 return -1;
2774 };
2775 match xsd_validate(&schema, &xml) {
2776 Ok(()) => 0,
2777 Err(errors) => {
2778 // Record the errors on the context so xmlSchemaGetValidErrors /
2779 // xmlSchemaIsValid see this run's state.
2780 valid_ctxt.errors = errors.clone();
2781 valid_ctxt.nb_errors = errors.len() as i32;
2782 unsafe { dispatch_valid_errors(ctxt as usize, &errors) };
2783 errors.len() as c_int
2784 }
2785 }
2786}
2787
2788/// Validate a stream of SAX events / input buffer content.
2789///
2790/// # UPSTREAM-PARITY
2791///
2792/// ```c
2793/// int xmlSchemaValidateStream(xmlSchemaValidCtxt *ctxt, xmlParserInputBuffer *input,
2794/// xmlCharEncoding enc, const xmlSAXHandler *sax,
2795/// void *user_data);
2796/// ```
2797///
2798/// The internal engine is DOM-based, so content is read from `input` via its
2799/// read callback (the only path in the internal engine that carries data)
2800/// and validated as a document. Returns the number of validation errors
2801/// (0 = valid), or -1 when no content can be obtained (NULL input, NULL
2802/// read callback — upstream SAX-push validation is not supported).
2803///
2804/// # SAFETY
2805///
2806/// - `ctxt` must be a validation context created by this crate; `input`,
2807/// `sax`, `user_data` must be valid or NULL as documented above.
2808#[no_mangle]
2809pub unsafe extern "C" fn xmlSchemaValidateStream(
2810 ctxt: *mut xmlSchemaValidCtxt,
2811 input: *mut _xmlParserInputBuffer,
2812 _enc: xmlCharEncoding,
2813 _sax: *const _xmlSAXHandler,
2814 _user_data: *mut c_void,
2815) -> c_int {
2816 if ctxt.is_null() || input.is_null() {
2817 return -1;
2818 }
2819 // SAFETY: input is a valid _xmlParserInputBuffer.
2820 let ib = unsafe { &*input };
2821 let Some(read) = ib.readcallback else {
2822 // The internal engine's memory/file input buffers carry no content,
2823 // so there is nothing to validate.
2824 return -1;
2825 };
2826 let mut content: Vec<u8> = Vec::new();
2827 let mut chunk = [0u8; 4096];
2828 // SAFETY: read is the caller-supplied callback; context/buffer are valid
2829 // for the duration of each call.
2830 loop {
2831 let n = unsafe { read(ib.context, chunk.as_mut_ptr() as *mut c_char, 4096) };
2832 if n <= 0 {
2833 break;
2834 }
2835 content.extend_from_slice(&chunk[..n as usize]);
2836 }
2837 let xml_str = String::from_utf8_lossy(&content).to_string();
2838 // SAFETY: Delegates to validate_doc_string with the same contract.
2839 unsafe { validate_doc_string(ctxt, &xml_str) }
2840}
2841
2842// ═══════════════════════════════════════════════════════════════════════════════
2843// xmlschemas.h — SAX plug
2844// ═══════════════════════════════════════════════════════════════════════════════
2845
2846/// Plug a schema validator into a SAX handler sequence.
2847///
2848/// # UPSTREAM-PARITY
2849///
2850/// ```c
2851/// xmlSchemaSAXPlugStruct * xmlSchemaSAXPlug(xmlSchemaValidCtxt *ctxt,
2852/// xmlSAXHandler **sax, void **user_data);
2853/// ```
2854///
2855/// Upstream replaces the caller's SAX handler with the validator's own and
2856/// returns a plug to restore it later. The internal engine performs DOM
2857/// validation and cannot intercept SAX events, so the plug is a pass-through:
2858/// `*sax` and `*user_data` are left untouched and a plug is returned so the
2859/// call sequence (plug → validate → unplug) still works. Returns NULL when
2860/// `ctxt` or `sax` is NULL.
2861///
2862/// # SAFETY
2863///
2864/// - `ctxt` must be a validation context created by this crate; `sax`/
2865/// `user_data` must be writable or NULL.
2866#[no_mangle]
2867pub unsafe extern "C" fn xmlSchemaSAXPlug(
2868 ctxt: *mut xmlSchemaValidCtxt,
2869 sax: *mut *mut _xmlSAXHandler,
2870 user_data: *mut *mut c_void,
2871) -> *mut xmlSchemaSAXPlugStruct {
2872 if ctxt.is_null() || sax.is_null() {
2873 return ptr::null_mut();
2874 }
2875 // SAFETY: sax/user_data are caller-guaranteed writable when non-NULL.
2876 let original_sax = unsafe {
2877 if sax.is_null() {
2878 ptr::null_mut()
2879 } else {
2880 *sax
2881 }
2882 };
2883 let original_ud = unsafe {
2884 if user_data.is_null() {
2885 ptr::null_mut()
2886 } else {
2887 *user_data
2888 }
2889 };
2890 let plug = Box::new(XsdSaxPlug {
2891 sax: original_sax as *const _xmlSAXHandler,
2892 user_data: original_ud,
2893 });
2894 Box::into_raw(plug) as *mut xmlSchemaSAXPlugStruct
2895}
2896
2897/// Unplug a schema validator from a SAX handler sequence.
2898///
2899/// # UPSTREAM-PARITY
2900///
2901/// ```c
2902/// int xmlSchemaSAXUnplug(xmlSchemaSAXPlugStruct *plug);
2903/// ```
2904///
2905/// Frees the plug. Returns 0 on success, -1 if `plug` is NULL.
2906///
2907/// # SAFETY
2908///
2909/// - `plug` must be a plug returned by `xmlSchemaSAXPlug` or NULL.
2910#[no_mangle]
2911pub unsafe extern "C" fn xmlSchemaSAXUnplug(plug: *mut xmlSchemaSAXPlugStruct) -> c_int {
2912 if plug.is_null() {
2913 return -1;
2914 }
2915 // SAFETY: plug is a Box created by xmlSchemaSAXPlug.
2916 unsafe { drop(Box::from_raw(plug as *mut XsdSaxPlug)) };
2917 0
2918}
2919
2920// ═══════════════════════════════════════════════════════════════════════════════
2921// xmlschemas.h — schema dump
2922// ═══════════════════════════════════════════════════════════════════════════════
2923
2924/// Dump a schema to a FILE*.
2925///
2926/// # UPSTREAM-PARITY
2927///
2928/// ```c
2929/// void xmlSchemaDump(FILE *output, xmlSchema *schema);
2930/// ```
2931///
2932/// Writes a human-readable listing of the compiled schema components
2933/// (target namespace, form defaults, component tree with types and
2934/// occurrence bounds) using the internal engine's `XsdSchema` data.
2935///
2936/// # SAFETY
2937///
2938/// - `output` must be a valid open `FILE*`; `schema` must be a schema
2939/// pointer produced by this crate's parser (or NULL).
2940#[no_mangle]
2941pub unsafe extern "C" fn xmlSchemaDump(output: *mut c_void, schema: *mut xmlSchema) {
2942 if output.is_null() || schema.is_null() {
2943 return;
2944 }
2945 // SAFETY: schema is the internal boxed XsdSchema.
2946 let s = unsafe { &*(schema as *const XsdSchema) };
2947 let mut out = String::new();
2948 out.push_str("Schema dump\n");
2949 if let Some(ref tns) = s.target_namespace {
2950 out.push_str(&format!(" target namespace: {}\n", tns));
2951 } else {
2952 out.push_str(" target namespace: (none)\n");
2953 }
2954 out.push_str(&format!(
2955 " element form default: {}\n",
2956 s.element_form_default.as_deref().unwrap_or("unqualified")
2957 ));
2958 out.push_str(&format!(
2959 " attribute form default: {}\n",
2960 s.attribute_form_default.as_deref().unwrap_or("unqualified")
2961 ));
2962 out.push_str(" components:\n");
2963 for c in &s.components {
2964 dump_component(&mut out, c, 2);
2965 }
2966 // SAFETY: output is a valid FILE*.
2967 unsafe { libc::fputs(out.as_ptr() as *const c_char, output as *mut libc::FILE) };
2968}
2969
2970/// Append a component (and its children) to the dump text.
2971fn dump_component(out: &mut String, c: &crate::xml::schemas::XsdComponent, depth: usize) {
2972 let indent = " ".repeat(depth);
2973 let ctype = format!("{:?}", c.component_type).to_lowercase();
2974 let mut line = format!("{}{}", indent, ctype);
2975 if let Some(ref name) = c.name {
2976 line.push_str(&format!(" name='{}'", name));
2977 }
2978 if let Some(ref dtype) = c.datatype {
2979 line.push_str(&format!(" type={:?}", dtype));
2980 }
2981 if c.min_occurs != 1 || c.max_occurs != 1 {
2982 line.push_str(&format!(
2983 " minOccurs={} maxOccurs={}",
2984 c.min_occurs,
2985 if c.max_occurs == -1 {
2986 "unbounded".to_string()
2987 } else {
2988 c.max_occurs.to_string()
2989 }
2990 ));
2991 }
2992 if !c.facets.is_empty() {
2993 let facets: Vec<String> = c
2994 .facets
2995 .iter()
2996 .map(|(k, v)| format!("{:?}={}", k, v))
2997 .collect();
2998 line.push_str(&format!(" facets=[{}]", facets.join(", ")));
2999 }
3000 out.push_str(&line);
3001 out.push('\n');
3002 for child in &c.children {
3003 dump_component(out, child, depth + 1);
3004 }
3005 for attr in &c.attributes {
3006 dump_component(out, attr, depth + 1);
3007 }
3008}
3009
3010// ═══════════════════════════════════════════════════════════════════════════════
3011// schematron.h
3012// ═══════════════════════════════════════════════════════════════════════════════
3013
3014/// Create a Schematron parser context from an already-parsed document.
3015///
3016/// # UPSTREAM-PARITY
3017///
3018/// ```c
3019/// xmlSchematronParserCtxt * xmlSchematronNewDocParserCtxt(xmlDoc *doc);
3020/// ```
3021///
3022/// The document is serialized and compiled through the internal Schematron
3023/// engine (`schematron_parse`). Following that engine's convention,
3024/// `xmlSchematronParse` returns its context as the schema pointer. Returns
3025/// NULL if `doc` is NULL or the schema fails to compile.
3026///
3027/// # SAFETY
3028///
3029/// - `doc` must be a valid `_xmlDoc` pointer.
3030#[no_mangle]
3031pub unsafe extern "C" fn xmlSchematronNewDocParserCtxt(
3032 doc: *mut _xmlDoc,
3033) -> *mut xmlSchematronParserCtxt {
3034 // SAFETY: doc_to_string requires a valid doc.
3035 let Some(xml) = (unsafe { doc_to_string(doc) }) else {
3036 return ptr::null_mut();
3037 };
3038 match schematron_parse(&xml) {
3039 Ok(schema) => Box::into_raw(Box::new(schema)) as *mut xmlSchematronParserCtxt,
3040 Err(_) => ptr::null_mut(),
3041 }
3042}
3043
3044/// Set the structured error callback of a Schematron validation context.
3045///
3046/// # UPSTREAM-PARITY
3047///
3048/// ```c
3049/// void xmlSchematronSetValidStructuredErrors(xmlSchematronValidCtxt *ctxt,
3050/// xmlStructuredErrorFunc serror, void *ctx);
3051/// ```
3052///
3053/// The callback is stored in a side registry keyed by the context address.
3054/// (The internal Schematron engine reports errors through its own channel;
3055/// the stored callback is not invoked by it.)
3056///
3057/// # SAFETY
3058///
3059/// - `ctxt` must be a Schematron validation context created by this crate;
3060/// `serror` must be a valid callback or NULL; `ctx` may be NULL.
3061#[no_mangle]
3062pub unsafe extern "C" fn xmlSchematronSetValidStructuredErrors(
3063 ctxt: *mut xmlSchematronValidCtxt,
3064 serror: Option<xmlStructuredErrorFunc>,
3065 ctx: *mut c_void,
3066) {
3067 if ctxt.is_null() {
3068 return;
3069 }
3070 let mut guard = SCHEMATRON_VALID_STATES.lock();
3071 let e = guard.entry(ctxt as usize).or_default();
3072 e.serror = serror;
3073 e.sctx = ctx as usize;
3074}
3075
3076/// Install a custom resource loader on an XML Schema parser context
3077/// (upstream xmlschemas.c `xmlSchemaSetResourceLoader`).
3078///
3079/// # SAFETY
3080///
3081/// - `ctxt` must be a valid parser context pointer or NULL.
3082#[no_mangle]
3083pub unsafe extern "C" fn xmlSchemaSetResourceLoader(
3084 ctxt: *mut c_void,
3085 loader: Option<crate::abi::callbacks::xmlResourceLoader>,
3086 data: *mut c_void,
3087) {
3088 if ctxt.is_null() {
3089 return;
3090 }
3091 let mut map = PARSER_STATES.lock();
3092 let st = map.entry(ctxt as usize).or_default();
3093 st.resource_loader = loader;
3094 st.resource_ctxt = data as usize;
3095}
3096
3097/// Install a custom resource loader on an XInclude context
3098/// (upstream xinclude.c `xmlXIncludeSetResourceLoader`).
3099///
3100/// # SAFETY
3101///
3102/// - `ctxt` must be a valid XInclude context pointer or NULL.
3103#[no_mangle]
3104pub unsafe extern "C" fn xmlXIncludeSetResourceLoader(
3105 ctxt: crate::abi::exports_xinclude::xmlXIncludeCtxtPtr,
3106 loader: Option<crate::abi::callbacks::xmlResourceLoader>,
3107 data: *mut c_void,
3108) {
3109 if ctxt.is_null() {
3110 return;
3111 }
3112 let mut map = PARSER_STATES.lock();
3113 let st = map.entry(ctxt as usize).or_default();
3114 st.resource_loader = loader;
3115 st.resource_ctxt = data as usize;
3116}