Skip to main content

libxml_rs/abi/
exports_xinclude.rs

1//! C ABI exports for the XInclude family — `xmlXInclude*` (upstream
2//! `xinclude.h`, 2.15.3).
3//!
4//! Implements the context-based XInclude entry points by wrapping the
5//! native-Rust XInclude engine (`src/xml/xinclude`), which also powers
6//! `xmllint --xinclude`.
7//!
8//! The context struct (`_xmlXIncludeCtxt`) is opaque at the C boundary — no
9//! field is ever dereferenced by the caller. The candidate engine keeps all
10//! per-include state (URL stack, circular-reference tracking, fallback
11//! handling) in Rust-owned structures that live for the duration of a single
12//! process call, so the context only needs to record the document, the
13//! processing flags, the error handler and the last error code.
14//!
15//! # Engine scope
16//!
17//! The engine's public entry points (`xinclude_process`,
18//! `xinclude_process_flags`) always walk the whole document starting at its
19//! root element. The tree-based entry points below therefore process the
20//! document that owns the given node; for a node that is the document root
21//! (the common case) this is exactly the upstream subtree semantics.
22
23#![allow(
24    missing_docs,
25    non_snake_case,
26    non_camel_case_types,
27    non_upper_case_globals
28)]
29
30use core::ffi::c_void;
31use core::mem::size_of;
32use core::ptr;
33use std::os::raw::{c_char, c_int};
34
35use crate::abi::allocator::{xmlFreeImpl, xmlMallocZero};
36use crate::abi::structs::{_xmlDoc, _xmlNode};
37use crate::abi::types::{XML_ERR_ARGUMENT, XML_ERR_INTERNAL_ERROR, XML_ERR_OK};
38use crate::xml::xinclude;
39
40/// Generic failure return for the process entry points (upstream `-1`).
41const XINCLUDE_ERROR: c_int = -1;
42
43/// XInclude error callback (upstream `xmlXIncludeErrorFunc`, xinclude.h).
44///
45/// `ctx` is the application data pointer registered with the handler,
46/// `code` is the error code, `message` the human-readable message.
47///
48/// # UPSTREAM-PARITY
49///
50/// ```c
51/// typedef void (*xmlXIncludeErrorFunc) (void *context, int code,
52///                                       const char *message);
53/// ```
54pub type xmlXIncludeErrorFunc =
55    unsafe extern "C" fn(ctx: *mut c_void, code: c_int, message: *const c_char);
56
57/// XInclude processing context (upstream `struct _xmlXIncludeCtxt`).
58///
59/// Opaque to C callers; only the fields the candidate engine needs are kept.
60#[repr(C)]
61#[derive(Debug)]
62pub struct _xmlXIncludeCtxt {
63    /// The source document being processed.
64    pub doc: *mut _xmlDoc,
65    /// Error handling function (never invoked by the candidate engine).
66    pub error: Option<xmlXIncludeErrorFunc>,
67    /// Application data passed to the error handler.
68    pub data: *mut c_void,
69    /// Processing flags (e.g. `XML_PARSE_NOXINCNODE`, `XML_PARSE_NONET`).
70    pub flags: c_int,
71    /// Error code of the last failure during processing.
72    pub lastError: c_int,
73}
74
75/// XInclude context pointer (upstream `xmlXIncludeCtxtPtr`).
76pub type xmlXIncludeCtxtPtr = *mut _xmlXIncludeCtxt;
77
78// ═══════════════════════════════════════════════════════════════════════════════
79// Context lifecycle
80// ═══════════════════════════════════════════════════════════════════════════════
81
82/// Create a new XInclude processing context.
83///
84/// Returns the context, or NULL on allocation failure. `doc` may be NULL;
85/// processing calls then fail with an argument error.
86///
87/// # UPSTREAM-PARITY
88///
89/// ```c
90/// xmlXIncludeCtxt *xmlXIncludeNewContext(xmlDoc *doc);
91/// ```
92///
93/// # SAFETY
94///
95/// `doc` must be NULL or a valid pointer to a parsed `_xmlDoc`.
96#[no_mangle]
97pub unsafe extern "C" fn xmlXIncludeNewContext(doc: *mut _xmlDoc) -> xmlXIncludeCtxtPtr {
98    let ctxt = unsafe { xmlMallocZero(size_of::<_xmlXIncludeCtxt>()) } as *mut _xmlXIncludeCtxt;
99    if ctxt.is_null() {
100        return ptr::null_mut();
101    }
102    unsafe {
103        (*ctxt).doc = doc;
104        (*ctxt).error = None;
105        (*ctxt).data = ptr::null_mut();
106        (*ctxt).flags = 0;
107        (*ctxt).lastError = XML_ERR_OK;
108    }
109    ctxt
110}
111
112/// Free an XInclude processing context.
113///
114/// # UPSTREAM-PARITY
115///
116/// ```c
117/// void xmlXIncludeFreeContext(xmlXIncludeCtxt *ctxt);
118/// ```
119///
120/// # SAFETY
121///
122/// `ctxt` must be NULL or a pointer previously returned by
123/// `xmlXIncludeNewContext` that has not already been freed.
124#[no_mangle]
125pub unsafe extern "C" fn xmlXIncludeFreeContext(ctxt: xmlXIncludeCtxtPtr) {
126    if ctxt.is_null() {
127        return;
128    }
129    // The context owns no heap allocations of its own (the engine keeps its
130    // per-include state only for the duration of a process call), so the
131    // struct itself is the only thing to release.
132    unsafe { xmlFreeImpl(ctxt as *mut c_void) };
133}
134
135// ═══════════════════════════════════════════════════════════════════════════════
136// Processing
137// ═══════════════════════════════════════════════════════════════════════════════
138
139/// Run the internal engine over the context's document, recording the
140/// outcome in `lastError`.
141///
142/// Returns the number of XInclude nodes processed, or -1 on error.
143///
144/// # SAFETY
145///
146/// `ctxt` must be a valid, non-null pointer to a context created by
147/// `xmlXIncludeNewContext`.
148unsafe fn process_ctxt_doc(ctxt: xmlXIncludeCtxtPtr) -> c_int {
149    let doc = unsafe { (*ctxt).doc };
150    if doc.is_null() {
151        unsafe { (*ctxt).lastError = XML_ERR_ARGUMENT };
152        return XINCLUDE_ERROR;
153    }
154    let flags = unsafe { (*ctxt).flags };
155    let ret = unsafe { xinclude::xinclude_process_flags(doc, flags) };
156    if ret < 0 {
157        // The engine reports failure without a fine-grained error code, so
158        // record a generic parser error to make xmlXIncludeGetLastError
159        // non-zero after a failure.
160        unsafe { (*ctxt).lastError = XML_ERR_INTERNAL_ERROR };
161    }
162    ret
163}
164
165/// Process the XInclude nodes in the tree rooted at `tree`, using the
166/// context's document and flags.
167///
168/// Returns the number of XInclude nodes processed, or -1 on error.
169///
170/// # UPSTREAM-PARITY
171///
172/// ```c
173/// int xmlXIncludeProcessNode(xmlXIncludeCtxt *ctxt, xmlNode *tree);
174/// ```
175///
176/// # SAFETY
177///
178/// `ctxt` must be a valid context created by `xmlXIncludeNewContext`; `tree`
179/// must be NULL or a valid `_xmlNode`.
180#[no_mangle]
181pub unsafe extern "C" fn xmlXIncludeProcessNode(
182    ctxt: xmlXIncludeCtxtPtr,
183    tree: *mut _xmlNode,
184) -> c_int {
185    if ctxt.is_null() || tree.is_null() {
186        return XINCLUDE_ERROR;
187    }
188    unsafe { process_ctxt_doc(ctxt) }
189}
190
191/// Process all XInclude nodes in the document containing `tree`.
192///
193/// Returns the number of XInclude nodes processed, or -1 on error.
194///
195/// # UPSTREAM-PARITY
196///
197/// ```c
198/// int xmlXIncludeProcessTree(xmlNode *tree);
199/// ```
200///
201/// # SAFETY
202///
203/// `tree` must be NULL or a valid `_xmlNode` attached to a document.
204#[no_mangle]
205pub unsafe extern "C" fn xmlXIncludeProcessTree(tree: *mut _xmlNode) -> c_int {
206    if tree.is_null() {
207        return XINCLUDE_ERROR;
208    }
209    let doc = unsafe { (*tree).doc };
210    if doc.is_null() {
211        return XINCLUDE_ERROR;
212    }
213    let ctxt = unsafe { xmlXIncludeNewContext(doc) };
214    if ctxt.is_null() {
215        return XINCLUDE_ERROR;
216    }
217    let ret = unsafe { xmlXIncludeProcessNode(ctxt, tree) };
218    unsafe { xmlXIncludeFreeContext(ctxt) };
219    ret
220}
221
222/// Process all XInclude nodes in the document containing `tree`, with flags.
223///
224/// Returns the number of XInclude nodes processed, or -1 on error.
225///
226/// # UPSTREAM-PARITY
227///
228/// ```c
229/// int xmlXIncludeProcessTreeFlags(xmlNode *tree, int flags);
230/// ```
231///
232/// # SAFETY
233///
234/// `tree` must be NULL or a valid `_xmlNode` attached to a document.
235#[no_mangle]
236pub unsafe extern "C" fn xmlXIncludeProcessTreeFlags(tree: *mut _xmlNode, flags: c_int) -> c_int {
237    if tree.is_null() {
238        return XINCLUDE_ERROR;
239    }
240    let doc = unsafe { (*tree).doc };
241    if doc.is_null() {
242        return XINCLUDE_ERROR;
243    }
244    let ctxt = unsafe { xmlXIncludeNewContext(doc) };
245    if ctxt.is_null() {
246        return XINCLUDE_ERROR;
247    }
248    unsafe {
249        (*ctxt).flags = flags;
250    }
251    let ret = unsafe { xmlXIncludeProcessNode(ctxt, tree) };
252    unsafe { xmlXIncludeFreeContext(ctxt) };
253    ret
254}
255
256/// Process all XInclude nodes in the document containing `tree`, with flags
257/// and an error-handler data pointer.
258///
259/// Returns the number of XInclude nodes processed, or -1 on error.
260///
261/// # UPSTREAM-PARITY
262///
263/// ```c
264/// int xmlXIncludeProcessTreeFlagsData(xmlNode *tree, int flags, void *data);
265/// ```
266///
267/// # SAFETY
268///
269/// `tree` must be NULL or a valid `_xmlNode` attached to a document; `data`
270/// must be NULL or a valid pointer for the lifetime of the call.
271#[no_mangle]
272pub unsafe extern "C" fn xmlXIncludeProcessTreeFlagsData(
273    tree: *mut _xmlNode,
274    flags: c_int,
275    data: *mut c_void,
276) -> c_int {
277    if tree.is_null() {
278        return XINCLUDE_ERROR;
279    }
280    let doc = unsafe { (*tree).doc };
281    if doc.is_null() {
282        return XINCLUDE_ERROR;
283    }
284    let ctxt = unsafe { xmlXIncludeNewContext(doc) };
285    if ctxt.is_null() {
286        return XINCLUDE_ERROR;
287    }
288    unsafe {
289        (*ctxt).flags = flags;
290        (*ctxt).data = data;
291        // Upstream also clears the handler function when only data is
292        // supplied; the candidate engine never invokes the handler, so the
293        // data pointer is the only observable part.
294        (*ctxt).error = None;
295    }
296    let ret = unsafe { xmlXIncludeProcessNode(ctxt, tree) };
297    unsafe { xmlXIncludeFreeContext(ctxt) };
298    ret
299}
300
301/// Process all XInclude nodes in `doc`, with flags and an error-handler data
302/// pointer.
303///
304/// Returns the number of XInclude nodes processed, or -1 on error.
305///
306/// # UPSTREAM-PARITY
307///
308/// ```c
309/// int xmlXIncludeProcessFlagsData(xmlDoc *doc, int flags, void *data);
310/// ```
311///
312/// # SAFETY
313///
314/// `doc` must be NULL or a valid pointer to a parsed `_xmlDoc`; `data` must
315/// be NULL or a valid pointer for the lifetime of the call.
316#[no_mangle]
317pub unsafe extern "C" fn xmlXIncludeProcessFlagsData(
318    doc: *mut _xmlDoc,
319    flags: c_int,
320    data: *mut c_void,
321) -> c_int {
322    if doc.is_null() {
323        return XINCLUDE_ERROR;
324    }
325    let ctxt = unsafe { xmlXIncludeNewContext(doc) };
326    if ctxt.is_null() {
327        return XINCLUDE_ERROR;
328    }
329    unsafe {
330        (*ctxt).flags = flags;
331        (*ctxt).data = data;
332    }
333    let ret = unsafe { process_ctxt_doc(ctxt) };
334    unsafe { xmlXIncludeFreeContext(ctxt) };
335    ret
336}
337
338// ═══════════════════════════════════════════════════════════════════════════════
339// Context configuration & state
340// ═══════════════════════════════════════════════════════════════════════════════
341
342/// Set the processing flags on the context.
343///
344/// Returns the previous set of flags, or -1 if `ctxt` is NULL.
345///
346/// # UPSTREAM-PARITY
347///
348/// ```c
349/// int xmlXIncludeSetFlags(xmlXIncludeCtxt *ctxt, int flags);
350/// ```
351///
352/// # SAFETY
353///
354/// `ctxt` must be NULL or a valid context created by `xmlXIncludeNewContext`.
355#[no_mangle]
356pub unsafe extern "C" fn xmlXIncludeSetFlags(ctxt: xmlXIncludeCtxtPtr, flags: c_int) -> c_int {
357    if ctxt.is_null() {
358        return XINCLUDE_ERROR;
359    }
360    let old_flags = unsafe { (*ctxt).flags };
361    unsafe {
362        (*ctxt).flags = flags;
363    }
364    old_flags
365}
366
367/// Set the error handler and its data on the context.
368///
369/// # UPSTREAM-PARITY
370///
371/// ```c
372/// void xmlXIncludeSetErrorHandler(xmlXIncludeCtxt *ctxt,
373///                                 xmlXIncludeErrorFunc handler,
374///                                 void *data);
375/// ```
376///
377/// # SAFETY
378///
379/// `ctxt` must be NULL or a valid context created by `xmlXIncludeNewContext`;
380/// `data` must be NULL or a valid pointer for as long as the handler may be
381/// called.
382#[no_mangle]
383pub unsafe extern "C" fn xmlXIncludeSetErrorHandler(
384    ctxt: xmlXIncludeCtxtPtr,
385    handler: Option<xmlXIncludeErrorFunc>,
386    data: *mut c_void,
387) {
388    if ctxt.is_null() {
389        return;
390    }
391    unsafe {
392        (*ctxt).error = handler;
393        (*ctxt).data = data;
394    }
395}
396
397/// Get the error code of the last failure during XInclude processing on this
398/// context.
399///
400/// Returns the last error code, or -1 if `ctxt` is NULL.
401///
402/// # UPSTREAM-PARITY
403///
404/// ```c
405/// int xmlXIncludeGetLastError(xmlXIncludeCtxt *ctxt);
406/// ```
407///
408/// # SAFETY
409///
410/// `ctxt` must be NULL or a valid context created by `xmlXIncludeNewContext`.
411#[no_mangle]
412pub unsafe extern "C" fn xmlXIncludeGetLastError(ctxt: xmlXIncludeCtxtPtr) -> c_int {
413    if ctxt.is_null() {
414        return XINCLUDE_ERROR;
415    }
416    unsafe { (*ctxt).lastError }
417}