Skip to main content

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}