1use crate::abi::callbacks::{
23 xmlCharEncodingOutputFunc, xmlOutputCloseCallback, xmlOutputWriteCallback,
24};
25use crate::abi::structs::{_xmlDoc, _xmlNode, _xmlOutputBuffer};
26use crate::abi::types::xmlChar;
27use crate::xml::io;
28use std::os::raw::{c_char, c_int, c_long};
29use std::ptr;
30
31pub const XML_SAVE_FORMAT: c_int = 1 << 0;
33pub const XML_SAVE_NO_DECL: c_int = 1 << 1;
35pub const XML_SAVE_NO_EMPTY: c_int = 1 << 2;
37
38#[repr(C)]
40pub struct _xmlSaveCtxt {
41 pub buf: *mut _xmlOutputBuffer,
42 pub options: c_int,
43 pub format: c_int,
44 pub no_decl: c_int,
45 pub no_empty: c_int,
46 pub indent: *mut xmlChar,
47 pub escape: Option<xmlCharEncodingOutputFunc>,
48 pub attrEscape: Option<xmlCharEncodingOutputFunc>,
49}
50
51unsafe fn save_ctxt_new(buf: *mut _xmlOutputBuffer, options: c_int) -> *mut _xmlSaveCtxt {
53 if buf.is_null() {
54 return ptr::null_mut();
55 }
56 let ctxt = libc::calloc(1, core::mem::size_of::<_xmlSaveCtxt>()) as *mut _xmlSaveCtxt;
57 if ctxt.is_null() {
58 io::output_buffer_close(buf);
59 return ptr::null_mut();
60 }
61 (*ctxt).buf = buf;
62 (*ctxt).options = options;
63 (*ctxt).format = if (options & XML_SAVE_FORMAT) != 0 {
64 1
65 } else {
66 0
67 };
68 (*ctxt).no_decl = if (options & XML_SAVE_NO_DECL) != 0 {
69 1
70 } else {
71 0
72 };
73 (*ctxt).no_empty = if (options & XML_SAVE_NO_EMPTY) != 0 {
74 1
75 } else {
76 0
77 };
78 ctxt
79}
80
81unsafe fn encoding_handler(
83 encoding: *const c_char,
84) -> *mut crate::abi::structs::_xmlCharEncodingHandler {
85 if encoding.is_null() {
86 return ptr::null_mut();
87 }
88 crate::xml::encoding::xmlFindCharEncodingHandler(encoding)
89}
90
91#[no_mangle]
97pub unsafe extern "C" fn xmlSaveToFd(
98 fd: c_int,
99 encoding: *const c_char,
100 options: c_int,
101) -> *mut _xmlSaveCtxt {
102 let enc = unsafe { encoding_handler(encoding) };
103 let out = io::output_buffer_create_fd(fd, enc);
104 unsafe { save_ctxt_new(out, options) }
105}
106
107#[no_mangle]
113pub unsafe extern "C" fn xmlSaveToFilename(
114 filename: *const c_char,
115 encoding: *const c_char,
116 options: c_int,
117) -> *mut _xmlSaveCtxt {
118 let enc = unsafe { encoding_handler(encoding) };
119 let out = io::output_buffer_create_filename(filename, enc, 0);
120 unsafe { save_ctxt_new(out, options) }
121}
122
123#[no_mangle]
129pub unsafe extern "C" fn xmlSaveToBuffer(
130 buffer: *mut crate::abi::structs::_xmlBuffer,
131 encoding: *const c_char,
132 options: c_int,
133) -> *mut _xmlSaveCtxt {
134 let enc = unsafe { encoding_handler(encoding) };
135 let out = io::output_buffer_create_buffer(buffer, enc);
136 unsafe { save_ctxt_new(out, options) }
137}
138
139#[no_mangle]
145pub unsafe extern "C" fn xmlSaveToIO(
146 iowrite: Option<xmlOutputWriteCallback>,
147 ioclose: Option<xmlOutputCloseCallback>,
148 ioctx: *mut core::ffi::c_void,
149 encoding: *const c_char,
150 options: c_int,
151) -> *mut _xmlSaveCtxt {
152 let enc = unsafe { encoding_handler(encoding) };
153 let out = io::output_buffer_create_io(iowrite, ioclose, ioctx, enc);
154 unsafe { save_ctxt_new(out, options) }
155}
156
157#[no_mangle]
166pub unsafe extern "C" fn xmlSaveDoc(ctxt: *mut _xmlSaveCtxt, doc: *mut _xmlDoc) -> c_long {
167 unsafe { save_doc_or_tree(ctxt, doc as *mut _xmlNode) }
168}
169
170#[no_mangle]
179pub unsafe extern "C" fn xmlSaveTree(ctxt: *mut _xmlSaveCtxt, node: *mut _xmlNode) -> c_long {
180 unsafe { save_doc_or_tree(ctxt, node) }
181}
182
183unsafe fn save_doc_or_tree(ctxt: *mut _xmlSaveCtxt, node: *mut _xmlNode) -> c_long {
184 if ctxt.is_null() || node.is_null() {
185 return -1;
186 }
187 let buf = io::buf_create(-1);
188 if buf.is_null() {
189 return -1;
190 }
191 let indent = (*ctxt).indent;
192 let format = (*ctxt).format;
193 let no_decl = (*ctxt).no_decl;
194 crate::xml::tree::serialize_node_opts(node, buf, format, 0, indent, no_decl);
195
196 let before = io::buf_length(buf);
197 let content = io::buf_content(buf);
198 let ret = if before > 0 && !content.is_null() {
199 io::output_buffer_write((*ctxt).buf, before, content as *const c_char)
200 } else {
201 0
202 };
203 io::buf_free(buf);
204 if ret < 0 {
205 -1
206 } else {
207 ret as c_long
208 }
209}
210
211#[no_mangle]
217pub unsafe extern "C" fn xmlSaveFlush(ctxt: *mut _xmlSaveCtxt) -> c_int {
218 if ctxt.is_null() {
219 return -1;
220 }
221 io::output_buffer_flush((*ctxt).buf)
222}
223
224#[no_mangle]
236pub unsafe extern "C" fn xmlSaveClose(ctxt: *mut _xmlSaveCtxt) -> c_int {
237 if ctxt.is_null() {
238 return -1;
239 }
240 let flush_ret = if (*ctxt).buf.is_null() {
241 -1
242 } else {
243 io::output_buffer_flush((*ctxt).buf)
244 };
245 if !(*ctxt).buf.is_null() {
247 io::output_buffer_close((*ctxt).buf);
248 }
249 if !(*ctxt).indent.is_null() {
250 libc::free((*ctxt).indent as *mut libc::c_void);
251 }
252 libc::free(ctxt as *mut libc::c_void);
253 flush_ret
254}
255
256#[no_mangle]
268pub unsafe extern "C" fn xmlSaveFinish(ctxt: *mut _xmlSaveCtxt) -> c_int {
269 if ctxt.is_null() {
270 return -1;
271 }
272 let ret = if (*ctxt).buf.is_null() {
273 -1
274 } else {
275 io::output_buffer_close((*ctxt).buf)
276 };
277 if !(*ctxt).indent.is_null() {
278 libc::free((*ctxt).indent as *mut libc::c_void);
279 }
280 libc::free(ctxt as *mut libc::c_void);
281 if ret < 0 {
282 -ret
283 } else {
284 0
285 }
286}
287
288#[no_mangle]
296pub unsafe extern "C" fn xmlSaveSetIndentString(
297 ctxt: *mut _xmlSaveCtxt,
298 indent: *const c_char,
299) -> c_int {
300 if ctxt.is_null() || indent.is_null() {
304 return -1;
305 }
306 let len = libc::strlen(indent) as usize;
307 if len == 0 || len > 60 {
308 return -1;
309 }
310 if !(*ctxt).indent.is_null() {
311 libc::free((*ctxt).indent as *mut libc::c_void);
312 (*ctxt).indent = ptr::null_mut();
313 }
314 let copy = libc::malloc(len + 1) as *mut xmlChar;
315 if copy.is_null() {
316 return -1;
317 }
318 libc::memcpy(
319 copy as *mut libc::c_void,
320 indent as *const libc::c_void,
321 len + 1,
322 );
323 (*ctxt).indent = copy;
324 0
325}
326
327#[no_mangle]
333pub unsafe extern "C" fn xmlSaveSetEscape(
334 ctxt: *mut _xmlSaveCtxt,
335 escape: Option<xmlCharEncodingOutputFunc>,
336) -> c_int {
337 if ctxt.is_null() {
338 return -1;
339 }
340 (*ctxt).escape = escape;
341 0
342}
343
344#[no_mangle]
350pub unsafe extern "C" fn xmlSaveSetAttrEscape(
351 ctxt: *mut _xmlSaveCtxt,
352 escape: Option<xmlCharEncodingOutputFunc>,
353) -> c_int {
354 if ctxt.is_null() {
355 return -1;
356 }
357 (*ctxt).attrEscape = escape;
358 0
359}
360
361unsafe fn save_ctxt_wrap(buf: *mut _xmlOutputBuffer, options: c_int) -> *mut _xmlSaveCtxt {
365 if buf.is_null() {
366 return ptr::null_mut();
367 }
368 let ctxt = libc::calloc(1, core::mem::size_of::<_xmlSaveCtxt>()) as *mut _xmlSaveCtxt;
369 if ctxt.is_null() {
370 return ptr::null_mut();
371 }
372 (*ctxt).buf = buf;
373 (*ctxt).options = options;
374 (*ctxt).format = if (options & XML_SAVE_FORMAT) != 0 {
375 1
376 } else {
377 0
378 };
379 (*ctxt).no_decl = if (options & XML_SAVE_NO_DECL) != 0 {
380 1
381 } else {
382 0
383 };
384 (*ctxt).no_empty = if (options & XML_SAVE_NO_EMPTY) != 0 {
385 1
386 } else {
387 0
388 };
389 ctxt
390}
391
392#[no_mangle]
401pub unsafe extern "C" fn xmlSaveFormatFileTo(
402 buf: *mut _xmlOutputBuffer,
403 cur: *mut _xmlDoc,
404 encoding: *const c_char,
405 format: c_int,
406) -> c_int {
407 let _ = encoding;
408 let options = if format != 0 { XML_SAVE_FORMAT } else { 0 };
409 let ctxt = unsafe { save_ctxt_wrap(buf, options) };
410 if ctxt.is_null() {
411 return -1;
412 }
413 let ret = unsafe { xmlSaveDoc(ctxt, cur) };
414 let close_ret = unsafe { xmlSaveClose(ctxt) };
415 if ret < 0 {
416 -1
417 } else {
418 close_ret
419 }
420}
421
422#[no_mangle]
430pub unsafe extern "C" fn xmlSaveFileTo(
431 buf: *mut _xmlOutputBuffer,
432 cur: *mut _xmlDoc,
433 encoding: *const c_char,
434) -> c_int {
435 unsafe { xmlSaveFormatFileTo(buf, cur, encoding, 0) }
436}
437
438#[cfg(test)]
439mod tests {
440 use super::*;
441 use crate::xml::tree::new_doc;
442
443 fn doc_with_root() -> *mut _xmlDoc {
444 unsafe {
445 let doc = new_doc(b"1.0\0".as_ptr() as *const xmlChar);
446 let root =
447 crate::xml::tree::new_node(ptr::null_mut(), b"root\0".as_ptr() as *const xmlChar);
448 crate::xml::tree::doc_set_root_element(doc, root);
449 doc
450 }
451 }
452
453 #[test]
454 fn test_save_to_buffer_format_and_nodes() {
455 unsafe {
456 let doc = doc_with_root();
457 let buf = io::buf_create(-1);
458 let ctxt = xmlSaveToBuffer(buf, ptr::null(), XML_SAVE_FORMAT);
459 assert!(!ctxt.is_null());
460 assert!(xmlSaveDoc(ctxt, doc) >= 0);
461 assert_eq!(xmlSaveFinish(ctxt), 0);
462 let content = io::buf_content(buf);
463 let len = io::buf_length(buf);
464 let s = core::slice::from_raw_parts(content, len as usize);
465 let expected = "<?xml version=\"1.0\"?>\n<root/>\n";
466 assert_eq!(s, expected.as_bytes());
467 crate::xml::tree::free_doc(doc);
468 io::buf_free(buf);
469 }
470 }
471
472 #[test]
473 fn test_save_no_decl() {
474 unsafe {
475 let doc = doc_with_root();
476 let buf = io::buf_create(-1);
477 let ctxt = xmlSaveToBuffer(buf, ptr::null(), XML_SAVE_NO_DECL);
478 assert!(!ctxt.is_null());
479 xmlSaveDoc(ctxt, doc);
480 xmlSaveFinish(ctxt);
481 let content = io::buf_content(buf);
482 let len = io::buf_length(buf);
483 let s = core::slice::from_raw_parts(content, len as usize);
484 assert_eq!(s, b"<root/>\n");
485 crate::xml::tree::free_doc(doc);
486 io::buf_free(buf);
487 }
488 }
489
490 #[test]
491 fn test_save_set_indent_string() {
492 unsafe {
493 let doc = doc_with_root();
494 let child =
495 crate::xml::tree::new_node(ptr::null_mut(), b"child\0".as_ptr() as *const xmlChar);
496 crate::xml::tree::add_child(crate::xml::tree::doc_get_root_element(doc), child);
497 let buf = io::buf_create(-1);
498 let ctxt = xmlSaveToBuffer(buf, ptr::null(), XML_SAVE_FORMAT);
499 assert!(!ctxt.is_null());
500 assert_eq!(
501 xmlSaveSetIndentString(ctxt, b"\t\0".as_ptr() as *const c_char),
502 0
503 );
504 xmlSaveDoc(ctxt, doc);
505 xmlSaveFinish(ctxt);
506 let content = io::buf_content(buf);
507 let len = io::buf_length(buf);
508 let s = core::slice::from_raw_parts(content, len as usize);
509 let expected = "<?xml version=\"1.0\"?>\n<root>\n\t<child/>\n</root>\n";
510 assert_eq!(s, expected.as_bytes());
511 crate::xml::tree::free_doc(doc);
512 io::buf_free(buf);
513 }
514 }
515
516 #[test]
517 fn test_save_close_null_and_errors() {
518 unsafe {
519 assert!(xmlSaveToFd(-1, ptr::null(), 0).is_null());
520 assert_eq!(xmlSaveFlush(ptr::null_mut()), -1);
521 assert_eq!(xmlSaveFinish(ptr::null_mut()), -1);
522 assert_eq!(xmlSaveClose(ptr::null_mut()), -1);
523 assert_eq!(xmlSaveSetIndentString(ptr::null_mut(), ptr::null()), -1);
524 assert_eq!(xmlSaveSetEscape(ptr::null_mut(), None), -1);
525 assert_eq!(xmlSaveSetAttrEscape(ptr::null_mut(), None), -1);
526 assert_eq!(xmlSaveDoc(ptr::null_mut(), ptr::null_mut()), -1);
527 assert_eq!(xmlSaveTree(ptr::null_mut(), ptr::null_mut()), -1);
528 }
529 }
530}