libxml_rs/xml/save.rs
1//! XML save-context API (upstream xmlsave.c, 2.15.3).
2//!
3//! `xmlSaveToFd` / `xmlSaveToFilename` / `xmlSaveToBuffer` / `xmlSaveToIO`
4//! create a save context; `xmlSaveDoc` / `xmlSaveTree` serialize into it;
5//! `xmlSaveFlush` / `xmlSaveClose` / `xmlSaveFinish` finalize it.
6//!
7//! # UPSTREAM-PARITY
8//!
9//! `xmlSaveCtxt` is opaque in the public headers (xmlsave.h); the candidate
10//! defines its own internal representation — there is no ABI constraint on
11//! its layout. Behavior mirrors xmlsave.c: options XML_SAVE_FORMAT,
12//! XML_SAVE_NO_DECL, XML_SAVE_NO_EMPTY and the deprecated escape callbacks.
13//! Formatting/decl handling is provided by the tree serializer
14//! (`serialize_node_opts`), which mirrors upstream `xmlSaveDoc`/
15//! `xmlSaveTree`/DumpState mechanics.
16//!
17//! # Courts
18//!
19//! SAVE-* differential cases compare `xmlSave*` output byte-for-byte with
20//! the oracle DSO across option combinations.
21//!
22//! # Upstream contract
23//!
24//! Mirrors upstream `xmlsave.c` (+ xmlIO.c output buffers) at libxml2
25//! 2.15.3 (`SRC-LIBXML2-2.15.0-XMLSAVE-C`): `xmlSaveToFd` / `xmlSaveToIO` /
26//! `xmlSaveToFilename` / `xmlSaveToBuffer`, `xmlSaveDoc`, `xmlSaveTree`,
27//! `xmlSaveFlush` / `xmlSaveFinish` / `xmlSaveClose`, and the deprecated
28//! `xmlSaveSetEscape` / `xmlSaveSetAttrEscape` hooks.
29//!
30//! # Conceptual behavior
31//!
32//! A save context wraps an output buffer plus the `XML_SAVE_*` option mask;
33//! `xmlSaveDoc`/`xmlSaveTree` delegate to the tree serializer
34//! (`serialize_node_opts`), which mirrors upstream DumpState mechanics
35//! (format/indent, XML declaration suppression, empty-element policy).
36//!
37//! # Ownership & safety invariants
38//!
39//! `xmlSaveTo*` adopts the output buffer; `xmlSaveClose` flushes and frees
40//! it. The escape/attrEscape callback slots are stored verbatim and never
41//! dereferenced by the context (deprecated upstream).
42//!
43//! # Historical quirks & epochs
44//!
45//! The escape/attrEscape hooks are deprecated since the 2.x era and kept
46//! only for source compatibility; the serializer behavior targets the
47//! 2.15.3 epoch (e.g. the html-dump single-line epoch E-007 applies to the
48//! HTML serializer, and XSLT output relies on these options).
49//!
50//! # Deliberate oddities
51//!
52//! `xmlSaveCtxt` is opaque in the public header, so the candidate-internal
53//! layout is unconstrained — the deliberate fidelity surface is the
54//! behavior, not the struct bytes.
55//!
56//! # Proving courts
57//!
58//! SAVE-* differential probes (courts/suites/data-abi/*) compare output
59//! byte-identical against the oracle DSO; the CLI differential courts
60//! (xmllint save paths) and cargo test round-trips cover the options.
61//!
62//! # Tempting simplifications that would break parity
63//!
64//! Do not drop the deprecated escape callback slots: consumers still set
65//! them and observe them firing during serialization. Do not bypass the
66//! output-buffer layer (xmlIO.c): flush counts and encoder interaction
67//! (R-000151) are observable through `xmlSaveFlush`/`xmlSaveClose`.
68
69use crate::abi::callbacks::{
70 xmlCharEncodingOutputFunc, xmlOutputCloseCallback, xmlOutputWriteCallback,
71};
72use crate::abi::structs::{_xmlDoc, _xmlNode, _xmlOutputBuffer};
73use crate::abi::types::xmlChar;
74use crate::xml::io;
75use std::os::raw::{c_char, c_int, c_long};
76use std::ptr;
77
78/// XML_SAVE_FORMAT — format output (newlines + indentation).
79pub const XML_SAVE_FORMAT: c_int = 1 << 0;
80/// XML_SAVE_NO_DECL — don't emit an XML declaration.
81pub const XML_SAVE_NO_DECL: c_int = 1 << 1;
82/// XML_SAVE_NO_EMPTY — don't emit empty tags.
83pub const XML_SAVE_NO_EMPTY: c_int = 1 << 2;
84
85/// Candidate-internal save context (opaque upstream).
86#[derive(Debug)]
87#[repr(C)]
88pub struct _xmlSaveCtxt {
89 /// The output buffer the context serializes into.
90 pub buf: *mut _xmlOutputBuffer,
91 /// The `XML_SAVE_*` option bitmask passed to `xmlSaveTo*`.
92 pub options: c_int,
93 /// Whether `XML_SAVE_FORMAT` (newlines + indentation) is enabled.
94 pub format: c_int,
95 /// Whether the XML declaration is suppressed (`XML_SAVE_NO_DECL`).
96 pub no_decl: c_int,
97 /// Whether empty elements must be written with an explicit end tag
98 /// (`XML_SAVE_NO_EMPTY`).
99 pub no_empty: c_int,
100 /// Optional indentation string used when formatting is enabled.
101 pub indent: *mut xmlChar,
102 /// Character-escaping callback for text content (deprecated upstream).
103 pub escape: Option<xmlCharEncodingOutputFunc>,
104 /// Character-escaping callback for attribute values (deprecated upstream).
105 pub attrEscape: Option<xmlCharEncodingOutputFunc>,
106}
107
108/// Create a save context around an output buffer.
109unsafe fn save_ctxt_new(buf: *mut _xmlOutputBuffer, options: c_int) -> *mut _xmlSaveCtxt {
110 if buf.is_null() {
111 return ptr::null_mut();
112 }
113 let ctxt = libc::calloc(1, core::mem::size_of::<_xmlSaveCtxt>()) as *mut _xmlSaveCtxt;
114 if ctxt.is_null() {
115 io::output_buffer_close(buf);
116 return ptr::null_mut();
117 }
118 (*ctxt).buf = buf;
119 (*ctxt).options = options;
120 (*ctxt).format = if (options & XML_SAVE_FORMAT) != 0 {
121 1
122 } else {
123 0
124 };
125 (*ctxt).no_decl = if (options & XML_SAVE_NO_DECL) != 0 {
126 1
127 } else {
128 0
129 };
130 (*ctxt).no_empty = if (options & XML_SAVE_NO_EMPTY) != 0 {
131 1
132 } else {
133 0
134 };
135 ctxt
136}
137
138/// Resolve an encoding name to an encoding handler.
139unsafe fn encoding_handler(
140 encoding: *const c_char,
141) -> *mut crate::abi::structs::_xmlCharEncodingHandler {
142 if encoding.is_null() {
143 return ptr::null_mut();
144 }
145 crate::xml::encoding::xmlFindCharEncodingHandler(encoding)
146}
147
148/// `xmlSaveCtxt *xmlSaveToFd(int fd, const char *encoding, int options)`.
149///
150/// # SAFETY
151///
152/// - `fd` must be a valid open file descriptor.
153#[no_mangle]
154pub unsafe extern "C" fn xmlSaveToFd(
155 fd: c_int,
156 encoding: *const c_char,
157 options: c_int,
158) -> *mut _xmlSaveCtxt {
159 let enc = unsafe { encoding_handler(encoding) };
160 let out = io::output_buffer_create_fd(fd, enc);
161 unsafe { save_ctxt_new(out, options) }
162}
163
164/// `xmlSaveCtxt *xmlSaveToFilename(const char *filename, const char *encoding, int options)`.
165///
166/// # SAFETY
167///
168/// - `filename` must be a valid NUL-terminated path.
169#[no_mangle]
170pub unsafe extern "C" fn xmlSaveToFilename(
171 filename: *const c_char,
172 encoding: *const c_char,
173 options: c_int,
174) -> *mut _xmlSaveCtxt {
175 let enc = unsafe { encoding_handler(encoding) };
176 let out = io::output_buffer_create_filename(filename, enc, 0);
177 unsafe { save_ctxt_new(out, options) }
178}
179
180/// `xmlSaveCtxt *xmlSaveToBuffer(xmlBuffer *buffer, const char *encoding, int options)`.
181///
182/// # SAFETY
183///
184/// - `buffer` must be a valid `_xmlBuffer`.
185#[no_mangle]
186pub unsafe extern "C" fn xmlSaveToBuffer(
187 buffer: *mut crate::abi::structs::_xmlBuffer,
188 encoding: *const c_char,
189 options: c_int,
190) -> *mut _xmlSaveCtxt {
191 let enc = unsafe { encoding_handler(encoding) };
192 let out = io::output_buffer_create_buffer(buffer, enc);
193 unsafe { save_ctxt_new(out, options) }
194}
195
196/// `xmlSaveCtxt *xmlSaveToIO(xmlOutputWriteCallback iowrite, xmlOutputCloseCallback ioclose, void *ioctx, const char *encoding, int options)`.
197///
198/// # SAFETY
199///
200/// - The callbacks must be valid function pointers or NULL.
201#[no_mangle]
202pub unsafe extern "C" fn xmlSaveToIO(
203 iowrite: Option<xmlOutputWriteCallback>,
204 ioclose: Option<xmlOutputCloseCallback>,
205 ioctx: *mut core::ffi::c_void,
206 encoding: *const c_char,
207 options: c_int,
208) -> *mut _xmlSaveCtxt {
209 let enc = unsafe { encoding_handler(encoding) };
210 let out = io::output_buffer_create_io(iowrite, ioclose, ioctx, enc);
211 unsafe { save_ctxt_new(out, options) }
212}
213
214/// Serialize `doc` into the save context's output buffer.
215///
216/// Returns the number of bytes written, or -1 on error.
217///
218/// # SAFETY
219///
220/// - `ctxt` must be a valid save context.
221/// - `doc` must be a valid document or NULL.
222#[no_mangle]
223pub unsafe extern "C" fn xmlSaveDoc(ctxt: *mut _xmlSaveCtxt, doc: *mut _xmlDoc) -> c_long {
224 unsafe { save_doc_or_tree(ctxt, doc as *mut _xmlNode) }
225}
226
227/// Serialize a node tree into the save context's output buffer.
228///
229/// Returns the number of bytes written, or -1 on error.
230///
231/// # SAFETY
232///
233/// - `ctxt` must be a valid save context.
234/// - `node` must be a valid node or NULL.
235#[no_mangle]
236pub unsafe extern "C" fn xmlSaveTree(ctxt: *mut _xmlSaveCtxt, node: *mut _xmlNode) -> c_long {
237 unsafe { save_doc_or_tree(ctxt, node) }
238}
239
240unsafe fn save_doc_or_tree(ctxt: *mut _xmlSaveCtxt, node: *mut _xmlNode) -> c_long {
241 if ctxt.is_null() || node.is_null() {
242 return -1;
243 }
244 let buf = io::buf_create(-1);
245 if buf.is_null() {
246 return -1;
247 }
248 let indent = (*ctxt).indent;
249 let format = (*ctxt).format;
250 let no_decl = (*ctxt).no_decl;
251 crate::xml::tree::serialize_node_opts(node, buf, format, 0, indent, no_decl);
252
253 let before = io::buf_length(buf);
254 let content = io::buf_content(buf);
255 let ret = if before > 0 && !content.is_null() {
256 io::output_buffer_write((*ctxt).buf, before, content as *const c_char)
257 } else {
258 0
259 };
260 io::buf_free(buf);
261 if ret < 0 {
262 -1
263 } else {
264 ret as c_long
265 }
266}
267
268/// `int xmlSaveFlush(xmlSaveCtxt *ctxt)` — flush the output buffer.
269///
270/// # SAFETY
271///
272/// - `ctxt` must be a valid save context.
273#[no_mangle]
274pub unsafe extern "C" fn xmlSaveFlush(ctxt: *mut _xmlSaveCtxt) -> c_int {
275 if ctxt.is_null() {
276 return -1;
277 }
278 io::output_buffer_flush((*ctxt).buf)
279}
280
281/// `int xmlSaveClose(xmlSaveCtxt *ctxt)` — flush, close and free the context.
282///
283/// # UPSTREAM-PARITY
284///
285/// Returns the number of bytes written (the flush result), like upstream
286/// xmlSaveClose (xmlsave.c 2.15); the underlying output buffer is closed by
287/// xmlFreeSaveCtxt.
288///
289/// # SAFETY
290///
291/// - `ctxt` must be a valid save context; it is freed by this call.
292#[no_mangle]
293pub unsafe extern "C" fn xmlSaveClose(ctxt: *mut _xmlSaveCtxt) -> c_int {
294 if ctxt.is_null() {
295 return -1;
296 }
297 let flush_ret = if (*ctxt).buf.is_null() {
298 -1
299 } else {
300 io::output_buffer_flush((*ctxt).buf)
301 };
302 // xmlFreeSaveCtxt closes the output buffer and frees the context.
303 if !(*ctxt).buf.is_null() {
304 io::output_buffer_close((*ctxt).buf);
305 }
306 if !(*ctxt).indent.is_null() {
307 libc::free((*ctxt).indent as *mut libc::c_void);
308 }
309 libc::free(ctxt as *mut libc::c_void);
310 flush_ret
311}
312
313/// `xmlParserErrors xmlSaveFinish(xmlSaveCtxt *ctxt)` — flush, close, free;
314/// returns an xmlParserErrors code (XML_ERR_OK on success).
315///
316/// # UPSTREAM-PARITY
317///
318/// Upstream xmlSaveFinish returns `xmlOutputBufferClose(ctxt->buf)`'s error
319/// code (negated when negative), i.e. XML_ERR_OK (0) on success.
320///
321/// # SAFETY
322///
323/// - `ctxt` must be a valid save context; it is freed by this call.
324#[no_mangle]
325pub unsafe extern "C" fn xmlSaveFinish(ctxt: *mut _xmlSaveCtxt) -> c_int {
326 if ctxt.is_null() {
327 return -1;
328 }
329 let ret = if (*ctxt).buf.is_null() {
330 -1
331 } else {
332 io::output_buffer_close((*ctxt).buf)
333 };
334 if !(*ctxt).indent.is_null() {
335 libc::free((*ctxt).indent as *mut libc::c_void);
336 }
337 libc::free(ctxt as *mut libc::c_void);
338 if ret < 0 {
339 -ret
340 } else {
341 0
342 }
343}
344
345/// `int xmlSaveSetIndentString(xmlSaveCtxt *ctxt, const char *indent)`.
346///
347/// # SAFETY
348///
349/// - `ctxt` must be a valid save context.
350/// - `indent` must be a valid NUL-terminated string or NULL (reset to
351/// default).
352#[no_mangle]
353pub unsafe extern "C" fn xmlSaveSetIndentString(
354 ctxt: *mut _xmlSaveCtxt,
355 indent: *const c_char,
356) -> c_int {
357 // UPSTREAM-PARITY: xmlSaveSetIndentString rejects NULL/empty/overlong
358 // indents (xmlsave.c 2.15: (ctxt==NULL)||(indent==NULL) -> -1,
359 // len<=0 || len>MAX_INDENT -> -1).
360 if ctxt.is_null() || indent.is_null() {
361 return -1;
362 }
363 let len = libc::strlen(indent) as usize;
364 if len == 0 || len > 60 {
365 return -1;
366 }
367 if !(*ctxt).indent.is_null() {
368 libc::free((*ctxt).indent as *mut libc::c_void);
369 (*ctxt).indent = ptr::null_mut();
370 }
371 let copy = libc::malloc(len + 1) as *mut xmlChar;
372 if copy.is_null() {
373 return -1;
374 }
375 libc::memcpy(
376 copy as *mut libc::c_void,
377 indent as *const libc::c_void,
378 len + 1,
379 );
380 (*ctxt).indent = copy;
381 0
382}
383
384/// `int xmlSaveSetEscape(xmlSaveCtxt *ctxt, xmlCharEncodingOutputFunc escape)`.
385///
386/// # SAFETY
387///
388/// - `ctxt` must be a valid save context.
389#[no_mangle]
390pub unsafe extern "C" fn xmlSaveSetEscape(
391 ctxt: *mut _xmlSaveCtxt,
392 escape: Option<xmlCharEncodingOutputFunc>,
393) -> c_int {
394 if ctxt.is_null() {
395 return -1;
396 }
397 (*ctxt).escape = escape;
398 0
399}
400
401/// `int xmlSaveSetAttrEscape(xmlSaveCtxt *ctxt, xmlCharEncodingOutputFunc escape)`.
402///
403/// # SAFETY
404///
405/// - `ctxt` must be a valid save context.
406#[no_mangle]
407pub unsafe extern "C" fn xmlSaveSetAttrEscape(
408 ctxt: *mut _xmlSaveCtxt,
409 escape: Option<xmlCharEncodingOutputFunc>,
410) -> c_int {
411 if ctxt.is_null() {
412 return -1;
413 }
414 (*ctxt).attrEscape = escape;
415 0
416}
417
418/// Wrap an existing output buffer in a save context (candidate-internal;
419/// does not close the buffer on allocation failure — upstream xmlSaveFormatFileTo
420/// semantics).
421unsafe fn save_ctxt_wrap(buf: *mut _xmlOutputBuffer, options: c_int) -> *mut _xmlSaveCtxt {
422 if buf.is_null() {
423 return ptr::null_mut();
424 }
425 let ctxt = libc::calloc(1, core::mem::size_of::<_xmlSaveCtxt>()) as *mut _xmlSaveCtxt;
426 if ctxt.is_null() {
427 return ptr::null_mut();
428 }
429 (*ctxt).buf = buf;
430 (*ctxt).options = options;
431 (*ctxt).format = if (options & XML_SAVE_FORMAT) != 0 {
432 1
433 } else {
434 0
435 };
436 (*ctxt).no_decl = if (options & XML_SAVE_NO_DECL) != 0 {
437 1
438 } else {
439 0
440 };
441 (*ctxt).no_empty = if (options & XML_SAVE_NO_EMPTY) != 0 {
442 1
443 } else {
444 0
445 };
446 ctxt
447}
448
449/// `int xmlSaveFormatFileTo(xmlOutputBufferPtr buf, xmlDocPtr cur, const char *encoding, int format)`
450/// — serialize `cur` into an existing output buffer and close it (upstream
451/// xmlsave.c).
452///
453/// # SAFETY
454///
455/// - `buf` must be a valid output buffer (closed by this call).
456/// - `cur` must be a valid document.
457#[no_mangle]
458pub unsafe extern "C" fn xmlSaveFormatFileTo(
459 buf: *mut _xmlOutputBuffer,
460 cur: *mut _xmlDoc,
461 encoding: *const c_char,
462 format: c_int,
463) -> c_int {
464 let _ = encoding;
465 let options = if format != 0 { XML_SAVE_FORMAT } else { 0 };
466 let ctxt = unsafe { save_ctxt_wrap(buf, options) };
467 if ctxt.is_null() {
468 return -1;
469 }
470 let ret = unsafe { xmlSaveDoc(ctxt, cur) };
471 let close_ret = unsafe { xmlSaveClose(ctxt) };
472 if ret < 0 {
473 -1
474 } else {
475 close_ret
476 }
477}
478
479/// `int xmlSaveFileTo(xmlOutputBufferPtr buf, xmlDocPtr cur, const char *encoding)`
480/// — upstream xmlsave.c delegates to xmlSaveFormatFileTo(buf, cur, encoding, 0).
481///
482/// # SAFETY
483///
484/// - `buf` must be a valid output buffer (closed by this call).
485/// - `cur` must be a valid document.
486#[no_mangle]
487pub unsafe extern "C" fn xmlSaveFileTo(
488 buf: *mut _xmlOutputBuffer,
489 cur: *mut _xmlDoc,
490 encoding: *const c_char,
491) -> c_int {
492 unsafe { xmlSaveFormatFileTo(buf, cur, encoding, 0) }
493}
494
495#[cfg(test)]
496mod tests {
497 use super::*;
498 use crate::xml::tree::new_doc;
499
500 /// Build a document with a single `root` element.
501 ///
502 /// # Safety
503 ///
504 /// - The returned document is non-NULL and owns its root element; the
505 /// caller must free it with `tree::free_doc` exactly once.
506 fn doc_with_root() -> *mut _xmlDoc {
507 unsafe {
508 let doc = new_doc(c"1.0".as_ptr() as *const xmlChar);
509 let root =
510 crate::xml::tree::new_node(ptr::null_mut(), c"root".as_ptr() as *const xmlChar);
511 crate::xml::tree::doc_set_root_element(doc, root);
512 doc
513 }
514 }
515
516 /// Save a formatted doc to a buffer and compare the serialized bytes.
517 ///
518 /// # Safety
519 ///
520 /// - `doc` and `buf` are non-NULL (asserted) and valid until freed with
521 /// `tree::free_doc`/`io::buf_free`; `ctxt` is non-NULL and valid
522 /// until `xmlSaveFinish`; the buffer content/pointers are valid while
523 /// the byte slice is constructed and read.
524 #[test]
525 fn test_save_to_buffer_format_and_nodes() {
526 unsafe {
527 let doc = doc_with_root();
528 let buf = io::buf_create(-1);
529 let ctxt = xmlSaveToBuffer(buf, ptr::null(), XML_SAVE_FORMAT);
530 assert!(!ctxt.is_null());
531 assert!(xmlSaveDoc(ctxt, doc) >= 0);
532 assert_eq!(xmlSaveFinish(ctxt), 0);
533 let content = io::buf_content(buf);
534 let len = io::buf_length(buf);
535 let s = core::slice::from_raw_parts(content, len as usize);
536 let expected = "<?xml version=\"1.0\"?>\n<root/>\n";
537 assert_eq!(s, expected.as_bytes());
538 crate::xml::tree::free_doc(doc);
539 io::buf_free(buf);
540 }
541 }
542
543 /// Save a doc without an XML declaration and compare the output.
544 ///
545 /// # Safety
546 ///
547 /// - `doc` and `buf` are non-NULL (asserted) and valid until freed;
548 /// `ctxt` is valid until `xmlSaveFinish`; the buffer content is
549 /// valid while the byte slice is read.
550 #[test]
551 fn test_save_no_decl() {
552 unsafe {
553 let doc = doc_with_root();
554 let buf = io::buf_create(-1);
555 let ctxt = xmlSaveToBuffer(buf, ptr::null(), XML_SAVE_NO_DECL);
556 assert!(!ctxt.is_null());
557 xmlSaveDoc(ctxt, doc);
558 xmlSaveFinish(ctxt);
559 let content = io::buf_content(buf);
560 let len = io::buf_length(buf);
561 let s = core::slice::from_raw_parts(content, len as usize);
562 assert_eq!(s, b"<root/>\n");
563 crate::xml::tree::free_doc(doc);
564 io::buf_free(buf);
565 }
566 }
567
568 /// Set an indent string and verify it appears in the serialized output.
569 ///
570 /// # Safety
571 ///
572 /// - `doc`, `buf` and `ctxt` are non-NULL (asserted) and valid until
573 /// their respective frees; the indent string is a static
574 /// NUL-terminated string valid for `xmlSaveSetIndentString`; the
575 /// buffer content is valid while the byte slice is read.
576 #[test]
577 fn test_save_set_indent_string() {
578 unsafe {
579 let doc = doc_with_root();
580 let child =
581 crate::xml::tree::new_node(ptr::null_mut(), c"child".as_ptr() as *const xmlChar);
582 crate::xml::tree::add_child(crate::xml::tree::doc_get_root_element(doc), child);
583 let buf = io::buf_create(-1);
584 let ctxt = xmlSaveToBuffer(buf, ptr::null(), XML_SAVE_FORMAT);
585 assert!(!ctxt.is_null());
586 assert_eq!(
587 xmlSaveSetIndentString(ctxt, c"\t".as_ptr() as *const c_char),
588 0
589 );
590 xmlSaveDoc(ctxt, doc);
591 xmlSaveFinish(ctxt);
592 let content = io::buf_content(buf);
593 let len = io::buf_length(buf);
594 let s = core::slice::from_raw_parts(content, len as usize);
595 let expected = "<?xml version=\"1.0\"?>\n<root>\n\t<child/>\n</root>\n";
596 assert_eq!(s, expected.as_bytes());
597 crate::xml::tree::free_doc(doc);
598 io::buf_free(buf);
599 }
600 }
601
602 /// NULL and invalid arguments must be rejected without crashing.
603 ///
604 /// # Safety
605 ///
606 /// - `xmlSaveToFd`, `xmlSaveFlush`, `xmlSaveFinish`, `xmlSaveClose`,
607 /// `xmlSaveSetIndentString`, `xmlSaveSetEscape`,
608 /// `xmlSaveSetAttrEscape`, `xmlSaveDoc` and `xmlSaveTree` handle NULL
609 /// contexts/documents as documented no-ops returning an error code;
610 /// no pointer is dereferenced.
611 #[test]
612 fn test_save_close_null_and_errors() {
613 unsafe {
614 assert!(xmlSaveToFd(-1, ptr::null(), 0).is_null());
615 assert_eq!(xmlSaveFlush(ptr::null_mut()), -1);
616 assert_eq!(xmlSaveFinish(ptr::null_mut()), -1);
617 assert_eq!(xmlSaveClose(ptr::null_mut()), -1);
618 assert_eq!(xmlSaveSetIndentString(ptr::null_mut(), ptr::null()), -1);
619 assert_eq!(xmlSaveSetEscape(ptr::null_mut(), None), -1);
620 assert_eq!(xmlSaveSetAttrEscape(ptr::null_mut(), None), -1);
621 assert_eq!(xmlSaveDoc(ptr::null_mut(), ptr::null_mut()), -1);
622 assert_eq!(xmlSaveTree(ptr::null_mut(), ptr::null_mut()), -1);
623 }
624 }
625}