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 =
368                crate::abi::allocator::xmlMemStrdupImpl(URI as *const c_char) as *mut c_char;
369            if new_uri.is_null() {
370                return -1;
371            }
372            xmlFreeImpl((*cur).uri as *mut c_void);
373            (*cur).uri = new_uri;
374            return 0;
375        }
376        cur = (*cur).next;
377    }
378    let entry = xmlMallocImpl(size_of::<ExtPrefixEntry>()) as *mut ExtPrefixEntry;
379    if entry.is_null() {
380        return -1;
381    }
382    let p = crate::abi::allocator::xmlMemStrdupImpl(prefix as *const c_char) as *mut c_char;
383    let u = crate::abi::allocator::xmlMemStrdupImpl(URI as *const c_char) as *mut c_char;
384    if p.is_null() || u.is_null() {
385        if !p.is_null() {
386            xmlFreeImpl(p as *mut c_void);
387        }
388        if !u.is_null() {
389            xmlFreeImpl(u as *mut c_void);
390        }
391        xmlFreeImpl(entry as *mut c_void);
392        return -1;
393    }
394    ptr::write(
395        entry,
396        ExtPrefixEntry {
397            next: (*style).extInfos as *mut ExtPrefixEntry,
398            prefix: p,
399            uri: u,
400        },
401    );
402    (*style).extInfos = entry as *mut c_void;
403    0
404}
405
406/// `xsltCheckExtPrefix` (extensions.c): 1 if `prefix` is registered as an
407/// extension prefix on the stylesheet (or is a literal-result element
408/// prefix), 0 otherwise.
409///
410/// # UPSTREAM-PARITY
411///
412/// ```c
413/// int xsltCheckExtPrefix(xsltStylesheetPtr style, const xmlChar *prefix);
414/// ```
415#[no_mangle]
416pub unsafe extern "C" fn xsltCheckExtPrefix(
417    style: *mut _xsltStylesheet,
418    prefix: *const xmlChar,
419) -> c_int {
420    if style.is_null() || prefix.is_null() {
421        return 0;
422    }
423    let mut cur = (*style).extInfos as *mut ExtPrefixEntry;
424    while !cur.is_null() {
425        if !(*cur).prefix.is_null()
426            && libc::strcmp((*cur).prefix as *const c_char, prefix as *const c_char) == 0
427        {
428            return 1;
429        }
430        cur = (*cur).next;
431    }
432    0
433}
434
435/// `xsltCheckExtURI` (extensions.c): 1 if `URI` is registered as an
436/// extension namespace on the stylesheet, 0 otherwise.
437///
438/// # UPSTREAM-PARITY
439///
440/// ```c
441/// int xsltCheckExtURI(xsltStylesheetPtr style, const xmlChar *URI);
442/// ```
443#[no_mangle]
444pub unsafe extern "C" fn xsltCheckExtURI(
445    style: *mut _xsltStylesheet,
446    URI: *const xmlChar,
447) -> c_int {
448    if style.is_null() || URI.is_null() {
449        return 0;
450    }
451    let mut cur = (*style).extInfos as *mut ExtPrefixEntry;
452    while !cur.is_null() {
453        if !(*cur).uri.is_null()
454            && libc::strcmp((*cur).uri as *const c_char, URI as *const c_char) == 0
455        {
456            return 1;
457        }
458        cur = (*cur).next;
459    }
460    0
461}
462
463/// `xsltExtElementLookup` (extensions.c): resolve an extension element's
464/// transform function, consulting the per-context registrations first then
465/// the global module registry.
466///
467/// # UPSTREAM-PARITY
468///
469/// ```c
470/// xsltTransformFunction xsltExtElementLookup(xsltTransformContextPtr ctxt,
471///                                            const xmlChar *name,
472///                                            const xmlChar *URI);
473/// ```
474#[no_mangle]
475pub unsafe extern "C" fn xsltExtElementLookup(
476    ctxt: *mut _xsltTransformContext,
477    name: *const xmlChar,
478    URI: *const xmlChar,
479) -> *mut c_void {
480    if ctxt.is_null() || name.is_null() || URI.is_null() {
481        return ptr::null_mut();
482    }
483    // Per-context registrations (xsltRegisterExtElement).
484    let found = crate::xslt::extensions::xsltFindExtElement(ctxt, name, URI);
485    if !found.is_null() {
486        return found;
487    }
488    let Some(key) = ext_key(name, URI) else {
489        return ptr::null_mut();
490    };
491    EXT_ELEMENTS
492        .read()
493        .get(&key)
494        .map(|e| e.transform as *mut c_void)
495        .unwrap_or(ptr::null_mut())
496}
497
498/// `xsltExtModuleElementLookup` (extensions.c): global element lookup.
499///
500/// # UPSTREAM-PARITY
501///
502/// ```c
503/// xsltTransformFunction xsltExtModuleElementLookup(const xmlChar *name,
504///                                                  const xmlChar *URI);
505/// ```
506#[no_mangle]
507pub unsafe extern "C" fn xsltExtModuleElementLookup(
508    name: *const xmlChar,
509    URI: *const xmlChar,
510) -> *mut c_void {
511    let Some(key) = ext_key(name, URI) else {
512        return ptr::null_mut();
513    };
514    EXT_ELEMENTS
515        .read()
516        .get(&key)
517        .map(|e| e.transform as *mut c_void)
518        .unwrap_or(ptr::null_mut())
519}
520
521/// `xsltExtModuleFunctionLookup` (extensions.c): global function lookup.
522///
523/// # UPSTREAM-PARITY
524///
525/// ```c
526/// xmlXPathFunction xsltExtModuleFunctionLookup(const xmlChar *name,
527///                                              const xmlChar *URI);
528/// ```
529#[no_mangle]
530pub unsafe extern "C" fn xsltExtModuleFunctionLookup(
531    name: *const xmlChar,
532    URI: *const xmlChar,
533) -> *mut c_void {
534    let Some(key) = ext_key(name, URI) else {
535        return ptr::null_mut();
536    };
537    EXT_FUNCTIONS
538        .read()
539        .get(&key)
540        .copied()
541        .map(|p| p as *mut c_void)
542        .unwrap_or(ptr::null_mut())
543}
544
545/// `xsltExtModuleElementPreComputeLookup` (extensions.c).
546///
547/// # UPSTREAM-PARITY
548///
549/// ```c
550/// xsltPreComputeFunction xsltExtModuleElementPreComputeLookup(
551///     const xmlChar *name, const xmlChar *URI);
552/// ```
553#[no_mangle]
554pub unsafe extern "C" fn xsltExtModuleElementPreComputeLookup(
555    name: *const xmlChar,
556    URI: *const xmlChar,
557) -> *mut c_void {
558    let Some(key) = ext_key(name, URI) else {
559        return ptr::null_mut();
560    };
561    EXT_ELEMENTS
562        .read()
563        .get(&key)
564        .map(|e| e.precomp as *mut c_void)
565        .unwrap_or(ptr::null_mut())
566}
567
568/// `xsltExtModuleTopLevelLookup` (extensions.c).
569///
570/// # UPSTREAM-PARITY
571///
572/// ```c
573/// xsltTopLevelFunction xsltExtModuleTopLevelLookup(const xmlChar *name,
574///                                                  const xmlChar *URI);
575/// ```
576#[no_mangle]
577pub unsafe extern "C" fn xsltExtModuleTopLevelLookup(
578    name: *const xmlChar,
579    URI: *const xmlChar,
580) -> *mut c_void {
581    let Some(key) = ext_key(name, URI) else {
582        return ptr::null_mut();
583    };
584    EXT_TOPLEVELS
585        .read()
586        .get(&key)
587        .copied()
588        .map(|p| p as *mut c_void)
589        .unwrap_or(ptr::null_mut())
590}
591
592/// `xsltInitCtxtExts` (extensions.c): call the init function of every module
593/// whose URI the stylesheet uses (registered extension prefixes).
594///
595/// # UPSTREAM-PARITY
596///
597/// ```c
598/// int xsltInitCtxtExts(xsltTransformContextPtr ctxt);
599/// ```
600///
601/// Returns 0 on success, -1 on error.
602#[no_mangle]
603pub unsafe extern "C" fn xsltInitCtxtExts(ctxt: *mut _xsltTransformContext) -> c_int {
604    if ctxt.is_null() || (*ctxt).style.is_null() {
605        return 0;
606    }
607    let style = (*ctxt).style;
608    let mut cur = (*style).extInfos as *mut ExtPrefixEntry;
609    while !cur.is_null() {
610        if !(*cur).uri.is_null() {
611            let key = CStr::from_ptr((*cur).uri as *const c_char)
612                .to_bytes()
613                .to_vec();
614            if let Some(module) = EXT_MODULES.read().get(&key).copied() {
615                if let Some(init) = module.init_func {
616                    let data = init(ctxt, (*cur).uri as *const xmlChar);
617                    if data.is_null() {
618                        return -1;
619                    }
620                    // Record (URI -> data) in the context's extInfos list.
621                    let entry = xmlMallocImpl(size_of::<ExtDataEntry>()) as *mut ExtDataEntry;
622                    if entry.is_null() {
623                        return -1;
624                    }
625                    let u = crate::abi::allocator::xmlMemStrdupImpl((*cur).uri as *const c_char)
626                        as *mut c_char;
627                    if u.is_null() {
628                        xmlFreeImpl(entry as *mut c_void);
629                        return -1;
630                    }
631                    ptr::write(
632                        entry,
633                        ExtDataEntry {
634                            next: (*ctxt).extInfos as *mut ExtDataEntry,
635                            uri: u,
636                            data,
637                        },
638                    );
639                    (*ctxt).extInfos = entry as *mut c_void;
640                }
641            }
642        }
643        cur = (*cur).next;
644    }
645    0
646}
647
648/// `xsltShutdownCtxtExts` (extensions.c): call the shutdown function of
649/// every initialised module on the context.
650///
651/// # UPSTREAM-PARITY
652///
653/// ```c
654/// void xsltShutdownCtxtExts(xsltTransformContextPtr ctxt);
655/// ```
656#[no_mangle]
657pub unsafe extern "C" fn xsltShutdownCtxtExts(ctxt: *mut _xsltTransformContext) {
658    if ctxt.is_null() {
659        return;
660    }
661    let mut cur = (*ctxt).extInfos as *mut ExtDataEntry;
662    while !cur.is_null() {
663        if !(*cur).uri.is_null() {
664            let key = CStr::from_ptr((*cur).uri as *const c_char)
665                .to_bytes()
666                .to_vec();
667            if let Some(module) = EXT_MODULES.read().get(&key).copied() {
668                if let Some(shutdown) = module.shutdown_func {
669                    shutdown(ctxt, (*cur).uri as *const xmlChar, (*cur).data);
670                }
671            }
672        }
673        cur = (*cur).next;
674    }
675}
676
677/// `xsltFreeCtxtExts` (extensions.c): free the context's extension data.
678///
679/// # UPSTREAM-PARITY
680///
681/// ```c
682/// void xsltFreeCtxtExts(xsltTransformContextPtr ctxt);
683/// ```
684#[no_mangle]
685pub unsafe extern "C" fn xsltFreeCtxtExts(ctxt: *mut _xsltTransformContext) {
686    if ctxt.is_null() {
687        return;
688    }
689    let mut cur = (*ctxt).extInfos as *mut ExtDataEntry;
690    (*ctxt).extInfos = ptr::null_mut();
691    while !cur.is_null() {
692        let next = (*cur).next;
693        if !(*cur).uri.is_null() {
694            xmlFreeImpl((*cur).uri as *mut c_void);
695        }
696        xmlFreeImpl(cur as *mut c_void);
697        cur = next;
698    }
699}
700
701/// `xsltGetExtData` (extensions.c): the per-context data of a module.
702///
703/// # UPSTREAM-PARITY
704///
705/// ```c
706/// void *xsltGetExtData(xsltTransformContextPtr ctxt, const xmlChar *URI);
707/// ```
708#[no_mangle]
709pub unsafe extern "C" fn xsltGetExtData(
710    ctxt: *mut _xsltTransformContext,
711    URI: *const xmlChar,
712) -> *mut c_void {
713    if ctxt.is_null() || URI.is_null() {
714        return ptr::null_mut();
715    }
716    let mut cur = (*ctxt).extInfos as *mut ExtDataEntry;
717    while !cur.is_null() {
718        if !(*cur).uri.is_null()
719            && libc::strcmp((*cur).uri as *const c_char, URI as *const c_char) == 0
720        {
721            return (*cur).data;
722        }
723        cur = (*cur).next;
724    }
725    ptr::null_mut()
726}
727
728/// `xsltStyleGetExtData` (extensions.c): the per-stylesheet data of a
729/// module, initialising it on first use via the style init hook.
730///
731/// # UPSTREAM-PARITY
732///
733/// ```c
734/// void *xsltStyleGetExtData(xsltStylesheetPtr style, const xmlChar *URI);
735/// ```
736#[no_mangle]
737pub unsafe extern "C" fn xsltStyleGetExtData(
738    style: *mut _xsltStylesheet,
739    URI: *const xmlChar,
740) -> *mut c_void {
741    if style.is_null() || URI.is_null() {
742        return ptr::null_mut();
743    }
744    let mut cur = (*style).extInfos as *mut ExtDataEntry;
745    while !cur.is_null() {
746        if !(*cur).uri.is_null()
747            && libc::strcmp((*cur).uri as *const c_char, URI as *const c_char) == 0
748        {
749            return (*cur).data;
750        }
751        cur = (*cur).next;
752    }
753    let key = CStr::from_ptr(URI as *const c_char).to_bytes().to_vec();
754    let module = EXT_MODULES.read().get(&key).copied();
755    let data = match module {
756        Some(m) => match m.style_init_func {
757            Some(init) => init(style, URI),
758            None => ptr::null_mut(),
759        },
760        None => ptr::null_mut(),
761    };
762    let entry = xmlMallocImpl(size_of::<ExtDataEntry>()) as *mut ExtDataEntry;
763    if entry.is_null() {
764        return ptr::null_mut();
765    }
766    let u = crate::abi::allocator::xmlMemStrdupImpl(URI as *const c_char) as *mut c_char;
767    if u.is_null() {
768        xmlFreeImpl(entry as *mut c_void);
769        return ptr::null_mut();
770    }
771    ptr::write(
772        entry,
773        ExtDataEntry {
774            next: (*style).extInfos as *mut ExtDataEntry,
775            uri: u,
776            data,
777        },
778    );
779    (*style).extInfos = entry as *mut c_void;
780    data
781}
782
783/// `xsltStyleStylesheetLevelGetExtData` (extensions.c): the stylesheet-level
784/// extension data of a module — the same lookup/init-on-first-use logic as
785/// `xsltStyleGetExtData` (which upstream defines as a thin wrapper of this
786/// function).
787///
788/// # UPSTREAM-PARITY
789///
790/// ```c
791/// void *xsltStyleStylesheetLevelGetExtData(xsltStylesheetPtr style,
792///                                          const xmlChar *URI);
793/// ```
794#[no_mangle]
795pub unsafe extern "C" fn xsltStyleStylesheetLevelGetExtData(
796    style: *mut _xsltStylesheet,
797    URI: *const xmlChar,
798) -> *mut c_void {
799    unsafe { xsltStyleGetExtData(style, URI) }
800}
801
802/// `xsltGetExtInfo` (extensions.c): the stylesheet's extension-data list
803/// head (upstream returns the `style->extInfos` hash pointer).
804///
805/// # UPSTREAM-PARITY
806///
807/// ```c
808/// xmlHashTablePtr xsltGetExtInfo(xsltStylesheetPtr style, const xmlChar *URI);
809/// ```
810#[no_mangle]
811pub unsafe extern "C" fn xsltGetExtInfo(
812    style: *mut _xsltStylesheet,
813    _URI: *const xmlChar,
814) -> *mut c_void {
815    if style.is_null() {
816        return ptr::null_mut();
817    }
818    (*style).extInfos
819}
820
821/// `xsltRegisterAllExtras` (extra.c): register the EXSLT "extra" extension
822/// elements (exsl:document) into the global module registry.
823///
824/// # UPSTREAM-PARITY
825///
826/// ```c
827/// void xsltRegisterAllExtras(void);
828/// ```
829#[no_mangle]
830pub unsafe extern "C" fn xsltRegisterAllExtras() {
831    // exsl:document — handled natively by the transform engine
832    // (process_exsl_document); registering the module URI makes
833    // xsltCheckExtURI agree with upstream.
834    xsltRegisterExtModule(
835        b"http://exslt.org/common\0".as_ptr() as *const xmlChar,
836        None,
837        None,
838    );
839}
840
841/// `xsltRegisterExtras` (extra.c): register the EXSLT functions into the
842/// context's XPath context (upstream calls xsltRegisterAllFunctions).
843///
844/// # UPSTREAM-PARITY
845///
846/// ```c
847/// void xsltRegisterExtras(xsltTransformContextPtr ctxt);
848/// ```
849#[no_mangle]
850pub unsafe extern "C" fn xsltRegisterExtras(ctxt: *mut _xsltTransformContext) {
851    if ctxt.is_null() || (*ctxt).xpathCtxt.is_null() {
852        return;
853    }
854    crate::abi::exports_xslt_functions::xsltRegisterAllFunctions((*ctxt).xpathCtxt);
855}
856
857/// `xsltRegisterAllElement` (extra.c): register the EXSLT elements into the
858/// transform context.
859///
860/// # UPSTREAM-PARITY
861///
862/// ```c
863/// void xsltRegisterAllElement(xsltTransformContextPtr ctxt);
864/// ```
865#[no_mangle]
866pub unsafe extern "C" fn xsltRegisterAllElement(ctxt: *mut _xsltTransformContext) {
867    if ctxt.is_null() {
868        return;
869    }
870    // The engine dispatches EXSLT elements natively (process_exsl_document
871    // and the exslt module registrations); nothing to add to the context's
872    // per-context registration lists.
873}
874
875/// `xsltRegisterTestModule` (extensions.c): register the libxslt self-test
876/// extension module (a no-op surface in the candidate).
877///
878/// # UPSTREAM-PARITY
879///
880/// ```c
881/// void xsltRegisterTestModule(void);
882/// ```
883#[no_mangle]
884pub unsafe extern "C" fn xsltRegisterTestModule() {
885    xsltRegisterExtModule(
886        b"http://xmlsoft.org/XSLT/\0".as_ptr() as *const xmlChar,
887        None,
888        None,
889    );
890}
891
892// ── Internal helper structures (not part of the ABI) ───────────────────────
893
894/// Stylesheet extension-prefix registration (upstream style->extInfos hash
895/// entries; the candidate uses a linked list).
896#[repr(C)]
897pub struct ExtPrefixEntry {
898    pub next: *mut ExtPrefixEntry,
899    pub prefix: *mut c_char,
900    pub uri: *mut c_char,
901}
902
903/// Per-context / per-style extension data record (URI -> init data).
904#[repr(C)]
905pub struct ExtDataEntry {
906    pub next: *mut ExtDataEntry,
907    pub uri: *mut c_char,
908    pub data: *mut c_void,
909}