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