libxml_rs/abi/exports_buffer.rs
1//! C ABI exports for the buffer family — xmlBuf* / xmlBuffer* / xmlCharStr*
2//! (upstream buf.c, tree.c, xmlstring.c, 2.15.3).
3//!
4//! In 2.15 `xmlBuf` and `xmlBuffer` are the same C struct (typedef xmlBuffer
5//! xmlBuf), but the candidate mirrors them as two distinct structs
6//! (`_xmlBuffer` with `{content, use_, size, alloc, contentIO}` and `_xmlBuf`
7//! with `{content, use_, size, alloc, error, buffer, io}`). The `xmlBuf*`
8//! exports therefore operate on the `_xmlBuf` fields directly, allocating
9//! through the candidate allocator (`xmlMalloc`/`xmlFree`), exactly as
10//! upstream manipulates `buf->content`/`buf->use`/`buf->size`.
11
12#![allow(
13 missing_docs,
14 non_snake_case,
15 non_camel_case_types,
16 non_upper_case_globals
17)]
18
19use core::ptr;
20use std::os::raw::{c_char, c_int, c_uint, c_void};
21
22use crate::abi::allocator::{xmlFreeImpl, xmlMallocImpl};
23use crate::abi::structs::{_xmlBuf, _xmlBuffer, _xmlDoc, _xmlNode, _xmlNs};
24use crate::abi::types::{xmlChar, xmlElementType, XML_ERR_ARGUMENT, XML_ERR_NO_MEMORY, XML_ERR_OK};
25use crate::xml::io;
26use crate::xml::tree;
27
28// ── libc FILE* plumbing for xmlBufferDump ───────────────────────────────
29//
30// The FILE* is opaque at the ABI boundary and is passed as *mut c_void.
31// fwrite(3) is declared here rather than pulled from the libc crate so the
32// dependency stays explicit; `stdout` is the libc data symbol used by
33// upstream's `if (file == NULL) file = stdout;` fallback.
34
35extern "C" {
36 /// libc `size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)`.
37 fn fwrite(ptr: *const c_void, size: usize, nmemb: usize, stream: *mut c_void) -> usize;
38 /// The libc `FILE *stdout` variable.
39 static mut stdout: *mut c_void;
40}
41
42// ═══════════════════════════════════════════════════════════════════════════════
43// 1. xmlBuf operations (modern replacement, tree.h)
44// ═══════════════════════════════════════════════════════════════════════════════
45
46/// Get pointer into buffer content.
47///
48/// # UPSTREAM-PARITY
49///
50/// ```c
51/// xmlChar *xmlBufContent(const xmlBuf *buf);
52/// ```
53///
54/// buf.c 2.15: returns `buf->content`, or NULL when `buf` is NULL or the
55/// buffer is in error state (`BUF_ERROR`).
56///
57/// # SAFETY
58///
59/// - `buf` must be a valid pointer to a `_xmlBuf` (or NULL).
60/// - The returned pointer is owned by `buf` and must not be freed by the
61/// caller.
62#[no_mangle]
63pub unsafe extern "C" fn xmlBufContent(buf: *const _xmlBuf) -> *mut xmlChar {
64 if buf.is_null() || unsafe { (*buf).error != 0 } {
65 return ptr::null_mut();
66 }
67 unsafe { (*buf).content }
68}
69
70/// Return a pointer to the end of the buffer content.
71///
72/// # UPSTREAM-PARITY
73///
74/// ```c
75/// xmlChar *xmlBufEnd(xmlBuf *buf);
76/// ```
77///
78/// buf.c 2.15: returns `&buf->content[buf->use]`, or NULL when `buf` is NULL
79/// or the buffer is in error state.
80///
81/// # SAFETY
82///
83/// - `buf` must be a valid pointer to a `_xmlBuf` (or NULL).
84/// - The returned pointer is owned by `buf` and must not be freed by the
85/// caller.
86#[no_mangle]
87pub unsafe extern "C" fn xmlBufEnd(buf: *mut _xmlBuf) -> *mut xmlChar {
88 if buf.is_null() || unsafe { (*buf).error != 0 } {
89 return ptr::null_mut();
90 }
91 unsafe { (*buf).content.add((*buf).use_ as usize) }
92}
93
94/// Append the string value of a node to `buf`.
95///
96/// For text/CDATA/comment/PI nodes the string value is the node content;
97/// otherwise it is the concatenation of the string values of the node's
98/// descendants, with entity references substituted. Namespace declaration
99/// nodes contribute their href.
100///
101/// # UPSTREAM-PARITY
102///
103/// ```c
104/// int xmlBufGetNodeContent(xmlBuf *buf, const xmlNode *cur);
105/// ```
106///
107/// tree.c 2.15: returns -1 only when `cur` or `buf` is NULL; otherwise 0
108/// (append failures are ignored upstream and here, matching the void
109/// contract).
110///
111/// # SAFETY
112///
113/// - `buf` must be a valid pointer to a `_xmlBuf` (or NULL).
114/// - `cur` must be a valid pointer to a `_xmlNode`/`_xmlNs` (or NULL).
115#[no_mangle]
116pub unsafe extern "C" fn xmlBufGetNodeContent(buf: *mut _xmlBuf, cur: *const _xmlNode) -> c_int {
117 if cur.is_null() || buf.is_null() {
118 return -1;
119 }
120
121 // Upstream xmlBufGetNodeContent appends the namespace href for
122 // XML_NAMESPACE_DECL nodes; node_get_content has no arm for them.
123 if unsafe { (*cur).type_ } == xmlElementType::XML_NAMESPACE_DECL as c_int {
124 let ns = cur as *const _xmlNs;
125 if !unsafe { (*ns).href }.is_null() {
126 io::xml_buf_cat(buf, unsafe { (*ns).href });
127 }
128 return 0;
129 }
130
131 // node_get_content mirrors tree.c xmlNodeGetContent: recursive string
132 // value of the node (text descendants, entity expansion, attribute
133 // value, comment/PI content).
134 let content = unsafe { tree::node_get_content(cur as *mut _xmlNode) };
135 if content.is_null() {
136 // Allocation failure: nothing was appended; upstream still returns 0.
137 return 0;
138 }
139 io::xml_buf_cat(buf, content);
140 unsafe { xmlFreeImpl(content as *mut c_void) };
141 0
142}
143
144/// Serialize an XML node to an xmlBuf.
145///
146/// # UPSTREAM-PARITY
147///
148/// ```c
149/// size_t xmlBufNodeDump(xmlBuf *buf, xmlDoc *doc, xmlNode *cur, int level, int format);
150/// ```
151///
152/// xmlsave.c/tree.c 2.15: returns the number of bytes written to `buf`, or
153/// `(size_t)-1` when `buf`/`cur` is NULL or serialization fails. The level
154/// is clamped to [0, 100] (xmlNodeDumpOutput). `doc` is ignored, matching
155/// upstream's `(void) doc`.
156///
157/// # SAFETY
158///
159/// - `buf` must be a valid pointer to a `_xmlBuf` (or NULL).
160/// - `cur` must be a valid pointer to a `_xmlNode` (or NULL).
161/// - `doc` is unused and may be NULL.
162#[no_mangle]
163pub unsafe extern "C" fn xmlBufNodeDump(
164 buf: *mut _xmlBuf,
165 doc: *mut _xmlDoc,
166 cur: *mut _xmlNode,
167 level: c_int,
168 format: c_int,
169) -> usize {
170 let _ = doc;
171 if cur.is_null() || buf.is_null() {
172 return usize::MAX; // (size_t)-1
173 }
174 let level = level.clamp(0, 100);
175
176 // Serialize through a temporary xmlBuffer (the candidate serializer
177 // targets _xmlBuffer), then append the serialized bytes to the xmlBuf.
178 let tmp = io::buf_create(-1);
179 if tmp.is_null() {
180 return usize::MAX;
181 }
182 tree::serialize_node_opts(cur, tmp, format, level, ptr::null(), 0);
183
184 let len = io::buf_length(tmp);
185 let content = io::buf_content(tmp);
186 let written = if len > 0 && !content.is_null() {
187 io::xml_buf_add(buf, content, len)
188 } else {
189 0
190 };
191 io::buf_free(tmp);
192
193 if written < 0 {
194 usize::MAX
195 } else {
196 written as usize
197 }
198}
199
200/// Discard bytes at the start of a buffer.
201///
202/// # UPSTREAM-PARITY
203///
204/// ```c
205/// size_t xmlBufShrink(xmlBuf *buf, size_t len);
206/// ```
207///
208/// buf.c 2.15: removes `len` bytes from the front by advancing
209/// `buf->content` and decreasing `buf->use`/`buf->size`; returns the number
210/// of bytes removed, or 0 when `buf` is NULL, the buffer is in error state,
211/// `len` is 0, or `len` exceeds `buf->use`. Unlike `xmlBufferShrink`, errors
212/// return 0 rather than -1 (size_t return type).
213///
214/// # SAFETY
215///
216/// - `buf` must be a valid pointer to a `_xmlBuf` (or NULL).
217#[no_mangle]
218pub unsafe extern "C" fn xmlBufShrink(buf: *mut _xmlBuf, len: usize) -> usize {
219 if buf.is_null() || unsafe { (*buf).error != 0 } {
220 return 0;
221 }
222 if len == 0 {
223 return 0;
224 }
225 let b = unsafe { &mut *buf };
226 if len > b.use_ as usize {
227 return 0;
228 }
229 b.use_ -= len as c_uint;
230 b.content = unsafe { b.content.add(len) };
231 b.size -= len as c_uint;
232 len
233}
234
235/// Return the size of the buffer content.
236///
237/// # UPSTREAM-PARITY
238///
239/// ```c
240/// size_t xmlBufUse(xmlBuf *buf);
241/// ```
242///
243/// buf.c 2.15: returns `buf->use`, or 0 when `buf` is NULL or the buffer is
244/// in error state.
245///
246/// # SAFETY
247///
248/// - `buf` must be a valid pointer to a `_xmlBuf` (or NULL).
249#[no_mangle]
250pub unsafe extern "C" fn xmlBufUse(buf: *mut _xmlBuf) -> usize {
251 if buf.is_null() || unsafe { (*buf).error != 0 } {
252 return 0;
253 }
254 unsafe { (*buf).use_ as usize }
255}
256
257// ═══════════════════════════════════════════════════════════════════════════════
258// 2. xmlBuffer operations (deprecated, tree.h)
259// ═══════════════════════════════════════════════════════════════════════════════
260
261/// Append a zero-terminated C string to a buffer.
262///
263/// # UPSTREAM-PARITY
264///
265/// ```c
266/// int xmlBufferCCat(xmlBuffer *buf, const char *str);
267/// ```
268///
269/// buf.c 2.15: forwards to `xmlBufferAdd(buf, (const xmlChar *) str, -1)`,
270/// returning XML_ERR_ARGUMENT when `buf` or `str` is NULL, XML_ERR_OK on
271/// success (including the empty string), and XML_ERR_NO_MEMORY when growth
272/// fails.
273///
274/// # SAFETY
275///
276/// - `buf` must be a valid pointer to a `_xmlBuffer` (or NULL).
277/// - `str` must be a valid NUL-terminated C string (or NULL).
278#[no_mangle]
279pub unsafe extern "C" fn xmlBufferCCat(buf: *mut _xmlBuffer, str: *const c_char) -> c_int {
280 if buf.is_null() || str.is_null() {
281 return XML_ERR_ARGUMENT;
282 }
283 let ret = io::buf_cat(buf, str as *const xmlChar);
284 if ret < 0 {
285 XML_ERR_NO_MEMORY
286 } else {
287 XML_ERR_OK
288 }
289}
290
291/// Dump a buffer to a `FILE`.
292///
293/// # UPSTREAM-PARITY
294///
295/// ```c
296/// int xmlBufferDump(FILE *file, xmlBuffer *buf);
297/// ```
298///
299/// buf.c 2.15: returns 0 when `buf` is NULL or has no content, defaults a
300/// NULL `file` to stdout, and otherwise returns the `fwrite` byte count
301/// clamped to INT_MAX. Upstream never returns -1 from this function; write
302/// errors surface as a short count.
303///
304/// # SAFETY
305///
306/// - `file` must be a valid `FILE*` or NULL (then stdout is used).
307/// - `buf` must be a valid pointer to a `_xmlBuffer` (or NULL).
308#[no_mangle]
309pub unsafe extern "C" fn xmlBufferDump(file: *mut c_void, buf: *mut _xmlBuffer) -> c_int {
310 if buf.is_null() {
311 return 0;
312 }
313 let b = unsafe { &*buf };
314 if b.content.is_null() {
315 return 0;
316 }
317 let stream = if file.is_null() {
318 // Upstream: `if (file == NULL) file = stdout;`
319 unsafe { stdout }
320 } else {
321 file
322 };
323 let n = unsafe { fwrite(b.content as *const c_void, 1, b.use_ as usize, stream) };
324 if n > c_int::MAX as usize {
325 c_int::MAX
326 } else {
327 n as c_int
328 }
329}
330
331/// Resize a buffer to a minimum size.
332///
333/// # UPSTREAM-PARITY
334///
335/// ```c
336/// int xmlBufferResize(xmlBuffer *buf, unsigned int size);
337/// ```
338///
339/// buf.c 2.15: returns 0 when `buf` is NULL, 1 when `size` is below the
340/// current capacity, otherwise grows the buffer so its total capacity is at
341/// least `size` and returns 1 on success / 0 on allocation failure.
342///
343/// # SAFETY
344///
345/// - `buf` must be a valid pointer to a `_xmlBuffer` (or NULL).
346#[no_mangle]
347pub unsafe extern "C" fn xmlBufferResize(buf: *mut _xmlBuffer, size: c_uint) -> c_int {
348 if buf.is_null() {
349 return 0;
350 }
351 if size < unsafe { (*buf).size } {
352 return 1;
353 }
354 let res = io::buf_grow(buf, size);
355 if res < 0 {
356 0
357 } else {
358 1
359 }
360}
361
362/// Append a zero-terminated `xmlChar` string to a buffer.
363///
364/// # UPSTREAM-PARITY
365///
366/// ```c
367/// void xmlBufferWriteCHAR(xmlBuffer *buf, const xmlChar *string);
368/// ```
369///
370/// buf.c 2.15: `xmlBufferAdd(buf, string, -1)`, i.e. append the
371/// NUL-terminated string; failures are ignored (void return).
372///
373/// # SAFETY
374///
375/// - `buf` must be a valid pointer to a `_xmlBuffer` (or NULL).
376/// - `string` must be a valid NUL-terminated xmlChar string (or NULL).
377#[no_mangle]
378pub unsafe extern "C" fn xmlBufferWriteCHAR(buf: *mut _xmlBuffer, string: *const xmlChar) {
379 let _ = io::buf_cat(buf, string);
380}
381
382/// Append a zero-terminated C string to a buffer.
383///
384/// Same as `xmlBufferCCat`.
385///
386/// # UPSTREAM-PARITY
387///
388/// ```c
389/// void xmlBufferWriteChar(xmlBuffer *buf, const char *string);
390/// ```
391///
392/// buf.c 2.15: `xmlBufferAdd(buf, (const xmlChar *) string, -1)`; failures
393/// are ignored (void return).
394///
395/// # SAFETY
396///
397/// - `buf` must be a valid pointer to a `_xmlBuffer` (or NULL).
398/// - `string` must be a valid NUL-terminated C string (or NULL).
399#[no_mangle]
400pub unsafe extern "C" fn xmlBufferWriteChar(buf: *mut _xmlBuffer, string: *const c_char) {
401 let _ = io::buf_cat(buf, string as *const xmlChar);
402}
403
404/// Append a quoted string to a buffer.
405///
406/// Appends the string wrapped in quotes. If the string contains both single
407/// and double quotes, double quotes are escaped with `"` (upstream
408/// buf.c 2.15; no backslash or CR/LF escaping exists in this version).
409///
410/// # UPSTREAM-PARITY
411///
412/// ```c
413/// void xmlBufferWriteQuotedString(xmlBuffer *buf, const xmlChar *string);
414/// ```
415///
416/// buf.c 2.15: with a double-quote-containing string that also contains a
417/// single quote, emits `"` + string with `"` → `"` + `"`; with only
418/// double quotes emits `'` + string + `'`; otherwise emits `"` + string +
419/// `"`. A NULL string yields the bare quote pair, exactly like upstream
420/// (xmlStrchr(NULL) misses, xmlBufferCat(NULL) fails silently).
421///
422/// # SAFETY
423///
424/// - `buf` must be a valid pointer to a `_xmlBuffer` (or NULL).
425/// - `string` must be a valid NUL-terminated xmlChar string (or NULL).
426#[no_mangle]
427pub unsafe extern "C" fn xmlBufferWriteQuotedString(buf: *mut _xmlBuffer, string: *const xmlChar) {
428 if buf.is_null() {
429 return;
430 }
431 let has_dquote = !crate::abi::exports_xml2::xmlStrchr(string, b'"').is_null();
432 let has_squote = !crate::abi::exports_xml2::xmlStrchr(string, b'\'').is_null();
433 if has_dquote {
434 if has_squote {
435 // Escape every double quote with " inside double quotes.
436 io::buf_cat(buf, b"\"".as_ptr());
437 let mut base = string;
438 let mut cur = string;
439 while !cur.is_null() && unsafe { *cur } != 0 {
440 if unsafe { *cur } == b'"' {
441 if base != cur {
442 let n = cur.offset_from(base) as c_int;
443 io::buf_add(buf, base, n);
444 }
445 io::buf_cat(buf, b""".as_ptr());
446 cur = cur.add(1);
447 base = cur;
448 } else {
449 cur = cur.add(1);
450 }
451 }
452 if base != cur {
453 let n = cur.offset_from(base) as c_int;
454 io::buf_add(buf, base, n);
455 }
456 io::buf_cat(buf, b"\"".as_ptr());
457 } else {
458 // Only double quotes: single-quote the string.
459 io::buf_cat(buf, b"\'".as_ptr());
460 io::buf_cat(buf, string);
461 io::buf_cat(buf, b"\'".as_ptr());
462 }
463 } else {
464 io::buf_cat(buf, b"\"".as_ptr());
465 io::buf_cat(buf, string);
466 io::buf_cat(buf, b"\"".as_ptr());
467 }
468}
469
470// ═══════════════════════════════════════════════════════════════════════════════
471// 3. xmlChar string duplication (xmlstring.h)
472// ═══════════════════════════════════════════════════════════════════════════════
473
474/// Duplicate a `char *` string to a new `xmlChar *` string.
475///
476/// # UPSTREAM-PARITY
477///
478/// ```c
479/// xmlChar *xmlCharStrdup(const char *cur);
480/// ```
481///
482/// xmlstring.c 2.15: returns NULL for a NULL input, otherwise
483/// `xmlCharStrndup(cur, strlen(cur))`.
484///
485/// # SAFETY
486///
487/// - `cur` must be a valid NUL-terminated C string or NULL.
488/// - The returned pointer is allocated with `xmlMalloc` and must be freed
489/// with `xmlFree`.
490#[no_mangle]
491pub unsafe extern "C" fn xmlCharStrdup(cur: *const c_char) -> *mut xmlChar {
492 if cur.is_null() {
493 return ptr::null_mut();
494 }
495 let len = unsafe { libc::strlen(cur) } as c_int;
496 unsafe { xmlCharStrndup(cur, len) }
497}
498
499/// Duplicate `len` bytes of a `char *` string to a new `xmlChar *` string.
500///
501/// # UPSTREAM-PARITY
502///
503/// ```c
504/// xmlChar *xmlCharStrndup(const char *cur, int len);
505/// ```
506///
507/// xmlstring.c 2.15: returns NULL when `cur` is NULL or `len` is negative;
508/// otherwise allocates `len + 1` bytes, copies at most `len` bytes, stops
509/// early (and returns immediately) if an embedded NUL is copied, and always
510/// NUL-terminates.
511///
512/// # SAFETY
513///
514/// - `cur` must be a valid pointer to at least `len` readable bytes or NULL.
515/// - The returned pointer is allocated with `xmlMalloc` and must be freed
516/// with `xmlFree`.
517#[no_mangle]
518pub unsafe extern "C" fn xmlCharStrndup(cur: *const c_char, len: c_int) -> *mut xmlChar {
519 if cur.is_null() || len < 0 {
520 return ptr::null_mut();
521 }
522 let ret = unsafe { xmlMallocImpl(len as usize + 1) } as *mut xmlChar;
523 if ret.is_null() {
524 return ptr::null_mut();
525 }
526 unsafe {
527 let mut i: usize = 0;
528 while i < len as usize {
529 let byte = *cur.add(i) as xmlChar;
530 *ret.add(i) = byte;
531 if byte == 0 {
532 return ret; // Embedded NUL: already terminated.
533 }
534 i += 1;
535 }
536 *ret.add(len as usize) = 0;
537 }
538 ret
539}