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