Skip to main content

libxml_rs/abi/
exports_xslt_compile.rs

1//! C ABI exports for libxslt.so.1 — the "compile" family (§16, Phase 8).
2//!
3//! This module implements the stylesheet-compilation entry points of the
4//! libxslt 1.1.45 C ABI:
5//!
6//! - Stylesheet creation: `xsltNewStylesheet`, `xsltParseStylesheetProcess`,
7//!   `xsltParseStylesheetUser`, `xsltParseStylesheetImportedDoc`
8//! - Imports/includes: `xsltParseStylesheetImport`, `xsltParseStylesheetInclude`
9//! - Top-level constructs: `xsltParseStylesheetOutput`,
10//!   `xsltParseStylesheetAttributeSet`, `xsltParseGlobalVariable`,
11//!   `xsltParseGlobalParam`
12//! - Content preprocessing: `xsltParseTemplateContent`, `xsltCompileAttr`
13//! - Precomputed instructions: `xsltDocumentComp`, `xsltStylePreCompute`,
14//!   `xsltPreComputeExtModuleElement`, `xsltNormalizeCompSteps`,
15//!   `xsltFreeStylePreComps`
16//! - Style documents: `xsltNewStyleDocument`, `xsltLoadStyleDocument`,
17//!   `xsltFreeStyleDocuments`
18//! - Global state: `xsltInitGlobals`, `xsltUninit`, `xsltFreeExts`,
19//!   `xsltShutdownExts`, `xsltDebugDumpExtensions`
20//!
21//! # UPSTREAM-PARITY
22//!
23//! Every function is a faithful port of the upstream libxslt 1.1.45 sources
24//! in `archaeology/libxslt-git/libxslt/` (xslt.c, imports.c, preproc.c,
25//! attributes.c, attrvt.c, documents.c, variables.c, extensions.c, pattern.c).
26//! The oracle build has `XSLT_REFACTORED` disabled, so the *old* (non
27//! refactored) code paths are the authoritative semantics.
28//!
29//! # Engine wiring
30//!
31//! The native-Rust engine in `src/xslt/compiler` compiles stylesheets
32//! eagerly (top-level constructs) but compiles *instructions* lazily: at
33//! transform time the runtime dispatches on the raw instruction node
34//! (`src/xslt/transform`, `xsltProcessInstruction`) and never consults
35//! `node->psvi`. Consequently the upstream per-instruction compilers
36//! (`xsltApplyTemplatesComp` et al.) have no data to store; the ABI
37//! functions below keep their *observable* semantics (the grammar checks
38//! that bump `style->errors` / `style->warnings`, the return values, the
39//! `style->preComps` chain for the structures that genuinely exist) and
40//! skip the dead precomp allocation. Each such divergence is documented
41//! at the function.
42
43#![allow(non_snake_case)]
44#![allow(unused_variables)]
45#![allow(clippy::missing_safety_doc)]
46#![allow(clippy::not_unsafe_ptr_arg_deref)]
47
48use core::ffi::c_void;
49use core::ptr;
50use std::os::raw::{c_char, c_int};
51
52use crate::abi::allocator::{xmlFreeImpl, xmlMallocImpl};
53use crate::abi::exports_hash::xmlDictReference;
54use crate::abi::exports_string::xmlStrstr;
55use crate::abi::exports_tree::xmlNodeGetBase;
56use crate::abi::exports_uri::xmlBuildURI;
57use crate::abi::exports_xml2::*;
58use crate::abi::structs::*;
59use crate::abi::types::xmlElementType::*;
60use crate::abi::types::*;
61use crate::xml::xpath::exports::xmlXPathNewString;
62
63/// The XSLT namespace URI (upstream `XSLT_NAMESPACE`, xslt.h).
64const XSLT_NAMESPACE: &[u8] = b"http://www.w3.org/1999/XSL/Transform";
65
66/// `XSLT_PARSE_OPTIONS` (xslt.h): NOENT | DTDLOAD | DTDATTR | NOCDATA.
67const XSLT_PARSE_OPTIONS: c_int = (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
68
69/// `XSLT_LOAD_STYLESHEET` (documents.h).
70const XSLT_LOAD_STYLESHEET: c_int = 1;
71
72/// `xsltStyleType` values (xsltInternals.h, non-refactored enum).
73const XSLT_FUNC_DOCUMENT: c_int = 17;
74const XSLT_FUNC_EXTENSION: c_int = 22;
75
76/// `XSLT_VAR_PARAM` (variables.c): stack-elem PARAM flag used to
77/// distinguish global variables from parameters in `_xsltStackElem.flags`.
78const XSLT_VAR_PARAM: c_int = 1 << 1;
79
80/// `XSLT_SECPREF_READ_FILE` / `XSLT_SECPREF_READ_NETWORK` (security.c).
81const XSLT_SECPREF_READ_FILE: c_int = 1;
82const XSLT_SECPREF_READ_NETWORK: c_int = 4;
83
84/// `XSLT_MAX_NESTING` (imports.c).
85const XSLT_MAX_NESTING: c_int = 40;
86
87/// `xsltExtMarker` (preproc.c) — the sentinel stored in `inst->psvi` for
88/// extension elements with no registered precomputation.
89///
90/// # UPSTREAM-PARITY
91///
92/// Upstream exports `xsltExtMarker` as a variable; the candidate engine
93/// never reads `psvi`, so the marker is carried as a private static (the
94/// ext-family ABI exports own the exported variable).
95static XSLT_EXT_MARKER: [u8; 18] = *b"Extension Element\0";
96
97// ═══════════════════════════════════════════════════════════════════════════════
98// Types & structures
99// ═══════════════════════════════════════════════════════════════════════════════
100
101/// `xsltTransformFunction` (xsltInternals.h): the handling function of a
102/// compiled instruction/extension element.
103pub type xsltTransformFunction = unsafe extern "C" fn(
104    ctxt: *mut _xsltTransformContext,
105    node: *mut _xmlNode,
106    inst: *mut _xmlNode,
107    comp: *mut c_void,
108);
109
110/// `xsltElemPreCompDeallocator` (xsltInternals.h): deallocates a precomp.
111pub type xsltElemPreCompDeallocator = unsafe extern "C" fn(comp: *mut c_void);
112
113/// `xsltPreComputeFunction` (extensions.h): precomputation callback of an
114/// extension element.
115pub type xsltPreComputeFunction = unsafe extern "C" fn(
116    style: *mut _xsltStylesheet,
117    inst: *mut _xmlNode,
118    function: Option<xsltTransformFunction>,
119) -> *mut c_void;
120
121/// `_xsltElemPreComp` (xsltInternals.h, non-refactored layout).
122///
123/// ```c
124/// struct _xsltElemPreComp {
125///     xsltElemPreCompPtr next;    /* next item in the global chained list
126///                                    held by xsltStylesheet. */
127///     xsltStyleType type;         /* type of the element */
128///     xsltTransformFunction func; /* handling function */
129///     xmlNodePtr inst;            /* the node in the stylesheet's tree
130///                                    corresponding to this item */
131///     /* end of common part */
132///     xsltElemPreCompDeallocator free; /* the deallocator */
133/// };
134/// ```
135#[repr(C)]
136pub struct _xsltElemPreComp {
137    pub next: *mut _xsltElemPreComp,
138    pub type_: c_int, // xsltStyleType
139    pub func: Option<xsltTransformFunction>,
140    pub inst: *mut _xmlNode,
141    pub free: Option<xsltElemPreCompDeallocator>,
142}
143
144/// The old (non-refactored) `_xsltStylePreComp` (xsltInternals.h) extends
145/// `_xsltElemPreComp` with per-instruction precomputed values. The
146/// candidate engine compiles instructions lazily, so only the fields that
147/// the compile-family itself writes (`ver11`, `filename`, `has_filename`,
148/// used by `xsltDocumentComp`) are carried; the remaining upstream fields
149/// (sort/name/select/numdata/comp/nsList…) hold nothing in this engine and
150/// are omitted (documented divergence — nothing reads them).
151#[repr(C)]
152struct _xsltStylePreComp {
153    pub base: _xsltElemPreComp,
154    pub ver11: c_int,
155    pub filename: *const xmlChar,
156    pub has_filename: c_int,
157}
158
159/// Extension-element registry entry (upstream `xsltElementsHash` payload
160/// `_xsltExtElement { precomp, transform }`, extended with the lookup key).
161#[repr(C)]
162struct _xsltExtElementEntry {
163    pub next: *mut _xsltExtElementEntry,
164    pub name: *mut xmlChar,
165    pub URI: *mut xmlChar,
166    pub precomp: Option<xsltPreComputeFunction>,
167    pub transform: Option<xsltTransformFunction>,
168}
169
170/// Global registry of registered extension elements, keyed by
171/// `(name, namespace-URI)` — the candidate mirror of upstream's global
172/// `xsltElementsHash`. Upstream guards it with `xsltExtMutex`; the
173/// candidate build is single-threaded for the compile phase, matching the
174/// rest of the crate's registry handling.
175static mut XSLT_ELEMENTS_REGISTRY: *mut _xsltExtElementEntry = ptr::null_mut();
176
177/// Global registry of registered extension *modules* — the candidate
178/// mirror of upstream's `xsltExtensionsHash` (used by
179/// `xsltDebugDumpExtensions` and `xsltShutdownExts`).
180#[repr(C)]
181struct _xsltExtModuleEntry {
182    pub next: *mut _xsltExtModuleEntry,
183    pub URI: *mut xmlChar,
184    pub shutdownFunc: Option<unsafe extern "C" fn(*mut c_void, *const xmlChar, *mut c_void)>,
185}
186
187static mut XSLT_MODULES_REGISTRY: *mut _xsltExtModuleEntry = ptr::null_mut();
188
189/// Whether `xsltInitGlobals` has run (mirrors upstream `xsltExtMutex !=
190/// NULL`).
191static mut XSLT_GLOBALS_INITIALIZED: c_int = 0;
192
193// ═══════════════════════════════════════════════════════════════════════════════
194// Helpers
195// ═══════════════════════════════════════════════════════════════════════════════
196
197/// IS_XSLT_ELEM (xsltutils.h).
198unsafe fn is_xslt_elem(n: *mut _xmlNode) -> bool {
199    if n.is_null() || (*n).type_ != XML_ELEMENT_NODE as c_int || (*n).ns.is_null() {
200        return false;
201    }
202    xmlStrEqual((*(*n).ns).href, XSLT_NAMESPACE.as_ptr() as *const xmlChar) != 0
203}
204
205/// IS_XSLT_NAME (xsltutils.h).
206unsafe fn is_xslt_name(n: *mut _xmlNode, val: &[u8]) -> bool {
207    if n.is_null() || (*n).name.is_null() {
208        return false;
209    }
210    let len = libc::strlen((*n).name as *const libc::c_char) as usize;
211    len == val.len() && core::slice::from_raw_parts((*n).name, len) == val
212}
213
214/// IS_BLANK (xsltutils.h): a string made only of XML whitespace.
215unsafe fn is_blank_str(str: *const xmlChar) -> bool {
216    if str.is_null() {
217        return true;
218    }
219    let mut cur = str;
220    while *cur != 0 {
221        if *cur != b' ' && *cur != b'\t' && *cur != b'\n' && *cur != b'\r' {
222            return false;
223        }
224        cur = cur.add(1);
225    }
226    true
227}
228
229/// Report a compile-time error (xsltTransformError; the candidate records
230/// the literal message, matching the crate's non-variadic convention).
231/// Render a NUL-terminated C string as a byte slice for `report_error`.
232unsafe fn cbytes(p: *const u8) -> &'static [u8] {
233    if p.is_null() {
234        return b"";
235    }
236    core::ffi::CStr::from_ptr(p as *const c_char).to_bytes()
237}
238
239unsafe fn report_error(style: *mut _xsltStylesheet, inst: *mut _xmlNode, msg: &[u8]) {
240    let mut m = msg.to_vec();
241    m.push(0);
242    crate::xslt::errors::xsltTransformError(
243        ptr::null_mut(),
244        style,
245        inst,
246        m.as_ptr() as *const c_char,
247    );
248}
249
250/// `xsltFreeExtDef` (extensions.c): free one extension-prefix def.
251///
252/// Not used by the compile family itself (see `xsltFreeExts`), but kept
253/// for symmetry with the upstream def-list handling.
254#[allow(dead_code)]
255unsafe fn xslt_free_ext_def(entry: *mut c_void) {
256    // The candidate never allocates xsltExtDef lists; this is unreachable
257    // and kept only to document the upstream shape.
258    let _ = entry;
259}
260
261/// Look up a registered extension element by (name, namespace-URI).
262unsafe fn ext_element_lookup(
263    name: *const xmlChar,
264    uri: *const xmlChar,
265) -> *mut _xsltExtElementEntry {
266    if name.is_null() || uri.is_null() {
267        return ptr::null_mut();
268    }
269    let mut cur = XSLT_ELEMENTS_REGISTRY;
270    while !cur.is_null() {
271        if !(*cur).name.is_null()
272            && !(*cur).URI.is_null()
273            && xmlStrEqual((*cur).name, name) != 0
274            && xmlStrEqual((*cur).URI, uri) != 0
275        {
276            return cur;
277        }
278        cur = (*cur).next;
279    }
280    ptr::null_mut()
281}
282
283/// Duplicate a NUL-terminated string with the xml allocator.
284unsafe fn dup_str(s: *const xmlChar) -> *mut xmlChar {
285    if s.is_null() {
286        return ptr::null_mut();
287    }
288    let len = libc::strlen(s as *const libc::c_char);
289    let copy = xmlMallocImpl(len + 1) as *mut xmlChar;
290    if copy.is_null() {
291        return ptr::null_mut();
292    }
293    core::ptr::copy_nonoverlapping(s, copy, len);
294    *copy.add(len) = 0;
295    copy
296}
297
298/// `xsltCheckRead` (security.c) for the file/network split. The candidate
299/// security module exposes only the check-fn registry; the URI-scheme
300/// analysis is reduced to upstream's first-order file-vs-network test
301/// (a `://` in the value selects the network check).
302///
303/// Returns 1 if read is allowed, 0 if denied, -1 on error.
304unsafe fn xslt_check_read(
305    sec: *mut c_void,
306    ctxt: *mut _xsltTransformContext,
307    url: *const xmlChar,
308) -> c_int {
309    if sec.is_null() {
310        return 1;
311    }
312    let is_network = !xmlStrstr(url, b"://\0".as_ptr() as *const xmlChar).is_null();
313    let option = if is_network {
314        XSLT_SECPREF_READ_NETWORK
315    } else {
316        XSLT_SECPREF_READ_FILE
317    };
318    let check = crate::xslt::security::xsltGetSecurityPrefs(sec, option);
319    if let Some(check_fn) = check {
320        let ret = check_fn(sec, ctxt as *mut c_void, url as *const c_char);
321        if ret == 0 {
322            if is_network {
323                report_error(ptr::null_mut(), ptr::null_mut(), b"Network access for ");
324            } else {
325                report_error(ptr::null_mut(), ptr::null_mut(), b"Local file read for ");
326            }
327            report_error(ptr::null_mut(), ptr::null_mut(), cbytes(url as *const u8));
328            report_error(ptr::null_mut(), ptr::null_mut(), b" refused\n");
329            return 0;
330        }
331        return ret;
332    }
333    1
334}
335
336/// `xsltDocDefaultLoader` (documents.c) for the candidate engine: if a
337/// global loader function is registered it is invoked (the returned
338/// parser input cannot be fed into a document by this engine and is
339/// freed, matching `src/xslt/documents` `load_via_loader`); otherwise the
340/// URI is parsed as a file. Returns a parsed document or NULL.
341unsafe fn xslt_doc_default_loader(
342    uri: *const xmlChar,
343    _dict: *mut c_void,
344    options: c_int,
345    ctxt: *mut c_void,
346    _type: c_int,
347) -> *mut _xmlDoc {
348    let loader = crate::xslt::documents::xsltGetLoaderFunc();
349    if let Some(loader_fn) = loader {
350        let input = loader_fn(
351            ctxt,
352            ptr::null(), // base URL
353            uri as *const c_char,
354            ptr::null(), // ns
355            0,           // secondary
356        );
357        if !input.is_null() {
358            crate::xml::parser::helpers::free_parser_input(input);
359        }
360    }
361    xmlReadFile(uri as *const c_char, ptr::null(), options)
362}
363
364/// `xsltNewDecimalFormat` (xslt.c): create a decimal format with the
365/// default values. `name`/`nsUri` are borrowed (not owned).
366pub(crate) unsafe fn xslt_new_decimal_format(
367    nsUri: *const xmlChar,
368    name: *mut xmlChar,
369) -> *mut _xsltDecimalFormat {
370    let self_ =
371        xmlMallocImpl(core::mem::size_of::<_xsltDecimalFormat>()) as *mut _xsltDecimalFormat;
372    if !self_.is_null() {
373        ptr::write_bytes(
374            self_ as *mut u8,
375            0,
376            core::mem::size_of::<_xsltDecimalFormat>(),
377        );
378        (*self_).nsUri = nsUri;
379        (*self_).name = name;
380        // Default values (xslt.c, UTF-8 for U+2030 PER MILLE SIGN).
381        (*self_).digit = xmlStrdup(b"#\0".as_ptr() as *const xmlChar);
382        (*self_).patternSeparator = xmlStrdup(b";\0".as_ptr() as *const xmlChar);
383        (*self_).decimalPoint = xmlStrdup(b".\0".as_ptr() as *const xmlChar);
384        (*self_).grouping = xmlStrdup(b",\0".as_ptr() as *const xmlChar);
385        (*self_).percent = xmlStrdup(b"%\0".as_ptr() as *const xmlChar);
386        (*self_).permille = xmlStrdup("\u{2030}\0".as_ptr() as *const xmlChar);
387        (*self_).zeroDigit = xmlStrdup(b"0\0".as_ptr() as *const xmlChar);
388        (*self_).minusSign = xmlStrdup(b"-\0".as_ptr() as *const xmlChar);
389        (*self_).infinity = xmlStrdup(b"Infinity\0".as_ptr() as *const xmlChar);
390        (*self_).noNumber = xmlStrdup(b"NaN\0".as_ptr() as *const xmlChar);
391    }
392    self_
393}
394
395/// `xsltNewStylesheetInternal` (xslt.c).
396unsafe fn xslt_new_stylesheet_internal(parent: *mut _xsltStylesheet) -> *mut _xsltStylesheet {
397    let ret = xmlMallocImpl(core::mem::size_of::<_xsltStylesheet>()) as *mut _xsltStylesheet;
398    if ret.is_null() {
399        report_error(
400            ptr::null_mut(),
401            ptr::null_mut(),
402            b"xsltNewStylesheet : malloc failed\n",
403        );
404        return ptr::null_mut();
405    }
406    ptr::write_bytes(ret as *mut u8, 0, core::mem::size_of::<_xsltStylesheet>());
407
408    (*ret).parent = parent;
409    (*ret).omitXmlDeclaration = -1;
410    (*ret).standalone = -1;
411    (*ret).decimalFormat = xslt_new_decimal_format(ptr::null(), ptr::null_mut());
412    (*ret).indent = -1;
413    (*ret).errors = 0;
414    (*ret).warnings = 0;
415    (*ret).exclPrefixNr = 0;
416    (*ret).exclPrefixMax = 0;
417    (*ret).exclPrefixTab = ptr::null_mut();
418    (*ret).extInfos = ptr::null_mut();
419    (*ret).extrasNr = 0;
420    (*ret).internalized = 1;
421    (*ret).literal_result = 0;
422    (*ret).forwards_compatible = 0;
423    (*ret).dict = xmlDictCreate();
424
425    if parent.is_null() {
426        (*ret).principal = ret;
427        (*ret).xpathCtxt = xmlXPathNewContext(ptr::null_mut());
428        if (*ret).xpathCtxt.is_null() {
429            report_error(
430                ptr::null_mut(),
431                ptr::null_mut(),
432                b"xsltNewStylesheet: xmlXPathNewContext failed\n",
433            );
434            crate::xslt::stylesheet::xsltFreeStylesheet(ret);
435            return ptr::null_mut();
436        }
437        if crate::xml::xpath::exports::xmlXPathContextSetCache((*ret).xpathCtxt, 1, -1, 0) == -1 {
438            crate::xslt::stylesheet::xsltFreeStylesheet(ret);
439            return ptr::null_mut();
440        }
441    } else {
442        (*ret).principal = (*parent).principal;
443    }
444
445    // Upstream calls xsltInit() (registers built-in extras, sets the
446    // initialized flag). The candidate has no built-in extras; xsltInit
447    // only marks the library initialized.
448    crate::abi::exports_xslt::xsltInit();
449
450    ret
451}
452
453// ═══════════════════════════════════════════════════════════════════════════════
454// 1. Stylesheet creation & parsing (xslt.c)
455// ═══════════════════════════════════════════════════════════════════════════════
456
457/// Create a new XSLT stylesheet.
458///
459/// # UPSTREAM-PARITY
460///
461/// ```c
462/// xsltStylesheetPtr
463/// xsltNewStylesheet(void) {
464///     return xsltNewStylesheetInternal(NULL);
465/// }
466/// ```
467///
468/// See `xsltNewStylesheetInternal` (xslt.c 1.1.45): xmlMalloc + memset, a
469/// default decimal format, `dict = xmlDictCreate()`, `internalized = 1`,
470/// and for the principal stylesheet an XPath context with its cache
471/// enabled. `version`/`method`/`encoding` are left NULL (they are set
472/// later by `xsltParseStylesheetOutput`/version processing).
473///
474/// # SAFETY
475///
476/// The caller owns the returned stylesheet and must free it with
477/// `xsltFreeStylesheet`.
478#[no_mangle]
479pub unsafe extern "C" fn xsltNewStylesheet() -> *mut _xsltStylesheet {
480    xslt_new_stylesheet_internal(ptr::null_mut())
481}
482
483/// Parse an XSLT stylesheet, adding the associated structures.
484///
485/// # UPSTREAM-PARITY
486///
487/// ```c
488/// xsltStylesheetPtr
489/// xsltParseStylesheetProcess(xsltStylesheetPtr ret, xmlDocPtr doc) {
490///     xsltInitGlobals();
491///     if (doc == NULL) return(NULL);
492///     if (ret == NULL) return(ret);
493///     cur = xmlDocGetRootElement(doc);
494///     if (cur == NULL) { ... "empty stylesheet" ... return(NULL); }
495///     ...
496/// }
497/// ```
498///
499/// # ENGINE-WIRING
500///
501/// The heavy lifting (tree preprocessing, top-level compilation, or the
502/// simplified-stylesheet implicit template) is performed by the engine's
503/// `crate::xslt::compiler::compile`, which returns 0 on success.
504///
505/// # SAFETY
506///
507/// - `style` must be a valid `_xsltStylesheet`, or NULL.
508/// - `doc` must be a valid parsed document, or NULL.
509#[no_mangle]
510pub unsafe extern "C" fn xsltParseStylesheetProcess(
511    style: *mut _xsltStylesheet,
512    doc: *mut _xmlDoc,
513) -> *mut _xsltStylesheet {
514    xsltInitGlobals();
515
516    if doc.is_null() {
517        return ptr::null_mut();
518    }
519    if style.is_null() {
520        return style;
521    }
522
523    let root = crate::xml::tree::doc_get_root_element(doc);
524    if root.is_null() {
525        report_error(
526            style,
527            doc as *mut _xmlNode,
528            b"xsltParseStylesheetProcess : empty stylesheet\n",
529        );
530        return ptr::null_mut();
531    }
532
533    let ret = crate::xslt::compiler::compile(style, doc);
534    if ret != 0 {
535        return ptr::null_mut();
536    }
537    style
538}
539
540/// Parse an XSLT stylesheet with a user-provided stylesheet struct.
541///
542/// # UPSTREAM-PARITY
543///
544/// ```c
545/// int
546/// xsltParseStylesheetUser(xsltStylesheetPtr style, xmlDocPtr doc) {
547///     if ((style == NULL) || (doc == NULL)) return(-1);
548///     if (doc->dict != NULL) {
549///         xmlDictFree(style->dict);
550///         style->dict = doc->dict;
551///         xmlDictReference(style->dict);
552///     }
553///     xsltGatherNamespaces(style);
554///     style->doc = doc;
555///     if (xsltParseStylesheetProcess(style, doc) == NULL) {
556///         style->doc = NULL;
557///         return(-1);
558///     }
559///     if (style->parent == NULL)
560///         xsltResolveStylesheetAttributeSet(style);
561///     if (style->errors != 0) {
562///         style->doc = NULL;
563///         ... cleanup ...
564///         return(-1);
565///     }
566///     return(0);
567/// }
568/// ```
569///
570/// # ENGINE-WIRING
571///
572/// `xsltGatherNamespaces` (namespaces.c) builds `style->nsHash` for the
573/// upstream engine; the candidate resolves namespaces at runtime from the
574/// node tree, so the call has no candidate equivalent (documented
575/// divergence — `nsHash` is unused by the engine).
576///
577/// # SAFETY
578///
579/// - `style` must be a valid `_xsltStylesheet`, or NULL.
580/// - `doc` must be a valid parsed document, or NULL.
581#[no_mangle]
582pub unsafe extern "C" fn xsltParseStylesheetUser(
583    style: *mut _xsltStylesheet,
584    doc: *mut _xmlDoc,
585) -> c_int {
586    if style.is_null() || doc.is_null() {
587        return -1;
588    }
589
590    // Adjust the string dict (xslt.c 1.1.45).
591    if !(*doc).dict.is_null() {
592        xmlDictFree((*style).dict);
593        (*style).dict = (*doc).dict;
594        xmlDictReference((*style).dict);
595    }
596
597    // xsltGatherNamespaces(style) — no-op in the candidate engine, see
598    // module docs.
599
600    (*style).doc = doc;
601    if xsltParseStylesheetProcess(style, doc).is_null() {
602        (*style).doc = ptr::null_mut();
603        return -1;
604    }
605
606    if (*style).parent.is_null() {
607        crate::abi::exports_xslt_apply::xsltResolveStylesheetAttributeSet(style);
608    }
609
610    if (*style).errors != 0 {
611        // Detach the doc from the stylesheet; otherwise the doc would be
612        // freed by xsltFreeStylesheet(). The caller keeps ownership.
613        (*style).doc = ptr::null_mut();
614        return -1;
615    }
616
617    0
618}
619
620/// Parse an XSLT stylesheet from a document, with a parent stylesheet
621/// context (used for `xsl:import`).
622///
623/// # UPSTREAM-PARITY
624///
625/// ```c
626/// xsltStylesheetPtr
627/// xsltParseStylesheetImportedDoc(xmlDocPtr doc,
628///                                xsltStylesheetPtr parentStyle) {
629///     if (doc == NULL) return(NULL);
630///     retStyle = xsltNewStylesheetInternal(parentStyle);
631///     if (retStyle == NULL) return(NULL);
632///     if (xsltParseStylesheetUser(retStyle, doc) != 0) {
633///         xsltFreeStylesheet(retStyle);
634///         return(NULL);
635///     }
636///     return(retStyle);
637/// }
638/// ```
639///
640/// # SAFETY
641///
642/// - `doc` must be a valid parsed document, or NULL. On failure the
643///   document is detached from the stylesheet and remains owned by the
644///   caller.
645#[no_mangle]
646pub unsafe extern "C" fn xsltParseStylesheetImportedDoc(
647    doc: *mut _xmlDoc,
648    parentStyle: *mut _xsltStylesheet,
649) -> *mut _xsltStylesheet {
650    if doc.is_null() {
651        return ptr::null_mut();
652    }
653
654    let retStyle = xslt_new_stylesheet_internal(parentStyle);
655    if retStyle.is_null() {
656        return ptr::null_mut();
657    }
658
659    if xsltParseStylesheetUser(retStyle, doc) != 0 {
660        crate::xslt::stylesheet::xsltFreeStylesheet(retStyle);
661        return ptr::null_mut();
662    }
663
664    retStyle
665}
666
667// ═══════════════════════════════════════════════════════════════════════════════
668// 2. Imports & includes (imports.c)
669// ═══════════════════════════════════════════════════════════════════════════════
670
671/// `xsltFixImportedCompSteps` (imports.c): normalize the compiled steps of
672/// an imported stylesheet against the master's extra slots.
673///
674/// # ENGINE-WIRING
675///
676/// Upstream scans the imported templates hash with `xsltNormalizeCompSteps`
677/// (which re-bases step extra indices). The candidate's compiled patterns
678/// carry no step-extra state (`xsltNormalizeCompSteps` is a no-op), so
679/// only the `extrasNr` accumulation is observable.
680unsafe fn xslt_fix_imported_comp_steps(master: *mut _xsltStylesheet, style: *mut _xsltStylesheet) {
681    (*master).extrasNr += (*style).extrasNr;
682    let mut res = (*style).imports;
683    while !res.is_null() {
684        xslt_fix_imported_comp_steps(master, res);
685        res = (*res).next;
686    }
687}
688
689/// `xsltCheckCycle` (imports.c): detect import/include recursion.
690unsafe fn xslt_check_cycle(
691    style: *mut _xsltStylesheet,
692    cur: *mut _xmlNode,
693    uri: *const xmlChar,
694) -> c_int {
695    let mut depth: c_int = 0;
696    let mut ancestor = style;
697    while !ancestor.is_null() {
698        depth += 1;
699        if depth >= XSLT_MAX_NESTING {
700            report_error(style, cur, b"maximum nesting depth exceeded: ");
701            report_error(style, cur, cbytes(uri as *const u8));
702            report_error(style, cur, b"\n");
703            return -1;
704        }
705        if !(*ancestor).doc.is_null()
706            && !(*(*ancestor).doc).URL.is_null()
707            && xmlStrEqual((*(*ancestor).doc).URL, uri) != 0
708        {
709            report_error(style, cur, b"recursion detected on imported URL ");
710            report_error(style, cur, cbytes(uri as *const u8));
711            report_error(style, cur, b"\n");
712            return -1;
713        }
714
715        // Check included stylesheets.
716        let mut docptr = (*ancestor).includes;
717        while !docptr.is_null() {
718            depth += 1;
719            if depth >= XSLT_MAX_NESTING {
720                report_error(style, cur, b"maximum nesting depth exceeded: ");
721                report_error(style, cur, cbytes(uri as *const u8));
722                report_error(style, cur, b"\n");
723                return -1;
724            }
725            if !(*docptr).doc.is_null()
726                && !(*(*docptr).doc).URL.is_null()
727                && xmlStrEqual((*(*docptr).doc).URL, uri) != 0
728            {
729                report_error(style, cur, b"recursion detected on included URL ");
730                report_error(style, cur, cbytes(uri as *const u8));
731                report_error(style, cur, b"\n");
732                return -1;
733            }
734            docptr = (*docptr).includes;
735        }
736
737        ancestor = (*ancestor).parent;
738    }
739
740    0
741}
742
743/// Parse an XSLT stylesheet import element.
744///
745/// # UPSTREAM-PARITY
746///
747/// ```c
748/// int
749/// xsltParseStylesheetImport(xsltStylesheetPtr style, xmlNodePtr cur) {
750///     ... href/base/URI resolution, cycle + security checks,
751///         xsltDocDefaultLoader(...), xsltParseStylesheetImportedDoc(),
752///         res->next = style->imports; style->imports = res;
753///         xsltFixImportedCompSteps(style, res) when style->parent == NULL
754/// }
755/// ```
756///
757/// Returns 0 on success, -1 on failure.
758///
759/// # SAFETY
760///
761/// - `style` must be a valid `_xsltStylesheet`, or NULL.
762/// - `cur` must be a valid `xsl:import` element node, or NULL.
763#[no_mangle]
764pub unsafe extern "C" fn xsltParseStylesheetImport(
765    style: *mut _xsltStylesheet,
766    cur: *mut _xmlNode,
767) -> c_int {
768    let mut ret: c_int = -1;
769    let mut uriRef: *mut xmlChar = ptr::null_mut();
770    let mut base: *mut xmlChar = ptr::null_mut();
771    let mut uri: *mut xmlChar = ptr::null_mut();
772
773    if cur.is_null() || style.is_null() {
774        return ret;
775    }
776
777    uriRef = xmlGetNsProp(cur, b"href\0".as_ptr() as *const xmlChar, ptr::null());
778    if uriRef.is_null() {
779        report_error(style, cur, b"xsl:import : missing href attribute\n");
780        if !uriRef.is_null() {
781            xmlFreeImpl(uriRef as *mut c_void);
782        }
783        if !base.is_null() {
784            xmlFreeImpl(base as *mut c_void);
785        }
786        if !uri.is_null() {
787            xmlFreeImpl(uri as *mut c_void);
788        }
789        return ret;
790    }
791
792    base = xmlNodeGetBase((*style).doc, cur);
793    uri = xmlBuildURI(uriRef as *const c_char, base as *const c_char);
794    if uri.is_null() {
795        report_error(style, cur, b"xsl:import : invalid URI reference ");
796        report_error(style, cur, cbytes(uriRef as *const u8));
797        report_error(style, cur, b"\n");
798        if !uriRef.is_null() {
799            xmlFreeImpl(uriRef as *mut c_void);
800        }
801        if !base.is_null() {
802            xmlFreeImpl(base as *mut c_void);
803        }
804        if !uri.is_null() {
805            xmlFreeImpl(uri as *mut c_void);
806        }
807        return ret;
808    }
809
810    if xslt_check_cycle(style, cur, uri) < 0 {
811        if !uriRef.is_null() {
812            xmlFreeImpl(uriRef as *mut c_void);
813        }
814        if !base.is_null() {
815            xmlFreeImpl(base as *mut c_void);
816        }
817        if !uri.is_null() {
818            xmlFreeImpl(uri as *mut c_void);
819        }
820        return ret;
821    }
822
823    // Security framework check.
824    let sec = crate::xslt::security::xsltGetDefaultSecurityPrefs();
825    if !sec.is_null() {
826        let secres = xslt_check_read(sec, ptr::null_mut(), uri);
827        if secres <= 0 {
828            if secres == 0 {
829                report_error(
830                    ptr::null_mut(),
831                    ptr::null_mut(),
832                    b"xsl:import: read rights for ",
833                );
834                report_error(ptr::null_mut(), ptr::null_mut(), cbytes(uri as *const u8));
835                report_error(ptr::null_mut(), ptr::null_mut(), b" denied\n");
836            }
837            if !uriRef.is_null() {
838                xmlFreeImpl(uriRef as *mut c_void);
839            }
840            if !base.is_null() {
841                xmlFreeImpl(base as *mut c_void);
842            }
843            if !uri.is_null() {
844                xmlFreeImpl(uri as *mut c_void);
845            }
846            return ret;
847        }
848    }
849
850    let import = xslt_doc_default_loader(
851        uri,
852        (*style).dict,
853        XSLT_PARSE_OPTIONS,
854        style as *mut c_void,
855        XSLT_LOAD_STYLESHEET,
856    );
857    if import.is_null() {
858        report_error(style, cur, b"xsl:import : unable to load ");
859        report_error(style, cur, cbytes(uri as *const u8));
860        report_error(style, cur, b"\n");
861        if !uriRef.is_null() {
862            xmlFreeImpl(uriRef as *mut c_void);
863        }
864        if !base.is_null() {
865            xmlFreeImpl(base as *mut c_void);
866        }
867        if !uri.is_null() {
868            xmlFreeImpl(uri as *mut c_void);
869        }
870        return ret;
871    }
872
873    let res = xsltParseStylesheetImportedDoc(import, style);
874    if !res.is_null() {
875        (*res).next = (*style).imports;
876        (*style).imports = res;
877        if (*style).parent.is_null() {
878            xslt_fix_imported_comp_steps(style, res);
879        }
880        ret = 0;
881    } else {
882        crate::xml::tree::free_doc(import);
883    }
884
885    if !uriRef.is_null() {
886        xmlFreeImpl(uriRef as *mut c_void);
887    }
888    if !base.is_null() {
889        xmlFreeImpl(base as *mut c_void);
890    }
891    if !uri.is_null() {
892        xmlFreeImpl(uri as *mut c_void);
893    }
894
895    ret
896}
897
898/// Parse an XSLT stylesheet include element.
899///
900/// # UPSTREAM-PARITY
901///
902/// ```c
903/// int
904/// xsltParseStylesheetInclude(xsltStylesheetPtr style, xmlNodePtr cur) {
905///     ... href/base/URI resolution, cycle check,
906///         include = xsltLoadStyleDocument(style, URI);
907///         oldDoc = style->doc; style->doc = include->doc;
908///         include->includes = style->includes; style->includes = include;
909///         oldNopreproc = style->nopreproc;
910///         style->nopreproc = include->preproc;
911///         result = xsltParseStylesheetProcess(style, include->doc);
912///         style->nopreproc = oldNopreproc;
913///         include->preproc = 1;
914///         style->includes = include->includes;
915///         style->doc = oldDoc;
916///         if (result == NULL) { ret = -1; goto error; }
917///         ret = 0;
918/// }
919/// ```
920///
921/// Returns 0 on success, -1 on failure.
922///
923/// # SAFETY
924///
925/// - `style` must be a valid `_xsltStylesheet`, or NULL.
926/// - `cur` must be a valid `xsl:include` element node, or NULL.
927#[no_mangle]
928pub unsafe extern "C" fn xsltParseStylesheetInclude(
929    style: *mut _xsltStylesheet,
930    cur: *mut _xmlNode,
931) -> c_int {
932    let mut ret: c_int = -1;
933    let mut uriRef: *mut xmlChar = ptr::null_mut();
934    let mut base: *mut xmlChar = ptr::null_mut();
935    let mut uri: *mut xmlChar = ptr::null_mut();
936
937    if cur.is_null() || style.is_null() {
938        return ret;
939    }
940
941    uriRef = xmlGetNsProp(cur, b"href\0".as_ptr() as *const xmlChar, ptr::null());
942    if uriRef.is_null() {
943        report_error(style, cur, b"xsl:include : missing href attribute\n");
944        if !uriRef.is_null() {
945            xmlFreeImpl(uriRef as *mut c_void);
946        }
947        if !base.is_null() {
948            xmlFreeImpl(base as *mut c_void);
949        }
950        if !uri.is_null() {
951            xmlFreeImpl(uri as *mut c_void);
952        }
953        return ret;
954    }
955
956    base = xmlNodeGetBase((*style).doc, cur);
957    uri = xmlBuildURI(uriRef as *const c_char, base as *const c_char);
958    if uri.is_null() {
959        report_error(style, cur, b"xsl:include : invalid URI reference ");
960        report_error(style, cur, cbytes(uriRef as *const u8));
961        report_error(style, cur, b"\n");
962        if !uriRef.is_null() {
963            xmlFreeImpl(uriRef as *mut c_void);
964        }
965        if !base.is_null() {
966            xmlFreeImpl(base as *mut c_void);
967        }
968        if !uri.is_null() {
969            xmlFreeImpl(uri as *mut c_void);
970        }
971        return ret;
972    }
973
974    if xslt_check_cycle(style, cur, uri) < 0 {
975        if !uriRef.is_null() {
976            xmlFreeImpl(uriRef as *mut c_void);
977        }
978        if !base.is_null() {
979            xmlFreeImpl(base as *mut c_void);
980        }
981        if !uri.is_null() {
982            xmlFreeImpl(uri as *mut c_void);
983        }
984        return ret;
985    }
986
987    let include = xsltLoadStyleDocument(style, uri);
988    if include.is_null() {
989        report_error(style, cur, b"xsl:include : unable to load ");
990        report_error(style, cur, cbytes(uri as *const u8));
991        report_error(style, cur, b"\n");
992        if !uriRef.is_null() {
993            xmlFreeImpl(uriRef as *mut c_void);
994        }
995        if !base.is_null() {
996            xmlFreeImpl(base as *mut c_void);
997        }
998        if !uri.is_null() {
999            xmlFreeImpl(uri as *mut c_void);
1000        }
1001        return ret;
1002    }
1003
1004    let oldDoc = (*style).doc;
1005    (*style).doc = (*include).doc;
1006    // Chain to the stylesheet for recursion checking.
1007    (*include).includes = (*style).includes;
1008    (*style).includes = include;
1009    let oldNopreproc = (*style).nopreproc;
1010    (*style).nopreproc = (*include).preproc;
1011    // ENGINE-WIRING: upstream skips the whole-tree preprocessing when the
1012    // include was already preprocessed (`include->preproc`); the candidate
1013    // compiler's preprocessing is idempotent (blank-stripping and text
1014    // merging), so re-running it is safe. The `nopreproc` flag is restored
1015    // exactly like upstream.
1016    let result = xsltParseStylesheetProcess(style, (*include).doc);
1017    (*style).nopreproc = oldNopreproc;
1018    (*include).preproc = 1;
1019    (*style).includes = (*include).includes;
1020    (*style).doc = oldDoc;
1021    if result.is_null() {
1022        ret = -1;
1023        if !uriRef.is_null() {
1024            xmlFreeImpl(uriRef as *mut c_void);
1025        }
1026        if !base.is_null() {
1027            xmlFreeImpl(base as *mut c_void);
1028        }
1029        if !uri.is_null() {
1030            xmlFreeImpl(uri as *mut c_void);
1031        }
1032        return ret;
1033    }
1034    ret = 0;
1035
1036    if !uriRef.is_null() {
1037        xmlFreeImpl(uriRef as *mut c_void);
1038    }
1039    if !base.is_null() {
1040        xmlFreeImpl(base as *mut c_void);
1041    }
1042    if !uri.is_null() {
1043        xmlFreeImpl(uri as *mut c_void);
1044    }
1045    return ret;
1046}
1047
1048// ═══════════════════════════════════════════════════════════════════════════════
1049// 3. Top-level constructs (xslt.c, attributes.c, variables.c)
1050// ═══════════════════════════════════════════════════════════════════════════════
1051
1052/// `xsltParseContentError` (xslt.c): report a misplaced child node.
1053unsafe fn xslt_parse_content_error(style: *mut _xsltStylesheet, node: *mut _xmlNode) {
1054    if style.is_null() || node.is_null() {
1055        return;
1056    }
1057    if is_xslt_elem(node) {
1058        report_error(
1059            style,
1060            node,
1061            b"The XSLT-element is not allowed at this position.\n",
1062        );
1063    } else {
1064        report_error(
1065            style,
1066            node,
1067            b"The element is not allowed at this position.\n",
1068        );
1069    }
1070    (*style).errors += 1;
1071}
1072
1073/// Parse an XSLT stylesheet output element and record the output settings.
1074///
1075/// # UPSTREAM-PARITY
1076///
1077/// ```c
1078/// void
1079/// xsltParseStylesheetOutput(xsltStylesheetPtr style, xmlNodePtr cur);
1080/// ```
1081///
1082/// Ported from xslt.c 1.1.45: version/encoding/method (with QName
1083/// resolution via `xsltGetQNameURI`), doctype-system/public, standalone,
1084/// indent, omit-xml-declaration, cdata-section-elements (a
1085/// `{name, ns-URI}` hash holding the sentinel "cdata"), media-type, and
1086/// the content-error check for children. Invalid enum values bump
1087/// `style->errors`; an invalid method bumps `style->warnings`.
1088///
1089/// # SAFETY
1090///
1091/// - `style` must be a valid `_xsltStylesheet`, or NULL.
1092/// - `cur` must be a valid `xsl:output` element node, or NULL.
1093#[no_mangle]
1094pub unsafe extern "C" fn xsltParseStylesheetOutput(
1095    style: *mut _xsltStylesheet,
1096    cur: *mut _xmlNode,
1097) {
1098    if cur.is_null() || style.is_null() || (*cur).type_ != XML_ELEMENT_NODE as c_int {
1099        return;
1100    }
1101
1102    // version
1103    let mut prop = xmlGetNsProp(cur, b"version\0".as_ptr() as *const xmlChar, ptr::null());
1104    if !prop.is_null() {
1105        if !(*style).version.is_null() {
1106            xmlFreeImpl((*style).version as *mut c_void);
1107        }
1108        (*style).version = prop;
1109        prop = ptr::null_mut();
1110    }
1111
1112    // encoding
1113    prop = xmlGetNsProp(cur, b"encoding\0".as_ptr() as *const xmlChar, ptr::null());
1114    if !prop.is_null() {
1115        if !(*style).encoding.is_null() {
1116            xmlFreeImpl((*style).encoding as *mut c_void);
1117        }
1118        (*style).encoding = prop;
1119        prop = ptr::null_mut();
1120    }
1121
1122    // method (relaxed to support xt:document)
1123    prop = xmlGetNsProp(cur, b"method\0".as_ptr() as *const xmlChar, ptr::null());
1124    if !prop.is_null() {
1125        if !(*style).method.is_null() {
1126            xmlFreeImpl((*style).method as *mut c_void);
1127        }
1128        (*style).method = ptr::null_mut();
1129        if !(*style).methodURI.is_null() {
1130            xmlFreeImpl((*style).methodURI as *mut c_void);
1131        }
1132        (*style).methodURI = ptr::null_mut();
1133
1134        let mut method = prop;
1135        let uri = crate::abi::exports_xslt_avt::xsltGetQNameURI(cur, &mut method);
1136        if method.is_null() {
1137            if !style.is_null() {
1138                (*style).errors += 1;
1139            }
1140        } else if uri.is_null() {
1141            if xmlStrEqual(method, b"xml\0".as_ptr() as *const xmlChar) != 0
1142                || xmlStrEqual(method, b"html\0".as_ptr() as *const xmlChar) != 0
1143                || xmlStrEqual(method, b"text\0".as_ptr() as *const xmlChar) != 0
1144            {
1145                (*style).method = method;
1146            } else {
1147                report_error(style, cur, b"invalid value for method: ");
1148                report_error(style, cur, cbytes(method as *const u8));
1149                report_error(style, cur, b"\n");
1150                if !style.is_null() {
1151                    (*style).warnings += 1;
1152                }
1153                xmlFreeImpl(method as *mut c_void);
1154            }
1155        } else {
1156            (*style).method = method;
1157            (*style).methodURI = xmlStrdup(uri);
1158        }
1159        prop = ptr::null_mut();
1160    }
1161
1162    // doctype-system
1163    prop = xmlGetNsProp(
1164        cur,
1165        b"doctype-system\0".as_ptr() as *const xmlChar,
1166        ptr::null(),
1167    );
1168    if !prop.is_null() {
1169        if !(*style).doctypeSystem.is_null() {
1170            xmlFreeImpl((*style).doctypeSystem as *mut c_void);
1171        }
1172        (*style).doctypeSystem = prop;
1173        prop = ptr::null_mut();
1174    }
1175
1176    // doctype-public
1177    prop = xmlGetNsProp(
1178        cur,
1179        b"doctype-public\0".as_ptr() as *const xmlChar,
1180        ptr::null(),
1181    );
1182    if !prop.is_null() {
1183        if !(*style).doctypePublic.is_null() {
1184            xmlFreeImpl((*style).doctypePublic as *mut c_void);
1185        }
1186        (*style).doctypePublic = prop;
1187        prop = ptr::null_mut();
1188    }
1189
1190    // standalone
1191    prop = xmlGetNsProp(cur, b"standalone\0".as_ptr() as *const xmlChar, ptr::null());
1192    if !prop.is_null() {
1193        if xmlStrEqual(prop, b"yes\0".as_ptr() as *const xmlChar) != 0 {
1194            (*style).standalone = 1;
1195        } else if xmlStrEqual(prop, b"no\0".as_ptr() as *const xmlChar) != 0 {
1196            (*style).standalone = 0;
1197        } else {
1198            report_error(style, cur, b"invalid value for standalone\n");
1199            (*style).errors += 1;
1200        }
1201        xmlFreeImpl(prop as *mut c_void);
1202    }
1203
1204    // indent
1205    prop = xmlGetNsProp(cur, b"indent\0".as_ptr() as *const xmlChar, ptr::null());
1206    if !prop.is_null() {
1207        if xmlStrEqual(prop, b"yes\0".as_ptr() as *const xmlChar) != 0 {
1208            (*style).indent = 1;
1209        } else if xmlStrEqual(prop, b"no\0".as_ptr() as *const xmlChar) != 0 {
1210            (*style).indent = 0;
1211        } else {
1212            report_error(style, cur, b"invalid value for indent\n");
1213            (*style).errors += 1;
1214        }
1215        xmlFreeImpl(prop as *mut c_void);
1216    }
1217
1218    // omit-xml-declaration
1219    prop = xmlGetNsProp(
1220        cur,
1221        b"omit-xml-declaration\0".as_ptr() as *const xmlChar,
1222        ptr::null(),
1223    );
1224    if !prop.is_null() {
1225        if xmlStrEqual(prop, b"yes\0".as_ptr() as *const xmlChar) != 0 {
1226            (*style).omitXmlDeclaration = 1;
1227        } else if xmlStrEqual(prop, b"no\0".as_ptr() as *const xmlChar) != 0 {
1228            (*style).omitXmlDeclaration = 0;
1229        } else {
1230            report_error(style, cur, b"invalid value for omit-xml-declaration\n");
1231            (*style).errors += 1;
1232        }
1233        xmlFreeImpl(prop as *mut c_void);
1234    }
1235
1236    // cdata-section-elements
1237    let elements = xmlGetNsProp(
1238        cur,
1239        b"cdata-section-elements\0".as_ptr() as *const xmlChar,
1240        ptr::null(),
1241    );
1242    if !elements.is_null() {
1243        if (*style).cdataSection.is_null() {
1244            (*style).cdataSection = crate::xml::hash::hash_create(10) as *mut c_void;
1245        }
1246        if (*style).cdataSection.is_null() {
1247            xmlFreeImpl(elements as *mut c_void);
1248            return;
1249        }
1250
1251        let mut element: *mut xmlChar = elements;
1252        while *element != 0 {
1253            while matches!(*element, b' ' | b'\t' | b'\n' | b'\r') {
1254                element = element.add(1);
1255            }
1256            if *element == 0 {
1257                break;
1258            }
1259            let mut end = element;
1260            while *end != 0 && !matches!(*end, b' ' | b'\t' | b'\n' | b'\r') {
1261                end = end.add(1);
1262            }
1263            let len = end.offset_from(element) as usize;
1264            let token = xmlMallocImpl(len + 1) as *mut xmlChar;
1265            if !token.is_null() {
1266                core::ptr::copy_nonoverlapping(element, token, len);
1267                *token.add(len) = 0;
1268                if xmlValidateQName(token, 0) != 0 {
1269                    report_error(
1270                        style,
1271                        cur,
1272                        b"Attribute 'cdata-section-elements': The value is not a valid QName.\n",
1273                    );
1274                    xmlFreeImpl(token as *mut c_void);
1275                    (*style).errors += 1;
1276                } else {
1277                    let mut qname = token;
1278                    let quri = crate::abi::exports_xslt_avt::xsltGetQNameURI(cur, &mut qname);
1279                    if qname.is_null() {
1280                        report_error(
1281                            style,
1282                            cur,
1283                            b"Attribute 'cdata-section-elements': Not a valid QName.\n",
1284                        );
1285                        (*style).errors += 1;
1286                    } else {
1287                        let mut uri = quri;
1288                        // XSLT-1.0: QNames without a prefix use the default
1289                        // namespace in effect on xsl:output (bug #339570).
1290                        if uri.is_null() {
1291                            let ns = xmlSearchNs((*style).doc, cur, ptr::null());
1292                            if !ns.is_null() {
1293                                uri = (*ns).href;
1294                            }
1295                        }
1296                        crate::xml::hash::hash_add_entry2(
1297                            (*style).cdataSection as *mut crate::xml::hash::HashTable,
1298                            qname,
1299                            uri,
1300                            b"cdata\0".as_ptr() as *const c_void as *mut c_void,
1301                        );
1302                        xmlFreeImpl(qname as *mut c_void);
1303                    }
1304                }
1305            }
1306            element = end;
1307        }
1308        xmlFreeImpl(elements as *mut c_void);
1309    }
1310
1311    // media-type
1312    prop = xmlGetNsProp(cur, b"media-type\0".as_ptr() as *const xmlChar, ptr::null());
1313    if !prop.is_null() {
1314        if !(*style).mediaType.is_null() {
1315            xmlFreeImpl((*style).mediaType as *mut c_void);
1316        }
1317        (*style).mediaType = prop;
1318        prop = ptr::null_mut();
1319    }
1320
1321    // Content of xsl:output must be empty (upstream checks the first
1322    // child only).
1323    if !(*cur).children.is_null() {
1324        xslt_parse_content_error(style, (*cur).children);
1325    }
1326}
1327
1328/// Parse an XSLT stylesheet attribute-set element.
1329///
1330/// # UPSTREAM-PARITY
1331///
1332/// ```c
1333/// void
1334/// xsltParseStylesheetAttributeSet(xsltStylesheetPtr style, xmlNodePtr cur);
1335/// ```
1336///
1337/// # ENGINE-WIRING
1338///
1339/// Wired to `crate::xslt::attributes::xsltCompileAttrSet`, which records
1340/// the set (name/instruction/stylesheet) on `style->attributeSets`. The
1341/// upstream QName validation and `use-attribute-sets` processing are
1342/// subsumed: the engine resolves referenced sets by name at apply time
1343/// (`xsltApplyAttrSets`). The QName check is kept for parity.
1344///
1345/// # SAFETY
1346///
1347/// - `style` must be a valid `_xsltStylesheet`, or NULL.
1348/// - `cur` must be a valid `xsl:attribute-set` element node, or NULL.
1349#[no_mangle]
1350pub unsafe extern "C" fn xsltParseStylesheetAttributeSet(
1351    style: *mut _xsltStylesheet,
1352    cur: *mut _xmlNode,
1353) {
1354    if cur.is_null() || style.is_null() || (*cur).type_ != XML_ELEMENT_NODE as c_int {
1355        return;
1356    }
1357
1358    let value = xmlGetNsProp(cur, b"name\0".as_ptr() as *const xmlChar, ptr::null());
1359    if value.is_null() || *value == 0 {
1360        if !value.is_null() {
1361            xmlFreeImpl(value as *mut c_void);
1362        }
1363        return;
1364    }
1365    if xmlValidateQName(value, 0) != 0 {
1366        report_error(
1367            style,
1368            cur,
1369            b"xsl:attribute-set : The name is not a valid QName.\n",
1370        );
1371        (*style).errors += 1;
1372        xmlFreeImpl(value as *mut c_void);
1373        return;
1374    }
1375    xmlFreeImpl(value as *mut c_void);
1376
1377    crate::xslt::attributes::xsltCompileAttrSet(style, cur);
1378}
1379
1380/// Parse a global XSLT `variable` declaration at compilation time and
1381/// register it.
1382///
1383/// # UPSTREAM-PARITY
1384///
1385/// ```c
1386/// void
1387/// xsltParseGlobalVariable(xsltStylesheetPtr style, xmlNodePtr cur);
1388/// ```
1389///
1390/// # ENGINE-WIRING
1391///
1392/// Wired to the engine's `crate::xslt::compiler::compile_variable`
1393/// (is_param = 0), which allocates the `_xsltStackElem`, copies
1394/// name/select, records the content tree and prepends it to
1395/// `style->variables`. The upstream "missing name" and "redefinition of
1396/// global variable" diagnostics are reproduced here (the redefinition
1397/// check compares the local name only — the candidate stores no nameURI
1398/// for globals, documented divergence).
1399///
1400/// # SAFETY
1401///
1402/// - `style` must be a valid `_xsltStylesheet`, or NULL.
1403/// - `cur` must be a valid `xsl:variable` element node, or NULL.
1404#[no_mangle]
1405pub unsafe extern "C" fn xsltParseGlobalVariable(style: *mut _xsltStylesheet, cur: *mut _xmlNode) {
1406    if cur.is_null() || style.is_null() || (*cur).type_ != XML_ELEMENT_NODE as c_int {
1407        return;
1408    }
1409
1410    let name = xmlGetNsProp(cur, b"name\0".as_ptr() as *const xmlChar, ptr::null());
1411    if name.is_null() {
1412        report_error(style, cur, b"xsl:variable : missing name attribute\n");
1413        return;
1414    }
1415
1416    // Upstream reports a redefinition error for duplicate global
1417    // variables (not params).
1418    let mut tmp = (*style).variables;
1419    while !tmp.is_null() {
1420        if ((*tmp).flags & XSLT_VAR_PARAM) == 0
1421            && !(*tmp).name.is_null()
1422            && xmlStrEqual((*tmp).name, name) != 0
1423        {
1424            report_error(style, cur, b"redefinition of global variable ");
1425            report_error(style, cur, cbytes(name as *const u8));
1426            report_error(style, cur, b"\n");
1427            (*style).errors += 1;
1428            break;
1429        }
1430        tmp = (*tmp).next;
1431    }
1432    xmlFreeImpl(name as *mut c_void);
1433
1434    // Parse the content (a sequence constructor).
1435    if !(*cur).children.is_null() {
1436        xsltParseTemplateContent(style, cur);
1437    }
1438
1439    crate::xslt::compiler::compile_variable(style, cur, 0, 0);
1440}
1441
1442/// Parse a global XSLT `param` declaration at compilation time and
1443/// register it.
1444///
1445/// # UPSTREAM-PARITY
1446///
1447/// ```c
1448/// void
1449/// xsltParseGlobalParam(xsltStylesheetPtr style, xmlNodePtr cur);
1450/// ```
1451///
1452/// # ENGINE-WIRING
1453///
1454/// Same as `xsltParseGlobalVariable` with is_param = 1 (the engine marks
1455/// the stack element with the `XSLT_VAR_PARAM` flag).
1456///
1457/// # SAFETY
1458///
1459/// - `style` must be a valid `_xsltStylesheet`, or NULL.
1460/// - `cur` must be a valid `xsl:param` element node, or NULL.
1461#[no_mangle]
1462pub unsafe extern "C" fn xsltParseGlobalParam(style: *mut _xsltStylesheet, cur: *mut _xmlNode) {
1463    if cur.is_null() || style.is_null() || (*cur).type_ != XML_ELEMENT_NODE as c_int {
1464        return;
1465    }
1466
1467    let name = xmlGetNsProp(cur, b"name\0".as_ptr() as *const xmlChar, ptr::null());
1468    if name.is_null() {
1469        report_error(style, cur, b"xsl:param : missing name attribute\n");
1470        return;
1471    }
1472    xmlFreeImpl(name as *mut c_void);
1473
1474    // Parse the content (a sequence constructor).
1475    if !(*cur).children.is_null() {
1476        xsltParseTemplateContent(style, cur);
1477    }
1478
1479    crate::xslt::compiler::compile_variable(style, cur, 0, 1);
1480}
1481
1482// ═══════════════════════════════════════════════════════════════════════════════
1483// 4. Template content & attribute compilation (xslt.c, attrvt.c)
1484// ═══════════════════════════════════════════════════════════════════════════════
1485
1486/// Parse a template content-model: precompute each XSLT instruction and
1487/// the AVTs of literal result elements.
1488///
1489/// # UPSTREAM-PARITY
1490///
1491/// ```c
1492/// void
1493/// xsltParseTemplateContent(xsltStylesheetPtr style, xmlNodePtr templ);
1494/// ```
1495///
1496/// Ported from xslt.c 1.1.45 (old behaviour): walk the subtree, run
1497/// `xsltStylePreCompute` on XSLT and extension elements, `xsltCompileAttr`
1498/// on literal-result-element attributes, and remove misplaced `xsl:param`
1499/// elements (with a warning).
1500///
1501/// # ENGINE-WIRING
1502///
1503/// Upstream *replaces* `xsl:text` with its children during this pass and
1504/// deletes the instruction node. The candidate engine evaluates `xsl:text`
1505/// directly at runtime (`xsltProcessInstruction` → `process_text`), so the
1506/// unwrap/delete is intentionally skipped — the tree stays intact
1507/// (documented divergence; observable output is identical).
1508///
1509/// # SAFETY
1510///
1511/// - `style` must be a valid `_xsltStylesheet`, or NULL.
1512/// - `templ` must be a valid node whose children form the content, or
1513///   NULL.
1514#[no_mangle]
1515pub unsafe extern "C" fn xsltParseTemplateContent(
1516    style: *mut _xsltStylesheet,
1517    templ: *mut _xmlNode,
1518) {
1519    if style.is_null() || templ.is_null() || (*templ).type_ == XML_NAMESPACE_DECL as c_int {
1520        return;
1521    }
1522
1523    let mut cur = (*templ).children;
1524    while !cur.is_null() {
1525        if !(*style).principal.is_null() {
1526            (*(*style).principal).opCount += 1;
1527        }
1528
1529        if is_xslt_elem(cur) {
1530            xsltStylePreCompute(style, cur);
1531            // xsl:text is evaluated at runtime by the engine; upstream's
1532            // unwrap + node deletion is not performed (see module docs).
1533        } else if !(*cur).ns.is_null() && !ext_ns_registered((*(*cur).ns).href) {
1534            // Not an XSLT element and not a registered extension element:
1535            // falls through to the literal-result-element branch below.
1536        } else if !(*cur).ns.is_null() && ext_ns_registered((*(*cur).ns).href) {
1537            // Extension element: compile it too.
1538            xsltStylePreCompute(style, cur);
1539        } else if (*cur).type_ == XML_ELEMENT_NODE as c_int {
1540            // A literal result element: precompile the AVTs of its
1541            // attributes.
1542            if (*cur).ns.is_null() && !(*style).defaultAlias.is_null() {
1543                (*cur).ns = xmlSearchNsByHref((*cur).doc, cur, (*style).defaultAlias);
1544            }
1545            if !(*cur).properties.is_null() {
1546                let mut attr = (*cur).properties;
1547                while !attr.is_null() {
1548                    xsltCompileAttr(style, attr);
1549                    attr = (*attr).next;
1550                }
1551            }
1552        }
1553
1554        // Descend into children, else next sibling, else pop up to
1555        // `templ`.
1556        if !(*cur).children.is_null() {
1557            if (*(*cur).children).type_ != XML_ENTITY_DECL as c_int {
1558                cur = (*cur).children;
1559                continue;
1560            }
1561        }
1562        if !(*cur).next.is_null() {
1563            cur = (*cur).next;
1564            continue;
1565        }
1566        loop {
1567            cur = (*cur).parent;
1568            if cur.is_null() {
1569                break;
1570            }
1571            if cur == templ {
1572                cur = ptr::null_mut();
1573                break;
1574            }
1575            if !(*cur).next.is_null() {
1576                cur = (*cur).next;
1577                break;
1578            }
1579        }
1580    }
1581
1582    // Skip the first params.
1583    let mut cur = (*templ).children;
1584    while !cur.is_null() {
1585        if is_xslt_elem(cur) && !is_xslt_name(cur, b"param") {
1586            break;
1587        }
1588        cur = (*cur).next;
1589    }
1590
1591    // Browse the remainder of the template, removing misplaced params.
1592    while !cur.is_null() {
1593        if is_xslt_elem(cur) && is_xslt_name(cur, b"param") {
1594            let param = cur;
1595            report_error(
1596                style,
1597                cur,
1598                b"xsltParseTemplateContent: ignoring misplaced param element\n",
1599            );
1600            if !style.is_null() {
1601                (*style).warnings += 1;
1602            }
1603            cur = (*cur).next;
1604            crate::xml::tree::unlink_node(param);
1605            crate::xml::tree::free_node(param);
1606        } else {
1607            break;
1608        }
1609    }
1610}
1611
1612/// Whether a namespace URI is registered as an extension namespace (used
1613/// to distinguish extension elements from literal result elements).
1614unsafe fn ext_ns_registered(uri: *const xmlChar) -> bool {
1615    if uri.is_null() {
1616        return false;
1617    }
1618    let mut cur = XSLT_ELEMENTS_REGISTRY;
1619    while !cur.is_null() {
1620        if !(*cur).URI.is_null() && xmlStrEqual((*cur).URI, uri) != 0 {
1621            return true;
1622        }
1623        cur = (*cur).next;
1624    }
1625    false
1626}
1627
1628/// Precompile an attribute in a stylesheet: check whether it is an
1629/// attribute value template and validate its structure.
1630///
1631/// # UPSTREAM-PARITY
1632///
1633/// ```c
1634/// void
1635/// xsltCompileAttr(xsltStylesheetPtr style, xmlAttrPtr attr);
1636/// ```
1637///
1638/// # ENGINE-WIRING
1639///
1640/// Upstream parses the AVT into a segment list (`xsltAttrVT`) and stores
1641/// it in `attr->psvi` / `style->attVTs`. The candidate engine evaluates
1642/// AVTs lazily at transform time from the raw attribute string
1643/// (`crate::xslt::transform::eval_avt`), so no AVT object is allocated;
1644/// the compile-time *diagnostics* are kept for parity: a multi-node or
1645/// non-text attribute content, and unmatched `{`/`}` (an unmatched `}` is
1646/// reported without bumping the error counter, exactly like upstream).
1647///
1648/// # SAFETY
1649///
1650/// - `style` must be a valid `_xsltStylesheet`, or NULL.
1651/// - `attr` must be a valid attribute of the stylesheet tree, or NULL.
1652#[no_mangle]
1653pub unsafe extern "C" fn xsltCompileAttr(style: *mut _xsltStylesheet, attr: *mut _xmlAttr) {
1654    if style.is_null() || attr.is_null() || (*attr).children.is_null() {
1655        return;
1656    }
1657    if (*(*attr).children).type_ != XML_TEXT_NODE as c_int || !(*(*attr).children).next.is_null() {
1658        report_error(
1659            style,
1660            (*attr).parent,
1661            b"Attribute ': The content is expected to be a single text node when compiling an AVT.\n",
1662        );
1663        (*style).errors += 1;
1664        return;
1665    }
1666
1667    let str_ = (*(*attr).children).content;
1668    if xmlStrchr(str_, b'{' as xmlChar).is_null() && xmlStrchr(str_, b'}' as xmlChar).is_null() {
1669        return;
1670    }
1671    if !(*attr).psvi.is_null() {
1672        // Already compiled.
1673        return;
1674    }
1675
1676    // Validate the AVT structure (no object is built — the engine
1677    // evaluates the raw string lazily).
1678    let mut cur = str_;
1679    while *cur != 0 {
1680        if *cur == b'{' {
1681            if !cur.add(1).is_null() && *cur.add(1) == b'{' {
1682                // Escaped '{'.
1683                cur = cur.add(2);
1684                continue;
1685            }
1686            if !cur.add(1).is_null() && *cur.add(1) == b'}' {
1687                // Empty AVT.
1688                cur = cur.add(2);
1689                continue;
1690            }
1691            // Scan to the closing '}', honouring quoted literals
1692            // (bug539741).
1693            let mut p = cur.add(1);
1694            while *p != 0 && *p != b'}' {
1695                if *p == b'\'' || *p == b'"' {
1696                    let delim = *p;
1697                    p = p.add(1);
1698                    while *p != 0 && *p != delim {
1699                        p = p.add(1);
1700                    }
1701                    if *p != 0 {
1702                        p = p.add(1);
1703                    }
1704                } else {
1705                    p = p.add(1);
1706                }
1707            }
1708            if *p == 0 {
1709                report_error(
1710                    style,
1711                    (*attr).parent,
1712                    b"Attribute ': The AVT has an unmatched '{'.\n",
1713                );
1714                (*style).errors += 1;
1715                return;
1716            }
1717            cur = p.add(1);
1718        } else if *cur == b'}' {
1719            if !cur.add(1).is_null() && *cur.add(1) == b'}' {
1720                // Escaped '}'.
1721                cur = cur.add(2);
1722                continue;
1723            }
1724            report_error(
1725                style,
1726                (*attr).parent,
1727                b"Attribute ': The AVT has an unmatched '}'.\n",
1728            );
1729            return;
1730        } else {
1731            cur = cur.add(1);
1732        }
1733    }
1734}
1735
1736// ═══════════════════════════════════════════════════════════════════════════════
1737// 5. Precomputed instructions (preproc.c, extensions.c)
1738// ═══════════════════════════════════════════════════════════════════════════════
1739
1740/// Free a (non-extension) style precomp: upstream also releases the
1741/// compiled XPath expression, number patterns and ns-list; the candidate
1742/// engine compiles lazily, so none of those exist and only the struct is
1743/// freed.
1744unsafe fn xslt_free_style_pre_comp(comp: *mut c_void) {
1745    if comp.is_null() {
1746        return;
1747    }
1748    xmlFreeImpl(comp);
1749}
1750
1751/// `xsltFreeElemPreComp` (extensions.c).
1752unsafe extern "C" fn xslt_free_elem_pre_comp(comp: *mut c_void) {
1753    xmlFreeImpl(comp);
1754}
1755
1756/// `xsltNewStylePreComp` (preproc.c) for the non-refactored engine: build
1757/// an old-style precomp of the requested type and chain it onto
1758/// `style->preComps`.
1759///
1760/// # ENGINE-WIRING
1761///
1762/// The upstream per-type transform-function assignment (xsltCopy, xsltIf,
1763/// …) is omitted: the candidate dispatches instructions by node name at
1764/// runtime, so `func` is only meaningful to external readers of the
1765/// structure.
1766unsafe fn xslt_new_style_pre_comp(
1767    style: *mut _xsltStylesheet,
1768    type_: c_int,
1769) -> *mut _xsltStylePreComp {
1770    if style.is_null() {
1771        return ptr::null_mut();
1772    }
1773    let cur = xmlMallocImpl(core::mem::size_of::<_xsltStylePreComp>()) as *mut _xsltStylePreComp;
1774    if cur.is_null() {
1775        report_error(
1776            style,
1777            ptr::null_mut(),
1778            b"xsltNewStylePreComp : malloc failed\n",
1779        );
1780        (*style).errors += 1;
1781        return ptr::null_mut();
1782    }
1783    ptr::write_bytes(cur as *mut u8, 0, core::mem::size_of::<_xsltStylePreComp>());
1784
1785    (*cur).base.type_ = type_;
1786    (*cur).base.next = (*style).preComps as *mut _xsltElemPreComp;
1787    (*style).preComps = cur as *mut c_void;
1788
1789    cur
1790}
1791
1792/// Preprocess an XSLT-1.1 `document` (and the saxon/xalan/xt/exslt
1793/// document-like extension) element.
1794///
1795/// # UPSTREAM-PARITY
1796///
1797/// ```c
1798/// xsltElemPreCompPtr
1799/// xsltDocumentComp(xsltStylesheetPtr style, xmlNodePtr inst,
1800///                  xsltTransformFunction function ATTRIBUTE_UNUSED);
1801/// ```
1802///
1803/// Allocates an old-style precomp of type `XSLT_FUNC_DOCUMENT`, evaluates
1804/// the static `file`/`href` attribute template (`has_filename`), and marks
1805/// `ver11` when the element is `xsl:document` in the XSLT namespace.
1806///
1807/// # SAFETY
1808///
1809/// - `style` must be a valid `_xsltStylesheet`.
1810/// - `inst` must be a valid instruction element node.
1811#[no_mangle]
1812pub unsafe extern "C" fn xsltDocumentComp(
1813    style: *mut _xsltStylesheet,
1814    inst: *mut _xmlNode,
1815    _function: Option<xsltTransformFunction>,
1816) -> *mut _xsltElemPreComp {
1817    if style.is_null() || inst.is_null() || (*inst).type_ != XML_ELEMENT_NODE as c_int {
1818        return ptr::null_mut();
1819    }
1820
1821    let comp = xslt_new_style_pre_comp(style, XSLT_FUNC_DOCUMENT);
1822    if comp.is_null() {
1823        return ptr::null_mut();
1824    }
1825    (*comp).base.inst = inst;
1826    (*comp).ver11 = 0;
1827    let mut filename: *const xmlChar = ptr::null();
1828
1829    if is_xslt_name(inst, b"output") {
1830        // saxon:output — @file is an AVT.
1831        filename = crate::abi::exports_xslt_avt::xsltEvalStaticAttrValueTemplate(
1832            style,
1833            inst,
1834            b"file\0".as_ptr() as *const xmlChar,
1835            ptr::null(),
1836            &mut (*comp).has_filename,
1837        );
1838    } else if is_xslt_name(inst, b"write") {
1839        // xalan:write — the filename is interpreted at run time.
1840    } else if is_xslt_name(inst, b"document") {
1841        if !(*inst).ns.is_null() {
1842            if xmlStrEqual(
1843                (*(*inst).ns).href,
1844                XSLT_NAMESPACE.as_ptr() as *const xmlChar,
1845            ) != 0
1846            {
1847                // xsl:document from the abandoned XSLT 1.1 draft.
1848                (*comp).ver11 = 1;
1849            }
1850            // exslt:document / xt:document need no extra marking.
1851        }
1852        filename = crate::abi::exports_xslt_avt::xsltEvalStaticAttrValueTemplate(
1853            style,
1854            inst,
1855            b"href\0".as_ptr() as *const xmlChar,
1856            ptr::null(),
1857            &mut (*comp).has_filename,
1858        );
1859    }
1860    if (*comp).has_filename != 0 {
1861        (*comp).filename = filename;
1862    }
1863
1864    &mut (*comp).base as *mut _xsltElemPreComp
1865}
1866
1867/// `xsltInitElemPreComp` (extensions.c): initialize an existing precomp
1868/// and chain it onto the stylesheet's precomp list.
1869///
1870/// This helper is intentionally not exported (the ext-family ABI exports
1871/// own the public symbol); it is used by the compile family to initialize
1872/// extension-element precomps.
1873///
1874/// # SAFETY
1875///
1876/// - All pointers must be valid.
1877unsafe fn xslt_init_elem_pre_comp(
1878    comp: *mut _xsltElemPreComp,
1879    style: *mut _xsltStylesheet,
1880    inst: *mut _xmlNode,
1881    function: Option<xsltTransformFunction>,
1882    free_func: Option<xsltElemPreCompDeallocator>,
1883) {
1884    (*comp).type_ = XSLT_FUNC_EXTENSION;
1885    (*comp).func = function;
1886    (*comp).inst = inst;
1887    (*comp).free = free_func;
1888
1889    (*comp).next = (*style).preComps as *mut _xsltElemPreComp;
1890    (*style).preComps = comp as *mut c_void;
1891}
1892
1893/// `xsltNewElemPreComp` (extensions.c): allocate and initialize an
1894/// `_xsltElemPreComp`.
1895///
1896/// # SAFETY
1897///
1898/// - `style` must be a valid `_xsltStylesheet`.
1899/// - `inst` must be a valid element node.
1900unsafe fn xslt_new_elem_pre_comp(
1901    style: *mut _xsltStylesheet,
1902    inst: *mut _xmlNode,
1903    function: Option<xsltTransformFunction>,
1904) -> *mut _xsltElemPreComp {
1905    let cur = xmlMallocImpl(core::mem::size_of::<_xsltElemPreComp>()) as *mut _xsltElemPreComp;
1906    if cur.is_null() {
1907        report_error(
1908            style,
1909            ptr::null_mut(),
1910            b"xsltNewExtElement : malloc failed\n",
1911        );
1912        return ptr::null_mut();
1913    }
1914    ptr::write_bytes(cur as *mut u8, 0, core::mem::size_of::<_xsltElemPreComp>());
1915
1916    xslt_init_elem_pre_comp(cur, style, inst, function, Some(xslt_free_elem_pre_comp));
1917
1918    cur
1919}
1920
1921/// Precompute an extension module element.
1922///
1923/// # UPSTREAM-PARITY
1924///
1925/// ```c
1926/// xsltElemPreCompPtr
1927/// xsltPreComputeExtModuleElement(xsltStylesheetPtr style, xmlNodePtr inst);
1928/// ```
1929///
1930/// Looks the element up in the extension-element registry by
1931/// `(inst->name, inst->ns->href)`; if the registered module provides a
1932/// precomputation callback it is used, otherwise a default
1933/// `_xsltElemPreComp` is created with the registered transform function.
1934///
1935/// # ENGINE-WIRING
1936///
1937/// The candidate mirror of upstream's global `xsltElementsHash` is the
1938/// private registry in this module (`XSLT_ELEMENTS_REGISTRY`). The
1939/// ext-family ABI (exports_xslt_ext.rs) owns the public registration
1940/// functions; this module's registry is populated through them by
1941/// whichever agent wires the module-level registration (documented
1942/// cross-family wire-up point). With an empty registry the function
1943/// returns NULL exactly like upstream with no registered elements.
1944///
1945/// # SAFETY
1946///
1947/// - `style` must be a valid `_xsltStylesheet`, or NULL.
1948/// - `inst` must be a valid element node, or NULL.
1949#[no_mangle]
1950pub unsafe extern "C" fn xsltPreComputeExtModuleElement(
1951    style: *mut _xsltStylesheet,
1952    inst: *mut _xmlNode,
1953) -> *mut _xsltElemPreComp {
1954    if style.is_null()
1955        || inst.is_null()
1956        || (*inst).type_ != XML_ELEMENT_NODE as c_int
1957        || (*inst).ns.is_null()
1958    {
1959        return ptr::null_mut();
1960    }
1961
1962    let ext = ext_element_lookup((*inst).name, (*(*inst).ns).href);
1963    if ext.is_null() {
1964        return ptr::null_mut();
1965    }
1966
1967    let mut comp: *mut _xsltElemPreComp = ptr::null_mut();
1968    if let Some(precomp) = (*ext).precomp {
1969        comp = precomp(style, inst, (*ext).transform) as *mut _xsltElemPreComp;
1970    }
1971    if comp.is_null() {
1972        // Default creation of an _xsltElemPreComp.
1973        comp = xslt_new_elem_pre_comp(style, inst, (*ext).transform);
1974    }
1975
1976    comp
1977}
1978
1979/// Free all precomputed blocks of a stylesheet.
1980///
1981/// # UPSTREAM-PARITY
1982///
1983/// ```c
1984/// void
1985/// xsltFreeStylePreComps(xsltStylesheetPtr style);
1986/// ```
1987///
1988/// Walks `style->preComps`; extension-typed precomps are released through
1989/// their registered deallocator, all others through `xsltFreeStylePreComp`
1990/// (which, in this engine, only frees the struct — no compiled
1991/// expressions or pattern lists exist).
1992///
1993/// # SAFETY
1994///
1995/// - `style` must be a valid `_xsltStylesheet`, or NULL.
1996#[no_mangle]
1997pub unsafe extern "C" fn xsltFreeStylePreComps(style: *mut _xsltStylesheet) {
1998    if style.is_null() {
1999        return;
2000    }
2001
2002    let mut cur = (*style).preComps as *mut _xsltElemPreComp;
2003    (*style).preComps = ptr::null_mut();
2004    while !cur.is_null() {
2005        let next = (*cur).next;
2006        if (*cur).type_ == XSLT_FUNC_EXTENSION {
2007            if let Some(free_func) = (*cur).free {
2008                free_func(cur as *mut c_void);
2009            } else {
2010                xslt_free_style_pre_comp(cur as *mut c_void);
2011            }
2012        } else {
2013            xslt_free_style_pre_comp(cur as *mut c_void);
2014        }
2015        cur = next;
2016    }
2017}
2018
2019/// Precompute an XSLT stylesheet element.
2020///
2021/// # UPSTREAM-PARITY
2022///
2023/// ```c
2024/// void
2025/// xsltStylePreCompute(xsltStylesheetPtr style, xmlNodePtr inst);
2026/// ```
2027///
2028/// Ported from preproc.c 1.1.45 (old behaviour): the grammar checks
2029/// (`xsltCheckTopLevelElement` / `xsltCheckInstructionElement` /
2030/// `xsltCheckParentElement`) and the per-instruction dispatch, including
2031/// the `xsl:document` precomp and the extension-element fallback
2032/// (`xsltPreComputeExtModuleElement`, else the `xsltExtMarker` sentinel).
2033///
2034/// # ENGINE-WIRING
2035///
2036/// The candidate engine compiles instructions lazily at transform time
2037/// from the raw node (it never reads `inst->psvi`), so the per-instruction
2038/// compilers allocate nothing; their observable effect — the error and
2039/// warning counters and the `style->preComps` chain for `xsl:document` and
2040/// extension elements — is preserved.
2041///
2042/// # SAFETY
2043///
2044/// - `style` must be a valid `_xsltStylesheet`, or NULL.
2045/// - `inst` must be a valid element node, or NULL.
2046#[no_mangle]
2047pub unsafe extern "C" fn xsltStylePreCompute(style: *mut _xsltStylesheet, inst: *mut _xmlNode) {
2048    if inst.is_null() || (*inst).type_ != XML_ELEMENT_NODE as c_int || !(*inst).psvi.is_null() {
2049        return;
2050    }
2051
2052    if is_xslt_elem(inst) {
2053        if is_xslt_name(inst, b"apply-templates") {
2054            xslt_check_instruction_element(style, inst);
2055            // xsltApplyTemplatesComp — lazy in this engine.
2056        } else if is_xslt_name(inst, b"with-param") {
2057            xslt_check_parent_element(
2058                style,
2059                inst,
2060                b"apply-templates",
2061                b"call-template\0".as_ptr() as *const u8,
2062            );
2063            // xsltWithParamComp — lazy.
2064        } else if is_xslt_name(inst, b"value-of") {
2065            xslt_check_instruction_element(style, inst);
2066        } else if is_xslt_name(inst, b"copy") {
2067            xslt_check_instruction_element(style, inst);
2068        } else if is_xslt_name(inst, b"copy-of") {
2069            xslt_check_instruction_element(style, inst);
2070        } else if is_xslt_name(inst, b"if") {
2071            xslt_check_instruction_element(style, inst);
2072        } else if is_xslt_name(inst, b"when") {
2073            xslt_check_parent_element(style, inst, b"choose", ptr::null());
2074        } else if is_xslt_name(inst, b"choose") {
2075            xslt_check_instruction_element(style, inst);
2076        } else if is_xslt_name(inst, b"for-each") {
2077            xslt_check_instruction_element(style, inst);
2078        } else if is_xslt_name(inst, b"apply-imports") {
2079            xslt_check_instruction_element(style, inst);
2080        } else if is_xslt_name(inst, b"attribute") {
2081            let parent = (*inst).parent;
2082            let is_in_attr_set = !parent.is_null()
2083                && (*parent).type_ == XML_ELEMENT_NODE as c_int
2084                && !(*parent).ns.is_null()
2085                && xmlStrEqual(
2086                    (*(*parent).ns).href,
2087                    XSLT_NAMESPACE.as_ptr() as *const xmlChar,
2088                ) != 0
2089                && is_xslt_name(parent, b"attribute-set");
2090            if !is_in_attr_set {
2091                xslt_check_instruction_element(style, inst);
2092            }
2093            // xsltAttributeComp — lazy.
2094        } else if is_xslt_name(inst, b"element") {
2095            xslt_check_instruction_element(style, inst);
2096        } else if is_xslt_name(inst, b"text") {
2097            xslt_check_instruction_element(style, inst);
2098        } else if is_xslt_name(inst, b"sort") {
2099            xslt_check_parent_element(
2100                style,
2101                inst,
2102                b"apply-templates",
2103                b"for-each\0".as_ptr() as *const u8,
2104            );
2105        } else if is_xslt_name(inst, b"comment") {
2106            xslt_check_instruction_element(style, inst);
2107        } else if is_xslt_name(inst, b"number") {
2108            xslt_check_instruction_element(style, inst);
2109        } else if is_xslt_name(inst, b"processing-instruction") {
2110            xslt_check_instruction_element(style, inst);
2111        } else if is_xslt_name(inst, b"call-template") {
2112            xslt_check_instruction_element(style, inst);
2113        } else if is_xslt_name(inst, b"param") {
2114            if xslt_check_top_level_element(style, inst, 0) == 0 {
2115                xslt_check_instruction_element(style, inst);
2116            }
2117            // xsltParamComp — lazy.
2118        } else if is_xslt_name(inst, b"variable") {
2119            if xslt_check_top_level_element(style, inst, 0) == 0 {
2120                xslt_check_instruction_element(style, inst);
2121            }
2122            // xsltVariableComp — lazy.
2123        } else if is_xslt_name(inst, b"otherwise") {
2124            xslt_check_parent_element(style, inst, b"choose", ptr::null());
2125            xslt_check_instruction_element(style, inst);
2126            return;
2127        } else if is_xslt_name(inst, b"template") {
2128            xslt_check_top_level_element(style, inst, 1);
2129            return;
2130        } else if is_xslt_name(inst, b"output") {
2131            xslt_check_top_level_element(style, inst, 1);
2132            return;
2133        } else if is_xslt_name(inst, b"preserve-space") {
2134            xslt_check_top_level_element(style, inst, 1);
2135            return;
2136        } else if is_xslt_name(inst, b"strip-space") {
2137            xslt_check_top_level_element(style, inst, 1);
2138            return;
2139        } else if is_xslt_name(inst, b"stylesheet") || is_xslt_name(inst, b"transform") {
2140            let parent = (*inst).parent;
2141            if parent.is_null() || (*parent).type_ != XML_DOCUMENT_NODE as c_int {
2142                report_error(style, inst, b"element only allowed only as root element\n");
2143                (*style).errors += 1;
2144            }
2145            return;
2146        } else if is_xslt_name(inst, b"key") {
2147            xslt_check_top_level_element(style, inst, 1);
2148            return;
2149        } else if is_xslt_name(inst, b"message") {
2150            xslt_check_instruction_element(style, inst);
2151            return;
2152        } else if is_xslt_name(inst, b"attribute-set") {
2153            xslt_check_top_level_element(style, inst, 1);
2154            return;
2155        } else if is_xslt_name(inst, b"namespace-alias") {
2156            xslt_check_top_level_element(style, inst, 1);
2157            return;
2158        } else if is_xslt_name(inst, b"include") {
2159            xslt_check_top_level_element(style, inst, 1);
2160            return;
2161        } else if is_xslt_name(inst, b"import") {
2162            xslt_check_top_level_element(style, inst, 1);
2163            return;
2164        } else if is_xslt_name(inst, b"decimal-format") {
2165            xslt_check_top_level_element(style, inst, 1);
2166            return;
2167        } else if is_xslt_name(inst, b"fallback") {
2168            xslt_check_instruction_element(style, inst);
2169            return;
2170        } else if is_xslt_name(inst, b"document") {
2171            xslt_check_instruction_element(style, inst);
2172            (*inst).psvi = xsltDocumentComp(style, inst, None) as *mut c_void;
2173        } else if style.is_null() || (*style).forwards_compatible == 0 {
2174            report_error(
2175                style,
2176                inst,
2177                b"xsltStylePreCompute: unknown xsl: instruction\n",
2178            );
2179            if !style.is_null() {
2180                (*style).warnings += 1;
2181            }
2182        }
2183    } else {
2184        // Unknown element: maybe an extension element registered at the
2185        // module level.
2186        (*inst).psvi = xsltPreComputeExtModuleElement(style, inst) as *mut c_void;
2187        if (*inst).psvi.is_null() {
2188            (*inst).psvi = XSLT_EXT_MARKER.as_ptr() as *mut c_void;
2189        }
2190    }
2191}
2192
2193/// `xsltCheckTopLevelElement` (preproc.c): check that the instruction is
2194/// instantiated as a top-level element.
2195///
2196/// Returns -1 on invalid args, 0 if the check failed, 1 on success.
2197unsafe fn xslt_check_top_level_element(
2198    style: *mut _xsltStylesheet,
2199    inst: *mut _xmlNode,
2200    err: c_int,
2201) -> c_int {
2202    if style.is_null() || inst.is_null() || (*inst).ns.is_null() {
2203        return -1;
2204    }
2205
2206    let parent = (*inst).parent;
2207    if parent.is_null() {
2208        if err != 0 {
2209            report_error(style, inst, b"internal problem: element has no parent\n");
2210            (*style).errors += 1;
2211        }
2212        return 0;
2213    }
2214    if (*parent).ns.is_null()
2215        || (*parent).type_ != XML_ELEMENT_NODE as c_int
2216        || (xmlStrEqual((*(*parent).ns).href, (*(*inst).ns).href) == 0)
2217        || (!is_xslt_name(parent, b"stylesheet") && !is_xslt_name(parent, b"transform"))
2218    {
2219        if err != 0 {
2220            report_error(
2221                style,
2222                inst,
2223                b"element only allowed as child of stylesheet\n",
2224            );
2225            (*style).errors += 1;
2226        }
2227        return 0;
2228    }
2229    1
2230}
2231
2232/// `xsltCheckInstructionElement` (preproc.c): check that the instruction
2233/// is instantiated as an instruction element.
2234unsafe fn xslt_check_instruction_element(style: *mut _xsltStylesheet, inst: *mut _xmlNode) {
2235    if style.is_null() || inst.is_null() || (*inst).ns.is_null() || (*style).literal_result != 0 {
2236        return;
2237    }
2238
2239    let has_ext = !(*style).extInfos.is_null() || !ext_ns_registered(ptr::null());
2240
2241    let mut parent = (*inst).parent;
2242    if parent.is_null() {
2243        report_error(style, inst, b"internal problem: element has no parent\n");
2244        (*style).errors += 1;
2245        return;
2246    }
2247    while !parent.is_null() && (*parent).type_ != XML_DOCUMENT_NODE as c_int {
2248        if ((*parent).ns == (*inst).ns
2249            || (!(*parent).ns.is_null()
2250                && xmlStrEqual((*(*parent).ns).href, (*(*inst).ns).href) != 0))
2251            && (is_xslt_name(parent, b"template")
2252                || is_xslt_name(parent, b"param")
2253                || is_xslt_name(parent, b"attribute")
2254                || is_xslt_name(parent, b"variable"))
2255        {
2256            return;
2257        }
2258
2259        // If we are within an extension element all bets are off about the
2260        // semantics there (e.g. xsl:param within func:function).
2261        if has_ext && !(*parent).ns.is_null() && ext_ns_registered((*(*parent).ns).href) {
2262            return;
2263        }
2264
2265        parent = (*parent).parent;
2266    }
2267    report_error(
2268        style,
2269        inst,
2270        b"element only allowed within a template, variable or param\n",
2271    );
2272    (*style).errors += 1;
2273}
2274
2275/// `xsltCheckParentElement` (preproc.c): check that the instruction is a
2276/// child of one of the possible parents.
2277unsafe fn xslt_check_parent_element(
2278    style: *mut _xsltStylesheet,
2279    inst: *mut _xmlNode,
2280    allow1: &[u8],
2281    allow2: *const u8,
2282) {
2283    if style.is_null() || inst.is_null() || (*inst).ns.is_null() || (*style).literal_result != 0 {
2284        return;
2285    }
2286
2287    let parent = (*inst).parent;
2288    if parent.is_null() {
2289        report_error(style, inst, b"internal problem: element has no parent\n");
2290        (*style).errors += 1;
2291        return;
2292    }
2293    let allow2_bytes: &[u8] = if allow2.is_null() {
2294        b""
2295    } else {
2296        core::slice::from_raw_parts(allow2, libc::strlen(allow2 as *const libc::c_char) as usize)
2297    };
2298    if ((*parent).ns == (*inst).ns
2299        || (!(*parent).ns.is_null() && xmlStrEqual((*(*parent).ns).href, (*(*inst).ns).href) != 0))
2300        && (is_xslt_name(parent, allow1)
2301            || (!allow2_bytes.is_empty() && is_xslt_name(parent, allow2_bytes)))
2302    {
2303        return;
2304    }
2305
2306    if !ext_ns_registered(ptr::null()) {
2307        let mut p = parent;
2308        while !p.is_null() && (*p).type_ != XML_DOCUMENT_NODE as c_int {
2309            if !(*p).ns.is_null() && ext_ns_registered((*(*p).ns).href) {
2310                return;
2311            }
2312            p = (*p).parent;
2313        }
2314    }
2315    report_error(style, inst, b"element is not allowed within that context\n");
2316    (*style).errors += 1;
2317}
2318
2319/// Normalize the compiled steps of an imported stylesheet (hash scanner
2320/// callback).
2321///
2322/// # UPSTREAM-PARITY
2323///
2324/// ```c
2325/// void xsltNormalizeCompSteps(void *payload,
2326///         void *data, const xmlChar *name ATTRIBUTE_UNUSED) {
2327///     xsltCompMatchPtr comp = payload;
2328///     xsltStylesheetPtr style = data;
2329///     for (ix = 0; ix < comp->nbStep; ix++) {
2330///         comp->steps[ix].previousExtra += style->extrasNr;
2331///         comp->steps[ix].indexExtra += style->extrasNr;
2332///         comp->steps[ix].lenExtra += style->extrasNr;
2333///     }
2334/// }
2335/// ```
2336///
2337/// # ENGINE-WIRING
2338///
2339/// Upstream's `xsltCompMatch` carries a step array with extra-slot
2340/// indices; the candidate's compiled pattern (`_xsltCompMatch` in
2341/// `exports_xslt_apply.rs`) is an opaque pointer with no step array, so
2342/// there is nothing to re-base — the function is a faithful no-op for the
2343/// candidate representation (documented divergence).
2344///
2345/// # SAFETY
2346///
2347/// - `payload` and `data` are only passed through (never dereferenced).
2348#[no_mangle]
2349pub unsafe extern "C" fn xsltNormalizeCompSteps(
2350    payload: *mut c_void,
2351    data: *mut c_void,
2352    _name: *const xmlChar,
2353) {
2354    let _ = (payload, data);
2355}
2356
2357// ═══════════════════════════════════════════════════════════════════════════════
2358// 6. Style documents (documents.c)
2359// ═══════════════════════════════════════════════════════════════════════════════
2360
2361/// Register a new stylesheet document (wrap it in an `_xsltDocument`).
2362///
2363/// # UPSTREAM-PARITY
2364///
2365/// ```c
2366/// xsltDocumentPtr
2367/// xsltNewStyleDocument(xsltStylesheetPtr style, xmlDocPtr doc) {
2368///     cur = xmlMallocImpl(sizeof(xsltDocument));
2369///     if (cur == NULL) { ... return(NULL); }
2370///     memset(cur, 0, sizeof(xsltDocument));
2371///     cur->doc = doc;
2372///     if (style != NULL) {
2373///         cur->next = style->docList;
2374///         style->docList = cur;
2375///     }
2376///     return(cur);
2377/// }
2378/// ```
2379///
2380/// The wrapper does NOT own `doc` (ownership stays with the caller or the
2381/// stylesheet's `doc` field).
2382///
2383/// # SAFETY
2384///
2385/// - `style` must be a valid `_xsltStylesheet`, or NULL.
2386/// - `doc` must be a valid parsed document.
2387#[no_mangle]
2388pub unsafe extern "C" fn xsltNewStyleDocument(
2389    style: *mut _xsltStylesheet,
2390    doc: *mut _xmlDoc,
2391) -> *mut _xsltDocument {
2392    let cur = xmlMallocImpl(core::mem::size_of::<_xsltDocument>()) as *mut _xsltDocument;
2393    if cur.is_null() {
2394        report_error(
2395            style,
2396            doc as *mut _xmlNode,
2397            b"xsltNewStyleDocument : malloc failed\n",
2398        );
2399        return ptr::null_mut();
2400    }
2401    ptr::write_bytes(cur as *mut u8, 0, core::mem::size_of::<_xsltDocument>());
2402    (*cur).doc = doc;
2403    if !style.is_null() {
2404        (*cur).next = (*style).docList;
2405        (*style).docList = cur;
2406    }
2407    cur
2408}
2409
2410/// Load a stylesheet document by URI, reusing an already-loaded document
2411/// from the stylesheet's doc list when possible.
2412///
2413/// # UPSTREAM-PARITY
2414///
2415/// ```c
2416/// xsltDocumentPtr
2417/// xsltLoadStyleDocument(xsltStylesheetPtr style, const xmlChar *URI);
2418/// ```
2419///
2420/// # ENGINE-WIRING
2421///
2422/// The default loader (documents.c `xsltDocDefaultLoaderFunc`) parses the
2423/// URI with `XSLT_PARSE_OPTIONS`; the candidate's loader mirrors
2424/// `src/xslt/documents` `load_via_loader` (registered loader first, else
2425/// `xmlReadFile`). On failure the freshly parsed document is freed.
2426///
2427/// # SAFETY
2428///
2429/// - `style` must be a valid `_xsltStylesheet`, or NULL.
2430/// - `URI` must be a valid NUL-terminated string, or NULL.
2431#[no_mangle]
2432pub unsafe extern "C" fn xsltLoadStyleDocument(
2433    style: *mut _xsltStylesheet,
2434    uri: *const xmlChar,
2435) -> *mut _xsltDocument {
2436    if style.is_null() || uri.is_null() {
2437        return ptr::null_mut();
2438    }
2439
2440    // Security framework check.
2441    let sec = crate::xslt::security::xsltGetDefaultSecurityPrefs();
2442    if !sec.is_null() {
2443        let res = xslt_check_read(sec, ptr::null_mut(), uri);
2444        if res <= 0 {
2445            if res == 0 {
2446                report_error(
2447                    ptr::null_mut(),
2448                    ptr::null_mut(),
2449                    b"xsltLoadStyleDocument: read rights for ",
2450                );
2451                report_error(ptr::null_mut(), ptr::null_mut(), cbytes(uri as *const u8));
2452                report_error(ptr::null_mut(), ptr::null_mut(), b" denied\n");
2453            }
2454            return ptr::null_mut();
2455        }
2456    }
2457
2458    // Walk the style's document list for a preparsed match.
2459    let mut ret = (*style).docList;
2460    while !ret.is_null() {
2461        if !(*ret).doc.is_null()
2462            && !(*(*ret).doc).URL.is_null()
2463            && xmlStrEqual((*(*ret).doc).URL, uri) != 0
2464        {
2465            return ret;
2466        }
2467        ret = (*ret).next;
2468    }
2469
2470    let doc = xslt_doc_default_loader(
2471        uri,
2472        (*style).dict,
2473        XSLT_PARSE_OPTIONS,
2474        style as *mut c_void,
2475        XSLT_LOAD_STYLESHEET,
2476    );
2477    if doc.is_null() {
2478        return ptr::null_mut();
2479    }
2480
2481    let ret = xsltNewStyleDocument(style, doc);
2482    if ret.is_null() {
2483        crate::xml::tree::free_doc(doc);
2484    }
2485    ret
2486}
2487
2488/// Free the node-trees (and `_xsltDocument` structures) of all
2489/// stylesheet-modules of the stylesheet-level represented by `style`.
2490///
2491/// # UPSTREAM-PARITY
2492///
2493/// ```c
2494/// void
2495/// xsltFreeStyleDocuments(xsltStylesheetPtr style) {
2496///     if (style == NULL) return;
2497///     cur = style->docList;
2498///     while (cur != NULL) {
2499///         doc = cur; cur = cur->next;
2500///         xsltFreeDocumentKeys(doc);
2501///         if (!doc->main) xmlFreeDoc(doc->doc);
2502///         xmlFreeImpl(doc);
2503///     }
2504/// }
2505/// ```
2506///
2507/// # ENGINE-WIRING
2508///
2509/// `xsltFreeDocumentKeys` (keys.c) frees the key tables cached on the
2510/// wrapper; the candidate computes keys on demand under the transform
2511/// context and caches them on the context's document wrapper, so nothing
2512/// is cached on style documents (documented divergence).
2513///
2514/// # SAFETY
2515///
2516/// - `style` must be a valid `_xsltStylesheet`, or NULL.
2517#[no_mangle]
2518pub unsafe extern "C" fn xsltFreeStyleDocuments(style: *mut _xsltStylesheet) {
2519    if style.is_null() {
2520        return;
2521    }
2522
2523    let mut cur = (*style).docList;
2524    (*style).docList = ptr::null_mut();
2525    while !cur.is_null() {
2526        let doc = cur;
2527        cur = (*cur).next;
2528        if (*doc).main == 0 && !(*doc).doc.is_null() {
2529            crate::xml::tree::free_doc((*doc).doc);
2530        }
2531        xmlFreeImpl(doc as *mut c_void);
2532    }
2533}
2534
2535// ═══════════════════════════════════════════════════════════════════════════════
2536// 7. Global state & extensions (extensions.c, xslt.c)
2537// ═══════════════════════════════════════════════════════════════════════════════
2538
2539/// Initialize the global variables for extensions.
2540///
2541/// # UPSTREAM-PARITY
2542///
2543/// ```c
2544/// void
2545/// xsltInitGlobals(void) {
2546///     if (xsltExtMutex == NULL) {
2547///         xsltExtMutex = xmlNewMutex();
2548///     }
2549/// }
2550/// ```
2551///
2552/// # ENGINE-WIRING
2553///
2554/// The candidate's global extension registries are plain statics that
2555/// require no lazy initialization; the call is kept as the idempotent
2556/// initialization marker (documented no-op mirroring upstream's
2557/// mutex-creation).
2558#[no_mangle]
2559pub unsafe extern "C" fn xsltInitGlobals() {
2560    if XSLT_GLOBALS_INITIALIZED == 0 {
2561        XSLT_GLOBALS_INITIALIZED = 1;
2562    }
2563}
2564
2565/// Uninitialize the processor.
2566///
2567/// # UPSTREAM-PARITY
2568///
2569/// ```c
2570/// void
2571/// xsltUninit (void) {
2572/// #ifdef XSLT_LOCALE_WINAPI
2573///     xmlFreeRMutex(xsltLocaleMutex);
2574///     xsltLocaleMutex = NULL;
2575/// #endif
2576///     initialized = 0;
2577/// }
2578/// ```
2579///
2580/// # ENGINE-WIRING
2581///
2582/// On the oracle (non-Win32) build this only clears the global
2583/// initialized flag; the candidate keeps process-lifetime statics, so the
2584/// observable behaviour is a no-op. The marker is reset for symmetry.
2585#[no_mangle]
2586pub unsafe extern "C" fn xsltUninit() {
2587    XSLT_GLOBALS_INITIALIZED = 0;
2588}
2589
2590/// Free the memory used by XSLT extensions in a stylesheet.
2591///
2592/// # UPSTREAM-PARITY
2593///
2594/// ```c
2595/// void
2596/// xsltFreeExts(xsltStylesheetPtr style) {
2597///     if (style->nsDefs != NULL)
2598///         xsltFreeExtDefList((xsltExtDefPtr) style->nsDefs);
2599/// }
2600/// ```
2601///
2602/// # ENGINE-WIRING
2603///
2604/// Upstream keeps the stylesheet's extension-prefix definitions in
2605/// `style->nsDefs`; the candidate *repurposes* `style->nsDefs` for the
2606/// preserve-space rule list (see `src/xslt/compiler`
2607/// `compile_space_rules`, documented divergence) and frees it as such in
2608/// `xsltFreeStylesheet`. No extension-prefix def list exists, so there is
2609/// nothing to free here — the function is an intentionally empty port
2610/// (freeing `nsDefs` again would double-free the preserve-space list).
2611///
2612/// # SAFETY
2613///
2614/// - `style` must be a valid `_xsltStylesheet`, or NULL.
2615#[no_mangle]
2616pub unsafe extern "C" fn xsltFreeExts(style: *mut _xsltStylesheet) {
2617    if style.is_null() {
2618        return;
2619    }
2620    // See ENGINE-WIRING above: nothing to free in the candidate engine.
2621}
2622
2623/// Shut down the set of extension modules loaded for a stylesheet.
2624///
2625/// # UPSTREAM-PARITY
2626///
2627/// ```c
2628/// void
2629/// xsltShutdownExts(xsltStylesheetPtr style) {
2630///     if (style == NULL) return;
2631///     if (style->extInfos == NULL) return;
2632///     xmlHashScan(style->extInfos, xsltShutdownExt, style);
2633///     xmlHashFree(style->extInfos, xsltFreeExtDataEntry);
2634///     style->extInfos = NULL;
2635/// }
2636/// ```
2637///
2638/// # ENGINE-WIRING
2639///
2640/// Upstream populates `style->extInfos` per stylesheet when a registered
2641/// module provides a style-init function; the candidate has no such
2642/// registration path, so `style->extInfos` is NULL for every stylesheet
2643/// and the function returns immediately — the exact upstream behaviour for
2644/// that state. If a hash were ever attached by an external writer, it is
2645/// released without invoking shutdown callbacks (no module metadata is
2646/// available to the compile family; documented divergence).
2647///
2648/// # SAFETY
2649///
2650/// - `style` must be a valid `_xsltStylesheet`, or NULL.
2651#[no_mangle]
2652pub unsafe extern "C" fn xsltShutdownExts(style: *mut _xsltStylesheet) {
2653    if style.is_null() {
2654        return;
2655    }
2656    if (*style).extInfos.is_null() {
2657        return;
2658    }
2659    // Unreachable in the candidate engine (extInfos is never populated);
2660    // free the table so the stylesheet teardown stays leak-free for
2661    // external writers.
2662    crate::xml::hash::hash_free((*style).extInfos as *mut crate::xml::hash::HashTable, None);
2663    (*style).extInfos = ptr::null_mut();
2664}
2665
2666/// Dump a list of the registered XSLT extension functions and elements.
2667///
2668/// # UPSTREAM-PARITY
2669///
2670/// ```c
2671/// void
2672/// xsltDebugDumpExtensions(FILE * output);
2673/// ```
2674///
2675/// Prints the same headings as extensions.c 1.1.45 to the given `FILE*`
2676/// (stdout when NULL): the extension-function and top-level registries do
2677/// not exist in the candidate (always "No registered …"), while the
2678/// instruction-element and module registries print their entries
2679/// (`{URI}name` and `URI` lines respectively).
2680///
2681/// # SAFETY
2682///
2683/// - `output` must be a valid `FILE*`, or NULL (stdout).
2684#[no_mangle]
2685pub unsafe extern "C" fn xsltDebugDumpExtensions(output: *mut libc::FILE) {
2686    let out = if output.is_null() {
2687        libc::fdopen(1, b"w\0".as_ptr() as *const c_char)
2688    } else {
2689        output
2690    };
2691
2692    if out.is_null() {
2693        return;
2694    }
2695
2696    libc::fprintf(
2697        out,
2698        b"Registered XSLT Extensions\n--------------------------\n\0".as_ptr() as *const c_char,
2699    );
2700    libc::fprintf(
2701        out,
2702        b"No registered extension functions\n\0".as_ptr() as *const c_char,
2703    );
2704    libc::fprintf(
2705        out,
2706        b"\nNo registered top-level extension elements\n\0".as_ptr() as *const c_char,
2707    );
2708
2709    if XSLT_ELEMENTS_REGISTRY.is_null() {
2710        libc::fprintf(
2711            out,
2712            b"\nNo registered instruction extension elements\n\0".as_ptr() as *const c_char,
2713        );
2714    } else {
2715        libc::fprintf(
2716            out,
2717            b"\nRegistered instruction extension elements:\n\0".as_ptr() as *const c_char,
2718        );
2719        let mut cur = XSLT_ELEMENTS_REGISTRY;
2720        while !cur.is_null() {
2721            if !(*cur).URI.is_null() && !(*cur).name.is_null() {
2722                libc::fprintf(
2723                    out,
2724                    b"{%s}%s\n\0".as_ptr() as *const c_char,
2725                    (*cur).URI as *const c_char,
2726                    (*cur).name as *const c_char,
2727                );
2728            }
2729            cur = (*cur).next;
2730        }
2731    }
2732
2733    if XSLT_MODULES_REGISTRY.is_null() {
2734        libc::fprintf(
2735            out,
2736            b"\nNo registered extension modules\n\0".as_ptr() as *const c_char,
2737        );
2738    } else {
2739        libc::fprintf(
2740            out,
2741            b"\nRegistered extension modules:\n\0".as_ptr() as *const c_char,
2742        );
2743        let mut cur = XSLT_MODULES_REGISTRY;
2744        while !cur.is_null() {
2745            if !(*cur).URI.is_null() {
2746                libc::fprintf(
2747                    out,
2748                    b"%s\n\0".as_ptr() as *const c_char,
2749                    (*cur).URI as *const c_char,
2750                );
2751            }
2752            cur = (*cur).next;
2753        }
2754    }
2755}