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}