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