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