1#![allow(clippy::cast_possible_wrap)]
22#![warn(missing_docs)]
23
24mod types;
25
26pub use types::{TX_NULL_NODE, TxDocument, TxError, TxNodeId, TxNodeType, TxPrinter};
27
28use std::ffi::{CStr, CString, c_char, c_double, c_int};
29use std::ptr;
30
31unsafe fn cstr_to_str<'a>(s: *const c_char) -> Option<&'a str> {
44 if s.is_null() {
45 return None;
46 }
47 unsafe { CStr::from_ptr(s) }.to_str().ok()
48}
49
50fn cache_str(doc: &mut TxDocument, s: &str) -> *const c_char {
55 match CString::new(s) {
56 Ok(cs) => {
57 let ptr = cs.as_ptr();
58 doc.string_cache.push(cs);
59 ptr
60 }
61 Err(_) => ptr::null(),
62 }
63}
64
65macro_rules! ffi_catch {
68 ($default:expr, $body:expr) => {
69 match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| $body)) {
70 Ok(val) => val,
71 Err(_) => $default,
72 }
73 };
74}
75
76#[unsafe(no_mangle)]
89pub extern "C" fn tx_document_new() -> *mut TxDocument {
90 ffi_catch!(ptr::null_mut(), {
91 Box::into_raw(Box::new(TxDocument::new()))
92 })
93}
94
95#[unsafe(no_mangle)]
102pub unsafe extern "C" fn tx_document_free(doc: *mut TxDocument) {
103 if doc.is_null() {
104 return;
105 }
106 let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
107 drop(unsafe { Box::from_raw(doc) });
108 }));
109}
110
111#[unsafe(no_mangle)]
117pub unsafe extern "C" fn tx_document_clear(doc: *mut TxDocument) {
118 if doc.is_null() {
119 return;
120 }
121 let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
122 let doc = unsafe { &mut *doc };
123 doc.invalidate_caches();
124 doc.doc.clear();
125 }));
126}
127
128#[unsafe(no_mangle)]
135pub unsafe extern "C" fn tx_document_parse(doc: *mut TxDocument, xml: *const c_char) -> TxError {
136 if doc.is_null() {
137 return TxError::TxErrorInvalidNodeId;
138 }
139 ffi_catch!(TxError::TxErrorInvalidNodeId, {
140 let doc = unsafe { &mut *doc };
141 let Some(xml_str) = (unsafe { cstr_to_str(xml) }) else {
142 return TxError::TxErrorEmptyDocument;
143 };
144 doc.invalidate_caches();
145 TxError::from_result(&doc.doc.parse_str(xml_str))
146 })
147}
148
149#[unsafe(no_mangle)]
157pub unsafe extern "C" fn tx_document_load_file(
158 doc: *mut TxDocument,
159 path: *const c_char,
160) -> TxError {
161 if doc.is_null() {
162 return TxError::TxErrorInvalidNodeId;
163 }
164 ffi_catch!(TxError::TxErrorInvalidNodeId, {
165 let doc = unsafe { &mut *doc };
166 let Some(path_str) = (unsafe { cstr_to_str(path) }) else {
167 return TxError::TxErrorFileNotFound;
168 };
169 doc.invalidate_caches();
170 TxError::from_result(&doc.doc.load_file_mut(path_str))
171 })
172}
173
174#[unsafe(no_mangle)]
182pub unsafe extern "C" fn tx_document_save_file(
183 doc: *const TxDocument,
184 path: *const c_char,
185) -> TxError {
186 if doc.is_null() {
187 return TxError::TxErrorInvalidNodeId;
188 }
189 ffi_catch!(TxError::TxErrorInvalidNodeId, {
190 let doc = unsafe { &*doc };
191 let Some(path_str) = (unsafe { cstr_to_str(path) }) else {
192 return TxError::TxErrorFileNotFound;
193 };
194 TxError::from_result(&doc.doc.save_file(path_str))
195 })
196}
197
198#[unsafe(no_mangle)]
207pub unsafe extern "C" fn tx_document_to_string(doc: *mut TxDocument) -> *const c_char {
208 if doc.is_null() {
209 return ptr::null();
210 }
211 ffi_catch!(ptr::null(), {
212 let doc = unsafe { &mut *doc };
213 let s = doc.doc.to_string();
214 match CString::new(s) {
215 Ok(cs) => {
216 let ptr = cs.as_ptr();
217 doc.cached_to_string = Some(cs);
218 ptr
219 }
220 Err(_) => ptr::null(),
221 }
222 })
223}
224
225#[unsafe(no_mangle)]
234pub unsafe extern "C" fn tx_document_to_string_compact(doc: *mut TxDocument) -> *const c_char {
235 if doc.is_null() {
236 return ptr::null();
237 }
238 ffi_catch!(ptr::null(), {
239 let doc = unsafe { &mut *doc };
240 let s = doc.doc.to_string_compact();
241 match CString::new(s) {
242 Ok(cs) => {
243 let ptr = cs.as_ptr();
244 doc.cached_to_string_compact = Some(cs);
245 ptr
246 }
247 Err(_) => ptr::null(),
248 }
249 })
250}
251
252#[unsafe(no_mangle)]
260pub unsafe extern "C" fn tx_document_error(doc: *const TxDocument) -> TxError {
261 if doc.is_null() {
262 return TxError::TxErrorInvalidNodeId;
263 }
264 ffi_catch!(TxError::TxErrorInvalidNodeId, {
265 let doc = unsafe { &*doc };
266 match doc.doc.error() {
267 Some(e) => TxError::from_xml_error(&e),
268 None => TxError::TxSuccess,
269 }
270 })
271}
272
273#[unsafe(no_mangle)]
279pub unsafe extern "C" fn tx_document_error_line(doc: *const TxDocument) -> c_int {
280 if doc.is_null() {
281 return 0;
282 }
283 ffi_catch!(0, {
284 let doc = unsafe { &*doc };
285 doc.doc.error_line().unwrap_or(0) as c_int
286 })
287}
288
289#[unsafe(no_mangle)]
298pub unsafe extern "C" fn tx_document_error_name(doc: *mut TxDocument) -> *const c_char {
299 if doc.is_null() {
300 return ptr::null();
301 }
302 ffi_catch!(ptr::null(), {
303 let doc = unsafe { &mut *doc };
304 match doc.doc.error() {
305 Some(e) => {
306 let name = e.name();
307 match CString::new(name) {
308 Ok(cs) => {
309 let ptr = cs.as_ptr();
310 doc.cached_error_name = Some(cs);
311 ptr
312 }
313 Err(_) => ptr::null(),
314 }
315 }
316 None => ptr::null(),
317 }
318 })
319}
320
321#[unsafe(no_mangle)]
332pub unsafe extern "C" fn tx_new_element(doc: *mut TxDocument, name: *const c_char) -> TxNodeId {
333 if doc.is_null() {
334 return TX_NULL_NODE;
335 }
336 ffi_catch!(TX_NULL_NODE, {
337 let doc = unsafe { &mut *doc };
338 let Some(name_str) = (unsafe { cstr_to_str(name) }) else {
339 return TX_NULL_NODE;
340 };
341 doc.invalidate_caches();
342 TxNodeId::from_node_id(doc.doc.new_element(name_str))
343 })
344}
345
346#[unsafe(no_mangle)]
353pub unsafe extern "C" fn tx_new_text(doc: *mut TxDocument, text: *const c_char) -> TxNodeId {
354 if doc.is_null() {
355 return TX_NULL_NODE;
356 }
357 ffi_catch!(TX_NULL_NODE, {
358 let doc = unsafe { &mut *doc };
359 let Some(text_str) = (unsafe { cstr_to_str(text) }) else {
360 return TX_NULL_NODE;
361 };
362 doc.invalidate_caches();
363 TxNodeId::from_node_id(doc.doc.new_text(text_str))
364 })
365}
366
367#[unsafe(no_mangle)]
374pub unsafe extern "C" fn tx_new_comment(doc: *mut TxDocument, text: *const c_char) -> TxNodeId {
375 if doc.is_null() {
376 return TX_NULL_NODE;
377 }
378 ffi_catch!(TX_NULL_NODE, {
379 let doc = unsafe { &mut *doc };
380 let Some(text_str) = (unsafe { cstr_to_str(text) }) else {
381 return TX_NULL_NODE;
382 };
383 doc.invalidate_caches();
384 TxNodeId::from_node_id(doc.doc.new_comment(text_str))
385 })
386}
387
388#[unsafe(no_mangle)]
395pub unsafe extern "C" fn tx_new_declaration(doc: *mut TxDocument, text: *const c_char) -> TxNodeId {
396 if doc.is_null() {
397 return TX_NULL_NODE;
398 }
399 ffi_catch!(TX_NULL_NODE, {
400 let doc = unsafe { &mut *doc };
401 let Some(text_str) = (unsafe { cstr_to_str(text) }) else {
402 return TX_NULL_NODE;
403 };
404 doc.invalidate_caches();
405 TxNodeId::from_node_id(doc.doc.new_declaration(text_str))
406 })
407}
408
409#[unsafe(no_mangle)]
416pub unsafe extern "C" fn tx_new_unknown(doc: *mut TxDocument, text: *const c_char) -> TxNodeId {
417 if doc.is_null() {
418 return TX_NULL_NODE;
419 }
420 ffi_catch!(TX_NULL_NODE, {
421 let doc = unsafe { &mut *doc };
422 let Some(text_str) = (unsafe { cstr_to_str(text) }) else {
423 return TX_NULL_NODE;
424 };
425 doc.invalidate_caches();
426 TxNodeId::from_node_id(doc.doc.new_unknown(text_str))
427 })
428}
429
430#[unsafe(no_mangle)]
440pub unsafe extern "C" fn tx_insert_end_child(
441 doc: *mut TxDocument,
442 parent: TxNodeId,
443 child: TxNodeId,
444) -> TxError {
445 if doc.is_null() {
446 return TxError::TxErrorInvalidNodeId;
447 }
448 ffi_catch!(TxError::TxErrorInvalidNodeId, {
449 let doc = unsafe { &mut *doc };
450 doc.invalidate_caches();
451 let result = doc
452 .doc
453 .insert_end_child(parent.to_node_id(), child.to_node_id());
454 TxError::from_result(&result)
455 })
456}
457
458#[unsafe(no_mangle)]
464pub unsafe extern "C" fn tx_insert_first_child(
465 doc: *mut TxDocument,
466 parent: TxNodeId,
467 child: TxNodeId,
468) -> TxError {
469 if doc.is_null() {
470 return TxError::TxErrorInvalidNodeId;
471 }
472 ffi_catch!(TxError::TxErrorInvalidNodeId, {
473 let doc = unsafe { &mut *doc };
474 doc.invalidate_caches();
475 let result = doc
476 .doc
477 .insert_first_child(parent.to_node_id(), child.to_node_id());
478 TxError::from_result(&result)
479 })
480}
481
482#[unsafe(no_mangle)]
488pub unsafe extern "C" fn tx_insert_after_child(
489 doc: *mut TxDocument,
490 after: TxNodeId,
491 child: TxNodeId,
492) -> TxError {
493 if doc.is_null() {
494 return TxError::TxErrorInvalidNodeId;
495 }
496 ffi_catch!(TxError::TxErrorInvalidNodeId, {
497 let doc = unsafe { &mut *doc };
498 doc.invalidate_caches();
499 let result = doc
500 .doc
501 .insert_after_child(after.to_node_id(), child.to_node_id());
502 TxError::from_result(&result)
503 })
504}
505
506#[unsafe(no_mangle)]
512pub unsafe extern "C" fn tx_delete_child(
513 doc: *mut TxDocument,
514 parent: TxNodeId,
515 child: TxNodeId,
516) -> TxError {
517 if doc.is_null() {
518 return TxError::TxErrorInvalidNodeId;
519 }
520 ffi_catch!(TxError::TxErrorInvalidNodeId, {
521 let doc = unsafe { &mut *doc };
522 doc.invalidate_caches();
523 let result = doc
524 .doc
525 .delete_child(parent.to_node_id(), child.to_node_id());
526 TxError::from_result(&result)
527 })
528}
529
530#[unsafe(no_mangle)]
536pub unsafe extern "C" fn tx_delete_children(doc: *mut TxDocument, parent: TxNodeId) -> TxError {
537 if doc.is_null() {
538 return TxError::TxErrorInvalidNodeId;
539 }
540 ffi_catch!(TxError::TxErrorInvalidNodeId, {
541 let doc = unsafe { &mut *doc };
542 doc.invalidate_caches();
543 TxError::from_result(&doc.doc.delete_children(parent.to_node_id()))
544 })
545}
546
547#[unsafe(no_mangle)]
553pub unsafe extern "C" fn tx_delete_node(doc: *mut TxDocument, node: TxNodeId) -> TxError {
554 if doc.is_null() {
555 return TxError::TxErrorInvalidNodeId;
556 }
557 ffi_catch!(TxError::TxErrorInvalidNodeId, {
558 let doc = unsafe { &mut *doc };
559 doc.invalidate_caches();
560 TxError::from_result(&doc.doc.delete_node(node.to_node_id()))
561 })
562}
563
564#[unsafe(no_mangle)]
574pub unsafe extern "C" fn tx_parent(doc: *const TxDocument, node: TxNodeId) -> TxNodeId {
575 if doc.is_null() {
576 return TX_NULL_NODE;
577 }
578 ffi_catch!(TX_NULL_NODE, {
579 let doc = unsafe { &*doc };
580 TxNodeId::from_option(doc.doc.parent(node.to_node_id()))
581 })
582}
583
584#[unsafe(no_mangle)]
590pub unsafe extern "C" fn tx_first_child(doc: *const TxDocument, node: TxNodeId) -> TxNodeId {
591 if doc.is_null() {
592 return TX_NULL_NODE;
593 }
594 ffi_catch!(TX_NULL_NODE, {
595 let doc = unsafe { &*doc };
596 TxNodeId::from_option(doc.doc.first_child(node.to_node_id()))
597 })
598}
599
600#[unsafe(no_mangle)]
606pub unsafe extern "C" fn tx_last_child(doc: *const TxDocument, node: TxNodeId) -> TxNodeId {
607 if doc.is_null() {
608 return TX_NULL_NODE;
609 }
610 ffi_catch!(TX_NULL_NODE, {
611 let doc = unsafe { &*doc };
612 TxNodeId::from_option(doc.doc.last_child(node.to_node_id()))
613 })
614}
615
616#[unsafe(no_mangle)]
622pub unsafe extern "C" fn tx_prev_sibling(doc: *const TxDocument, node: TxNodeId) -> TxNodeId {
623 if doc.is_null() {
624 return TX_NULL_NODE;
625 }
626 ffi_catch!(TX_NULL_NODE, {
627 let doc = unsafe { &*doc };
628 TxNodeId::from_option(doc.doc.prev_sibling(node.to_node_id()))
629 })
630}
631
632#[unsafe(no_mangle)]
638pub unsafe extern "C" fn tx_next_sibling(doc: *const TxDocument, node: TxNodeId) -> TxNodeId {
639 if doc.is_null() {
640 return TX_NULL_NODE;
641 }
642 ffi_catch!(TX_NULL_NODE, {
643 let doc = unsafe { &*doc };
644 TxNodeId::from_option(doc.doc.next_sibling(node.to_node_id()))
645 })
646}
647
648#[unsafe(no_mangle)]
657pub unsafe extern "C" fn tx_first_child_element(
658 doc: *const TxDocument,
659 node: TxNodeId,
660 name: *const c_char,
661) -> TxNodeId {
662 if doc.is_null() {
663 return TX_NULL_NODE;
664 }
665 ffi_catch!(TX_NULL_NODE, {
666 let doc = unsafe { &*doc };
667 let name_opt = unsafe { cstr_to_str(name) };
668 TxNodeId::from_option(doc.doc.first_child_element(node.to_node_id(), name_opt))
669 })
670}
671
672#[unsafe(no_mangle)]
681pub unsafe extern "C" fn tx_next_sibling_element(
682 doc: *const TxDocument,
683 node: TxNodeId,
684 name: *const c_char,
685) -> TxNodeId {
686 if doc.is_null() {
687 return TX_NULL_NODE;
688 }
689 ffi_catch!(TX_NULL_NODE, {
690 let doc = unsafe { &*doc };
691 let name_opt = unsafe { cstr_to_str(name) };
692 TxNodeId::from_option(doc.doc.next_sibling_element(node.to_node_id(), name_opt))
693 })
694}
695
696#[unsafe(no_mangle)]
703pub unsafe extern "C" fn tx_root_element(doc: *const TxDocument) -> TxNodeId {
704 if doc.is_null() {
705 return TX_NULL_NODE;
706 }
707 ffi_catch!(TX_NULL_NODE, {
708 let doc = unsafe { &*doc };
709 TxNodeId::from_option(doc.doc.root_element())
710 })
711}
712
713#[unsafe(no_mangle)]
727pub unsafe extern "C" fn tx_element_name(doc: *mut TxDocument, element: TxNodeId) -> *const c_char {
728 if doc.is_null() {
729 return ptr::null();
730 }
731 ffi_catch!(ptr::null(), {
732 let doc = unsafe { &mut *doc };
733 let val = doc
734 .doc
735 .node_ref(element.to_node_id())
736 .map(|nr| nr.value().to_owned());
737 match val {
738 Some(s) => cache_str(doc, &s),
739 None => ptr::null(),
740 }
741 })
742}
743
744#[unsafe(no_mangle)]
753pub unsafe extern "C" fn tx_element_attribute(
754 doc: *mut TxDocument,
755 el: TxNodeId,
756 name: *const c_char,
757) -> *const c_char {
758 if doc.is_null() {
759 return ptr::null();
760 }
761 ffi_catch!(ptr::null(), {
762 let doc = unsafe { &mut *doc };
763 let Some(name_str) = (unsafe { cstr_to_str(name) }) else {
764 return ptr::null();
765 };
766 let val = doc
767 .doc
768 .attribute(el.to_node_id(), name_str)
769 .map(str::to_owned);
770 match val {
771 Some(s) => cache_str(doc, &s),
772 None => ptr::null(),
773 }
774 })
775}
776
777#[unsafe(no_mangle)]
786pub unsafe extern "C" fn tx_element_set_attribute(
787 doc: *mut TxDocument,
788 el: TxNodeId,
789 name: *const c_char,
790 value: *const c_char,
791) -> TxError {
792 if doc.is_null() {
793 return TxError::TxErrorInvalidNodeId;
794 }
795 ffi_catch!(TxError::TxErrorInvalidNodeId, {
796 let doc = unsafe { &mut *doc };
797 let Some(name_str) = (unsafe { cstr_to_str(name) }) else {
798 return TxError::TxErrorNoAttribute;
799 };
800 let Some(val_str) = (unsafe { cstr_to_str(value) }) else {
801 return TxError::TxErrorNoAttribute;
802 };
803 doc.invalidate_caches();
804 TxError::from_result(&doc.doc.set_attribute(el.to_node_id(), name_str, val_str))
805 })
806}
807
808#[unsafe(no_mangle)]
815pub unsafe extern "C" fn tx_element_delete_attribute(
816 doc: *mut TxDocument,
817 el: TxNodeId,
818 name: *const c_char,
819) -> TxError {
820 if doc.is_null() {
821 return TxError::TxErrorInvalidNodeId;
822 }
823 ffi_catch!(TxError::TxErrorInvalidNodeId, {
824 let doc = unsafe { &mut *doc };
825 let Some(name_str) = (unsafe { cstr_to_str(name) }) else {
826 return TxError::TxErrorNoAttribute;
827 };
828 doc.invalidate_caches();
829 TxError::from_result(&doc.doc.delete_attribute(el.to_node_id(), name_str))
830 })
831}
832
833#[unsafe(no_mangle)]
842pub unsafe extern "C" fn tx_element_get_text(
843 doc: *mut TxDocument,
844 element: TxNodeId,
845) -> *const c_char {
846 if doc.is_null() {
847 return ptr::null();
848 }
849 ffi_catch!(ptr::null(), {
850 let doc = unsafe { &mut *doc };
851 let val = doc.doc.get_text(element.to_node_id()).map(str::to_owned);
852 match val {
853 Some(s) => cache_str(doc, &s),
854 None => ptr::null(),
855 }
856 })
857}
858
859#[unsafe(no_mangle)]
869pub unsafe extern "C" fn tx_element_set_text(
870 doc: *mut TxDocument,
871 element: TxNodeId,
872 text: *const c_char,
873) -> TxError {
874 if doc.is_null() {
875 return TxError::TxErrorInvalidNodeId;
876 }
877 ffi_catch!(TxError::TxErrorInvalidNodeId, {
878 let doc = unsafe { &mut *doc };
879 let Some(text_str) = (unsafe { cstr_to_str(text) }) else {
880 return TxError::TxErrorNoTextNode;
881 };
882 doc.invalidate_caches();
883 TxError::from_result(&doc.doc.set_text(element.to_node_id(), text_str))
884 })
885}
886
887#[unsafe(no_mangle)]
901pub unsafe extern "C" fn tx_query_int_attribute(
902 doc: *const TxDocument,
903 el: TxNodeId,
904 name: *const c_char,
905 value: *mut c_int,
906) -> TxError {
907 if doc.is_null() || value.is_null() {
908 return TxError::TxErrorInvalidNodeId;
909 }
910 ffi_catch!(TxError::TxErrorInvalidNodeId, {
911 let doc = unsafe { &*doc };
912 let Some(name_str) = (unsafe { cstr_to_str(name) }) else {
913 return TxError::TxErrorNoAttribute;
914 };
915 match doc.doc.query_int_attribute(el.to_node_id(), name_str) {
916 Ok(v) => {
917 unsafe { *value = v as c_int };
918 TxError::TxSuccess
919 }
920 Err(e) => TxError::from_xml_error(&e),
921 }
922 })
923}
924
925#[unsafe(no_mangle)]
935pub unsafe extern "C" fn tx_query_bool_attribute(
936 doc: *const TxDocument,
937 el: TxNodeId,
938 name: *const c_char,
939 value: *mut bool,
940) -> TxError {
941 if doc.is_null() || value.is_null() {
942 return TxError::TxErrorInvalidNodeId;
943 }
944 ffi_catch!(TxError::TxErrorInvalidNodeId, {
945 let doc = unsafe { &*doc };
946 let Some(name_str) = (unsafe { cstr_to_str(name) }) else {
947 return TxError::TxErrorNoAttribute;
948 };
949 match doc.doc.query_bool_attribute(el.to_node_id(), name_str) {
950 Ok(v) => {
951 unsafe { *value = v };
952 TxError::TxSuccess
953 }
954 Err(e) => TxError::from_xml_error(&e),
955 }
956 })
957}
958
959#[unsafe(no_mangle)]
969pub unsafe extern "C" fn tx_query_double_attribute(
970 doc: *const TxDocument,
971 el: TxNodeId,
972 name: *const c_char,
973 value: *mut c_double,
974) -> TxError {
975 if doc.is_null() || value.is_null() {
976 return TxError::TxErrorInvalidNodeId;
977 }
978 ffi_catch!(TxError::TxErrorInvalidNodeId, {
979 let doc = unsafe { &*doc };
980 let Some(name_str) = (unsafe { cstr_to_str(name) }) else {
981 return TxError::TxErrorNoAttribute;
982 };
983 match doc.doc.query_double_attribute(el.to_node_id(), name_str) {
984 Ok(v) => {
985 unsafe { *value = v };
986 TxError::TxSuccess
987 }
988 Err(e) => TxError::from_xml_error(&e),
989 }
990 })
991}
992
993#[unsafe(no_mangle)]
1001pub unsafe extern "C" fn tx_int_attribute(
1002 doc: *const TxDocument,
1003 el: TxNodeId,
1004 name: *const c_char,
1005 default_val: c_int,
1006) -> c_int {
1007 if doc.is_null() {
1008 return default_val;
1009 }
1010 ffi_catch!(default_val, {
1011 let doc = unsafe { &*doc };
1012 let Some(name_str) = (unsafe { cstr_to_str(name) }) else {
1013 return default_val;
1014 };
1015 doc.doc
1016 .int_attribute(el.to_node_id(), name_str, default_val)
1017 })
1018}
1019
1020#[unsafe(no_mangle)]
1028pub unsafe extern "C" fn tx_bool_attribute(
1029 doc: *const TxDocument,
1030 el: TxNodeId,
1031 name: *const c_char,
1032 default_val: bool,
1033) -> bool {
1034 if doc.is_null() {
1035 return default_val;
1036 }
1037 ffi_catch!(default_val, {
1038 let doc = unsafe { &*doc };
1039 let Some(name_str) = (unsafe { cstr_to_str(name) }) else {
1040 return default_val;
1041 };
1042 doc.doc
1043 .bool_attribute(el.to_node_id(), name_str, default_val)
1044 })
1045}
1046
1047#[unsafe(no_mangle)]
1055pub unsafe extern "C" fn tx_double_attribute(
1056 doc: *const TxDocument,
1057 el: TxNodeId,
1058 name: *const c_char,
1059 default_val: c_double,
1060) -> c_double {
1061 if doc.is_null() {
1062 return default_val;
1063 }
1064 ffi_catch!(default_val, {
1065 let doc = unsafe { &*doc };
1066 let Some(name_str) = (unsafe { cstr_to_str(name) }) else {
1067 return default_val;
1068 };
1069 doc.doc
1070 .double_attribute(el.to_node_id(), name_str, default_val)
1071 })
1072}
1073
1074#[unsafe(no_mangle)]
1084pub extern "C" fn tx_printer_new() -> *mut TxPrinter {
1085 ffi_catch!(ptr::null_mut(), {
1086 Box::into_raw(Box::new(TxPrinter::new(false)))
1087 })
1088}
1089
1090#[unsafe(no_mangle)]
1096pub extern "C" fn tx_printer_new_compact() -> *mut TxPrinter {
1097 ffi_catch!(ptr::null_mut(), {
1098 Box::into_raw(Box::new(TxPrinter::new(true)))
1099 })
1100}
1101
1102#[unsafe(no_mangle)]
1110pub unsafe extern "C" fn tx_printer_free(printer: *mut TxPrinter) {
1111 if printer.is_null() {
1112 return;
1113 }
1114 let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1115 drop(unsafe { Box::from_raw(printer) });
1116 }));
1117}
1118
1119#[unsafe(no_mangle)]
1126pub unsafe extern "C" fn tx_printer_open_element(printer: *mut TxPrinter, name: *const c_char) {
1127 if printer.is_null() {
1128 return;
1129 }
1130 let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1131 let p = unsafe { &mut *printer };
1132 if let Some(name_str) = unsafe { cstr_to_str(name) } {
1133 p.cached_result = None;
1134 p.printer.open_element(name_str);
1135 }
1136 }));
1137}
1138
1139#[unsafe(no_mangle)]
1147pub unsafe extern "C" fn tx_printer_push_attribute(
1148 printer: *mut TxPrinter,
1149 name: *const c_char,
1150 value: *const c_char,
1151) {
1152 if printer.is_null() {
1153 return;
1154 }
1155 let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1156 let p = unsafe { &mut *printer };
1157 if let (Some(n), Some(v)) = (unsafe { cstr_to_str(name) }, unsafe { cstr_to_str(value) }) {
1158 p.cached_result = None;
1159 p.printer.push_attribute(n, v);
1160 }
1161 }));
1162}
1163
1164#[unsafe(no_mangle)]
1170pub unsafe extern "C" fn tx_printer_close_element(printer: *mut TxPrinter) {
1171 if printer.is_null() {
1172 return;
1173 }
1174 let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1175 let p = unsafe { &mut *printer };
1176 p.cached_result = None;
1177 p.printer.close_element();
1178 }));
1179}
1180
1181#[unsafe(no_mangle)]
1188pub unsafe extern "C" fn tx_printer_push_text(printer: *mut TxPrinter, text: *const c_char) {
1189 if printer.is_null() {
1190 return;
1191 }
1192 let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1193 let p = unsafe { &mut *printer };
1194 if let Some(text_str) = unsafe { cstr_to_str(text) } {
1195 p.cached_result = None;
1196 p.printer.push_text(text_str);
1197 }
1198 }));
1199}
1200
1201#[unsafe(no_mangle)]
1208pub unsafe extern "C" fn tx_printer_push_comment(printer: *mut TxPrinter, text: *const c_char) {
1209 if printer.is_null() {
1210 return;
1211 }
1212 let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1213 let p = unsafe { &mut *printer };
1214 if let Some(text_str) = unsafe { cstr_to_str(text) } {
1215 p.cached_result = None;
1216 p.printer.push_comment(text_str);
1217 }
1218 }));
1219}
1220
1221#[unsafe(no_mangle)]
1229pub unsafe extern "C" fn tx_printer_result(printer: *mut TxPrinter) -> *const c_char {
1230 if printer.is_null() {
1231 return ptr::null();
1232 }
1233 ffi_catch!(ptr::null(), {
1234 let p = unsafe { &mut *printer };
1235 let s = p.printer.result();
1236 match CString::new(s) {
1237 Ok(cs) => {
1238 let ptr = cs.as_ptr();
1239 p.cached_result = Some(cs);
1240 ptr
1241 }
1242 Err(_) => ptr::null(),
1243 }
1244 })
1245}
1246
1247#[unsafe(no_mangle)]
1253pub unsafe extern "C" fn tx_printer_clear(printer: *mut TxPrinter) {
1254 if printer.is_null() {
1255 return;
1256 }
1257 let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1258 let p = unsafe { &mut *printer };
1259 p.cached_result = None;
1260 p.printer.clear();
1261 }));
1262}
1263
1264#[unsafe(no_mangle)]
1274pub unsafe extern "C" fn tx_node_type(doc: *const TxDocument, node: TxNodeId) -> TxNodeType {
1275 if doc.is_null() {
1276 return TxNodeType::TxNodeUnknown;
1277 }
1278 ffi_catch!(TxNodeType::TxNodeUnknown, {
1279 let doc = unsafe { &*doc };
1280 match doc.doc.node_kind(node.to_node_id()) {
1281 Some(kind) => TxNodeType::from_node_kind(kind),
1282 None => TxNodeType::TxNodeUnknown,
1283 }
1284 })
1285}
1286
1287#[unsafe(no_mangle)]
1291pub extern "C" fn tx_node_is_null(node: TxNodeId) -> bool {
1292 node.is_null()
1293}
1294
1295#[unsafe(no_mangle)]
1308pub unsafe extern "C" fn tx_node_value(doc: *mut TxDocument, node: TxNodeId) -> *const c_char {
1309 if doc.is_null() {
1310 return ptr::null();
1311 }
1312 ffi_catch!(ptr::null(), {
1313 let doc = unsafe { &mut *doc };
1314 let val = doc
1315 .doc
1316 .node_ref(node.to_node_id())
1317 .map(|nr| nr.value().to_owned());
1318 match val {
1319 Some(s) => cache_str(doc, &s),
1320 None => ptr::null(),
1321 }
1322 })
1323}
1324
1325#[unsafe(no_mangle)]
1332pub unsafe extern "C" fn tx_node_line(doc: *const TxDocument, node: TxNodeId) -> c_int {
1333 if doc.is_null() {
1334 return 0;
1335 }
1336 ffi_catch!(0, {
1337 let doc = unsafe { &*doc };
1338 doc.doc.line_num(node.to_node_id()).unwrap_or(0) as c_int
1339 })
1340}