Skip to main content

sxd_document_no_unsafe/
lib.rs

1//!
2//! ```
3//! use sxd_document_no_unsafe::Package;
4//! let package = Package::new();
5//! let doc = package.as_document();
6//!
7//! let hello = doc.create_element("hello");
8//! hello.set_attribute_value("planet", "Earth");
9//! let comment = doc.create_comment("What about other planets?");
10//! let text = doc.create_text("Greetings, Earthlings!");
11//!
12//! hello.append_child(comment);
13//! hello.append_child(text);
14//! doc.root().append_child(hello);
15//! ```
16//!
17//! ### Memory and ownership
18//!
19//! The `Package` struct is responsible for owning every node in the
20//! document. Strings are interned, allowing repeated text to consume
21//! less memory. This is very useful for documents containing lots of
22//! the same attributes and tag names.
23//!
24//! The flip side of this decision is that allocated nodes and strings
25//! are not deallocated until the entire `Package` is dropped. This is
26//! a reasonable decision for two common cases: building up an XML
27//! document and reading an XML document. You may wish to perform
28//! large modifications to your data *before* creating a document.
29//!
30//! ### Namespaces, QNames, and Prefixes
31//!
32//! The names of elements and attributes may use namespaces. XML
33//! namespacing uses URIs to uniquely distinguish items with the same
34//! local name. A qualified name (`QName`) combines this optional URI
35//! with the local name.
36//!
37//! When an XML document is represented as text, namespaces are given
38//! a shorthand reference known as a prefix. Prefix names are
39//! non-authoritative, and only the URI can be used to namespace a
40//! name.
41//!
42//! Elements and attributes may specify a *preferred prefix*, which is
43//! an indication of what the user would like to be used as a
44//! prefix. There are times where the preferred prefix would cause a
45//! conflict, and so an autogenerated prefix will be used instead.
46//!
47//! ### Design decisions
48//!
49//! Try to leverage the type system as much as possible.
50
51#![deny(rust_2018_idioms)]
52
53#[macro_use]
54extern crate peresil;
55
56use std::fmt;
57
58mod lazy_hash_map;
59mod str;
60mod str_ext;
61
62#[cfg(not(feature = "no-unsafe"))]
63pub mod dom;
64#[cfg(not(feature = "no-unsafe"))]
65mod raw;
66#[cfg(not(feature = "no-unsafe"))]
67mod string_pool;
68#[cfg(not(feature = "no-unsafe"))]
69#[doc(hidden)]
70pub mod thindom;
71#[cfg(not(feature = "no-unsafe"))]
72pub mod writer;
73
74#[cfg(feature = "no-unsafe")]
75#[path = "raw_no_unsafe.rs"]
76mod raw;
77#[cfg(feature = "no-unsafe")]
78#[path = "string_pool_no_unsafe.rs"]
79mod string_pool;
80#[cfg(feature = "no-unsafe")]
81pub use string_pool::InternedString;
82#[cfg(feature = "no-unsafe")]
83#[path = "dom_no_unsafe.rs"]
84pub mod dom;
85#[cfg(feature = "no-unsafe")]
86#[path = "thindom_no_unsafe.rs"]
87#[doc(hidden)]
88pub mod thindom;
89#[cfg(feature = "no-unsafe")]
90#[path = "writer_no_unsafe.rs"]
91pub mod writer;
92
93pub mod parser;
94
95#[cfg(not(feature = "no-unsafe"))]
96#[cfg(feature = "__internal_expose_string_pool")]
97pub mod __internal {
98    pub use super::string_pool::StringPool;
99}
100
101#[cfg(feature = "no-unsafe")]
102#[cfg(feature = "__internal_expose_string_pool")]
103pub mod __internal {
104    pub use super::string_pool::StringPool;
105}
106
107pub use crate::str::XmlChar;
108
109#[cfg(not(feature = "no-unsafe"))]
110#[macro_export]
111macro_rules! as_str {
112    ($e:expr) => {
113        $e
114    };
115}
116
117#[cfg(not(feature = "no-unsafe"))]
118#[macro_export]
119macro_rules! as_opt_str {
120    ($e:expr) => {
121        $e
122    };
123}
124
125#[cfg(not(feature = "no-unsafe"))]
126#[macro_export]
127macro_rules! as_qname {
128    ($e:expr) => {
129        $e
130    };
131}
132
133#[cfg(feature = "no-unsafe")]
134#[macro_export]
135macro_rules! as_str {
136    ($e:expr) => {
137        &*$e
138    };
139}
140
141#[cfg(feature = "no-unsafe")]
142#[macro_export]
143macro_rules! as_opt_str {
144    ($e:expr) => {
145        $e.as_deref()
146    };
147}
148
149#[cfg(feature = "no-unsafe")]
150#[macro_export]
151macro_rules! as_qname {
152    ($e:expr) => {
153        $e.get()
154    };
155}
156
157#[cfg(not(feature = "no-unsafe"))]
158#[macro_export]
159macro_rules! to_ns_str {
160    ($e:expr) => {
161        $e
162    };
163}
164
165#[cfg(feature = "no-unsafe")]
166#[macro_export]
167macro_rules! to_ns_str {
168    ($e:expr) => {
169        ($e).to_owned()
170    };
171}
172
173/// String type used for namespace prefix/URI storage.
174/// `&'d str` in default mode, `String` in no-unsafe mode.
175#[cfg(not(feature = "no-unsafe"))]
176pub type NsStr<'d> = &'d str;
177/// String type used for namespace prefix/URI storage.
178/// `&'d str` in default mode, `String` in no-unsafe mode.
179#[cfg(feature = "no-unsafe")]
180pub type NsStr<'d> = String;
181
182static XML_NS_PREFIX: &str = "xml";
183static XML_NS_URI: &str = "http://www.w3.org/XML/1998/namespace";
184
185/// A prefixed name. This represents what is found in the string form
186/// of an XML document, and does not apply any namespace mapping.
187#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
188pub struct PrefixedName<'a> {
189    prefix: Option<&'a str>,
190    local_part: &'a str,
191}
192
193impl<'a> PrefixedName<'a> {
194    /// Create a `PrefixedName` without a prefix
195    pub fn new(local_part: &str) -> PrefixedName<'_> {
196        PrefixedName::with_prefix(None, local_part)
197    }
198
199    /// Create a `PrefixedName` without an optional prefix
200    pub fn with_prefix(prefix: Option<&'a str>, local_part: &'a str) -> PrefixedName<'a> {
201        PrefixedName { prefix, local_part }
202    }
203
204    pub fn prefix(&self) -> Option<&str> {
205        self.prefix
206    }
207    pub fn local_part(&self) -> &str {
208        self.local_part
209    }
210}
211
212/// A namespace-qualified name. This represents the name of an element
213/// or attribute *after* the prefix has been mapped to a specific
214/// namespace.
215#[derive(Debug, Copy, Clone, PartialEq)]
216pub struct QName<'s> {
217    namespace_uri: Option<&'s str>,
218    local_part: &'s str,
219}
220
221impl<'s> QName<'s> {
222    /// Create a `QName` without a namespace
223    pub fn new(local_part: &'s str) -> QName<'s> {
224        QName::with_namespace_uri(None, local_part)
225    }
226
227    /// Create a `QName` with an optional namespace
228    pub fn with_namespace_uri(namespace_uri: Option<&'s str>, local_part: &'s str) -> QName<'s> {
229        QName {
230            namespace_uri,
231            local_part,
232        }
233    }
234
235    pub fn namespace_uri(&self) -> Option<&'s str> {
236        self.namespace_uri
237    }
238    pub fn local_part(&self) -> &'s str {
239        self.local_part
240    }
241}
242
243impl<'s> From<(&'s str, &'s str)> for QName<'s> {
244    fn from(v: (&'s str, &'s str)) -> QName<'s> {
245        QName {
246            namespace_uri: Some(v.0),
247            local_part: v.1,
248        }
249    }
250}
251
252impl<'s> From<&'s str> for QName<'s> {
253    fn from(v: &'s str) -> QName<'s> {
254        QName {
255            namespace_uri: None,
256            local_part: v,
257        }
258    }
259}
260
261/// The main entrypoint to an XML document
262///
263/// This is an opaque structure that stores the internal details of
264/// the XML document. Modify the document via `as_document`.
265pub struct Package {
266    storage: raw::Storage,
267    connections: raw::Connections,
268}
269
270impl Default for Package {
271    fn default() -> Package {
272        let s = raw::Storage::new();
273        let root = s.create_root();
274        Package {
275            storage: s,
276            connections: raw::Connections::new(root),
277        }
278    }
279}
280
281impl Package {
282    pub fn new() -> Package {
283        Self::default()
284    }
285
286    pub fn as_document(&self) -> dom::Document<'_> {
287        dom::Document::new(&self.storage, &self.connections)
288    }
289
290    #[doc(hidden)]
291    pub fn as_thin_document(&self) -> (thindom::Storage<'_>, thindom::Connections<'_>) {
292        let s = thindom::Storage::new(&self.storage);
293        #[cfg(not(feature = "no-unsafe"))]
294        let c = thindom::Connections::new(&self.connections);
295        #[cfg(feature = "no-unsafe")]
296        let c = thindom::Connections::new(&self.connections, &self.storage);
297        (s, c)
298    }
299}
300
301impl PartialEq for Package {
302    fn eq(&self, other: &Package) -> bool {
303        std::ptr::eq(self, other)
304    }
305}
306
307impl fmt::Debug for Package {
308    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
309        write!(f, "Package")
310    }
311}