tinyxml2_capi/types.rs
1//! C-compatible types for the tinyxml2 FFI layer.
2//!
3//! This module defines all `#[repr(C)]` types, opaque wrapper structs, and
4//! conversion utilities used by the `extern "C"` functions in [`crate`].
5
6use std::ffi::CString;
7
8use tinyxml2::{Document, NodeId, NodeKind, ParseErrorKind, XmlError, XmlPrinter};
9
10// ============================================================
11// TxNodeId — C-compatible node identifier
12// ============================================================
13
14/// A C-compatible node identifier.
15///
16/// This is a `#[repr(C)]` mirror of the internal `NodeId` type. It carries
17/// both an arena index and a generation counter for use-after-free detection.
18#[repr(C)]
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub struct TxNodeId {
21 /// Index into the document's node arena.
22 pub index: u32,
23 /// Generation counter for validity checking.
24 pub generation: u32,
25}
26
27/// Sentinel value representing a null / invalid node.
28///
29/// Any function that would return "no node" returns this sentinel.
30/// Use [`tx_node_is_null`](crate::tx_node_is_null) to test for it.
31pub const TX_NULL_NODE: TxNodeId = TxNodeId {
32 index: u32::MAX,
33 generation: 0,
34};
35
36impl TxNodeId {
37 /// Converts a Rust `NodeId` into a C-compatible `TxNodeId`.
38 #[inline]
39 pub fn from_node_id(id: NodeId) -> Self {
40 let (index, generation) = id.raw_parts();
41 Self { index, generation }
42 }
43
44 /// Converts this C-compatible `TxNodeId` back into a Rust `NodeId`.
45 #[inline]
46 pub fn to_node_id(self) -> NodeId {
47 NodeId::from_raw_parts(self.index, self.generation)
48 }
49
50 /// Returns `true` if this node ID is the null sentinel.
51 #[inline]
52 pub fn is_null(self) -> bool {
53 self == TX_NULL_NODE
54 }
55
56 /// Converts an `Option<NodeId>` to a `TxNodeId`, mapping `None` to
57 /// [`TX_NULL_NODE`].
58 #[inline]
59 pub fn from_option(opt: Option<NodeId>) -> Self {
60 match opt {
61 Some(id) => Self::from_node_id(id),
62 None => TX_NULL_NODE,
63 }
64 }
65}
66
67// ============================================================
68// TxNodeType — C-compatible node kind enum
69// ============================================================
70
71/// C-compatible representation of the XML node type.
72#[repr(C)]
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub enum TxNodeType {
75 /// The document root node.
76 TxNodeDocument = 0,
77 /// An XML element (tag).
78 TxNodeElement = 1,
79 /// A text content node.
80 TxNodeText = 2,
81 /// An XML comment (`<!-- ... -->`).
82 TxNodeComment = 3,
83 /// An XML declaration (`<?xml ... ?>`).
84 TxNodeDeclaration = 4,
85 /// An unrecognized XML construct.
86 TxNodeUnknown = 5,
87}
88
89impl TxNodeType {
90 /// Converts a Rust `NodeKind` reference into a `TxNodeType`.
91 pub fn from_node_kind(kind: &NodeKind) -> Self {
92 match kind {
93 NodeKind::Document => Self::TxNodeDocument,
94 NodeKind::Element(_) => Self::TxNodeElement,
95 NodeKind::Text(_) => Self::TxNodeText,
96 NodeKind::Comment(_) => Self::TxNodeComment,
97 NodeKind::Declaration(_) => Self::TxNodeDeclaration,
98 NodeKind::Unknown(_) => Self::TxNodeUnknown,
99 }
100 }
101}
102
103// ============================================================
104// TxError — C-compatible error code enum
105// ============================================================
106
107/// C-compatible error code enum matching `TinyXML2` error values.
108#[repr(C)]
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110pub enum TxError {
111 /// Operation completed successfully.
112 TxSuccess = 0,
113 /// The document was empty or contained no XML content.
114 TxErrorEmptyDocument = 1,
115 /// Error while parsing an element.
116 TxErrorParsingElement = 2,
117 /// Error while parsing an attribute.
118 TxErrorParsingAttribute = 3,
119 /// Error while parsing text content.
120 TxErrorParsingText = 4,
121 /// Error while parsing a CDATA section.
122 TxErrorParsingCdata = 5,
123 /// Error while parsing a comment.
124 TxErrorParsingComment = 6,
125 /// Error while parsing a declaration.
126 TxErrorParsingDeclaration = 7,
127 /// Error while parsing an unknown construct.
128 TxErrorParsingUnknown = 8,
129 /// An element's closing tag did not match its opening tag.
130 TxErrorMismatchedElement = 9,
131 /// The specified file was not found.
132 TxErrorFileNotFound = 10,
133 /// An error occurred while reading/writing a file.
134 TxErrorFileRead = 11,
135 /// The maximum element nesting depth was exceeded.
136 TxErrorElementDepthExceeded = 12,
137 /// The requested attribute does not exist.
138 TxErrorNoAttribute = 13,
139 /// The attribute value could not be converted to the requested type.
140 TxErrorWrongAttributeType = 14,
141 /// Text content could not be converted to the requested type.
142 TxErrorCanNotConvertText = 15,
143 /// No text node was found as a child of the element.
144 TxErrorNoTextNode = 16,
145 /// The provided node ID is invalid or refers to a freed node.
146 TxErrorInvalidNodeId = 17,
147}
148
149impl TxError {
150 /// Converts a Rust `XmlError` into a C-compatible `TxError` code.
151 pub fn from_xml_error(err: &XmlError) -> Self {
152 match err {
153 XmlError::EmptyDocument => Self::TxErrorEmptyDocument,
154 XmlError::Parse { kind, .. } => match kind {
155 ParseErrorKind::Element | ParseErrorKind::General => Self::TxErrorParsingElement,
156 ParseErrorKind::Attribute => Self::TxErrorParsingAttribute,
157 ParseErrorKind::Text => Self::TxErrorParsingText,
158 ParseErrorKind::Cdata => Self::TxErrorParsingCdata,
159 ParseErrorKind::Comment => Self::TxErrorParsingComment,
160 ParseErrorKind::Declaration => Self::TxErrorParsingDeclaration,
161 ParseErrorKind::Unknown => Self::TxErrorParsingUnknown,
162 },
163 XmlError::MismatchedElement { .. } => Self::TxErrorMismatchedElement,
164 XmlError::Io(io_err) => {
165 if io_err.kind() == std::io::ErrorKind::NotFound {
166 Self::TxErrorFileNotFound
167 } else {
168 Self::TxErrorFileRead
169 }
170 }
171 XmlError::ElementDepthExceeded { .. } => Self::TxErrorElementDepthExceeded,
172 XmlError::NoAttribute => Self::TxErrorNoAttribute,
173 XmlError::WrongAttributeType => Self::TxErrorWrongAttributeType,
174 XmlError::CanNotConvertText => Self::TxErrorCanNotConvertText,
175 XmlError::NoTextNode => Self::TxErrorNoTextNode,
176 XmlError::InvalidNodeId => Self::TxErrorInvalidNodeId,
177 }
178 }
179
180 /// Converts a `Result<T>` into a `TxError`, mapping `Ok` to `TxSuccess`.
181 pub fn from_result<T>(result: &tinyxml2::Result<T>) -> Self {
182 match result {
183 Ok(_) => Self::TxSuccess,
184 Err(e) => Self::from_xml_error(e),
185 }
186 }
187}
188
189// ============================================================
190// TxDocument — Opaque document wrapper
191// ============================================================
192
193/// Opaque wrapper around a `Document` for FFI use.
194///
195/// This struct holds the actual document plus cached `CString` values so that
196/// `const char*` pointers returned across the FFI boundary remain valid until
197/// the document is mutated or freed.
198pub struct TxDocument {
199 /// The inner tinyxml2 document.
200 pub(crate) doc: Document,
201 /// Cached pretty-printed serialization.
202 pub(crate) cached_to_string: Option<CString>,
203 /// Cached compact serialization.
204 pub(crate) cached_to_string_compact: Option<CString>,
205 /// Cached error name string.
206 pub(crate) cached_error_name: Option<CString>,
207 /// Accumulated node-level string cache. Pointers into these `CString`
208 /// values remain valid until the next mutating operation or until the
209 /// document is freed.
210 pub(crate) string_cache: Vec<CString>,
211}
212
213impl TxDocument {
214 /// Creates a new wrapper around a fresh, empty `Document`.
215 pub fn new() -> Self {
216 Self {
217 doc: Document::new(),
218 cached_to_string: None,
219 cached_to_string_compact: None,
220 cached_error_name: None,
221 string_cache: Vec::new(),
222 }
223 }
224
225 /// Invalidates all cached strings. Called before any mutating operation.
226 pub fn invalidate_caches(&mut self) {
227 self.cached_to_string = None;
228 self.cached_to_string_compact = None;
229 self.cached_error_name = None;
230 self.string_cache.clear();
231 }
232}
233
234impl Default for TxDocument {
235 fn default() -> Self {
236 Self::new()
237 }
238}
239
240// ============================================================
241// TxPrinter — Opaque printer wrapper
242// ============================================================
243
244/// Opaque wrapper around an `XmlPrinter` for FFI use.
245///
246/// Caches the result string so that the `const char*` pointer returned by
247/// [`tx_printer_result`](crate::tx_printer_result) remains valid.
248pub struct TxPrinter {
249 /// The inner tinyxml2 printer.
250 pub(crate) printer: XmlPrinter,
251 /// Cached result string for FFI return.
252 pub(crate) cached_result: Option<CString>,
253}
254
255impl TxPrinter {
256 /// Creates a new printer wrapper.
257 pub fn new(compact: bool) -> Self {
258 let printer = if compact {
259 XmlPrinter::new_compact()
260 } else {
261 XmlPrinter::new()
262 };
263 Self {
264 printer,
265 cached_result: None,
266 }
267 }
268}