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}