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 (25/25) and HEADER-COMPILE (595/595)
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: *mut c_void,
236 transform: *mut c_void,
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 as usize,
245 transform: transform 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: *mut c_void,
265) -> c_int {
266 let Some(key) = ext_key(name, URI) else {
267 return -1;
268 };
269 EXT_FUNCTIONS.write().insert(key, function as usize);
270 0
271}
272
273/// `xsltRegisterExtModuleTopLevel` (extensions.c): register a top-level
274/// extension element handler.
275///
276/// # UPSTREAM-PARITY
277///
278/// ```c
279/// int xsltRegisterExtModuleTopLevel(const xmlChar *name, const xmlChar *URI,
280/// xsltTopLevelFunction function);
281/// ```
282#[no_mangle]
283pub unsafe extern "C" fn xsltRegisterExtModuleTopLevel(
284 name: *const xmlChar,
285 URI: *const xmlChar,
286 function: *mut c_void,
287) -> c_int {
288 let Some(key) = ext_key(name, URI) else {
289 return -1;
290 };
291 EXT_TOPLEVELS.write().insert(key, function as usize);
292 0
293}
294
295/// `xsltUnregisterExtModule` (extensions.c): unregister a module and all of
296/// its elements/functions/top-levels.
297///
298/// # UPSTREAM-PARITY
299///
300/// ```c
301/// int xsltUnregisterExtModule(const xmlChar *URI);
302/// ```
303#[no_mangle]
304pub unsafe extern "C" fn xsltUnregisterExtModule(URI: *const xmlChar) -> c_int {
305 if URI.is_null() {
306 return -1;
307 }
308 let uri_bytes = unsafe { CStr::from_ptr(URI as *const c_char).to_bytes().to_vec() };
309 let mut mods = EXT_MODULES.write();
310 if mods.remove(&uri_bytes).is_none() {
311 return -1;
312 }
313 drop(mods);
314 // Remove every element/function/top-level belonging to the URI.
315 let mut elems = EXT_ELEMENTS.write();
316 let mut funcs = EXT_FUNCTIONS.write();
317 let mut tops = EXT_TOPLEVELS.write();
318 let suffix: Vec<u8> = {
319 let mut s = vec![0];
320 s.extend_from_slice(&uri_bytes);
321 s
322 };
323 elems.retain(|k, _| !k.ends_with(&suffix));
324 funcs.retain(|k, _| !k.ends_with(&suffix));
325 tops.retain(|k, _| !k.ends_with(&suffix));
326 0
327}
328
329/// `xsltUnregisterExtModuleElement` (extensions.c).
330///
331/// # UPSTREAM-PARITY
332///
333/// ```c
334/// int xsltUnregisterExtModuleElement(const xmlChar *name, const xmlChar *URI);
335/// ```
336#[no_mangle]
337pub unsafe extern "C" fn xsltUnregisterExtModuleElement(
338 name: *const xmlChar,
339 URI: *const xmlChar,
340) -> c_int {
341 let Some(key) = ext_key(name, URI) else {
342 return -1;
343 };
344 if EXT_ELEMENTS.write().remove(&key).is_some() {
345 0
346 } else {
347 -1
348 }
349}
350
351/// `xsltUnregisterExtModuleFunction` (extensions.c).
352///
353/// # UPSTREAM-PARITY
354///
355/// ```c
356/// int xsltUnregisterExtModuleFunction(const xmlChar *name, const xmlChar *URI);
357/// ```
358#[no_mangle]
359pub unsafe extern "C" fn xsltUnregisterExtModuleFunction(
360 name: *const xmlChar,
361 URI: *const xmlChar,
362) -> c_int {
363 let Some(key) = ext_key(name, URI) else {
364 return -1;
365 };
366 if EXT_FUNCTIONS.write().remove(&key).is_some() {
367 0
368 } else {
369 -1
370 }
371}
372
373/// `xsltUnregisterExtModuleTopLevel` (extensions.c).
374///
375/// # UPSTREAM-PARITY
376///
377/// ```c
378/// int xsltUnregisterExtModuleTopLevel(const xmlChar *name, const xmlChar *URI);
379/// ```
380#[no_mangle]
381pub unsafe extern "C" fn xsltUnregisterExtModuleTopLevel(
382 name: *const xmlChar,
383 URI: *const xmlChar,
384) -> c_int {
385 let Some(key) = ext_key(name, URI) else {
386 return -1;
387 };
388 if EXT_TOPLEVELS.write().remove(&key).is_some() {
389 0
390 } else {
391 -1
392 }
393}
394
395/// `xsltRegisterExtPrefix` (extensions.c): register a prefix→URI mapping on
396/// the stylesheet so `xsltCheckExtPrefix` recognises it as an extension.
397///
398/// # UPSTREAM-PARITY
399///
400/// ```c
401/// int xsltRegisterExtPrefix(xsltStylesheetPtr style,
402/// const xmlChar *prefix, const xmlChar *URI);
403/// ```
404#[no_mangle]
405pub unsafe extern "C" fn xsltRegisterExtPrefix(
406 style: *mut _xsltStylesheet,
407 prefix: *const xmlChar,
408 URI: *const xmlChar,
409) -> c_int {
410 if style.is_null() || prefix.is_null() || URI.is_null() {
411 return -1;
412 }
413 // The candidate carries registered extension prefixes as a growable
414 // linked list in the stylesheet (upstream uses style->extInfos hash).
415 let mut cur = (*style).extInfos as *mut ExtPrefixEntry;
416 while !cur.is_null() {
417 if !(*cur).prefix.is_null()
418 && libc::strcmp((*cur).prefix as *const c_char, prefix as *const c_char) == 0
419 {
420 // Re-registration with a different URI updates the mapping.
421 let new_uri =
422 crate::abi::allocator::xmlMemStrdupImpl(URI as *const c_char) as *mut c_char;
423 if new_uri.is_null() {
424 return -1;
425 }
426 xmlFreeImpl((*cur).uri as *mut c_void);
427 (*cur).uri = new_uri;
428 return 0;
429 }
430 cur = (*cur).next;
431 }
432 let entry = xmlMallocImpl(size_of::<ExtPrefixEntry>()) as *mut ExtPrefixEntry;
433 if entry.is_null() {
434 return -1;
435 }
436 let p = crate::abi::allocator::xmlMemStrdupImpl(prefix as *const c_char) as *mut c_char;
437 let u = crate::abi::allocator::xmlMemStrdupImpl(URI as *const c_char) as *mut c_char;
438 if p.is_null() || u.is_null() {
439 if !p.is_null() {
440 xmlFreeImpl(p as *mut c_void);
441 }
442 if !u.is_null() {
443 xmlFreeImpl(u as *mut c_void);
444 }
445 xmlFreeImpl(entry as *mut c_void);
446 return -1;
447 }
448 ptr::write(
449 entry,
450 ExtPrefixEntry {
451 next: (*style).extInfos as *mut ExtPrefixEntry,
452 prefix: p,
453 uri: u,
454 },
455 );
456 (*style).extInfos = entry as *mut c_void;
457 0
458}
459
460/// `xsltCheckExtPrefix` (extensions.c): 1 if `prefix` is registered as an
461/// extension prefix on the stylesheet (or is a literal-result element
462/// prefix), 0 otherwise.
463///
464/// # UPSTREAM-PARITY
465///
466/// ```c
467/// int xsltCheckExtPrefix(xsltStylesheetPtr style, const xmlChar *prefix);
468/// ```
469#[no_mangle]
470pub unsafe extern "C" fn xsltCheckExtPrefix(
471 style: *mut _xsltStylesheet,
472 prefix: *const xmlChar,
473) -> c_int {
474 if style.is_null() || prefix.is_null() {
475 return 0;
476 }
477 let mut cur = (*style).extInfos as *mut ExtPrefixEntry;
478 while !cur.is_null() {
479 if !(*cur).prefix.is_null()
480 && libc::strcmp((*cur).prefix as *const c_char, prefix as *const c_char) == 0
481 {
482 return 1;
483 }
484 cur = (*cur).next;
485 }
486 0
487}
488
489/// `xsltCheckExtURI` (extensions.c): 1 if `URI` is registered as an
490/// extension namespace on the stylesheet, 0 otherwise.
491///
492/// # UPSTREAM-PARITY
493///
494/// ```c
495/// int xsltCheckExtURI(xsltStylesheetPtr style, const xmlChar *URI);
496/// ```
497#[no_mangle]
498pub unsafe extern "C" fn xsltCheckExtURI(
499 style: *mut _xsltStylesheet,
500 URI: *const xmlChar,
501) -> c_int {
502 if style.is_null() || URI.is_null() {
503 return 0;
504 }
505 let mut cur = (*style).extInfos as *mut ExtPrefixEntry;
506 while !cur.is_null() {
507 if !(*cur).uri.is_null()
508 && libc::strcmp((*cur).uri as *const c_char, URI as *const c_char) == 0
509 {
510 return 1;
511 }
512 cur = (*cur).next;
513 }
514 0
515}
516
517/// `xsltExtElementLookup` (extensions.c): resolve an extension element's
518/// transform function, consulting the per-context registrations first then
519/// the global module registry.
520///
521/// # UPSTREAM-PARITY
522///
523/// ```c
524/// xsltTransformFunction xsltExtElementLookup(xsltTransformContextPtr ctxt,
525/// const xmlChar *name,
526/// const xmlChar *URI);
527/// ```
528#[no_mangle]
529pub unsafe extern "C" fn xsltExtElementLookup(
530 ctxt: *mut _xsltTransformContext,
531 name: *const xmlChar,
532 URI: *const xmlChar,
533) -> *mut c_void {
534 if ctxt.is_null() || name.is_null() || URI.is_null() {
535 return ptr::null_mut();
536 }
537 // Per-context registrations (xsltRegisterExtElement).
538 let found = crate::xslt::extensions::xsltFindExtElement(ctxt, name, URI);
539 if !found.is_null() {
540 return found;
541 }
542 let Some(key) = ext_key(name, URI) else {
543 return ptr::null_mut();
544 };
545 EXT_ELEMENTS
546 .read()
547 .get(&key)
548 .map(|e| e.transform as *mut c_void)
549 .unwrap_or(ptr::null_mut())
550}
551
552/// `xsltExtModuleElementLookup` (extensions.c): global element lookup.
553///
554/// # UPSTREAM-PARITY
555///
556/// ```c
557/// xsltTransformFunction xsltExtModuleElementLookup(const xmlChar *name,
558/// const xmlChar *URI);
559/// ```
560#[no_mangle]
561pub unsafe extern "C" fn xsltExtModuleElementLookup(
562 name: *const xmlChar,
563 URI: *const xmlChar,
564) -> *mut c_void {
565 let Some(key) = ext_key(name, URI) else {
566 return ptr::null_mut();
567 };
568 EXT_ELEMENTS
569 .read()
570 .get(&key)
571 .map(|e| e.transform as *mut c_void)
572 .unwrap_or(ptr::null_mut())
573}
574
575/// `xsltExtModuleFunctionLookup` (extensions.c): global function lookup.
576///
577/// # UPSTREAM-PARITY
578///
579/// ```c
580/// xmlXPathFunction xsltExtModuleFunctionLookup(const xmlChar *name,
581/// const xmlChar *URI);
582/// ```
583#[no_mangle]
584pub unsafe extern "C" fn xsltExtModuleFunctionLookup(
585 name: *const xmlChar,
586 URI: *const xmlChar,
587) -> *mut c_void {
588 let Some(key) = ext_key(name, URI) else {
589 return ptr::null_mut();
590 };
591 EXT_FUNCTIONS
592 .read()
593 .get(&key)
594 .copied()
595 .map(|p| p as *mut c_void)
596 .unwrap_or(ptr::null_mut())
597}
598
599/// `xsltExtModuleElementPreComputeLookup` (extensions.c).
600///
601/// # UPSTREAM-PARITY
602///
603/// ```c
604/// xsltPreComputeFunction xsltExtModuleElementPreComputeLookup(
605/// const xmlChar *name, const xmlChar *URI);
606/// ```
607#[no_mangle]
608pub unsafe extern "C" fn xsltExtModuleElementPreComputeLookup(
609 name: *const xmlChar,
610 URI: *const xmlChar,
611) -> *mut c_void {
612 let Some(key) = ext_key(name, URI) else {
613 return ptr::null_mut();
614 };
615 EXT_ELEMENTS
616 .read()
617 .get(&key)
618 .map(|e| e.precomp as *mut c_void)
619 .unwrap_or(ptr::null_mut())
620}
621
622/// `xsltExtModuleTopLevelLookup` (extensions.c).
623///
624/// # UPSTREAM-PARITY
625///
626/// ```c
627/// xsltTopLevelFunction xsltExtModuleTopLevelLookup(const xmlChar *name,
628/// const xmlChar *URI);
629/// ```
630#[no_mangle]
631pub unsafe extern "C" fn xsltExtModuleTopLevelLookup(
632 name: *const xmlChar,
633 URI: *const xmlChar,
634) -> *mut c_void {
635 let Some(key) = ext_key(name, URI) else {
636 return ptr::null_mut();
637 };
638 EXT_TOPLEVELS
639 .read()
640 .get(&key)
641 .copied()
642 .map(|p| p as *mut c_void)
643 .unwrap_or(ptr::null_mut())
644}
645
646/// `xsltInitCtxtExts` (extensions.c): call the init function of every module
647/// whose URI the stylesheet uses (registered extension prefixes).
648///
649/// # UPSTREAM-PARITY
650///
651/// ```c
652/// int xsltInitCtxtExts(xsltTransformContextPtr ctxt);
653/// ```
654///
655/// Returns 0 on success, -1 on error.
656#[no_mangle]
657pub unsafe extern "C" fn xsltInitCtxtExts(ctxt: *mut _xsltTransformContext) -> c_int {
658 if ctxt.is_null() || (*ctxt).style.is_null() {
659 return 0;
660 }
661 let style = (*ctxt).style;
662 let mut cur = (*style).extInfos as *mut ExtPrefixEntry;
663 while !cur.is_null() {
664 if !(*cur).uri.is_null() {
665 let key = CStr::from_ptr((*cur).uri as *const c_char)
666 .to_bytes()
667 .to_vec();
668 if let Some(module) = EXT_MODULES.read().get(&key).copied() {
669 if let Some(init) = module.init_func {
670 let data = init(ctxt, (*cur).uri as *const xmlChar);
671 if data.is_null() {
672 return -1;
673 }
674 // Record (URI -> data) in the context's extInfos list.
675 let entry = xmlMallocImpl(size_of::<ExtDataEntry>()) as *mut ExtDataEntry;
676 if entry.is_null() {
677 return -1;
678 }
679 let u = crate::abi::allocator::xmlMemStrdupImpl((*cur).uri as *const c_char)
680 as *mut c_char;
681 if u.is_null() {
682 xmlFreeImpl(entry as *mut c_void);
683 return -1;
684 }
685 ptr::write(
686 entry,
687 ExtDataEntry {
688 next: (*ctxt).extInfos as *mut ExtDataEntry,
689 uri: u,
690 data,
691 },
692 );
693 (*ctxt).extInfos = entry as *mut c_void;
694 }
695 }
696 }
697 cur = (*cur).next;
698 }
699 0
700}
701
702/// `xsltShutdownCtxtExts` (extensions.c): call the shutdown function of
703/// every initialised module on the context.
704///
705/// # UPSTREAM-PARITY
706///
707/// ```c
708/// void xsltShutdownCtxtExts(xsltTransformContextPtr ctxt);
709/// ```
710#[no_mangle]
711pub unsafe extern "C" fn xsltShutdownCtxtExts(ctxt: *mut _xsltTransformContext) {
712 if ctxt.is_null() {
713 return;
714 }
715 let mut cur = (*ctxt).extInfos as *mut ExtDataEntry;
716 while !cur.is_null() {
717 if !(*cur).uri.is_null() {
718 let key = CStr::from_ptr((*cur).uri as *const c_char)
719 .to_bytes()
720 .to_vec();
721 if let Some(module) = EXT_MODULES.read().get(&key).copied() {
722 if let Some(shutdown) = module.shutdown_func {
723 shutdown(ctxt, (*cur).uri as *const xmlChar, (*cur).data);
724 }
725 }
726 }
727 cur = (*cur).next;
728 }
729}
730
731/// `xsltFreeCtxtExts` (extensions.c): free the context's extension data.
732///
733/// # UPSTREAM-PARITY
734///
735/// ```c
736/// void xsltFreeCtxtExts(xsltTransformContextPtr ctxt);
737/// ```
738#[no_mangle]
739pub unsafe extern "C" fn xsltFreeCtxtExts(ctxt: *mut _xsltTransformContext) {
740 if ctxt.is_null() {
741 return;
742 }
743 let mut cur = (*ctxt).extInfos as *mut ExtDataEntry;
744 (*ctxt).extInfos = ptr::null_mut();
745 while !cur.is_null() {
746 let next = (*cur).next;
747 if !(*cur).uri.is_null() {
748 xmlFreeImpl((*cur).uri as *mut c_void);
749 }
750 xmlFreeImpl(cur as *mut c_void);
751 cur = next;
752 }
753}
754
755/// `xsltGetExtData` (extensions.c): the per-context data of a module.
756///
757/// # UPSTREAM-PARITY
758///
759/// ```c
760/// void *xsltGetExtData(xsltTransformContextPtr ctxt, const xmlChar *URI);
761/// ```
762#[no_mangle]
763pub unsafe extern "C" fn xsltGetExtData(
764 ctxt: *mut _xsltTransformContext,
765 URI: *const xmlChar,
766) -> *mut c_void {
767 if ctxt.is_null() || URI.is_null() {
768 return ptr::null_mut();
769 }
770 let mut cur = (*ctxt).extInfos as *mut ExtDataEntry;
771 while !cur.is_null() {
772 if !(*cur).uri.is_null()
773 && libc::strcmp((*cur).uri as *const c_char, URI as *const c_char) == 0
774 {
775 return (*cur).data;
776 }
777 cur = (*cur).next;
778 }
779 ptr::null_mut()
780}
781
782/// `xsltStyleGetExtData` (extensions.c): the per-stylesheet data of a
783/// module, initialising it on first use via the style init hook.
784///
785/// # UPSTREAM-PARITY
786///
787/// ```c
788/// void *xsltStyleGetExtData(xsltStylesheetPtr style, const xmlChar *URI);
789/// ```
790#[no_mangle]
791pub unsafe extern "C" fn xsltStyleGetExtData(
792 style: *mut _xsltStylesheet,
793 URI: *const xmlChar,
794) -> *mut c_void {
795 if style.is_null() || URI.is_null() {
796 return ptr::null_mut();
797 }
798 let mut cur = (*style).extInfos as *mut ExtDataEntry;
799 while !cur.is_null() {
800 if !(*cur).uri.is_null()
801 && libc::strcmp((*cur).uri as *const c_char, URI as *const c_char) == 0
802 {
803 return (*cur).data;
804 }
805 cur = (*cur).next;
806 }
807 let key = CStr::from_ptr(URI as *const c_char).to_bytes().to_vec();
808 let module = EXT_MODULES.read().get(&key).copied();
809 let data = match module {
810 Some(m) => match m.style_init_func {
811 Some(init) => init(style, URI),
812 None => ptr::null_mut(),
813 },
814 None => ptr::null_mut(),
815 };
816 let entry = xmlMallocImpl(size_of::<ExtDataEntry>()) as *mut ExtDataEntry;
817 if entry.is_null() {
818 return ptr::null_mut();
819 }
820 let u = crate::abi::allocator::xmlMemStrdupImpl(URI as *const c_char) as *mut c_char;
821 if u.is_null() {
822 xmlFreeImpl(entry as *mut c_void);
823 return ptr::null_mut();
824 }
825 ptr::write(
826 entry,
827 ExtDataEntry {
828 next: (*style).extInfos as *mut ExtDataEntry,
829 uri: u,
830 data,
831 },
832 );
833 (*style).extInfos = entry as *mut c_void;
834 data
835}
836
837/// `xsltStyleStylesheetLevelGetExtData` (extensions.c): the stylesheet-level
838/// extension data of a module — the same lookup/init-on-first-use logic as
839/// `xsltStyleGetExtData` (which upstream defines as a thin wrapper of this
840/// function).
841///
842/// # UPSTREAM-PARITY
843///
844/// ```c
845/// void *xsltStyleStylesheetLevelGetExtData(xsltStylesheetPtr style,
846/// const xmlChar *URI);
847/// ```
848#[no_mangle]
849pub unsafe extern "C" fn xsltStyleStylesheetLevelGetExtData(
850 style: *mut _xsltStylesheet,
851 URI: *const xmlChar,
852) -> *mut c_void {
853 unsafe { xsltStyleGetExtData(style, URI) }
854}
855
856/// `xsltGetExtInfo` (extensions.c): the stylesheet's extension-data list
857/// head (upstream returns the `style->extInfos` hash pointer).
858///
859/// # UPSTREAM-PARITY
860///
861/// ```c
862/// xmlHashTablePtr xsltGetExtInfo(xsltStylesheetPtr style, const xmlChar *URI);
863/// ```
864#[no_mangle]
865pub unsafe extern "C" fn xsltGetExtInfo(
866 style: *mut _xsltStylesheet,
867 _URI: *const xmlChar,
868) -> *mut c_void {
869 if style.is_null() {
870 return ptr::null_mut();
871 }
872 (*style).extInfos
873}
874
875/// `xsltRegisterAllExtras` (extra.c): register the EXSLT "extra" extension
876/// elements (exsl:document) into the global module registry.
877///
878/// # UPSTREAM-PARITY
879///
880/// ```c
881/// void xsltRegisterAllExtras(void);
882/// ```
883#[no_mangle]
884pub unsafe extern "C" fn xsltRegisterAllExtras() {
885 // exsl:document — handled natively by the transform engine
886 // (process_exsl_document); registering the module URI makes
887 // xsltCheckExtURI agree with upstream.
888 xsltRegisterExtModule(
889 c"http://exslt.org/common".as_ptr() as *const xmlChar,
890 None,
891 None,
892 );
893}
894
895/// `xsltRegisterExtras` (extra.c): register the EXSLT functions into the
896/// context's XPath context (upstream calls xsltRegisterAllFunctions).
897///
898/// # UPSTREAM-PARITY
899///
900/// ```c
901/// void xsltRegisterExtras(xsltTransformContextPtr ctxt);
902/// ```
903#[no_mangle]
904pub unsafe extern "C" fn xsltRegisterExtras(ctxt: *mut _xsltTransformContext) {
905 if ctxt.is_null() || (*ctxt).xpathCtxt.is_null() {
906 return;
907 }
908 crate::abi::exports_xslt_functions::xsltRegisterAllFunctions((*ctxt).xpathCtxt);
909}
910
911/// `xsltRegisterAllElement` (extra.c): register the EXSLT elements into the
912/// transform context.
913///
914/// # UPSTREAM-PARITY
915///
916/// ```c
917/// void xsltRegisterAllElement(xsltTransformContextPtr ctxt);
918/// ```
919#[no_mangle]
920pub const unsafe extern "C" fn xsltRegisterAllElement(ctxt: *mut _xsltTransformContext) {
921 if ctxt.is_null() {}
922 // The engine dispatches EXSLT elements natively (process_exsl_document
923 // and the exslt module registrations); nothing to add to the context's
924 // per-context registration lists.
925}
926
927/// `xsltRegisterTestModule` (extensions.c): register the libxslt self-test
928/// extension module (a no-op surface in the candidate).
929///
930/// # UPSTREAM-PARITY
931///
932/// ```c
933/// void xsltRegisterTestModule(void);
934/// ```
935#[no_mangle]
936pub unsafe extern "C" fn xsltRegisterTestModule() {
937 xsltRegisterExtModule(
938 c"http://xmlsoft.org/XSLT/".as_ptr() as *const xmlChar,
939 None,
940 None,
941 );
942}
943
944// ── Internal helper structures (not part of the ABI) ───────────────────────
945
946/// Stylesheet extension-prefix registration (upstream style->extInfos hash
947/// entries; the candidate uses a linked list).
948#[derive(Debug)]
949#[repr(C)]
950pub struct ExtPrefixEntry {
951 /// Next entry in the linked list.
952 pub next: *mut ExtPrefixEntry,
953 /// The extension prefix mapped to `uri`.
954 pub prefix: *mut c_char,
955 /// The namespace URI the prefix is registered for.
956 pub uri: *mut c_char,
957}
958
959/// Per-context / per-style extension data record (URI -> init data).
960#[derive(Debug)]
961#[repr(C)]
962pub struct ExtDataEntry {
963 /// Next entry in the linked list.
964 pub next: *mut ExtDataEntry,
965 /// The namespace URI of the extension module.
966 pub uri: *mut c_char,
967 /// Module-specific initialization data.
968 pub data: *mut c_void,
969}