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