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