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