libxml_rs/xslt/serialization/
mod.rs1use crate::abi::allocator::xmlFreeImpl;
34use crate::abi::structs::*;
35use crate::abi::types::xmlElementType::*;
36use crate::abi::types::*;
37use crate::xml::encoding;
38use std::ffi::c_void;
39use std::os::raw::{c_char, c_int};
40use std::ptr;
41
42unsafe fn result_is_empty(result: *mut _xmlDoc) -> bool {
45 if result.is_null() {
46 return true;
47 }
48 let children = (*result).children;
49 if children.is_null() {
50 return true;
51 }
52 if (*children).type_ == XML_DTD_NODE as c_int && (*children).next.is_null() {
53 return true;
54 }
55 false
56}
57
58unsafe fn next_import(style: *mut _xsltStylesheet) -> *mut _xsltStylesheet {
61 if style.is_null() || (*style).imports.is_null() {
62 return ptr::null_mut();
63 }
64 let mut imp = (*style).imports;
65 while !(*imp).next.is_null() {
66 imp = (*imp).next;
67 }
68 imp
69}
70
71unsafe fn import_chain_str(
73 style: *mut _xsltStylesheet,
74 get: fn(&_xsltStylesheet) -> *const xmlChar,
75) -> *const xmlChar {
76 let mut s = style;
77 while !s.is_null() {
78 let v = get(unsafe { &*s });
79 if !v.is_null() {
80 return v;
81 }
82 s = next_import(s);
83 }
84 ptr::null()
85}
86
87unsafe fn import_chain_int(
89 style: *mut _xsltStylesheet,
90 get: fn(&_xsltStylesheet) -> c_int,
91) -> c_int {
92 let mut s = style;
93 while !s.is_null() {
94 let v = get(unsafe { &*s });
95 if v != -1 {
96 return v;
97 }
98 s = next_import(s);
99 }
100 -1
101}
102
103unsafe fn cstr_eq_ignore_case(s: *const xmlChar, lit: &[u8]) -> bool {
105 if s.is_null() {
106 return lit.is_empty();
107 }
108 let mut i = 0usize;
109 while i < lit.len() {
110 let c = unsafe { *s.add(i) };
111 let l = lit[i];
112 if c.to_ascii_lowercase() != l.to_ascii_lowercase() {
113 return false;
114 }
115 i += 1;
116 }
117 unsafe { *s.add(i) == 0 }
118}
119
120pub(crate) unsafe fn save_result_to_vec(
131 result: *mut _xmlDoc,
132 style: *mut _xsltStylesheet,
133) -> Result<Vec<u8>, c_int> {
134 if result.is_null() || style.is_null() {
135 return Err(-1);
136 }
137 if result_is_empty(result) {
138 return Ok(Vec::new());
139 }
140
141 if !(*style).methodURI.is_null()
143 && ((*style).method.is_null() || !cstr_eq_ignore_case((*style).method, b"xhtml"))
144 {
145 eprintln!("xsltSaveResultTo : unknown output method");
146 return Err(-1);
147 }
148
149 let method = import_chain_str(style, |st| st.method);
150 let encoding = import_chain_str(style, |st| st.encoding);
151 let indent = import_chain_int(style, |st| st.indent);
152
153 let method: Option<Vec<u8>> = if !method.is_null() {
154 Some(
155 crate::abi::versioning::c_str_to_bytes(method as *const c_char)
156 .unwrap_or(b"")
157 .to_vec(),
158 )
159 } else if (*result).type_ == XML_HTML_DOCUMENT_NODE as c_int {
160 Some(b"html".to_vec())
161 } else {
162 None
163 };
164
165 let mut out: Vec<u8> = Vec::new();
166
167 match method.as_deref() {
168 Some(b"html") => {
169 let fmt = if indent != 0 { 1 } else { 0 };
173 if (*result).encoding.is_null() {
178 let enc: *const xmlChar = if !encoding.is_null() {
179 encoding
180 } else {
181 b"UTF-8\0".as_ptr() as *const xmlChar
182 };
183 (*result).encoding =
184 crate::abi::allocator::xmlMemStrdupImpl(enc as *const c_char) as *mut xmlChar;
185 }
186 let buf = crate::xml::io::buf_create(-1);
187 if buf.is_null() {
188 return Err(-1);
189 }
190 crate::xml::html::serialize_node(result as *mut _xmlNode, buf, fmt, 0);
191 let len = crate::xml::io::buf_length(buf);
192 let content = crate::xml::io::buf_content(buf);
193 if len > 0 && !content.is_null() {
194 out.extend_from_slice(core::slice::from_raw_parts(content, len as usize));
195 }
196 crate::xml::io::buf_free(buf);
197 }
198 Some(b"xhtml") => {
199 let buf = crate::xml::io::buf_create(-1);
201 if buf.is_null() {
202 return Err(-1);
203 }
204 crate::xml::html::serialize_node(result as *mut _xmlNode, buf, 0, 0);
205 let len = crate::xml::io::buf_length(buf);
206 let content = crate::xml::io::buf_content(buf);
207 if len > 0 && !content.is_null() {
208 out.extend_from_slice(core::slice::from_raw_parts(content, len as usize));
209 }
210 crate::xml::io::buf_free(buf);
211 }
212 Some(b"text") => {
213 let mut cur = (*result).children;
216 while !cur.is_null() {
217 if (*cur).type_ == XML_TEXT_NODE as c_int && !(*cur).content.is_null() {
218 let len = crate::xml::tree::xml_strlen((*cur).content);
219 out.extend_from_slice(core::slice::from_raw_parts(
220 (*cur).content,
221 len as usize,
222 ));
223 }
224 if !(*cur).children.is_null() {
225 let ct = (*(*cur).children).type_;
226 if ct != XML_ENTITY_DECL as c_int
227 && ct != XML_ENTITY_REF_NODE as c_int
228 && ct != XML_ENTITY_NODE as c_int
229 {
230 cur = (*cur).children;
231 continue;
232 }
233 }
234 if !(*cur).next.is_null() {
235 cur = (*cur).next;
236 continue;
237 }
238 loop {
239 cur = (*cur).parent;
240 if cur.is_null() {
241 break;
242 }
243 if cur == style as *mut _xmlNode {
244 cur = ptr::null_mut();
245 break;
246 }
247 if !(*cur).next.is_null() {
248 cur = (*cur).next;
249 break;
250 }
251 }
252 }
253 }
254 _ => {
255 let omit = import_chain_int(style, |st| st.omitXmlDeclaration);
257 let standalone = import_chain_int(style, |st| st.standalone);
258
259 if omit != 1 {
260 out.extend_from_slice(b"<?xml version=\"");
261 if !(*result).version.is_null() {
262 if let Some(v) =
263 crate::abi::versioning::c_str_to_bytes((*result).version as *const c_char)
264 {
265 out.extend_from_slice(v);
266 }
267 } else {
268 out.extend_from_slice(b"1.0");
269 }
270 out.push(b'"');
271 let mut enc = encoding;
272 if enc.is_null() && !(*result).encoding.is_null() {
273 enc = (*result).encoding;
274 }
275 if !enc.is_null() {
276 out.extend_from_slice(b" encoding=\"");
277 if let Some(e) = crate::abi::versioning::c_str_to_bytes(enc as *const c_char) {
278 out.extend_from_slice(e);
279 }
280 out.push(b'"');
281 }
282 match standalone {
283 0 => out.extend_from_slice(b" standalone=\"no\""),
284 1 => out.extend_from_slice(b" standalone=\"yes\""),
285 _ => {}
286 }
287 out.extend_from_slice(b"?>\n");
288 }
289
290 if !(*result).children.is_null() {
291 let mut child = (*result).children;
292 while !child.is_null() {
293 append_serialized_node(&mut out, child, if indent == 1 { 1 } else { 0 });
294 if indent != 0 {
295 let ct = (*child).type_;
296 if ct == XML_DTD_NODE as c_int
297 || (ct == XML_COMMENT_NODE as c_int && !(*child).next.is_null())
298 {
299 out.push(b'\n');
300 }
301 }
302 child = (*child).next;
303 }
304 if indent != 0 {
305 out.push(b'\n');
306 }
307 }
308 }
309 }
310
311 Ok(out)
312}
313
314unsafe fn append_serialized_node(out: &mut Vec<u8>, node: *mut _xmlNode, format: c_int) {
321 let buf = crate::xml::io::buf_create(-1);
322 if buf.is_null() {
323 return;
324 }
325 crate::xml::tree::serialize_node(node, buf, format, 0);
326 let len = crate::xml::io::buf_length(buf);
327 let content = crate::xml::io::buf_content(buf);
328 if len > 0 && !content.is_null() {
329 out.extend_from_slice(core::slice::from_raw_parts(content, len as usize));
330 }
331 crate::xml::io::buf_free(buf);
332}
333
334#[no_mangle]
353pub unsafe extern "C" fn xsltSaveResultToString(
354 doc_txt_ptr: *mut *mut xmlChar,
355 doc_txt_len: *mut c_int,
356 result: *mut _xmlDoc,
357 style: *mut _xsltStylesheet,
358) -> c_int {
359 if doc_txt_ptr.is_null() || doc_txt_len.is_null() || result.is_null() {
360 return -1;
361 }
362 *doc_txt_ptr = ptr::null_mut();
363 *doc_txt_len = 0;
364
365 if (*result).children.is_null() {
366 return 0;
367 }
368
369 let bytes = match save_result_to_vec(result, style) {
370 Ok(b) => b,
371 Err(_) => {
372 let empty = crate::abi::allocator::xmlMallocImpl(1) as *mut xmlChar;
374 if empty.is_null() {
375 return -1;
376 }
377 *empty = 0;
378 *doc_txt_ptr = empty;
379 *doc_txt_len = 0;
380 return 0;
381 }
382 };
383
384 let encoding = if !style.is_null() {
386 import_chain_str(style, |st| st.encoding)
387 } else {
388 ptr::null()
389 };
390 let converted: Vec<u8> = if !encoding.is_null()
391 && !cstr_eq_ignore_case(encoding, b"UTF-8")
392 && !cstr_eq_ignore_case(encoding, b"UTF8")
393 {
394 let enc = crate::abi::versioning::c_str_to_bytes(encoding as *const c_char).unwrap_or(b"");
395 let enc_lower = enc.to_ascii_lowercase();
396 if enc_lower.as_slice() == b"iso-8859-1"
397 || enc_lower.as_slice() == b"latin1"
398 || enc_lower.as_slice() == b"latin-1"
399 {
400 match encoding::utf8_to_latin1(&bytes) {
401 Ok(c) => c,
402 Err(_) => bytes,
403 }
404 } else {
405 bytes
408 }
409 } else {
410 bytes
411 };
412
413 let out = crate::abi::allocator::xmlMallocImpl(converted.len() + 1) as *mut xmlChar;
414 if out.is_null() {
415 return -1;
416 }
417 if !converted.is_empty() {
418 core::ptr::copy_nonoverlapping(converted.as_ptr(), out, converted.len());
419 }
420 *out.add(converted.len()) = 0;
421 *doc_txt_ptr = out;
422 *doc_txt_len = converted.len() as c_int;
423 0
424}
425
426#[no_mangle]
433pub unsafe extern "C" fn xsltSaveResultToFile(
434 output: *mut c_void,
435 result: *mut _xmlDoc,
436 style: *mut _xsltStylesheet,
437) -> c_int {
438 if output.is_null() || result.is_null() || style.is_null() {
439 return -1;
440 }
441 if result_is_empty(result) {
442 return 0;
443 }
444 let mut txt: *mut xmlChar = ptr::null_mut();
445 let mut len: c_int = 0;
446 let ret = xsltSaveResultToString(&mut txt, &mut len, result, style);
447 if ret != 0 {
448 return -1;
449 }
450 let written = libc::fwrite(
451 txt as *const libc::c_void,
452 1,
453 len as usize,
454 output as *mut libc::FILE,
455 );
456 xmlFreeImpl(txt as *mut c_void);
457 written as c_int
458}
459
460#[no_mangle]
474pub unsafe extern "C" fn xsltSaveResultToFilename(
475 URL: *const c_char,
476 result: *mut _xmlDoc,
477 style: *mut _xsltStylesheet,
478 compression: c_int,
479) -> c_int {
480 let _ = compression;
481 if URL.is_null() || result.is_null() || style.is_null() {
482 return -1;
483 }
484 if result_is_empty(result) {
485 return 0;
486 }
487 let file = libc::fopen(URL, b"wb\0".as_ptr() as *const c_char);
488 if file.is_null() {
489 return -1;
490 }
491 let ret = xsltSaveResultToFile(file as *mut c_void, result, style);
492 libc::fclose(file);
493 ret
494}
495
496#[no_mangle]
502pub unsafe extern "C" fn xsltSaveResultToFd(
503 fd: c_int,
504 result: *mut _xmlDoc,
505 style: *mut _xsltStylesheet,
506) -> c_int {
507 if fd < 0 || result.is_null() || style.is_null() {
508 return -1;
509 }
510 if result_is_empty(result) {
511 return 0;
512 }
513 let mut txt: *mut xmlChar = ptr::null_mut();
514 let mut len: c_int = 0;
515 let ret = xsltSaveResultToString(&mut txt, &mut len, result, style);
516 if ret != 0 {
517 return -1;
518 }
519 let bytes = core::slice::from_raw_parts(txt, len as usize);
520 let mut written = 0usize;
521 while written < bytes.len() {
522 let n = libc::write(
523 fd,
524 bytes[written..].as_ptr() as *const libc::c_void,
525 bytes.len() - written,
526 );
527 if n < 0 {
528 break;
529 }
530 written += n as usize;
531 }
532 xmlFreeImpl(txt as *mut c_void);
533 written as c_int
534}
535
536#[cfg(test)]
537mod tests {
538 use super::*;
539 use crate::xml::tree::*;
540 use core::ptr;
541
542 #[test]
543 fn test_save_result_null() {
544 unsafe {
545 assert_eq!(
546 xsltSaveResultToString(
547 ptr::null_mut(),
548 ptr::null_mut(),
549 ptr::null_mut(),
550 ptr::null_mut()
551 ),
552 -1
553 );
554 }
555 }
556
557 #[test]
558 fn test_save_result_to_string() {
559 unsafe {
560 let doc = new_doc(b"1.0\0".as_ptr() as *const xmlChar);
561 let root = new_node(ptr::null_mut(), b"root\0".as_ptr() as *const xmlChar);
562 doc_set_root_element(doc, root);
563 let text = new_text(b"hello\0".as_ptr() as *const xmlChar);
564 add_child(root, text);
565 let style = crate::xslt::stylesheet::xsltStylesheetCreate();
566 let mut txt: *mut xmlChar = ptr::null_mut();
567 let mut len: c_int = 0;
568 let ret = xsltSaveResultToString(&mut txt, &mut len, doc, style);
569 assert_eq!(ret, 0);
570 assert!(!txt.is_null());
571 assert!(len > 0);
572 let bytes = core::slice::from_raw_parts(txt, len as usize);
575 assert_eq!(bytes, b"<?xml version=\"1.0\"?>\n<root>hello</root>\n");
576 xmlFreeImpl(txt as *mut c_void);
577 crate::xslt::stylesheet::xsltFreeStylesheet(style);
578 free_doc(doc);
579 }
580 }
581
582 #[test]
583 fn test_save_result_indent_quirk() {
584 unsafe {
587 let doc = new_doc(b"1.0\0".as_ptr() as *const xmlChar);
588 let root = new_node(ptr::null_mut(), b"root\0".as_ptr() as *const xmlChar);
589 doc_set_root_element(doc, root);
590 let style = crate::xslt::stylesheet::xsltStylesheetCreate();
591 (*style).indent = 0;
592 let mut txt: *mut xmlChar = ptr::null_mut();
593 let mut len: c_int = 0;
594 assert_eq!(xsltSaveResultToString(&mut txt, &mut len, doc, style), 0);
595 let bytes = core::slice::from_raw_parts(txt, len as usize);
596 assert_eq!(bytes, b"<?xml version=\"1.0\"?>\n<root/>");
597 xmlFreeImpl(txt as *mut c_void);
598 crate::xslt::stylesheet::xsltFreeStylesheet(style);
599 free_doc(doc);
600 }
601 }
602
603 #[test]
604 fn test_save_result_text_method() {
605 unsafe {
606 let doc = new_doc(b"1.0\0".as_ptr() as *const xmlChar);
607 let root = new_node(ptr::null_mut(), b"root\0".as_ptr() as *const xmlChar);
608 doc_set_root_element(doc, root);
609 let text = new_text(b"a\0".as_ptr() as *const xmlChar);
610 add_child(root, text);
611 let style = crate::xslt::stylesheet::xsltStylesheetCreate();
612 let method = libc::malloc(5) as *mut xmlChar;
613 core::ptr::copy_nonoverlapping(b"text\0".as_ptr(), method, 5);
614 (*style).method = method;
615 let mut txt: *mut xmlChar = ptr::null_mut();
616 let mut len: c_int = 0;
617 assert_eq!(xsltSaveResultToString(&mut txt, &mut len, doc, style), 0);
618 let bytes = core::slice::from_raw_parts(txt, len as usize);
619 assert_eq!(bytes, b"a");
620 xmlFreeImpl(txt as *mut c_void);
621 crate::xslt::stylesheet::xsltFreeStylesheet(style);
622 free_doc(doc);
623 }
624 }
625}