libxml_rs/abi/exports_xslt_ext.rs
1//! C ABI exports for libxslt.so.1 — the "ext" family (§16, Phase 8).
2//!
3//! The extension-module registry (`xsltRegisterExtModule*`,
4//! `xsltUnregisterExtModule*`, the `xsltExtModule*Lookup` queries) and the
5//! per-context/per-style extension data accessors (`xsltGetExtData`,
6//! `xsltStyleGetExtData`, `xsltGetExtInfo`), plus the EXSLT registration
7//! entry points (`xsltRegisterAllFunctions`, `xsltRegisterAllElement`,
8//! `xsltRegisterAllExtras`, `xsltRegisterExtras`).
9//!
10//! Semantics follow upstream libxslt 1.1.45 (`archaeology/libxslt-git/
11//! libxslt/extensions.c`, `extra.c`). The candidate keeps the module
12//! registry in process-lifetime `RwLock<HashMap>` tables (upstream uses
13//! global xmlHashTable instances) with the same observable contracts:
14//! registrations return 0 on success / -1 on failure and lookups resolve
15//! by (name, URI) case-sensitively.
16//!
17//! # Upstream contract
18//!
19//! Parity target is upstream libxslt 1.1.45 `extensions.c` and `extra.c` with
20//! the upstream headers; R-000165 (11.1-O) closed the extension registration
21//! gaps (the per-module EXSLT registration entry points).
22//!
23//! # Conceptual behavior
24//!
25//! This module implements the extension-module registry ABI: the
26//! `xsltRegisterExtModule*`/`xsltUnregisterExtModule*` lifecycle, the
27//! `xsltExtModule*Lookup` queries, per-context/per-style extension data
28//! accessors (`xsltGetExtData`, `xsltStyleGetExtData`, `xsltGetExtInfo`) and
29//! the EXSLT registration entry points. The registry lives in
30//! process-lifetime RwLock tables with the same observable contracts as
31//! upstreams global hash tables (0 success / -1 failure, case-sensitive
32//! (name, URI) lookups).
33//!
34//! # Ownership & safety invariants
35//!
36//! Extension module data is owned by the context/style that registered it and
37//! released in the documented order: `xsltShutdownCtxtExts` (callbacks)
38//! before `xsltFreeCtxtExts` (storage) per OWNERSHIP_ATLAS section 7. Module
39//! function pointers are caller-kept; the registry stores and returns them
40//! verbatim.
41//!
42//! # Historical quirks & epochs
43//!
44//! The extension mechanism matured with EXSLT in the 1.1 era (2004+, HISTORY.md
45//! 2.5) and is part of the frozen E-008 transform epoch; R-000165 added the
46//! missing per-module registration exports (exsltMathRegister et al.) so the
47//! oracle export set is complete.
48//!
49//! # Deliberate oddities
50//!
51//! The RwLock-backed registry instead of upstreams xmlHashTable is a
52//! deliberate internal substitution with identical observable behavior — the
53//! ABI never exposes the registry object itself.
54//!
55//! # Proving courts
56//!
57//! The EXSLT court family, the DSO-LOADER and HEADER-COMPILE
58//! courts and the callback-family probes (CALLBACK-001) cover this module;
59//! the extension unit tests run under cargo test.
60//!
61//! # Tempting simplifications that would break parity
62//!
63//! A tempting simplification is to make registrations case-insensitive for
64//! convenience — upstream resolves (name, URI) case-sensitively, so a
65//! downstream module registered under a different case would silently stop
66//! resolving. Another shortcut, freeing extension data when the module
67//! unregisters, would break the shutdown-before-free ordering the OWNERSHIP
68//! courts check.
69
70#![allow(non_snake_case)]
71#![allow(unused_variables)]
72#![allow(clippy::missing_safety_doc)]
73#![allow(clippy::not_unsafe_ptr_arg_deref)]
74
75use core::ptr;
76use std::collections::HashMap;
77use std::ffi::CStr;
78use std::os::raw::{c_char, c_int, c_void};
79
80use parking_lot::RwLock;
81
82use crate::abi::allocator::{xmlFreeImpl, xmlMallocImpl};
83use crate::abi::structs::*;
84use crate::abi::types::*;
85
86// ── Registry types (upstream _xsltExtModule, extensions.c) ────────────────
87
88/// `xsltExtInitFunction`: called when a stylesheet first uses the module.
89pub type xsltExtInitFunction =
90 unsafe extern "C" fn(ctxt: *mut _xsltTransformContext, URI: *const xmlChar) -> *mut c_void;
91/// `xsltExtShutdownFunction`: called when the context is freed.
92pub type xsltExtShutdownFunction =
93 unsafe extern "C" fn(ctxt: *mut _xsltTransformContext, URI: *const xmlChar, data: *mut c_void);
94/// `xsltStyleExtInitFunction`: called at stylesheet compile time.
95pub type xsltStyleExtInitFunction =
96 unsafe extern "C" fn(style: *mut _xsltStylesheet, URI: *const xmlChar) -> *mut c_void;
97/// `xsltStyleExtShutdownFunction`: called when the stylesheet is freed.
98pub type xsltStyleExtShutdownFunction =
99 unsafe extern "C" fn(style: *mut _xsltStylesheet, URI: *const xmlChar, data: *mut c_void);
100/// `xsltTopLevelFunction`: handles a top-level extension element.
101pub type xsltTopLevelFunction =
102 unsafe extern "C" fn(style: *mut _xsltStylesheet, node: *mut _xmlNode, data: *mut c_void);
103
104#[derive(Clone, Copy)]
105struct ExtModule {
106 init_func: Option<xsltExtInitFunction>,
107 shutdown_func: Option<xsltExtShutdownFunction>,
108 style_init_func: Option<xsltStyleExtInitFunction>,
109 #[allow(dead_code)]
110 style_shutdown_func: Option<xsltStyleExtShutdownFunction>,
111}
112
113/// `_xsltExtElement` entry: name + URI + precompute + transform handlers.
114/// `_xsltExtElement` entry: name + URI + precompute + transform handlers.
115/// The fn pointers are stored as `usize` so the registry is Send + Sync
116/// (they are cast back to raw pointers at lookup time).
117#[derive(Clone, Copy)]
118struct ExtElement {
119 precomp: usize, // xsltPreComputeFunction
120 transform: usize, // xsltTransformFunction
121}
122
123/// Registry key: "name\0URI\0" (upstream hashes the QName via xmlDictQLookup).
124fn ext_key(name: *const xmlChar, uri: *const xmlChar) -> Option<Vec<u8>> {
125 if name.is_null() || uri.is_null() {
126 return None;
127 }
128 let n = unsafe { CStr::from_ptr(name as *const c_char).to_bytes() };
129 let u = unsafe { CStr::from_ptr(uri as *const c_char).to_bytes() };
130 let mut k = Vec::with_capacity(n.len() + 1 + u.len());
131 k.extend_from_slice(n);
132 k.push(0);
133 k.extend_from_slice(u);
134 Some(k)
135}
136
137/// Global extension-module registry (upstream `xsltExtModules` hash).
138static EXT_MODULES: once_cell::sync::Lazy<RwLock<HashMap<Vec<u8>, ExtModule>>> =
139 once_cell::sync::Lazy::new(|| RwLock::new(HashMap::new()));
140/// Global extension-element registry (upstream `xsltExtElements` hash).
141/// Fn pointers stored as `usize` (Send + Sync).
142static EXT_ELEMENTS: once_cell::sync::Lazy<RwLock<HashMap<Vec<u8>, ExtElement>>> =
143 once_cell::sync::Lazy::new(|| RwLock::new(HashMap::new()));
144/// Global extension-function registry (upstream `xsltExtFunctions` hash).
145static EXT_FUNCTIONS: once_cell::sync::Lazy<RwLock<HashMap<Vec<u8>, usize>>> =
146 once_cell::sync::Lazy::new(|| RwLock::new(HashMap::new()));
147/// Global top-level-element registry (upstream `xsltExtTopLevels` hash).
148static EXT_TOPLEVELS: once_cell::sync::Lazy<RwLock<HashMap<Vec<u8>, usize>>> =
149 once_cell::sync::Lazy::new(|| RwLock::new(HashMap::new()));
150
151/// `xsltRegisterExtModule` (extensions.c): register a module by URI.
152///
153/// # UPSTREAM-PARITY
154///
155/// ```c
156/// int xsltRegisterExtModule(const xmlChar *URI,
157/// xsltExtInitFunction initFunc,
158/// xsltExtShutdownFunction shutdownFunc);
159/// ```
160///
161/// Returns 0 on success, -1 on error.
162#[no_mangle]
163pub unsafe extern "C" fn xsltRegisterExtModule(
164 URI: *const xmlChar,
165 initFunc: Option<xsltExtInitFunction>,
166 shutdownFunc: Option<xsltExtShutdownFunction>,
167) -> c_int {
168 // Upstream xsltRegisterExtModuleFull: NULL URI or NULL initFunc -> -1.
169 if URI.is_null() || initFunc.is_none() {
170 return -1;
171 }
172 let key = unsafe { CStr::from_ptr(URI as *const c_char).to_bytes().to_vec() };
173 EXT_MODULES.write().insert(
174 key,
175 ExtModule {
176 init_func: initFunc,
177 shutdown_func: shutdownFunc,
178 style_init_func: None,
179 style_shutdown_func: None,
180 },
181 );
182 0
183}
184
185/// `xsltRegisterExtModuleFull` (extensions.c): register a module including
186/// the stylesheet-level init/shutdown hooks.
187///
188/// # UPSTREAM-PARITY
189///
190/// ```c
191/// int xsltRegisterExtModuleFull(const xmlChar *URI,
192/// xsltExtInitFunction initFunc,
193/// xsltExtShutdownFunction shutdownFunc,
194/// xsltStyleExtInitFunction styleInitFunc,
195/// xsltStyleExtShutdownFunction styleShutdownFunc);
196/// ```
197#[no_mangle]
198pub unsafe extern "C" fn xsltRegisterExtModuleFull(
199 URI: *const xmlChar,
200 initFunc: Option<xsltExtInitFunction>,
201 shutdownFunc: Option<xsltExtShutdownFunction>,
202 styleInitFunc: Option<xsltStyleExtInitFunction>,
203 styleShutdownFunc: Option<xsltStyleExtShutdownFunction>,
204) -> c_int {
205 if URI.is_null() || initFunc.is_none() {
206 return -1;
207 }
208 let key = unsafe { CStr::from_ptr(URI as *const c_char).to_bytes().to_vec() };
209 EXT_MODULES.write().insert(
210 key,
211 ExtModule {
212 init_func: initFunc,
213 shutdown_func: shutdownFunc,
214 style_init_func: styleInitFunc,
215 style_shutdown_func: styleShutdownFunc,
216 },
217 );
218 0
219}
220
221/// `xsltRegisterExtModuleElement` (extensions.c): register an extension
222/// element (name in a module URI) with precompute + transform handlers.
223///
224/// # UPSTREAM-PARITY
225///
226/// ```c
227/// int xsltRegisterExtModuleElement(const xmlChar *name, const xmlChar *URI,
228/// xsltPreComputeFunction precomp,
229/// xsltTransformFunction transform);
230/// ```
231#[no_mangle]
232pub unsafe extern "C" fn xsltRegisterExtModuleElement(
233 name: *const xmlChar,
234 URI: *const xmlChar,
235 precomp: Option<crate::abi::exports_xslt_compile::xsltPreComputeFunction>,
236 transform: Option<crate::abi::exports_xslt_compile::xsltTransformFunction>,
237) -> c_int {
238 let Some(key) = ext_key(name, URI) else {
239 return -1;
240 };
241 EXT_ELEMENTS.write().insert(
242 key,
243 ExtElement {
244 precomp: precomp.map_or(0, |f| f as usize),
245 transform: transform.map_or(0, |f| f as usize),
246 },
247 );
248 0
249}
250
251/// `xsltRegisterExtModuleFunction` (extensions.c): register an XPath
252/// extension function (name in a module URI).
253///
254/// # UPSTREAM-PARITY
255///
256/// ```c
257/// int xsltRegisterExtModuleFunction(const xmlChar *name, const xmlChar *URI,
258/// xmlXPathFunction function);
259/// ```
260#[no_mangle]
261pub unsafe extern "C" fn xsltRegisterExtModuleFunction(
262 name: *const xmlChar,
263 URI: *const xmlChar,
264 function: Option<crate::abi::exports_xslt_compile::xmlXPathFunction>,
265) -> c_int {
266 let Some(key) = ext_key(name, URI) else {
267 return -1;
268 };
269 EXT_FUNCTIONS
270 .write()
271 .insert(key, function.map_or(0, |f| f as usize));
272 0
273}
274
275/// `xsltRegisterExtModuleTopLevel` (extensions.c): register a top-level
276/// extension element handler.
277///
278/// # UPSTREAM-PARITY
279///
280/// ```c
281/// int xsltRegisterExtModuleTopLevel(const xmlChar *name, const xmlChar *URI,
282/// xsltTopLevelFunction function);
283/// ```
284#[no_mangle]
285pub unsafe extern "C" fn xsltRegisterExtModuleTopLevel(
286 name: *const xmlChar,
287 URI: *const xmlChar,
288 function: Option<xsltTopLevelFunction>,
289) -> c_int {
290 let Some(key) = ext_key(name, URI) else {
291 return -1;
292 };
293 EXT_TOPLEVELS
294 .write()
295 .insert(key, function.map_or(0, |f| f as usize));
296 0
297}
298
299/// `xsltUnregisterExtModule` (extensions.c): unregister a module and all of
300/// its elements/functions/top-levels.
301///
302/// # UPSTREAM-PARITY
303///
304/// ```c
305/// int xsltUnregisterExtModule(const xmlChar *URI);
306/// ```
307#[no_mangle]
308pub unsafe extern "C" fn xsltUnregisterExtModule(URI: *const xmlChar) -> c_int {
309 if URI.is_null() {
310 return -1;
311 }
312 let uri_bytes = unsafe { CStr::from_ptr(URI as *const c_char).to_bytes().to_vec() };
313 let mut mods = EXT_MODULES.write();
314 if mods.remove(&uri_bytes).is_none() {
315 return -1;
316 }
317 drop(mods);
318 // Remove every element/function/top-level belonging to the URI.
319 let mut elems = EXT_ELEMENTS.write();
320 let mut funcs = EXT_FUNCTIONS.write();
321 let mut tops = EXT_TOPLEVELS.write();
322 let suffix: Vec<u8> = {
323 let mut s = vec![0];
324 s.extend_from_slice(&uri_bytes);
325 s
326 };
327 elems.retain(|k, _| !k.ends_with(&suffix));
328 funcs.retain(|k, _| !k.ends_with(&suffix));
329 tops.retain(|k, _| !k.ends_with(&suffix));
330 0
331}
332
333/// `xsltUnregisterExtModuleElement` (extensions.c).
334///
335/// # UPSTREAM-PARITY
336///
337/// ```c
338/// int xsltUnregisterExtModuleElement(const xmlChar *name, const xmlChar *URI);
339/// ```
340#[no_mangle]
341pub unsafe extern "C" fn xsltUnregisterExtModuleElement(
342 name: *const xmlChar,
343 URI: *const xmlChar,
344) -> c_int {
345 let Some(key) = ext_key(name, URI) else {
346 return -1;
347 };
348 if EXT_ELEMENTS.write().remove(&key).is_some() {
349 0
350 } else {
351 -1
352 }
353}
354
355/// `xsltUnregisterExtModuleFunction` (extensions.c).
356///
357/// # UPSTREAM-PARITY
358///
359/// ```c
360/// int xsltUnregisterExtModuleFunction(const xmlChar *name, const xmlChar *URI);
361/// ```
362#[no_mangle]
363pub unsafe extern "C" fn xsltUnregisterExtModuleFunction(
364 name: *const xmlChar,
365 URI: *const xmlChar,
366) -> c_int {
367 let Some(key) = ext_key(name, URI) else {
368 return -1;
369 };
370 if EXT_FUNCTIONS.write().remove(&key).is_some() {
371 0
372 } else {
373 -1
374 }
375}
376
377/// `xsltUnregisterExtModuleTopLevel` (extensions.c).
378///
379/// # UPSTREAM-PARITY
380///
381/// ```c
382/// int xsltUnregisterExtModuleTopLevel(const xmlChar *name, const xmlChar *URI);
383/// ```
384#[no_mangle]
385pub unsafe extern "C" fn xsltUnregisterExtModuleTopLevel(
386 name: *const xmlChar,
387 URI: *const xmlChar,
388) -> c_int {
389 let Some(key) = ext_key(name, URI) else {
390 return -1;
391 };
392 if EXT_TOPLEVELS.write().remove(&key).is_some() {
393 0
394 } else {
395 -1
396 }
397}
398
399/// `xsltRegisterExtPrefix` (extensions.c): register a prefix→URI mapping on
400/// the stylesheet so `xsltCheckExtPrefix` recognises it as an extension.
401///
402/// # UPSTREAM-PARITY
403///
404/// ```c
405/// int xsltRegisterExtPrefix(xsltStylesheetPtr style,
406/// const xmlChar *prefix, const xmlChar *URI);
407/// ```
408#[no_mangle]
409pub unsafe extern "C" fn xsltRegisterExtPrefix(
410 style: *mut _xsltStylesheet,
411 prefix: *const xmlChar,
412 URI: *const xmlChar,
413) -> c_int {
414 if style.is_null() || prefix.is_null() || URI.is_null() {
415 return -1;
416 }
417 // The candidate carries registered extension prefixes as a growable
418 // linked list in the stylesheet (upstream uses style->extInfos hash).
419 let mut cur = (*style).extInfos as *mut ExtPrefixEntry;
420 while !cur.is_null() {
421 if !(*cur).prefix.is_null()
422 && libc::strcmp((*cur).prefix as *const c_char, prefix as *const c_char) == 0
423 {
424 // Re-registration with a different URI updates the mapping.
425 let new_uri =
426 crate::abi::allocator::xmlMemStrdupImpl(URI as *const c_char) as *mut c_char;
427 if new_uri.is_null() {
428 return -1;
429 }
430 xmlFreeImpl((*cur).uri as *mut c_void);
431 (*cur).uri = new_uri;
432 return 0;
433 }
434 cur = (*cur).next;
435 }
436 let entry = xmlMallocImpl(size_of::<ExtPrefixEntry>()) as *mut ExtPrefixEntry;
437 if entry.is_null() {
438 return -1;
439 }
440 let p = crate::abi::allocator::xmlMemStrdupImpl(prefix as *const c_char) as *mut c_char;
441 let u = crate::abi::allocator::xmlMemStrdupImpl(URI as *const c_char) as *mut c_char;
442 if p.is_null() || u.is_null() {
443 if !p.is_null() {
444 xmlFreeImpl(p as *mut c_void);
445 }
446 if !u.is_null() {
447 xmlFreeImpl(u as *mut c_void);
448 }
449 xmlFreeImpl(entry as *mut c_void);
450 return -1;
451 }
452 ptr::write(
453 entry,
454 ExtPrefixEntry {
455 next: (*style).extInfos as *mut ExtPrefixEntry,
456 prefix: p,
457 uri: u,
458 },
459 );
460 (*style).extInfos = entry as *mut c_void;
461 0
462}
463
464/// `xsltCheckExtPrefix` (extensions.c): 1 if `prefix` is registered as an
465/// extension prefix on the stylesheet (or is a literal-result element
466/// prefix), 0 otherwise.
467///
468/// # UPSTREAM-PARITY
469///
470/// ```c
471/// int xsltCheckExtPrefix(xsltStylesheetPtr style, const xmlChar *prefix);
472/// ```
473#[no_mangle]
474pub unsafe extern "C" fn xsltCheckExtPrefix(
475 style: *mut _xsltStylesheet,
476 prefix: *const xmlChar,
477) -> c_int {
478 if style.is_null() || prefix.is_null() {
479 return 0;
480 }
481 let mut cur = (*style).extInfos as *mut ExtPrefixEntry;
482 while !cur.is_null() {
483 if !(*cur).prefix.is_null()
484 && libc::strcmp((*cur).prefix as *const c_char, prefix as *const c_char) == 0
485 {
486 return 1;
487 }
488 cur = (*cur).next;
489 }
490 0
491}
492
493/// `xsltCheckExtURI` (extensions.c): 1 if `URI` is registered as an
494/// extension namespace on the stylesheet, 0 otherwise.
495///
496/// # UPSTREAM-PARITY
497///
498/// ```c
499/// int xsltCheckExtURI(xsltStylesheetPtr style, const xmlChar *URI);
500/// ```
501#[no_mangle]
502pub unsafe extern "C" fn xsltCheckExtURI(
503 style: *mut _xsltStylesheet,
504 URI: *const xmlChar,
505) -> c_int {
506 if style.is_null() || URI.is_null() {
507 return 0;
508 }
509 let mut cur = (*style).extInfos as *mut ExtPrefixEntry;
510 while !cur.is_null() {
511 if !(*cur).uri.is_null()
512 && libc::strcmp((*cur).uri as *const c_char, URI as *const c_char) == 0
513 {
514 return 1;
515 }
516 cur = (*cur).next;
517 }
518 0
519}
520
521/// `xsltExtElementLookup` (extensions.c): resolve an extension element's
522/// transform function, consulting the per-context registrations first then
523/// the global module registry.
524///
525/// # UPSTREAM-PARITY
526///
527/// ```c
528/// xsltTransformFunction xsltExtElementLookup(xsltTransformContextPtr ctxt,
529/// const xmlChar *name,
530/// const xmlChar *URI);
531/// ```
532#[no_mangle]
533pub unsafe extern "C" fn xsltExtElementLookup(
534 ctxt: *mut _xsltTransformContext,
535 name: *const xmlChar,
536 URI: *const xmlChar,
537) -> Option<crate::abi::exports_xslt_compile::xsltTransformFunction> {
538 if ctxt.is_null() || name.is_null() || URI.is_null() {
539 return None;
540 }
541 // Per-context registrations (xsltRegisterExtElement).
542 let found = crate::xslt::extensions::xsltFindExtElement(ctxt, name, URI);
543 if !found.is_null() {
544 return Some(unsafe {
545 core::mem::transmute::<
546 *mut c_void,
547 crate::abi::exports_xslt_compile::xsltTransformFunction,
548 >(found)
549 });
550 }
551 let key = ext_key(name, URI)?;
552 EXT_ELEMENTS
553 .read()
554 .get(&key)
555 .and_then(|e| {
556 if e.transform == 0 {
557 None
558 } else {
559 Some(unsafe {
560 core::mem::transmute::<usize, crate::abi::exports_xslt_compile::xsltTransformFunction>(e.transform)
561 })
562 }
563 })
564}
565
566/// `xsltExtModuleElementLookup` (extensions.c): global element lookup.
567///
568/// # UPSTREAM-PARITY
569///
570/// ```c
571/// xsltTransformFunction xsltExtModuleElementLookup(const xmlChar *name,
572/// const xmlChar *URI);
573/// ```
574#[no_mangle]
575pub unsafe extern "C" fn xsltExtModuleElementLookup(
576 name: *const xmlChar,
577 URI: *const xmlChar,
578) -> Option<crate::abi::exports_xslt_compile::xsltTransformFunction> {
579 let key = ext_key(name, URI)?;
580 EXT_ELEMENTS
581 .read()
582 .get(&key)
583 .and_then(|e| {
584 if e.transform == 0 {
585 None
586 } else {
587 Some(unsafe {
588 core::mem::transmute::<usize, crate::abi::exports_xslt_compile::xsltTransformFunction>(e.transform)
589 })
590 }
591 })
592}
593
594/// `xsltExtModuleFunctionLookup` (extensions.c): global function lookup.
595///
596/// # UPSTREAM-PARITY
597///
598/// ```c
599/// xmlXPathFunction xsltExtModuleFunctionLookup(const xmlChar *name,
600/// const xmlChar *URI);
601/// ```
602#[no_mangle]
603pub unsafe extern "C" fn xsltExtModuleFunctionLookup(
604 name: *const xmlChar,
605 URI: *const xmlChar,
606) -> Option<crate::abi::exports_xslt_compile::xmlXPathFunction> {
607 let key = ext_key(name, URI)?;
608 EXT_FUNCTIONS.read().get(&key).copied().and_then(|p| {
609 if p == 0 {
610 None
611 } else {
612 Some(unsafe {
613 core::mem::transmute::<usize, crate::abi::exports_xslt_compile::xmlXPathFunction>(p)
614 })
615 }
616 })
617}
618
619/// `xsltExtModuleElementPreComputeLookup` (extensions.c).
620///
621/// # UPSTREAM-PARITY
622///
623/// ```c
624/// xsltPreComputeFunction xsltExtModuleElementPreComputeLookup(
625/// const xmlChar *name, const xmlChar *URI);
626/// ```
627#[no_mangle]
628pub unsafe extern "C" fn xsltExtModuleElementPreComputeLookup(
629 name: *const xmlChar,
630 URI: *const xmlChar,
631) -> Option<crate::abi::exports_xslt_compile::xsltPreComputeFunction> {
632 let key = ext_key(name, URI)?;
633 EXT_ELEMENTS.read().get(&key).and_then(|e| {
634 if e.precomp == 0 {
635 None
636 } else {
637 Some(unsafe {
638 core::mem::transmute::<
639 usize,
640 crate::abi::exports_xslt_compile::xsltPreComputeFunction,
641 >(e.precomp)
642 })
643 }
644 })
645}
646
647/// `xsltExtModuleTopLevelLookup` (extensions.c).
648///
649/// # UPSTREAM-PARITY
650///
651/// ```c
652/// xsltTopLevelFunction xsltExtModuleTopLevelLookup(const xmlChar *name,
653/// const xmlChar *URI);
654/// ```
655#[no_mangle]
656pub unsafe extern "C" fn xsltExtModuleTopLevelLookup(
657 name: *const xmlChar,
658 URI: *const xmlChar,
659) -> Option<xsltTopLevelFunction> {
660 let key = ext_key(name, URI)?;
661 EXT_TOPLEVELS.read().get(&key).copied().and_then(|p| {
662 if p == 0 {
663 None
664 } else {
665 Some(unsafe { core::mem::transmute::<usize, xsltTopLevelFunction>(p) })
666 }
667 })
668}
669
670/// `xsltInitCtxtExts` (extensions.c): call the init function of every module
671/// whose URI the stylesheet uses (registered extension prefixes).
672///
673/// # UPSTREAM-PARITY
674///
675/// ```c
676/// int xsltInitCtxtExts(xsltTransformContextPtr ctxt);
677/// ```
678///
679/// Returns 0 on success, -1 on error.
680#[no_mangle]
681pub unsafe extern "C" fn xsltInitCtxtExts(ctxt: *mut _xsltTransformContext) -> c_int {
682 if ctxt.is_null() || (*ctxt).style.is_null() {
683 return 0;
684 }
685 let style = (*ctxt).style;
686 let mut cur = (*style).extInfos as *mut ExtPrefixEntry;
687 while !cur.is_null() {
688 if !(*cur).uri.is_null() {
689 let key = CStr::from_ptr((*cur).uri as *const c_char)
690 .to_bytes()
691 .to_vec();
692 if let Some(module) = EXT_MODULES.read().get(&key).copied() {
693 if let Some(init) = module.init_func {
694 let data = init(ctxt, (*cur).uri as *const xmlChar);
695 if data.is_null() {
696 return -1;
697 }
698 // Record (URI -> data) in the context's extInfos list.
699 let entry = xmlMallocImpl(size_of::<ExtDataEntry>()) as *mut ExtDataEntry;
700 if entry.is_null() {
701 return -1;
702 }
703 let u = crate::abi::allocator::xmlMemStrdupImpl((*cur).uri as *const c_char)
704 as *mut c_char;
705 if u.is_null() {
706 xmlFreeImpl(entry as *mut c_void);
707 return -1;
708 }
709 ptr::write(
710 entry,
711 ExtDataEntry {
712 next: (*ctxt).extInfos as *mut ExtDataEntry,
713 uri: u,
714 data,
715 },
716 );
717 (*ctxt).extInfos = entry as *mut c_void;
718 }
719 }
720 }
721 cur = (*cur).next;
722 }
723 0
724}
725
726/// `xsltShutdownCtxtExts` (extensions.c): call the shutdown function of
727/// every initialised module on the context.
728///
729/// # UPSTREAM-PARITY
730///
731/// ```c
732/// void xsltShutdownCtxtExts(xsltTransformContextPtr ctxt);
733/// ```
734#[no_mangle]
735pub unsafe extern "C" fn xsltShutdownCtxtExts(ctxt: *mut _xsltTransformContext) {
736 if ctxt.is_null() {
737 return;
738 }
739 let mut cur = (*ctxt).extInfos as *mut ExtDataEntry;
740 while !cur.is_null() {
741 if !(*cur).uri.is_null() {
742 let key = CStr::from_ptr((*cur).uri as *const c_char)
743 .to_bytes()
744 .to_vec();
745 if let Some(module) = EXT_MODULES.read().get(&key).copied() {
746 if let Some(shutdown) = module.shutdown_func {
747 shutdown(ctxt, (*cur).uri as *const xmlChar, (*cur).data);
748 }
749 }
750 }
751 cur = (*cur).next;
752 }
753}
754
755/// `xsltFreeCtxtExts` (extensions.c): free the context's extension data.
756///
757/// # UPSTREAM-PARITY
758///
759/// ```c
760/// void xsltFreeCtxtExts(xsltTransformContextPtr ctxt);
761/// ```
762#[no_mangle]
763pub unsafe extern "C" fn xsltFreeCtxtExts(ctxt: *mut _xsltTransformContext) {
764 if ctxt.is_null() {
765 return;
766 }
767 let mut cur = (*ctxt).extInfos as *mut ExtDataEntry;
768 (*ctxt).extInfos = ptr::null_mut();
769 while !cur.is_null() {
770 let next = (*cur).next;
771 if !(*cur).uri.is_null() {
772 xmlFreeImpl((*cur).uri as *mut c_void);
773 }
774 xmlFreeImpl(cur as *mut c_void);
775 cur = next;
776 }
777}
778
779/// `xsltGetExtData` (extensions.c): the per-context data of a module.
780///
781/// # UPSTREAM-PARITY
782///
783/// ```c
784/// void *xsltGetExtData(xsltTransformContextPtr ctxt, const xmlChar *URI);
785/// ```
786#[no_mangle]
787pub unsafe extern "C" fn xsltGetExtData(
788 ctxt: *mut _xsltTransformContext,
789 URI: *const xmlChar,
790) -> *mut c_void {
791 if ctxt.is_null() || URI.is_null() {
792 return ptr::null_mut();
793 }
794 let mut cur = (*ctxt).extInfos as *mut ExtDataEntry;
795 while !cur.is_null() {
796 if !(*cur).uri.is_null()
797 && libc::strcmp((*cur).uri as *const c_char, URI as *const c_char) == 0
798 {
799 return (*cur).data;
800 }
801 cur = (*cur).next;
802 }
803 ptr::null_mut()
804}
805
806/// `xsltStyleGetExtData` (extensions.c): the per-stylesheet data of a
807/// module, initialising it on first use via the style init hook.
808///
809/// # UPSTREAM-PARITY
810///
811/// ```c
812/// void *xsltStyleGetExtData(xsltStylesheetPtr style, const xmlChar *URI);
813/// ```
814#[no_mangle]
815pub unsafe extern "C" fn xsltStyleGetExtData(
816 style: *mut _xsltStylesheet,
817 URI: *const xmlChar,
818) -> *mut c_void {
819 if style.is_null() || URI.is_null() {
820 return ptr::null_mut();
821 }
822 let mut cur = (*style).extInfos as *mut ExtDataEntry;
823 while !cur.is_null() {
824 if !(*cur).uri.is_null()
825 && libc::strcmp((*cur).uri as *const c_char, URI as *const c_char) == 0
826 {
827 return (*cur).data;
828 }
829 cur = (*cur).next;
830 }
831 let key = CStr::from_ptr(URI as *const c_char).to_bytes().to_vec();
832 let module = EXT_MODULES.read().get(&key).copied();
833 let data = match module {
834 Some(m) => match m.style_init_func {
835 Some(init) => init(style, URI),
836 None => ptr::null_mut(),
837 },
838 None => ptr::null_mut(),
839 };
840 let entry = xmlMallocImpl(size_of::<ExtDataEntry>()) as *mut ExtDataEntry;
841 if entry.is_null() {
842 return ptr::null_mut();
843 }
844 let u = crate::abi::allocator::xmlMemStrdupImpl(URI as *const c_char) as *mut c_char;
845 if u.is_null() {
846 xmlFreeImpl(entry as *mut c_void);
847 return ptr::null_mut();
848 }
849 ptr::write(
850 entry,
851 ExtDataEntry {
852 next: (*style).extInfos as *mut ExtDataEntry,
853 uri: u,
854 data,
855 },
856 );
857 (*style).extInfos = entry as *mut c_void;
858 data
859}
860
861/// `xsltStyleStylesheetLevelGetExtData` (extensions.c): the stylesheet-level
862/// extension data of a module — the same lookup/init-on-first-use logic as
863/// `xsltStyleGetExtData` (which upstream defines as a thin wrapper of this
864/// function).
865///
866/// # UPSTREAM-PARITY
867///
868/// ```c
869/// void *xsltStyleStylesheetLevelGetExtData(xsltStylesheetPtr style,
870/// const xmlChar *URI);
871/// ```
872#[no_mangle]
873pub unsafe extern "C" fn xsltStyleStylesheetLevelGetExtData(
874 style: *mut _xsltStylesheet,
875 URI: *const xmlChar,
876) -> *mut c_void {
877 unsafe { xsltStyleGetExtData(style, URI) }
878}
879
880/// `xsltGetExtInfo` (extensions.c): the stylesheet's extension-data list
881/// head (upstream returns the `style->extInfos` hash pointer).
882///
883/// # UPSTREAM-PARITY
884///
885/// ```c
886/// xmlHashTablePtr xsltGetExtInfo(xsltStylesheetPtr style, const xmlChar *URI);
887/// ```
888#[no_mangle]
889pub unsafe extern "C" fn xsltGetExtInfo(
890 style: *mut _xsltStylesheet,
891 _URI: *const xmlChar,
892) -> *mut c_void {
893 if style.is_null() {
894 return ptr::null_mut();
895 }
896 (*style).extInfos
897}
898
899/// `xsltRegisterAllExtras` (extra.c): register the EXSLT "extra" extension
900/// elements (exsl:document) into the global module registry.
901///
902/// # UPSTREAM-PARITY
903///
904/// ```c
905/// void xsltRegisterAllExtras(void);
906/// ```
907#[no_mangle]
908pub unsafe extern "C" fn xsltRegisterAllExtras() {
909 // exsl:document — handled natively by the transform engine
910 // (process_exsl_document); registering the module URI makes
911 // xsltCheckExtURI agree with upstream.
912 xsltRegisterExtModule(
913 c"http://exslt.org/common".as_ptr() as *const xmlChar,
914 None,
915 None,
916 );
917}
918
919/// `xsltRegisterExtras` (extra.c): register the EXSLT functions into the
920/// context's XPath context (upstream calls xsltRegisterAllFunctions).
921///
922/// # UPSTREAM-PARITY
923///
924/// ```c
925/// void xsltRegisterExtras(xsltTransformContextPtr ctxt);
926/// ```
927#[no_mangle]
928pub unsafe extern "C" fn xsltRegisterExtras(ctxt: *mut _xsltTransformContext) {
929 if ctxt.is_null() || (*ctxt).xpathCtxt.is_null() {
930 return;
931 }
932 crate::abi::exports_xslt_functions::xsltRegisterAllFunctions((*ctxt).xpathCtxt);
933}
934
935/// `xsltRegisterAllElement` (extra.c): register the EXSLT elements into the
936/// transform context.
937///
938/// # UPSTREAM-PARITY
939///
940/// ```c
941/// void xsltRegisterAllElement(xsltTransformContextPtr ctxt);
942/// ```
943#[no_mangle]
944pub const unsafe extern "C" fn xsltRegisterAllElement(ctxt: *mut _xsltTransformContext) {
945 if ctxt.is_null() {}
946 // The engine dispatches EXSLT elements natively (process_exsl_document
947 // and the exslt module registrations); nothing to add to the context's
948 // per-context registration lists.
949}
950
951/// `xsltRegisterTestModule` (extensions.c): register the libxslt self-test
952/// extension module (a no-op surface in the candidate).
953///
954/// # UPSTREAM-PARITY
955///
956/// ```c
957/// void xsltRegisterTestModule(void);
958/// ```
959#[no_mangle]
960pub unsafe extern "C" fn xsltRegisterTestModule() {
961 xsltRegisterExtModule(
962 c"http://xmlsoft.org/XSLT/".as_ptr() as *const xmlChar,
963 None,
964 None,
965 );
966}
967
968// ── Internal helper structures (not part of the ABI) ───────────────────────
969
970/// Stylesheet extension-prefix registration (upstream style->extInfos hash
971/// entries; the candidate uses a linked list).
972#[derive(Debug)]
973#[repr(C)]
974pub struct ExtPrefixEntry {
975 /// Next entry in the linked list.
976 pub next: *mut ExtPrefixEntry,
977 /// The extension prefix mapped to `uri`.
978 pub prefix: *mut c_char,
979 /// The namespace URI the prefix is registered for.
980 pub uri: *mut c_char,
981}
982
983/// Per-context / per-style extension data record (URI -> init data).
984#[derive(Debug)]
985#[repr(C)]
986pub struct ExtDataEntry {
987 /// Next entry in the linked list.
988 pub next: *mut ExtDataEntry,
989 /// The namespace URI of the extension module.
990 pub uri: *mut c_char,
991 /// Module-specific initialization data.
992 pub data: *mut c_void,
993}