Skip to main content

libxml_rs/xslt/errors/
mod.rs

1//! XSLT error handling (§33, §85 Phase 8).
2//!
3//! Defines error domains, error levels, error handler types, and the
4//! public API for reporting and retrieving XSLT errors.
5//!
6//! # Phase 8 status
7//!
8//! Constants and function types are fully defined. Functions are stubbed
9//! and will be implemented as part of Phase 8.
10
11use crate::abi::structs::*;
12use std::os::raw::c_int;
13
14// ── Error domains ─────────────────────────────────────────────────────────
15//
16// These constants identify the category of an XSLT error.
17// Source: xslt.h / xsltInternals.h (libxslt 1.1.39).
18
19/// No error.
20pub const XSLT_ERR_NONE: c_int = 0;
21
22/// Unknown error.
23pub const XSLT_ERR_UNKNOWN: c_int = 1;
24
25/// Missing required namespace.
26pub const XSLT_ERR_MISSING_NAMESPACE: c_int = 2;
27
28/// Invalid namespace.
29pub const XSLT_ERR_INVALID_NAMESPACE: c_int = 3;
30
31/// Missing required attribute.
32pub const XSLT_ERR_MISSING_ATTRIBUTE: c_int = 4;
33
34/// Invalid attribute value.
35pub const XSLT_ERR_INVALID_ATTRIBUTE: c_int = 5;
36
37/// Missing required element.
38pub const XSLT_ERR_MISSING_ELEMENT: c_int = 6;
39
40/// Invalid element.
41pub const XSLT_ERR_INVALID_ELEMENT: c_int = 7;
42
43/// Missing match attribute.
44pub const XSLT_ERR_MISSING_MATCH: c_int = 8;
45
46/// Missing name attribute.
47pub const XSLT_ERR_MISSING_NAME: c_int = 9;
48
49/// Missing select attribute.
50pub const XSLT_ERR_MISSING_SELECT: c_int = 10;
51
52/// Missing test attribute.
53pub const XSLT_ERR_MISSING_TEST: c_int = 11;
54
55/// Missing use attribute.
56pub const XSLT_ERR_MISSING_USE: c_int = 12;
57
58/// Invalid match pattern.
59pub const XSLT_ERR_INVALID_MATCH: c_int = 13;
60
61/// Invalid select expression.
62pub const XSLT_ERR_INVALID_SELECT: c_int = 14;
63
64/// Invalid test expression.
65pub const XSLT_ERR_INVALID_TEST: c_int = 15;
66
67/// Invalid use expression.
68pub const XSLT_ERR_INVALID_USE: c_int = 16;
69
70/// Missing namespace.
71pub const XSLT_ERR_MISSING_NS: c_int = 17;
72
73/// Cyclic reference detected.
74pub const XSLT_ERR_CYCLIC_REFERENCE: c_int = 18;
75
76/// Recursion limit exceeded.
77pub const XSLT_ERR_RECURSION: c_int = 19;
78
79/// Internal XSLT error.
80pub const XSLT_ERR_INTERNAL: c_int = 20;
81
82// ── Error levels ──────────────────────────────────────────────────────────
83//
84// These constants indicate the severity of an XSLT error.
85// Source: xslt.h (libxslt 1.1.39).
86
87/// No error level (unset).
88pub const XSLT_ERR_LEVEL_NONE: c_int = 0;
89
90/// Warning — non-fatal issue.
91pub const XSLT_ERR_LEVEL_WARNING: c_int = 1;
92
93/// Error — processing may continue but results may be incomplete.
94pub const XSLT_ERR_LEVEL_ERROR: c_int = 2;
95
96/// Fatal error — processing cannot continue.
97pub const XSLT_ERR_LEVEL_FATAL: c_int = 3;
98
99// ── Error handler types ───────────────────────────────────────────────────
100
101/// Global XSLT error handler function type.
102///
103/// Matches the upstream `xsltTransformErrorFunc` typedef:
104/// ```c
105/// typedef void (*xsltTransformErrorFunc)(void *ctxt, void *ctx,
106///                                        xsltStylesheetPtr style,
107///                                        const xmlChar *msg, ...);
108/// ```
109pub type xsltTransformErrorFunc = Option<
110    unsafe extern "C" fn(
111        *mut std::ffi::c_void,
112        *mut std::ffi::c_void,
113        *mut _xsltStylesheet,
114        *const crate::abi::types::xmlChar,
115        ...
116    ),
117>;
118
119// ── Public API ────────────────────────────────────────────────────────────
120
121/// The last XSLT error message (thread-local).
122use std::sync::Mutex;
123
124static LAST_XSLT_ERROR: Mutex<Option<Vec<u8>>> = Mutex::new(None);
125
126/// Global debug handler (upstream xsltGenericDebug).
127static mut XSLT_GENERIC_DEBUG: Option<
128    unsafe extern "C" fn(*mut std::ffi::c_void, *const std::os::raw::c_char),
129> = None;
130
131/// Set the generic debug handler (upstream `xsltSetGenericDebugFunc`).
132///
133/// # UPSTREAM-PARITY
134///
135/// ```c
136/// void xsltSetGenericDebugFunc(void *ctx, xmlGenericErrorFunc handler);
137/// ```
138///
139/// With a NULL handler, messages go to `stderr`; with a NULL context they
140/// are suppressed (upstream's default debug handler checks the context).
141#[no_mangle]
142pub unsafe extern "C" fn xsltSetGenericDebugFunc(
143    ctx: *mut std::ffi::c_void,
144    handler: Option<unsafe extern "C" fn(*mut std::ffi::c_void, *const std::os::raw::c_char)>,
145) {
146    unsafe {
147        XSLT_GENERIC_DEBUG_CONTEXT = ctx;
148        if handler.is_some() {
149            XSLT_GENERIC_DEBUG = handler;
150        }
151    }
152}
153
154static mut XSLT_GENERIC_DEBUG_CONTEXT: *mut std::ffi::c_void = std::ptr::null_mut();
155
156/// Emit a generic debug message (upstream xsltGenericDebug).
157#[no_mangle]
158pub unsafe extern "C" fn xsltGenericDebug(
159    ctx: *mut std::ffi::c_void,
160    msg: *const std::os::raw::c_char,
161) {
162    if ctx.is_null() || msg.is_null() {
163        return;
164    }
165    let len = libc::strlen(msg);
166    libc::write(2, msg as *const libc::c_void, len);
167}
168
169/// Set the transform error handler for a context.
170///
171/// Registers a per-context error handler that will be called for every
172/// error reported during the transformation. Pass `None` to restore the
173/// default handler.
174///
175/// # Parameters
176///
177/// * `ctxt`   — The transform context, or `std::ptr::null_mut()` for the
178///              global handler.
179/// * `ctx`    — Opaque user-data pointer passed to the handler.
180/// * `handler` — The error handler function, or `None` to reset.
181pub fn xsltSetTransformErrorFunc(
182    ctxt: *mut _xsltTransformContext,
183    ctx: *mut std::ffi::c_void,
184    handler: Option<unsafe extern "C" fn(*mut std::ffi::c_void, *const std::os::raw::c_char)>,
185) {
186    if ctxt.is_null() {
187        return;
188    }
189    // SAFETY: ctxt must be a valid _xsltTransformContext.
190    unsafe {
191        (*ctxt).errFunc = handler
192            .map(|h| h as *mut std::ffi::c_void)
193            .unwrap_or(std::ptr::null_mut());
194        (*ctxt).errCtxt = ctx;
195    }
196}
197
198/// Report an XSLT error.
199///
200/// Logs an error message associated with the given transform context,
201/// stylesheet, and instruction node. The message is a printf-style format
202/// string followed by variadic arguments. The variadic arguments are not
203/// expanded (matching the safe subset); the raw message is recorded.
204///
205/// # Parameters
206///
207/// * `ctxt`  — The transform context (may be null).
208/// * `style` — The stylesheet (may be null).
209/// * `inst`  — The instruction node that triggered the error (may be null).
210/// * `msg`   — The printf-style format string.
211pub fn xsltTransformError(
212    ctxt: *mut _xsltTransformContext,
213    style: *mut _xsltStylesheet,
214    inst: *mut _xmlNode,
215    msg: *const std::os::raw::c_char,
216) {
217    if msg.is_null() {
218        return;
219    }
220    // SAFETY: msg must be a valid NUL-terminated C string.
221    let bytes =
222        unsafe { core::slice::from_raw_parts(msg as *const u8, libc::strlen(msg) as usize) };
223    let text = String::from_utf8_lossy(bytes).into_owned();
224
225    // Record the last error.
226    if let Ok(mut last) = LAST_XSLT_ERROR.lock() {
227        *last = Some(text.clone().into_bytes());
228    }
229
230    // Build the prefix: "file:line: " when the instruction node provides it.
231    let mut prefix = String::new();
232    if !inst.is_null() {
233        // SAFETY: inst must be a valid node.
234        let node = unsafe { &*inst };
235        // SAFETY: node.doc must be valid while the node is alive.
236        let doc = unsafe { &*node.doc };
237        if !doc.URL.is_null() {
238            // SAFETY: URL must be a valid NUL-terminated string.
239            let url = unsafe {
240                core::slice::from_raw_parts(
241                    doc.URL as *const u8,
242                    libc::strlen(doc.URL as *const libc::c_char) as usize,
243                )
244            };
245            prefix.push_str(&String::from_utf8_lossy(url));
246            prefix.push(':');
247            prefix.push_str(&node.line.to_string());
248            prefix.push_str(": ");
249        }
250    }
251
252    // Invoke the per-context handler if one is registered.
253    if !ctxt.is_null() {
254        // SAFETY: ctxt must be a valid _xsltTransformContext.
255        let ctx = unsafe { &*ctxt };
256        if !ctx.errFunc.is_null() {
257            // SAFETY: errFunc is a valid handler registered by
258            // xsltSetTransformErrorFunc; errCtxt is the matching context.
259            let handler: unsafe extern "C" fn(*mut std::ffi::c_void, *const std::os::raw::c_char) =
260                unsafe { std::mem::transmute(ctx.errFunc) };
261            let full = format!("{}{}", prefix, text);
262            let mut cmsg = full.into_bytes();
263            cmsg.push(0);
264            unsafe { handler(ctx.errCtxt, cmsg.as_ptr() as *const std::os::raw::c_char) };
265            return;
266        }
267    }
268
269    // Default: write to stderr.
270    let full = format!("{}{}\n", prefix, text);
271    let _ = unsafe { libc::write(2, full.as_ptr() as *const libc::c_void, full.len()) };
272    let _ = style;
273}
274
275/// Get the last XSLT error message as a NUL-terminated heap string.
276///
277/// Returns a pointer to the last error message, or `std::ptr::null_mut()`
278/// if no error has occurred. The caller frees with `libc::free`.
279pub fn xsltGetLastError() -> *mut std::ffi::c_void {
280    let guard = match LAST_XSLT_ERROR.lock() {
281        Ok(g) => g,
282        Err(_) => return std::ptr::null_mut(),
283    };
284    match guard.as_ref() {
285        Some(bytes) => {
286            let len = bytes.len();
287            // SAFETY: malloc returns writable memory or NULL.
288            let p = unsafe { libc::malloc(len + 1) } as *mut u8;
289            if p.is_null() {
290                return std::ptr::null_mut();
291            }
292            unsafe {
293                core::ptr::copy_nonoverlapping(bytes.as_ptr(), p, len);
294                *p.add(len) = 0;
295            }
296            p as *mut std::ffi::c_void
297        }
298        None => std::ptr::null_mut(),
299    }
300}