1#![allow(
17 missing_docs,
18 non_snake_case,
19 non_camel_case_types,
20 non_upper_case_globals
21)]
22
23use core::ffi::c_void;
24use core::ptr;
25use std::os::raw::{c_char, c_int};
26
27use crate::abi::allocator;
28
29use crate::abi::structs::*;
30use crate::abi::types::*;
31use crate::xml::io;
32use crate::xml::tree;
33
34#[derive(Clone, Copy, PartialEq, Eq, Debug)]
44enum WriterState {
45 None,
47 Element,
49 Attribute,
51 CData,
53 Comment,
55 PI,
57 DTD,
59 DTDElem,
61 DTDAttr,
63 DTDEntity,
65 DTDNotation,
67 XMLDecl,
69}
70
71pub struct XmlTextWriter {
84 output: *mut _xmlOutputBuffer,
86 indent: c_int,
88 indent_string: Vec<u8>,
90 depth: c_int,
92 stack: Vec<Vec<u8>>,
94 encoding: Vec<u8>,
96 errors: Vec<String>,
98 state: WriterState,
100 doc: *mut _xmlDoc,
102 in_start_tag: bool,
104 elem_stack: Vec<(Vec<u8>, Vec<u8>)>,
107}
108
109impl XmlTextWriter {
110 unsafe fn new(output: *mut _xmlOutputBuffer) -> *mut Self {
116 let writer = allocator::xmlMallocZero(size_of::<XmlTextWriter>() as usize) as *mut Self;
117 if writer.is_null() {
118 return ptr::null_mut();
119 }
120 unsafe {
121 (*writer).output = output;
122 (*writer).indent = 0;
123 (*writer).indent_string = b" \0".to_vec();
124 (*writer).depth = 0;
125 (*writer).stack = Vec::new();
126 (*writer).encoding = b"UTF-8\0".to_vec();
127 (*writer).errors = Vec::new();
128 (*writer).state = WriterState::None;
129 (*writer).doc = ptr::null_mut();
130 (*writer).in_start_tag = false;
131 (*writer).elem_stack = Vec::new();
132 }
133 writer
134 }
135
136 unsafe fn write_raw(&mut self, data: *const u8, len: c_int) {
142 if self.output.is_null() || data.is_null() || len <= 0 {
143 return;
144 }
145 io::output_buffer_write(self.output, len, data as *const c_char);
146 }
147
148 unsafe fn write_str(&mut self, s: *const u8) {
150 if self.output.is_null() || s.is_null() {
151 return;
152 }
153 io::output_buffer_write_string(self.output, s as *const c_char);
154 }
155
156 unsafe fn write_slice(&mut self, slice: &[u8]) {
160 if self.output.is_null() || slice.is_empty() {
161 return;
162 }
163 io::output_buffer_write(
164 self.output,
165 slice.len() as c_int,
166 slice.as_ptr() as *const c_char,
167 );
168 }
169
170 unsafe fn write_byte(&mut self, b: u8) {
172 if self.output.is_null() {
173 return;
174 }
175 io::output_buffer_write_char(self.output, b as c_char);
176 }
177
178 unsafe fn write_indent(&mut self) {
182 if self.indent == 0 {
183 return;
184 }
185 self.write_byte(b'\n');
186 let indent_str = self.indent_string.clone();
187 for _ in 0..self.depth {
188 self.write_slice(&indent_str);
189 }
190 }
191
192 unsafe fn close_start_tag(&mut self) {
195 if self.in_start_tag {
196 self.write_byte(b'>');
197 self.in_start_tag = false;
198 }
199 }
200
201 fn can_write_content(&self) -> bool {
203 matches!(
204 self.state,
205 WriterState::None
206 | WriterState::Element
207 | WriterState::Attribute
208 | WriterState::CData
209 | WriterState::Comment
210 | WriterState::PI
211 | WriterState::DTD
212 | WriterState::DTDElem
213 | WriterState::DTDAttr
214 | WriterState::DTDEntity
215 | WriterState::DTDNotation
216 | WriterState::XMLDecl
217 )
218 }
219}
220
221#[no_mangle]
237pub unsafe extern "C" fn xmlFreeTextWriter(writer: *mut XmlTextWriter) {
238 if writer.is_null() {
239 return;
240 }
241 if !(*writer).output.is_null() {
244 io::output_buffer_flush((*writer).output);
245 }
246 unsafe {
248 ptr::drop_in_place(&mut (*writer).indent_string);
249 ptr::drop_in_place(&mut (*writer).stack);
250 ptr::drop_in_place(&mut (*writer).encoding);
251 ptr::drop_in_place(&mut (*writer).errors);
252 ptr::drop_in_place(&mut (*writer).elem_stack);
253 }
254 unsafe { allocator::xmlFree(writer as *mut c_void) };
256}
257
258#[no_mangle]
274pub unsafe extern "C" fn xmlNewTextWriter(out: *mut _xmlOutputBuffer) -> *mut XmlTextWriter {
275 if out.is_null() {
276 return ptr::null_mut();
277 }
278 XmlTextWriter::new(out)
280}
281
282#[no_mangle]
294pub unsafe extern "C" fn xmlNewTextWriterFilename(
295 uri: *const c_char,
296 compression: c_int,
297) -> *mut XmlTextWriter {
298 if uri.is_null() {
299 return ptr::null_mut();
300 }
301 let out = io::output_buffer_create_filename(uri, ptr::null_mut(), compression);
303 if out.is_null() {
304 return ptr::null_mut();
305 }
306 XmlTextWriter::new(out)
307}
308
309#[no_mangle]
321pub unsafe extern "C" fn xmlNewTextWriterMemory(
322 buf: *mut _xmlBuffer,
323 compression: c_int,
324) -> *mut XmlTextWriter {
325 let _ = compression;
326 if buf.is_null() {
327 return ptr::null_mut();
328 }
329 let out = io::output_buffer_create_buffer(buf, ptr::null_mut());
331 if out.is_null() {
332 return ptr::null_mut();
333 }
334 XmlTextWriter::new(out)
335}
336
337#[no_mangle]
349pub unsafe extern "C" fn xmlNewTextWriterDoc(
350 doc: *mut *mut _xmlDoc,
351 compression: c_int,
352) -> *mut XmlTextWriter {
353 let _ = compression;
354 if doc.is_null() {
355 return ptr::null_mut();
356 }
357 let new_doc = tree::new_doc(b"1.0\0" as *const u8);
360 if new_doc.is_null() {
361 return ptr::null_mut();
362 }
363 unsafe { *doc = new_doc };
364
365 let buf = io::buf_create(io::DEFAULT_BUFFER_SIZE as c_int);
367 if buf.is_null() {
368 tree::free_doc(new_doc);
369 return ptr::null_mut();
370 }
371
372 let out = io::output_buffer_create_buffer(buf, ptr::null_mut());
373 if out.is_null() {
374 io::buf_free(buf);
375 tree::free_doc(new_doc);
376 return ptr::null_mut();
377 }
378
379 let writer = XmlTextWriter::new(out);
380 if !writer.is_null() {
381 unsafe { (*writer).doc = new_doc };
382 }
383 writer
384}
385
386#[no_mangle]
399pub unsafe extern "C" fn xmlNewTextWriterTree(
400 doc: *mut _xmlDoc,
401 node: *mut _xmlNode,
402 compression: c_int,
403) -> *mut XmlTextWriter {
404 let _ = compression;
405 let _ = node; if doc.is_null() {
407 return ptr::null_mut();
408 }
409
410 let buf = io::buf_create(io::DEFAULT_BUFFER_SIZE as c_int);
411 if buf.is_null() {
412 return ptr::null_mut();
413 }
414
415 let out = io::output_buffer_create_buffer(buf, ptr::null_mut());
416 if out.is_null() {
417 io::buf_free(buf);
418 return ptr::null_mut();
419 }
420
421 let writer = XmlTextWriter::new(out);
422 if !writer.is_null() {
423 unsafe { (*writer).doc = doc };
424 }
425 writer
426}
427
428#[no_mangle]
450pub unsafe extern "C" fn xmlTextWriterStartDocument(
451 writer: *mut XmlTextWriter,
452 version: *const c_char,
453 encoding: *const c_char,
454 standalone: *const c_char,
455) -> c_int {
456 if writer.is_null() {
457 return -1;
458 }
459 let w = unsafe { &mut *writer };
461
462 w.write_raw(b"<?xml version=\"" as *const u8, 15);
463
464 let ver = if version.is_null() {
465 b"1.0\0" as *const u8
466 } else {
467 version as *const u8
468 };
469 w.write_str(ver);
470
471 w.write_raw(b"\"" as *const u8, 1);
472
473 let enc = if encoding.is_null() {
474 ptr::null()
475 } else {
476 encoding as *const u8
477 };
478 if !enc.is_null() {
479 w.write_raw(b" encoding=\"" as *const u8, 11);
480 w.write_str(enc);
481 w.write_byte(b'"');
482 }
483
484 if !standalone.is_null() {
485 let sa = standalone as *const u8;
486 w.write_raw(b" standalone=\"" as *const u8, 13);
487 w.write_str(sa);
488 w.write_byte(b'"');
489 }
490
491 w.write_raw(b"?>" as *const u8, 2);
492
493 if w.indent != 0 {
494 w.write_byte(b'\n');
495 }
496
497 w.state = WriterState::XMLDecl;
498 0
499}
500
501#[no_mangle]
515pub unsafe extern "C" fn xmlTextWriterEndDocument(writer: *mut XmlTextWriter) -> c_int {
516 if writer.is_null() {
517 return -1;
518 }
519 let w = unsafe { &mut *writer };
521
522 while w.depth > 0 {
524 xmlTextWriterEndElement(writer);
525 }
526
527 if w.indent != 0 {
529 w.write_byte(b'\n');
530 }
531
532 if !w.output.is_null() {
534 io::output_buffer_flush(w.output);
535 }
536
537 w.state = WriterState::None;
538 0
539}
540
541#[no_mangle]
558pub unsafe extern "C" fn xmlTextWriterStartElement(
559 writer: *mut XmlTextWriter,
560 name: *const xmlChar,
561) -> c_int {
562 if writer.is_null() || name.is_null() {
563 return -1;
564 }
565 let w = unsafe { &mut *writer };
567
568 w.close_start_tag();
570
571 w.write_indent();
573
574 w.write_byte(b'<');
576 w.write_str(name);
577
578 let name_bytes = unsafe { c_str_to_vec(name) };
580 w.elem_stack.push((b"".to_vec(), name_bytes.clone()));
581 let stack_name = if name_bytes.last() == Some(&0) {
583 name_bytes[..name_bytes.len() - 1].to_vec()
584 } else {
585 name_bytes.clone()
586 };
587 w.stack.push(stack_name);
588 w.depth += 1;
589 w.in_start_tag = true;
590 w.state = WriterState::Element;
591
592 0
593}
594
595#[no_mangle]
607pub unsafe extern "C" fn xmlTextWriterEndElement(writer: *mut XmlTextWriter) -> c_int {
608 if writer.is_null() {
609 return -1;
610 }
611 let w = unsafe { &mut *writer };
613
614 if w.depth <= 0 {
615 return -1;
616 }
617
618 if w.in_start_tag {
620 w.write_raw(b"/>" as *const u8, 2);
623 w.in_start_tag = false;
624 } else {
625 if w.indent != 0 {
627 w.write_byte(b'\n');
628 let indent_str = w.indent_string.clone();
629 for _ in 0..(w.depth - 1) {
630 w.write_slice(&indent_str);
631 }
632 }
633
634 let name = w.stack.pop().unwrap_or_default();
636 w.write_raw(b"</" as *const u8, 2);
637 w.write_slice(&name);
638 w.write_byte(b'>');
639 }
640
641 w.depth -= 1;
642 w.elem_stack.pop();
643 w.state = WriterState::None;
644
645 0
646}
647
648#[no_mangle]
664pub unsafe extern "C" fn xmlTextWriterStartElementNS(
665 writer: *mut XmlTextWriter,
666 prefix: *const xmlChar,
667 name: *const xmlChar,
668 namespaceURI: *const xmlChar,
669) -> c_int {
670 if writer.is_null() || name.is_null() {
671 return -1;
672 }
673 let w = unsafe { &mut *writer };
675
676 w.close_start_tag();
677 w.write_indent();
678
679 w.write_byte(b'<');
680
681 let prefix_bytes = if prefix.is_null() {
682 Vec::new()
683 } else {
684 unsafe { c_str_to_vec(prefix) }
685 };
686
687 let name_bytes = unsafe { c_str_to_vec(name) };
688
689 if !prefix_bytes.is_empty() {
690 let p = if prefix_bytes.last() == Some(&0) {
692 &prefix_bytes[..prefix_bytes.len() - 1]
693 } else {
694 &prefix_bytes
695 };
696 w.write_slice(p);
697 w.write_byte(b':');
698 }
699 let n = if name_bytes.last() == Some(&0) {
701 &name_bytes[..name_bytes.len() - 1]
702 } else {
703 &name_bytes
704 };
705 w.write_slice(n);
706
707 if !namespaceURI.is_null() {
709 let ns_uri_bytes = unsafe { c_str_to_vec(namespaceURI) };
710 if !prefix_bytes.is_empty() {
711 w.write_raw(b" xmlns:" as *const u8, 7);
712 let p = if prefix_bytes.last() == Some(&0) {
713 &prefix_bytes[..prefix_bytes.len() - 1]
714 } else {
715 &prefix_bytes
716 };
717 w.write_slice(p);
718 } else {
719 w.write_raw(b" xmlns" as *const u8, 6);
720 }
721 w.write_raw(b"=\"" as *const u8, 2);
722 let ns = if ns_uri_bytes.last() == Some(&0) {
723 &ns_uri_bytes[..ns_uri_bytes.len() - 1]
724 } else {
725 &ns_uri_bytes
726 };
727 w.write_slice(ns);
728 w.write_byte(b'"');
729 }
730
731 w.elem_stack.push((prefix_bytes, name_bytes.clone()));
732 let stack_name = if name_bytes.last() == Some(&0) {
734 name_bytes[..name_bytes.len() - 1].to_vec()
735 } else {
736 name_bytes
737 };
738 w.stack.push(stack_name);
739 w.depth += 1;
740 w.in_start_tag = true;
741 w.state = WriterState::Element;
742
743 0
744}
745
746#[no_mangle]
761pub unsafe extern "C" fn xmlTextWriterWriteElement(
762 writer: *mut XmlTextWriter,
763 name: *const xmlChar,
764 content: *const xmlChar,
765) -> c_int {
766 if writer.is_null() || name.is_null() {
767 return -1;
768 }
769 let ret = xmlTextWriterStartElement(writer, name);
770 if ret != 0 {
771 return ret;
772 }
773 if !content.is_null() {
774 let ret2 = xmlTextWriterWriteString(writer, content);
775 if ret2 != 0 {
776 return ret2;
777 }
778 }
779 xmlTextWriterEndElement(writer)
780}
781
782#[no_mangle]
799pub unsafe extern "C" fn xmlTextWriterWriteElementNS(
800 writer: *mut XmlTextWriter,
801 prefix: *const xmlChar,
802 name: *const xmlChar,
803 nsURI: *const xmlChar,
804 content: *const xmlChar,
805) -> c_int {
806 if writer.is_null() || name.is_null() {
807 return -1;
808 }
809 let ret = xmlTextWriterStartElementNS(writer, prefix, name, nsURI);
810 if ret != 0 {
811 return ret;
812 }
813 if !content.is_null() {
814 let ret2 = xmlTextWriterWriteString(writer, content);
815 if ret2 != 0 {
816 return ret2;
817 }
818 }
819 xmlTextWriterEndElement(writer)
820}
821
822#[no_mangle]
834pub unsafe extern "C" fn xmlTextWriterFullEndElement(writer: *mut XmlTextWriter) -> c_int {
835 if writer.is_null() {
836 return -1;
837 }
838 let w = unsafe { &mut *writer };
840
841 if w.depth <= 0 {
842 return -1;
843 }
844
845 if w.in_start_tag {
847 w.write_byte(b'>');
848 w.in_start_tag = false;
849 }
850
851 if w.indent != 0 {
853 w.write_byte(b'\n');
854 let indent_str = w.indent_string.clone();
855 for _ in 0..(w.depth - 1) {
856 w.write_slice(&indent_str);
857 }
858 }
859
860 let name = w.stack.pop().unwrap_or_default();
862 w.write_raw(b"</" as *const u8, 2);
863 w.write_slice(&name);
864 w.write_byte(b'>');
865
866 w.depth -= 1;
867 w.elem_stack.pop();
868 w.state = WriterState::None;
869
870 0
871}
872
873#[no_mangle]
892pub unsafe extern "C" fn xmlTextWriterWriteAttribute(
893 writer: *mut XmlTextWriter,
894 name: *const xmlChar,
895 content: *const xmlChar,
896) -> c_int {
897 if writer.is_null() || name.is_null() || content.is_null() {
898 return -1;
899 }
900 let w = unsafe { &mut *writer };
902
903 if !w.in_start_tag {
904 return -1;
905 }
906
907 w.write_byte(b' ');
909 w.write_str(name);
910 w.write_raw(b"=\"" as *const u8, 2);
911
912 tree::serialize_attr_value(
914 unsafe { &mut *((*w.output).buffer as *mut _xmlBuffer) },
915 content,
916 );
917
918 w.write_byte(b'"');
919 w.state = WriterState::Attribute;
920
921 0
922}
923
924#[no_mangle]
941pub unsafe extern "C" fn xmlTextWriterWriteAttributeNS(
942 writer: *mut XmlTextWriter,
943 prefix: *const xmlChar,
944 name: *const xmlChar,
945 nsURI: *const xmlChar,
946 content: *const xmlChar,
947) -> c_int {
948 let _ = nsURI;
949 if writer.is_null() || name.is_null() || content.is_null() {
950 return -1;
951 }
952 let w = unsafe { &mut *writer };
954
955 if !w.in_start_tag {
956 return -1;
957 }
958
959 w.write_byte(b' ');
960
961 if !prefix.is_null() {
962 w.write_str(prefix);
963 w.write_byte(b':');
964 }
965 w.write_str(name);
966
967 w.write_raw(b"=\"" as *const u8, 2);
968
969 let buf = unsafe { &mut *((*w.output).buffer as *mut _xmlBuffer) };
971 tree::serialize_attr_value(buf, content);
972
973 w.write_byte(b'"');
974 w.state = WriterState::Attribute;
975
976 0
977}
978
979#[no_mangle]
994
995#[no_mangle]
1008pub unsafe extern "C" fn xmlTextWriterStartAttribute(
1009 writer: *mut XmlTextWriter,
1010 name: *const xmlChar,
1011) -> c_int {
1012 if writer.is_null() || name.is_null() {
1013 return -1;
1014 }
1015 let w = unsafe { &mut *writer };
1017
1018 if !w.in_start_tag {
1019 return -1;
1020 }
1021
1022 w.write_byte(b' ');
1023 w.write_str(name);
1024 w.write_raw(b"=\"" as *const u8, 2);
1025 w.state = WriterState::Attribute;
1026
1027 0
1028}
1029
1030#[no_mangle]
1046pub unsafe extern "C" fn xmlTextWriterStartAttributeNS(
1047 writer: *mut XmlTextWriter,
1048 prefix: *const xmlChar,
1049 name: *const xmlChar,
1050 nsURI: *const xmlChar,
1051) -> c_int {
1052 let _ = nsURI;
1053 if writer.is_null() || name.is_null() {
1054 return -1;
1055 }
1056 let w = unsafe { &mut *writer };
1058
1059 if !w.in_start_tag {
1060 return -1;
1061 }
1062
1063 w.write_byte(b' ');
1064 if !prefix.is_null() {
1065 w.write_str(prefix);
1066 w.write_byte(b':');
1067 }
1068 w.write_str(name);
1069 w.write_raw(b"=\"" as *const u8, 2);
1070 w.state = WriterState::Attribute;
1071
1072 0
1073}
1074
1075#[no_mangle]
1087pub unsafe extern "C" fn xmlTextWriterEndAttribute(writer: *mut XmlTextWriter) -> c_int {
1088 if writer.is_null() {
1089 return -1;
1090 }
1091 let w = unsafe { &mut *writer };
1093
1094 if w.state != WriterState::Attribute {
1095 return -1;
1096 }
1097
1098 w.write_byte(b'"');
1099 w.state = WriterState::Element;
1100
1101 0
1102}
1103
1104#[no_mangle]
1121pub unsafe extern "C" fn xmlTextWriterWriteString(
1122 writer: *mut XmlTextWriter,
1123 content: *const xmlChar,
1124) -> c_int {
1125 if writer.is_null() || content.is_null() {
1126 return -1;
1127 }
1128 let w = unsafe { &mut *writer };
1130
1131 w.close_start_tag();
1132
1133 let buf = if w.output.is_null() {
1134 return -1;
1135 } else {
1136 unsafe { &mut *((*w.output).buffer as *mut _xmlBuffer) }
1137 };
1138
1139 let len = tree::xml_strlen(content);
1140 tree::serialize_text(buf, content, len);
1141
1142 0
1143}
1144
1145#[no_mangle]
1158pub unsafe extern "C" fn xmlTextWriterWriteRaw(
1159 writer: *mut XmlTextWriter,
1160 content: *const xmlChar,
1161) -> c_int {
1162 if writer.is_null() || content.is_null() {
1163 return -1;
1164 }
1165 let w = unsafe { &mut *writer };
1167
1168 w.close_start_tag();
1169 w.write_str(content);
1170
1171 0
1172}
1173
1174#[no_mangle]
1189pub unsafe extern "C" fn xmlTextWriterWriteRawLen(
1190 writer: *mut XmlTextWriter,
1191 content: *const xmlChar,
1192 len: c_int,
1193) -> c_int {
1194 if writer.is_null() || content.is_null() || len <= 0 {
1195 return -1;
1196 }
1197 let w = unsafe { &mut *writer };
1199
1200 w.close_start_tag();
1201 w.write_raw(content, len);
1202
1203 0
1204}
1205
1206#[no_mangle]
1218
1219#[no_mangle]
1235pub unsafe extern "C" fn xmlTextWriterWriteBase64(
1236 writer: *mut XmlTextWriter,
1237 data: *const c_char,
1238 start: c_int,
1239 len: c_int,
1240) -> c_int {
1241 if writer.is_null() || data.is_null() || len <= 0 || start < 0 {
1242 return -1;
1243 }
1244 let w = unsafe { &mut *writer };
1246
1247 w.close_start_tag();
1248
1249 let data_slice =
1251 unsafe { core::slice::from_raw_parts(data.add(start as usize) as *const u8, len as usize) };
1252 let encoded = base64_encode(data_slice);
1253 w.write_slice(&encoded);
1254
1255 0
1256}
1257
1258#[no_mangle]
1274pub unsafe extern "C" fn xmlTextWriterWriteBinHex(
1275 writer: *mut XmlTextWriter,
1276 data: *const c_char,
1277 start: c_int,
1278 len: c_int,
1279) -> c_int {
1280 if writer.is_null() || data.is_null() || len <= 0 || start < 0 {
1281 return -1;
1282 }
1283 let w = unsafe { &mut *writer };
1285
1286 w.close_start_tag();
1287
1288 let data_slice =
1290 unsafe { core::slice::from_raw_parts(data.add(start as usize) as *const u8, len as usize) };
1291 let encoded = hex_encode(data_slice);
1292 w.write_slice(&encoded);
1293
1294 0
1295}
1296
1297#[no_mangle]
1310pub unsafe extern "C" fn xmlTextWriterWriteCDATA(
1311 writer: *mut XmlTextWriter,
1312 content: *const xmlChar,
1313) -> c_int {
1314 if writer.is_null() {
1315 return -1;
1316 }
1317 let w = unsafe { &mut *writer };
1319
1320 w.close_start_tag();
1321
1322 w.write_raw(b"<![CDATA[" as *const u8, 9);
1323 if !content.is_null() {
1324 w.write_str(content);
1325 }
1326 w.write_raw(b"]]>" as *const u8, 3);
1327
1328 0
1329}
1330
1331#[no_mangle]
1343pub unsafe extern "C" fn xmlTextWriterStartCDATA(writer: *mut XmlTextWriter) -> c_int {
1344 if writer.is_null() {
1345 return -1;
1346 }
1347 let w = unsafe { &mut *writer };
1349
1350 w.close_start_tag();
1351 w.write_raw(b"<![CDATA[" as *const u8, 9);
1352 w.state = WriterState::CData;
1353
1354 0
1355}
1356
1357#[no_mangle]
1369pub unsafe extern "C" fn xmlTextWriterEndCDATA(writer: *mut XmlTextWriter) -> c_int {
1370 if writer.is_null() {
1371 return -1;
1372 }
1373 let w = unsafe { &mut *writer };
1375
1376 if w.state != WriterState::CData {
1377 return -1;
1378 }
1379
1380 w.write_raw(b"]]>" as *const u8, 3);
1381 w.state = WriterState::None;
1382
1383 0
1384}
1385
1386#[no_mangle]
1399pub unsafe extern "C" fn xmlTextWriterWriteComment(
1400 writer: *mut XmlTextWriter,
1401 content: *const xmlChar,
1402) -> c_int {
1403 if writer.is_null() {
1404 return -1;
1405 }
1406 let w = unsafe { &mut *writer };
1408
1409 w.close_start_tag();
1410 w.write_indent();
1411 w.write_raw(b"<!--" as *const u8, 4);
1412 if !content.is_null() {
1413 w.write_str(content);
1414 }
1415 w.write_raw(b"-->" as *const u8, 3);
1416
1417 0
1418}
1419
1420#[no_mangle]
1432pub unsafe extern "C" fn xmlTextWriterStartComment(writer: *mut XmlTextWriter) -> c_int {
1433 if writer.is_null() {
1434 return -1;
1435 }
1436 let w = unsafe { &mut *writer };
1438
1439 w.close_start_tag();
1440 w.write_indent();
1441 w.write_raw(b"<!--" as *const u8, 4);
1442 w.state = WriterState::Comment;
1443
1444 0
1445}
1446
1447#[no_mangle]
1459pub unsafe extern "C" fn xmlTextWriterEndComment(writer: *mut XmlTextWriter) -> c_int {
1460 if writer.is_null() {
1461 return -1;
1462 }
1463 let w = unsafe { &mut *writer };
1465
1466 if w.state != WriterState::Comment {
1467 return -1;
1468 }
1469
1470 w.write_raw(b"-->" as *const u8, 3);
1471 w.state = WriterState::None;
1472
1473 0
1474}
1475
1476#[no_mangle]
1491pub unsafe extern "C" fn xmlTextWriterWritePI(
1492 writer: *mut XmlTextWriter,
1493 target: *const xmlChar,
1494 content: *const xmlChar,
1495) -> c_int {
1496 if writer.is_null() || target.is_null() {
1497 return -1;
1498 }
1499 let w = unsafe { &mut *writer };
1501
1502 w.close_start_tag();
1503 w.write_indent();
1504 w.write_raw(b"<?" as *const u8, 2);
1505 w.write_str(target);
1506 if !content.is_null() && unsafe { *content != 0 } {
1507 w.write_byte(b' ');
1508 w.write_str(content);
1509 }
1510 w.write_raw(b"?>" as *const u8, 2);
1511
1512 0
1513}
1514
1515#[no_mangle]
1528pub unsafe extern "C" fn xmlTextWriterStartPI(
1529 writer: *mut XmlTextWriter,
1530 target: *const xmlChar,
1531) -> c_int {
1532 if writer.is_null() || target.is_null() {
1533 return -1;
1534 }
1535 let w = unsafe { &mut *writer };
1537
1538 w.close_start_tag();
1539 w.write_indent();
1540 w.write_raw(b"<?" as *const u8, 2);
1541 w.write_str(target);
1542 w.write_byte(b' ');
1543 w.state = WriterState::PI;
1544
1545 0
1546}
1547
1548#[no_mangle]
1560pub unsafe extern "C" fn xmlTextWriterEndPI(writer: *mut XmlTextWriter) -> c_int {
1561 if writer.is_null() {
1562 return -1;
1563 }
1564 let w = unsafe { &mut *writer };
1566
1567 if w.state != WriterState::PI {
1568 return -1;
1569 }
1570
1571 w.write_raw(b"?>" as *const u8, 2);
1572 w.state = WriterState::None;
1573
1574 0
1575}
1576
1577#[no_mangle]
1598pub unsafe extern "C" fn xmlTextWriterWriteDTD(
1599 writer: *mut XmlTextWriter,
1600 name: *const xmlChar,
1601 pubid: *const xmlChar,
1602 sysid: *const xmlChar,
1603 subset: *const xmlChar,
1604) -> c_int {
1605 if writer.is_null() || name.is_null() {
1606 return -1;
1607 }
1608 let w = unsafe { &mut *writer };
1610
1611 w.close_start_tag();
1612 w.write_raw(b"<!DOCTYPE " as *const u8, 10);
1613 w.write_str(name);
1614
1615 if !pubid.is_null() {
1616 w.write_raw(b" PUBLIC \"" as *const u8, 9);
1617 w.write_str(pubid);
1618 w.write_byte(b'"');
1619 if !sysid.is_null() {
1620 w.write_byte(b' ');
1621 w.write_byte(b'"');
1622 w.write_str(sysid);
1623 w.write_byte(b'"');
1624 }
1625 } else if !sysid.is_null() {
1626 w.write_raw(b" SYSTEM \"" as *const u8, 9);
1627 w.write_str(sysid);
1628 w.write_byte(b'"');
1629 }
1630
1631 if !subset.is_null() {
1632 w.write_raw(b" [" as *const u8, 2);
1633 w.write_str(subset);
1634 w.write_byte(b']');
1635 }
1636
1637 w.write_byte(b'>');
1638
1639 if w.indent != 0 {
1640 w.write_byte(b'\n');
1641 }
1642
1643 0
1644}
1645
1646#[no_mangle]
1661pub unsafe extern "C" fn xmlTextWriterWriteDTDElement(
1662 writer: *mut XmlTextWriter,
1663 name: *const xmlChar,
1664 content: *const xmlChar,
1665) -> c_int {
1666 if writer.is_null() || name.is_null() || content.is_null() {
1667 return -1;
1668 }
1669 let w = unsafe { &mut *writer };
1671
1672 w.close_start_tag();
1673 w.write_indent();
1674 w.write_raw(b"<!ELEMENT " as *const u8, 10);
1675 w.write_str(name);
1676 w.write_byte(b' ');
1677 w.write_str(content);
1678 w.write_raw(b">" as *const u8, 1);
1679
1680 0
1681}
1682
1683#[no_mangle]
1698pub unsafe extern "C" fn xmlTextWriterWriteDTDAttribute(
1699 writer: *mut XmlTextWriter,
1700 name: *const xmlChar,
1701 content: *const xmlChar,
1702) -> c_int {
1703 if writer.is_null() || name.is_null() || content.is_null() {
1704 return -1;
1705 }
1706 let w = unsafe { &mut *writer };
1708
1709 w.close_start_tag();
1710 w.write_indent();
1711 w.write_raw(b"<!ATTLIST " as *const u8, 10);
1712 w.write_str(name);
1713 w.write_byte(b' ');
1714 w.write_str(content);
1715 w.write_raw(b">" as *const u8, 1);
1716
1717 0
1718}
1719
1720#[no_mangle]
1735pub unsafe extern "C" fn xmlTextWriterWriteDTDEntity(
1736 writer: *mut XmlTextWriter,
1737 name: *const xmlChar,
1738 content: *const xmlChar,
1739) -> c_int {
1740 if writer.is_null() || name.is_null() || content.is_null() {
1741 return -1;
1742 }
1743 let w = unsafe { &mut *writer };
1745
1746 w.close_start_tag();
1747 w.write_indent();
1748 w.write_raw(b"<!ENTITY " as *const u8, 9);
1749 w.write_str(name);
1750 w.write_raw(b" \"" as *const u8, 2);
1751 w.write_str(content);
1752 w.write_raw(b"\">" as *const u8, 2);
1753
1754 0
1755}
1756
1757#[no_mangle]
1773pub unsafe extern "C" fn xmlTextWriterWriteDTDNotation(
1774 writer: *mut XmlTextWriter,
1775 name: *const xmlChar,
1776 pubid: *const xmlChar,
1777 sysid: *const xmlChar,
1778) -> c_int {
1779 if writer.is_null() || name.is_null() {
1780 return -1;
1781 }
1782 let w = unsafe { &mut *writer };
1784
1785 w.close_start_tag();
1786 w.write_indent();
1787 w.write_raw(b"<!NOTATION " as *const u8, 11);
1788 w.write_str(name);
1789
1790 if !pubid.is_null() {
1791 w.write_raw(b" PUBLIC \"" as *const u8, 9);
1792 w.write_str(pubid);
1793 w.write_byte(b'"');
1794 if !sysid.is_null() {
1795 w.write_byte(b' ');
1796 w.write_byte(b'"');
1797 w.write_str(sysid);
1798 w.write_byte(b'"');
1799 }
1800 } else if !sysid.is_null() {
1801 w.write_raw(b" SYSTEM \"" as *const u8, 9);
1802 w.write_str(sysid);
1803 w.write_byte(b'"');
1804 }
1805
1806 w.write_raw(b">" as *const u8, 1);
1807
1808 0
1809}
1810
1811#[no_mangle]
1829pub unsafe extern "C" fn xmlTextWriterStartDTD(
1830 writer: *mut XmlTextWriter,
1831 name: *const xmlChar,
1832 pubid: *const xmlChar,
1833 sysid: *const xmlChar,
1834) -> c_int {
1835 if writer.is_null() || name.is_null() {
1836 return -1;
1837 }
1838 let w = unsafe { &mut *writer };
1840
1841 w.close_start_tag();
1842 w.write_raw(b"<!DOCTYPE " as *const u8, 10);
1843 w.write_str(name);
1844
1845 if !pubid.is_null() {
1846 w.write_raw(b" PUBLIC \"" as *const u8, 9);
1847 w.write_str(pubid);
1848 w.write_byte(b'"');
1849 if !sysid.is_null() {
1850 w.write_byte(b' ');
1851 w.write_byte(b'"');
1852 w.write_str(sysid);
1853 w.write_byte(b'"');
1854 }
1855 } else if !sysid.is_null() {
1856 w.write_raw(b" SYSTEM \"" as *const u8, 9);
1857 w.write_str(sysid);
1858 w.write_byte(b'"');
1859 }
1860
1861 w.write_raw(b" [" as *const u8, 2);
1862 w.state = WriterState::DTD;
1863
1864 if w.indent != 0 {
1865 w.write_byte(b'\n');
1866 }
1867
1868 0
1869}
1870
1871#[no_mangle]
1883pub unsafe extern "C" fn xmlTextWriterEndDTD(writer: *mut XmlTextWriter) -> c_int {
1884 if writer.is_null() {
1885 return -1;
1886 }
1887 let w = unsafe { &mut *writer };
1889
1890 if w.state != WriterState::DTD {
1891 return -1;
1892 }
1893
1894 if w.indent != 0 {
1895 w.write_byte(b'\n');
1896 }
1897 w.write_raw(b"]>" as *const u8, 2);
1898
1899 if w.indent != 0 {
1900 w.write_byte(b'\n');
1901 }
1902
1903 w.state = WriterState::None;
1904
1905 0
1906}
1907
1908#[no_mangle]
1921pub unsafe extern "C" fn xmlTextWriterStartDTDElement(
1922 writer: *mut XmlTextWriter,
1923 name: *const xmlChar,
1924) -> c_int {
1925 if writer.is_null() || name.is_null() {
1926 return -1;
1927 }
1928 let w = unsafe { &mut *writer };
1930
1931 w.close_start_tag();
1932 w.write_indent();
1933 w.write_raw(b"<!ELEMENT " as *const u8, 10);
1934 w.write_str(name);
1935 w.write_byte(b' ');
1936 w.state = WriterState::DTDElem;
1937
1938 0
1939}
1940
1941#[no_mangle]
1953pub unsafe extern "C" fn xmlTextWriterEndDTDElement(writer: *mut XmlTextWriter) -> c_int {
1954 if writer.is_null() {
1955 return -1;
1956 }
1957 let w = unsafe { &mut *writer };
1959
1960 if w.state != WriterState::DTDElem {
1961 return -1;
1962 }
1963
1964 w.write_raw(b">" as *const u8, 1);
1965 w.state = WriterState::DTD;
1966
1967 0
1968}
1969
1970#[no_mangle]
1983pub unsafe extern "C" fn xmlTextWriterStartDTDAttribute(
1984 writer: *mut XmlTextWriter,
1985 name: *const xmlChar,
1986) -> c_int {
1987 if writer.is_null() || name.is_null() {
1988 return -1;
1989 }
1990 let w = unsafe { &mut *writer };
1992
1993 w.close_start_tag();
1994 w.write_indent();
1995 w.write_raw(b"<!ATTLIST " as *const u8, 10);
1996 w.write_str(name);
1997 w.write_byte(b' ');
1998 w.state = WriterState::DTDAttr;
1999
2000 0
2001}
2002
2003#[no_mangle]
2015pub unsafe extern "C" fn xmlTextWriterEndDTDAttribute(writer: *mut XmlTextWriter) -> c_int {
2016 if writer.is_null() {
2017 return -1;
2018 }
2019 let w = unsafe { &mut *writer };
2021
2022 if w.state != WriterState::DTDAttr {
2023 return -1;
2024 }
2025
2026 w.write_raw(b">" as *const u8, 1);
2027 w.state = WriterState::DTD;
2028
2029 0
2030}
2031
2032#[no_mangle]
2045pub unsafe extern "C" fn xmlTextWriterStartDTDEntity(
2046 writer: *mut XmlTextWriter,
2047 name: *const xmlChar,
2048) -> c_int {
2049 if writer.is_null() || name.is_null() {
2050 return -1;
2051 }
2052 let w = unsafe { &mut *writer };
2054
2055 w.close_start_tag();
2056 w.write_indent();
2057 w.write_raw(b"<!ENTITY " as *const u8, 9);
2058 w.write_str(name);
2059 w.write_raw(b" \"" as *const u8, 2);
2060 w.state = WriterState::DTDEntity;
2061
2062 0
2063}
2064
2065#[no_mangle]
2077pub unsafe extern "C" fn xmlTextWriterEndDTDEntity(writer: *mut XmlTextWriter) -> c_int {
2078 if writer.is_null() {
2079 return -1;
2080 }
2081 let w = unsafe { &mut *writer };
2083
2084 if w.state != WriterState::DTDEntity {
2085 return -1;
2086 }
2087
2088 w.write_raw(b"\">" as *const u8, 2);
2089 w.state = WriterState::DTD;
2090
2091 0
2092}
2093
2094#[no_mangle]
2110pub unsafe extern "C" fn xmlTextWriterFlush(writer: *mut XmlTextWriter) -> c_int {
2111 if writer.is_null() {
2112 return -1;
2113 }
2114 let w = unsafe { &mut *writer };
2116
2117 if w.output.is_null() {
2118 return -1;
2119 }
2120
2121 w.close_start_tag();
2123
2124 io::output_buffer_flush(w.output)
2125}
2126
2127#[no_mangle]
2139pub unsafe extern "C" fn xmlTextWriterSetIndent(
2140 writer: *mut XmlTextWriter,
2141 indent: c_int,
2142) -> c_int {
2143 if writer.is_null() {
2144 return -1;
2145 }
2146 unsafe { (*writer).indent = indent };
2148 0
2149}
2150
2151#[no_mangle]
2164pub unsafe extern "C" fn xmlTextWriterSetIndentString(
2165 writer: *mut XmlTextWriter,
2166 str: *const xmlChar,
2167) -> c_int {
2168 if writer.is_null() || str.is_null() {
2169 return -1;
2170 }
2171 let w = unsafe { &mut *writer };
2173 w.indent_string = unsafe { c_str_to_vec(str) };
2174 0
2175}
2176
2177unsafe fn c_str_to_vec(s: *const u8) -> Vec<u8> {
2187 if s.is_null() {
2188 return Vec::new();
2189 }
2190 let len = tree::xml_strlen(s);
2191 let mut v = Vec::with_capacity(len as usize + 1);
2192 unsafe {
2193 for i in 0..len as isize {
2194 v.push(*s.offset(i));
2195 }
2196 v.push(0);
2197 }
2198 v
2199}
2200
2201fn base64_encode(data: &[u8]) -> Vec<u8> {
2203 const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2204 let mut result = Vec::with_capacity((data.len() + 2) / 3 * 4);
2205 for chunk in data.chunks(3) {
2206 let b0 = chunk[0];
2207 let b1 = chunk.get(1).copied().unwrap_or(0);
2208 let b2 = chunk.get(2).copied().unwrap_or(0);
2209
2210 result.push(CHARS[((b0 >> 2) & 0x3F) as usize]);
2211 result.push(CHARS[(((b0 << 4) | (b1 >> 4)) & 0x3F) as usize]);
2212 result.push(if chunk.len() > 1 {
2213 CHARS[(((b1 << 2) | (b2 >> 6)) & 0x3F) as usize]
2214 } else {
2215 b'='
2216 });
2217 result.push(if chunk.len() > 2 {
2218 CHARS[(b2 & 0x3F) as usize]
2219 } else {
2220 b'='
2221 });
2222 }
2223 result
2224}
2225
2226fn hex_encode(data: &[u8]) -> Vec<u8> {
2228 const CHARS: &[u8] = b"0123456789abcdef";
2229 let mut result = Vec::with_capacity(data.len() * 2);
2230 for &b in data {
2231 result.push(CHARS[((b >> 4) & 0x0F) as usize]);
2232 result.push(CHARS[(b & 0x0F) as usize]);
2233 }
2234 result
2235}
2236
2237#[cfg(test)]
2242mod tests {
2243 use super::*;
2244 use core::ptr;
2245
2246 unsafe fn create_test_writer() -> (*mut XmlTextWriter, *mut _xmlBuffer) {
2248 let buf = io::buf_create(256);
2249 assert!(!buf.is_null(), "buf_create failed");
2250 let out = io::output_buffer_create_buffer(buf, ptr::null_mut());
2251 assert!(!out.is_null(), "output_buffer_create_buffer failed");
2252 let writer = xmlNewTextWriter(out);
2253 assert!(!writer.is_null(), "xmlNewTextWriter failed");
2254 (writer, buf)
2255 }
2256
2257 unsafe fn buf_to_string(buf: *mut _xmlBuffer) -> String {
2259 let content = io::buf_content(buf);
2260 let len = io::buf_length(buf);
2261 if content.is_null() || len <= 0 {
2262 return String::new();
2263 }
2264 let slice = unsafe { core::slice::from_raw_parts(content, len as usize) };
2265 String::from_utf8_lossy(slice).to_string()
2266 }
2267
2268 unsafe fn flush_and_get(writer: *mut XmlTextWriter, buf: *mut _xmlBuffer) -> String {
2270 xmlTextWriterFlush(writer);
2271 buf_to_string(buf)
2272 }
2273
2274 #[test]
2279 fn test_write_simple_document() {
2280 unsafe {
2281 let (writer, buf) = create_test_writer();
2282
2283 let r = xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
2284 assert_eq!(r, 0, "StartDocument failed");
2285
2286 let r = xmlTextWriterStartElement(writer, b"root\0" as *const u8);
2287 assert_eq!(r, 0, "StartElement(root) failed");
2288
2289 let r = xmlTextWriterWriteString(writer, b"Hello, World!\0" as *const u8);
2290 assert_eq!(r, 0, "WriteString failed");
2291
2292 let r = xmlTextWriterEndElement(writer);
2293 assert_eq!(r, 0, "EndElement failed");
2294
2295 let r = xmlTextWriterEndDocument(writer);
2296 assert_eq!(r, 0, "EndDocument failed");
2297
2298 let result = flush_and_get(writer, buf);
2299 assert!(
2300 result.contains("<?xml version=\"1.0\"?>"),
2301 "Missing XML declaration. Got: {}",
2302 result
2303 );
2304 assert!(
2305 result.contains("<root>"),
2306 "Missing <root> start tag. Got: {}",
2307 result
2308 );
2309 assert!(
2310 result.contains("Hello, World!"),
2311 "Missing content. Got: {}",
2312 result
2313 );
2314 assert!(
2315 result.contains("</root>"),
2316 "Missing </root> end tag. Got: {}",
2317 result
2318 );
2319
2320 xmlFreeTextWriter(writer);
2321 io::buf_free(buf);
2322 }
2323 }
2324
2325 #[test]
2330 fn test_write_element_with_attributes() {
2331 unsafe {
2332 let (writer, buf) = create_test_writer();
2333
2334 xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
2335 xmlTextWriterStartElement(writer, b"root\0" as *const u8);
2336 xmlTextWriterWriteAttribute(writer, b"id\0" as *const u8, b"123\0" as *const u8);
2337 xmlTextWriterWriteAttribute(
2338 writer,
2339 b"name\0" as *const u8,
2340 b"test & demo\0" as *const u8,
2341 );
2342 xmlTextWriterEndElement(writer);
2343 xmlTextWriterEndDocument(writer);
2344
2345 let result = flush_and_get(writer, buf);
2346 assert!(
2347 result.contains("id=\"123\""),
2348 "Missing id attribute. Got: {}",
2349 result
2350 );
2351 assert!(
2352 result.contains("name=\"test & demo\""),
2353 "Missing or improperly escaped name attribute. Got: {}",
2354 result
2355 );
2356 assert!(
2357 result.contains("<root"),
2358 "Missing root element. Got: {}",
2359 result
2360 );
2361
2362 xmlFreeTextWriter(writer);
2363 io::buf_free(buf);
2364 }
2365 }
2366
2367 #[test]
2372 fn test_write_with_namespaces() {
2373 unsafe {
2374 let (writer, buf) = create_test_writer();
2375
2376 xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
2377 xmlTextWriterStartElementNS(
2378 writer,
2379 b"ns\0" as *const u8,
2380 b"root\0" as *const u8,
2381 b"http://example.com/ns\0" as *const u8,
2382 );
2383 xmlTextWriterWriteAttributeNS(
2384 writer,
2385 ptr::null(),
2386 b"attr\0" as *const u8,
2387 ptr::null(),
2388 b"value\0" as *const u8,
2389 );
2390 xmlTextWriterEndElement(writer);
2391 xmlTextWriterEndDocument(writer);
2392
2393 let result = flush_and_get(writer, buf);
2394 assert!(
2395 result.contains("ns:root"),
2396 "Missing namespace prefix. Got: {}",
2397 result
2398 );
2399 assert!(
2400 result.contains("xmlns:ns=\"http://example.com/ns\""),
2401 "Missing xmlns declaration. Got: {}",
2402 result
2403 );
2404
2405 xmlFreeTextWriter(writer);
2406 io::buf_free(buf);
2407 }
2408 }
2409
2410 #[test]
2415 fn test_write_text_cdata_comment_pi() {
2416 unsafe {
2417 let (writer, buf) = create_test_writer();
2418
2419 xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
2420
2421 xmlTextWriterStartElement(writer, b"doc\0" as *const u8);
2422 xmlTextWriterWriteString(writer, b"text content\0" as *const u8);
2423 xmlTextWriterEndElement(writer);
2424
2425 xmlTextWriterWriteComment(writer, b"a comment\0" as *const u8);
2426
2427 xmlTextWriterWritePI(writer, b"target\0" as *const u8, b"data\0" as *const u8);
2428
2429 xmlTextWriterStartElement(writer, b"cdata\0" as *const u8);
2430 xmlTextWriterWriteCDATA(writer, b"<greeting>Hello</greeting>\0" as *const u8);
2431 xmlTextWriterEndElement(writer);
2432
2433 xmlTextWriterEndDocument(writer);
2434
2435 let result = flush_and_get(writer, buf);
2436 assert!(
2437 result.contains("text content"),
2438 "Missing text content. Got: {}",
2439 result
2440 );
2441 assert!(
2442 result.contains("<!--a comment-->"),
2443 "Missing comment. Got: {}",
2444 result
2445 );
2446 assert!(
2447 result.contains("<?target data?>"),
2448 "Missing PI. Got: {}",
2449 result
2450 );
2451 assert!(
2452 result.contains("<![CDATA["),
2453 "Missing CDATA start. Got: {}",
2454 result
2455 );
2456 assert!(
2457 result.contains("<greeting>Hello</greeting>"),
2458 "Missing CDATA content. Got: {}",
2459 result
2460 );
2461
2462 xmlFreeTextWriter(writer);
2463 io::buf_free(buf);
2464 }
2465 }
2466
2467 #[test]
2472 fn test_write_dtd() {
2473 unsafe {
2474 let (writer, buf) = create_test_writer();
2475
2476 xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
2477
2478 xmlTextWriterWriteDTD(
2479 writer,
2480 b"html\0" as *const u8,
2481 ptr::null(),
2482 b"http://www.w3.org/TR/html4/strict.dtd\0" as *const u8,
2483 ptr::null(),
2484 );
2485
2486 xmlTextWriterStartElement(writer, b"html\0" as *const u8);
2487 xmlTextWriterEndElement(writer);
2488 xmlTextWriterEndDocument(writer);
2489
2490 let result = flush_and_get(writer, buf);
2491 assert!(
2492 result.contains("<!DOCTYPE html SYSTEM"),
2493 "Missing DTD. Got: {}",
2494 result
2495 );
2496
2497 xmlFreeTextWriter(writer);
2498 io::buf_free(buf);
2499 }
2500 }
2501
2502 #[test]
2507 fn test_write_dtd_with_subset() {
2508 unsafe {
2509 let (writer, buf) = create_test_writer();
2510
2511 xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
2512
2513 xmlTextWriterStartDTD(writer, b"root\0" as *const u8, ptr::null(), ptr::null());
2514 xmlTextWriterWriteDTDElement(
2515 writer,
2516 b"child\0" as *const u8,
2517 b"(#PCDATA)\0" as *const u8,
2518 );
2519 xmlTextWriterWriteDTDAttribute(
2520 writer,
2521 b"child\0" as *const u8,
2522 b"id CDATA #IMPLIED\0" as *const u8,
2523 );
2524 xmlTextWriterWriteDTDEntity(
2525 writer,
2526 b"copy\0" as *const u8,
2527 b"Copyright Me\0" as *const u8,
2528 );
2529 xmlTextWriterWriteDTDNotation(
2530 writer,
2531 b"note\0" as *const u8,
2532 b"PublicID\0" as *const u8,
2533 ptr::null(),
2534 );
2535 xmlTextWriterEndDTD(writer);
2536
2537 xmlTextWriterStartElement(writer, b"root\0" as *const u8);
2538 xmlTextWriterEndElement(writer);
2539 xmlTextWriterEndDocument(writer);
2540
2541 let result = flush_and_get(writer, buf);
2542 assert!(
2543 result.contains("<!DOCTYPE root"),
2544 "Missing DTD start. Got: {}",
2545 result
2546 );
2547 assert!(
2548 result.contains("<!ELEMENT child (#PCDATA)>"),
2549 "Missing DTD element. Got: {}",
2550 result
2551 );
2552 assert!(
2553 result.contains("<!ATTLIST child id CDATA #IMPLIED>"),
2554 "Missing DTD attribute. Got: {}",
2555 result
2556 );
2557 assert!(
2558 result.contains("<!ENTITY copy \"Copyright Me\">"),
2559 "Missing DTD entity. Got: {}",
2560 result
2561 );
2562 assert!(
2563 result.contains("<!NOTATION note PUBLIC \"PublicID\">"),
2564 "Missing DTD notation. Got: {}",
2565 result
2566 );
2567
2568 xmlFreeTextWriter(writer);
2569 io::buf_free(buf);
2570 }
2571 }
2572
2573 #[test]
2578 fn test_indentation_control() {
2579 unsafe {
2580 let (writer, buf) = create_test_writer();
2581
2582 xmlTextWriterSetIndent(writer, 1);
2584 xmlTextWriterSetIndentString(writer, b"\t\0" as *const u8);
2585
2586 xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
2587 xmlTextWriterStartElement(writer, b"root\0" as *const u8);
2588 xmlTextWriterStartElement(writer, b"child\0" as *const u8);
2589 xmlTextWriterWriteString(writer, b"content\0" as *const u8);
2590 xmlTextWriterEndElement(writer);
2591 xmlTextWriterEndElement(writer);
2592 xmlTextWriterEndDocument(writer);
2593
2594 let result = flush_and_get(writer, buf);
2595
2596 assert!(
2598 result.contains('\t'),
2599 "Expected tab indentation. Got: {}",
2600 result
2601 );
2602 assert!(result.contains("<root>"), "Missing root. Got: {}", result);
2604 assert!(result.contains("<child>"), "Missing child. Got: {}", result);
2605
2606 xmlFreeTextWriter(writer);
2607 io::buf_free(buf);
2608 }
2609 }
2610
2611 #[test]
2616 fn test_memory_output() {
2617 unsafe {
2618 let buf = io::buf_create(256);
2619 assert!(!buf.is_null(), "buf_create failed");
2620
2621 let writer = xmlNewTextWriterMemory(buf, 0);
2622 assert!(!writer.is_null(), "xmlNewTextWriterMemory failed");
2623
2624 xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
2625 xmlTextWriterStartElement(writer, b"root\0" as *const u8);
2626 xmlTextWriterWriteString(writer, b"memory test\0" as *const u8);
2627 xmlTextWriterEndElement(writer);
2628 xmlTextWriterEndDocument(writer);
2629
2630 xmlTextWriterFlush(writer);
2631 let result = buf_to_string(buf);
2632 assert!(
2633 result.contains("memory test"),
2634 "Missing content in memory output. Got: {}",
2635 result
2636 );
2637
2638 xmlFreeTextWriter(writer);
2639 io::buf_free(buf);
2640 }
2641 }
2642
2643 #[test]
2648 fn test_flush_and_close() {
2649 unsafe {
2650 let (writer, buf) = create_test_writer();
2651
2652 xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
2653 xmlTextWriterStartElement(writer, b"root\0" as *const u8);
2654 xmlTextWriterWriteString(writer, b"flush me\0" as *const u8);
2655
2656 let r = xmlTextWriterFlush(writer);
2658 assert!(r >= 0, "Flush should return non-negative, got {}", r);
2659
2660 xmlTextWriterEndElement(writer);
2661 xmlTextWriterEndDocument(writer);
2662
2663 xmlFreeTextWriter(writer);
2664 io::buf_free(buf);
2665 }
2666 }
2667
2668 #[test]
2673 fn test_null_handling() {
2674 unsafe {
2675 assert_eq!(
2677 xmlTextWriterStartDocument(ptr::null_mut(), ptr::null(), ptr::null(), ptr::null()),
2678 -1
2679 );
2680 assert_eq!(xmlTextWriterEndDocument(ptr::null_mut()), -1);
2681 assert_eq!(
2682 xmlTextWriterStartElement(ptr::null_mut(), b"x\0" as *const u8),
2683 -1
2684 );
2685 assert_eq!(xmlTextWriterEndElement(ptr::null_mut()), -1);
2686 assert_eq!(
2687 xmlTextWriterWriteString(ptr::null_mut(), b"x\0" as *const u8),
2688 -1
2689 );
2690 assert_eq!(
2691 xmlTextWriterWriteRaw(ptr::null_mut(), b"x\0" as *const u8),
2692 -1
2693 );
2694 assert_eq!(
2695 xmlTextWriterWriteCDATA(ptr::null_mut(), b"x\0" as *const u8),
2696 -1
2697 );
2698 assert_eq!(
2699 xmlTextWriterWriteComment(ptr::null_mut(), b"x\0" as *const u8),
2700 -1
2701 );
2702 assert_eq!(
2703 xmlTextWriterWritePI(ptr::null_mut(), b"x\0" as *const u8, ptr::null()),
2704 -1
2705 );
2706 assert_eq!(xmlTextWriterFlush(ptr::null_mut()), -1);
2707 assert_eq!(xmlTextWriterSetIndent(ptr::null_mut(), 1), -1);
2708 assert_eq!(
2709 xmlTextWriterSetIndentString(ptr::null_mut(), b" \0" as *const u8),
2710 -1
2711 );
2712 assert_eq!(
2713 xmlTextWriterWriteAttribute(
2714 ptr::null_mut(),
2715 b"n\0" as *const u8,
2716 b"v\0" as *const u8
2717 ),
2718 -1
2719 );
2720
2721 xmlFreeTextWriter(ptr::null_mut());
2723 }
2724 }
2725
2726 #[test]
2731 fn test_nested_elements() {
2732 unsafe {
2733 let (writer, buf) = create_test_writer();
2734
2735 xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
2736 xmlTextWriterStartElement(writer, b"a\0" as *const u8);
2737 xmlTextWriterStartElement(writer, b"b\0" as *const u8);
2738 xmlTextWriterStartElement(writer, b"c\0" as *const u8);
2739 xmlTextWriterWriteString(writer, b"deep\0" as *const u8);
2740 xmlTextWriterEndElement(writer);
2741 xmlTextWriterEndElement(writer);
2742 xmlTextWriterEndElement(writer);
2743 xmlTextWriterEndDocument(writer);
2744
2745 let result = flush_and_get(writer, buf);
2746 assert!(result.contains("<a>"), "Missing <a>. Got: {}", result);
2747 assert!(result.contains("<b>"), "Missing <b>. Got: {}", result);
2748 assert!(result.contains("<c>"), "Missing <c>. Got: {}", result);
2749 assert!(result.contains("</a>"), "Missing </a>. Got: {}", result);
2750 assert!(result.contains("</b>"), "Missing </b>. Got: {}", result);
2751 assert!(result.contains("</c>"), "Missing </c>. Got: {}", result);
2752
2753 xmlFreeTextWriter(writer);
2754 io::buf_free(buf);
2755 }
2756 }
2757
2758 #[test]
2763 fn test_self_closing_element() {
2764 unsafe {
2765 let (writer, buf) = create_test_writer();
2766
2767 xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
2768 xmlTextWriterStartElement(writer, b"empty\0" as *const u8);
2769 xmlTextWriterEndElement(writer);
2770 xmlTextWriterEndDocument(writer);
2771
2772 let result = flush_and_get(writer, buf);
2773 assert!(
2774 result.contains("<empty/>"),
2775 "Expected self-closing <empty/>. Got: {}",
2776 result
2777 );
2778
2779 xmlFreeTextWriter(writer);
2780 io::buf_free(buf);
2781 }
2782 }
2783
2784 #[test]
2789 fn test_full_end_element() {
2790 unsafe {
2791 let (writer, buf) = create_test_writer();
2792
2793 xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
2794 xmlTextWriterStartElement(writer, b"container\0" as *const u8);
2795 xmlTextWriterFullEndElement(writer);
2796 xmlTextWriterEndDocument(writer);
2797
2798 let result = flush_and_get(writer, buf);
2799 assert!(
2800 result.contains("<container>"),
2801 "Missing <container>. Got: {}",
2802 result
2803 );
2804 assert!(
2805 result.contains("</container>"),
2806 "Missing </container>. Got: {}",
2807 result
2808 );
2809
2810 xmlFreeTextWriter(writer);
2811 io::buf_free(buf);
2812 }
2813 }
2814
2815 #[test]
2820 fn test_write_element_inline() {
2821 unsafe {
2822 let (writer, buf) = create_test_writer();
2823
2824 xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
2825 xmlTextWriterWriteElement(writer, b"greeting\0" as *const u8, b"Hello\0" as *const u8);
2826 xmlTextWriterEndDocument(writer);
2827
2828 let result = flush_and_get(writer, buf);
2829 assert!(
2830 result.contains("<greeting>Hello</greeting>"),
2831 "Expected <greeting>Hello</greeting>. Got: {}",
2832 result
2833 );
2834
2835 xmlFreeTextWriter(writer);
2836 io::buf_free(buf);
2837 }
2838 }
2839
2840 #[test]
2845 fn test_text_escaping() {
2846 unsafe {
2847 let (writer, buf) = create_test_writer();
2848
2849 xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
2850 xmlTextWriterStartElement(writer, b"esc\0" as *const u8);
2851 xmlTextWriterWriteString(writer, b"a < b & b > a\0" as *const u8);
2852 xmlTextWriterEndElement(writer);
2853 xmlTextWriterEndDocument(writer);
2854
2855 let result = flush_and_get(writer, buf);
2856 assert!(
2857 result.contains("a < b & b > a"),
2858 "Expected escaped content. Got: {}",
2859 result
2860 );
2861
2862 xmlFreeTextWriter(writer);
2863 io::buf_free(buf);
2864 }
2865 }
2866
2867 #[test]
2872 fn test_raw_content() {
2873 unsafe {
2874 let (writer, buf) = create_test_writer();
2875
2876 xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
2877 xmlTextWriterStartElement(writer, b"raw\0" as *const u8);
2878 xmlTextWriterWriteRaw(writer, b"<unencoded>&special;</unencoded>\0" as *const u8);
2879 xmlTextWriterEndElement(writer);
2880 xmlTextWriterEndDocument(writer);
2881
2882 let result = flush_and_get(writer, buf);
2883 assert!(
2884 result.contains("<unencoded>&special;</unencoded>"),
2885 "Expected raw unencoded content. Got: {}",
2886 result
2887 );
2888
2889 xmlFreeTextWriter(writer);
2890 io::buf_free(buf);
2891 }
2892 }
2893
2894 #[test]
2899 fn test_base64_write() {
2900 unsafe {
2901 let (writer, buf) = create_test_writer();
2902
2903 xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
2904 xmlTextWriterStartElement(writer, b"data\0" as *const u8);
2905 let test_data = b"Hello, World!";
2906 xmlTextWriterWriteBase64(
2907 writer,
2908 test_data.as_ptr() as *const c_char,
2909 0,
2910 test_data.len() as c_int,
2911 );
2912 xmlTextWriterEndElement(writer);
2913 xmlTextWriterEndDocument(writer);
2914
2915 let result = flush_and_get(writer, buf);
2916 assert!(
2917 result.contains("SGVsbG8sIFdvcmxkIQ"),
2918 "Expected Base64-encoded content. Got: {}",
2919 result
2920 );
2921
2922 xmlFreeTextWriter(writer);
2923 io::buf_free(buf);
2924 }
2925 }
2926
2927 #[test]
2932 fn test_incremental_cdata_comment_pi() {
2933 unsafe {
2934 let (writer, buf) = create_test_writer();
2935
2936 xmlTextWriterStartDocument(writer, ptr::null(), ptr::null(), ptr::null());
2937
2938 xmlTextWriterStartElement(writer, b"inc\0" as *const u8);
2940 xmlTextWriterStartCDATA(writer);
2941 xmlTextWriterWriteString(writer, b"cdata content\0" as *const u8);
2942 xmlTextWriterEndCDATA(writer);
2943 xmlTextWriterEndElement(writer);
2944
2945 xmlTextWriterStartComment(writer);
2947 xmlTextWriterWriteString(writer, b"comment text\0" as *const u8);
2948 xmlTextWriterEndComment(writer);
2949
2950 xmlTextWriterStartPI(writer, b"xml-stylesheet\0" as *const u8);
2952 xmlTextWriterWriteString(
2953 writer,
2954 b"type=\"text/xsl\" href=\"style.xsl\"\0" as *const u8,
2955 );
2956 xmlTextWriterEndPI(writer);
2957
2958 xmlTextWriterEndDocument(writer);
2959
2960 let result = flush_and_get(writer, buf);
2961 assert!(
2962 result.contains("<![CDATA["),
2963 "Missing CDATA. Got: {}",
2964 result
2965 );
2966 assert!(
2967 result.contains("<!--comment text-->"),
2968 "Missing comment. Got: {}",
2969 result
2970 );
2971 assert!(
2972 result.contains("<?xml-stylesheet"),
2973 "Missing PI. Got: {}",
2974 result
2975 );
2976
2977 xmlFreeTextWriter(writer);
2978 io::buf_free(buf);
2979 }
2980 }
2981
2982 #[test]
2987 fn test_new_writer_filename_null() {
2988 unsafe {
2989 let writer = xmlNewTextWriterFilename(ptr::null(), 0);
2990 assert!(writer.is_null(), "Expected NULL for null URI");
2991 }
2992 }
2993
2994 #[test]
2999 fn test_new_writer_null_output() {
3000 unsafe {
3001 let writer = xmlNewTextWriter(ptr::null_mut());
3002 assert!(writer.is_null(), "Expected NULL for null output");
3003 }
3004 }
3005}