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}