Skip to main content

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}