1mod read;
19mod write;
20
21#[derive(Debug, Clone, PartialEq, Eq, Hash)]
27pub enum Ns {
28 Office,
30 Text,
32 Style,
34 Table,
36 Draw,
38 Presentation,
40 Chart,
42 Fo,
45 Svg,
47 Number,
49 Meta,
51 Manifest,
53 Config,
55 Of,
57 Xlink,
59 Dc,
61 Loext,
63 Calcext,
65 Xmlns,
68 Other(Box<str>),
71 None,
73}
74
75impl Ns {
76 pub fn from_uri(uri: &str) -> Self {
78 match uri {
79 "urn:oasis:names:tc:opendocument:xmlns:office:1.0" => Self::Office,
80 "urn:oasis:names:tc:opendocument:xmlns:text:1.0" => Self::Text,
81 "urn:oasis:names:tc:opendocument:xmlns:style:1.0" => Self::Style,
82 "urn:oasis:names:tc:opendocument:xmlns:table:1.0" => Self::Table,
83 "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" => Self::Draw,
84 "urn:oasis:names:tc:opendocument:xmlns:presentation:1.0" => Self::Presentation,
85 "urn:oasis:names:tc:opendocument:xmlns:chart:1.0" => Self::Chart,
86 "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" => Self::Fo,
87 "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" => Self::Svg,
88 "urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" => Self::Number,
89 "urn:oasis:names:tc:opendocument:xmlns:meta:1.0" => Self::Meta,
90 "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0" => Self::Manifest,
91 "urn:oasis:names:tc:opendocument:xmlns:config:1.0" => Self::Config,
92 "urn:oasis:names:tc:opendocument:xmlns:of:1.2" => Self::Of,
93 "http://www.w3.org/1999/xlink" => Self::Xlink,
94 "http://purl.org/dc/elements/1.1/" => Self::Dc,
95 "urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" => Self::Loext,
96 "urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" => Self::Calcext,
97 other => Self::Other(other.into()),
98 }
99 }
100}
101
102impl Ns {
103 pub fn uri(&self) -> Option<&'static str> {
105 Some(match self {
106 Self::Office => "urn:oasis:names:tc:opendocument:xmlns:office:1.0",
107 Self::Text => "urn:oasis:names:tc:opendocument:xmlns:text:1.0",
108 Self::Style => "urn:oasis:names:tc:opendocument:xmlns:style:1.0",
109 Self::Table => "urn:oasis:names:tc:opendocument:xmlns:table:1.0",
110 Self::Draw => "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0",
111 Self::Presentation => "urn:oasis:names:tc:opendocument:xmlns:presentation:1.0",
112 Self::Chart => "urn:oasis:names:tc:opendocument:xmlns:chart:1.0",
113 Self::Fo => "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0",
114 Self::Svg => "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0",
115 Self::Number => "urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0",
116 Self::Meta => "urn:oasis:names:tc:opendocument:xmlns:meta:1.0",
117 Self::Manifest => "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0",
118 Self::Config => "urn:oasis:names:tc:opendocument:xmlns:config:1.0",
119 Self::Of => "urn:oasis:names:tc:opendocument:xmlns:of:1.2",
120 Self::Xlink => "http://www.w3.org/1999/xlink",
121 Self::Dc => "http://purl.org/dc/elements/1.1/",
122 Self::Loext => "urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0",
123 Self::Calcext => "urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0",
124 Self::Xmlns | Self::Other(_) | Self::None => return None,
125 })
126 }
127
128 pub fn conventional_prefix(&self) -> &str {
131 match self {
132 Self::Office => "office",
133 Self::Text => "text",
134 Self::Style => "style",
135 Self::Table => "table",
136 Self::Draw => "draw",
137 Self::Presentation => "presentation",
138 Self::Chart => "chart",
139 Self::Fo => "fo",
140 Self::Svg => "svg",
141 Self::Number => "number",
142 Self::Meta => "meta",
143 Self::Manifest => "manifest",
144 Self::Config => "config",
145 Self::Of => "of",
146 Self::Xlink => "xlink",
147 Self::Dc => "dc",
148 Self::Loext => "loext",
149 Self::Calcext => "calcext",
150 Self::Xmlns => "xmlns",
151 Self::Other(_) | Self::None => "",
152 }
153 }
154}
155
156#[derive(Debug, Clone, PartialEq, Eq)]
162pub struct Name {
163 pub prefix: Option<Box<str>>,
165 pub local: Box<str>,
167 pub ns: Ns,
169}
170
171impl Name {
172 pub fn new(prefix: &str, local: &str, ns: Ns) -> Self {
174 Self {
175 prefix: if prefix.is_empty() {
176 None
177 } else {
178 Some(prefix.into())
179 },
180 local: local.into(),
181 ns,
182 }
183 }
184
185 pub fn is(&self, ns: &Ns, local: &str) -> bool {
187 self.ns == *ns && &*self.local == local
188 }
189}
190
191#[derive(Debug, Clone, PartialEq, Eq)]
193pub struct Attribute {
194 pub name: Name,
196 pub value: String,
198}
199
200#[derive(Debug, Clone, PartialEq, Eq)]
202pub enum Node {
203 Element(Element),
205 Text(String),
208 CData(String),
210 Comment(String),
212 ProcessingInstruction(String),
214}
215
216#[derive(Debug, Clone, PartialEq, Eq)]
219pub struct Element {
220 pub name: Name,
222 pub attrs: Vec<Attribute>,
224 pub children: Vec<Node>,
226 pub self_closing: bool,
230}
231
232impl Element {
233 pub fn new(prefix: &str, local: &str, ns: Ns) -> Self {
235 Self {
236 name: Name::new(prefix, local, ns),
237 attrs: Vec::new(),
238 children: Vec::new(),
239 self_closing: true,
240 }
241 }
242
243 pub fn is(&self, ns: &Ns, local: &str) -> bool {
245 self.name.is(ns, local)
246 }
247
248 pub fn attr(&self, ns: &Ns, local: &str) -> Option<&str> {
250 self.attrs
251 .iter()
252 .find(|a| a.name.is(ns, local))
253 .map(|a| a.value.as_str())
254 }
255
256 pub fn attr_usize(&self, ns: &Ns, local: &str) -> Option<usize> {
261 self.attr(ns, local)?.trim().parse().ok()
262 }
263
264 pub fn elements(&self) -> impl Iterator<Item = &Element> {
266 self.children.iter().filter_map(|n| match n {
267 Node::Element(e) => Some(e),
268 _ => None,
269 })
270 }
271
272 pub fn elements_indexed(&self) -> impl Iterator<Item = (usize, &Element)> {
275 self.children
276 .iter()
277 .enumerate()
278 .filter_map(|(i, n)| match n {
279 Node::Element(e) => Some((i, e)),
280 _ => None,
281 })
282 }
283
284 pub fn at(&self, path: &[usize]) -> Option<&Element> {
287 let mut element = self;
288 for &step in path {
289 let Node::Element(child) = element.children.get(step)? else {
290 return None;
291 };
292 element = child;
293 }
294 Some(element)
295 }
296
297 pub fn child_mut(&mut self, ns: &Ns, local: &str) -> Option<&mut Element> {
299 self.children.iter_mut().find_map(|n| match n {
300 Node::Element(e) if e.is(ns, local) => Some(e),
301 _ => None,
302 })
303 }
304
305 pub fn at_mut(&mut self, path: &[usize]) -> Option<&mut Element> {
307 let mut element = self;
308 for &step in path {
309 let Node::Element(child) = element.children.get_mut(step)? else {
310 return None;
311 };
312 element = child;
313 }
314 Some(element)
315 }
316
317 pub fn set_attr(&mut self, name: Name, value: impl Into<String>) {
320 let value = value.into();
321 match self.attrs.iter_mut().find(|a| a.name == name) {
322 Some(attr) => attr.value = value,
323 None => self.attrs.push(Attribute { name, value }),
324 }
325 }
326
327 pub fn remove_attr(&mut self, ns: &Ns, local: &str) -> Option<String> {
329 let at = self.attrs.iter().position(|a| a.name.is(ns, local))?;
330 Some(self.attrs.remove(at).value)
331 }
332
333 pub fn name_for(&self, ns: &Ns, local: &str) -> Name {
336 let declared = self
337 .attrs
338 .iter()
339 .filter(|a| a.name.ns == Ns::Xmlns)
340 .find(|a| Ns::from_uri(&a.value) == *ns)
341 .map(|a| &*a.name.local);
342 Name::new(
343 declared.unwrap_or(ns.conventional_prefix()),
344 local,
345 ns.clone(),
346 )
347 }
348
349 pub fn declares(&self, ns: &Ns) -> bool {
351 self.attrs
352 .iter()
353 .any(|a| a.name.ns == Ns::Xmlns && Ns::from_uri(&a.value) == *ns)
354 }
355
356 pub fn declare(&mut self, ns: &Ns) -> bool {
361 if self.declares(ns) {
362 return true;
363 }
364 let Some(uri) = ns.uri() else {
365 return false;
366 };
367 let prefix = ns.conventional_prefix();
368 if self
369 .attrs
370 .iter()
371 .any(|a| a.name.ns == Ns::Xmlns && &*a.name.local == prefix)
372 {
373 return false;
374 }
375 self.attrs.push(Attribute {
376 name: Name::new("xmlns", prefix, Ns::Xmlns),
377 value: uri.to_owned(),
378 });
379 true
380 }
381
382 pub fn child(&self, ns: &Ns, local: &str) -> Option<&Element> {
384 self.elements().find(|e| e.is(ns, local))
385 }
386
387 pub fn descendant(&self, ns: &Ns, local: &str) -> Option<&Element> {
389 for e in self.elements() {
390 if e.is(ns, local) {
391 return Some(e);
392 }
393 if let Some(found) = e.descendant(ns, local) {
394 return Some(found);
395 }
396 }
397 None
398 }
399
400 pub fn plain_text(&self) -> String {
408 let mut out = String::new();
409 self.write_plain_text(&mut out);
410 out
411 }
412
413 fn write_plain_text(&self, out: &mut String) {
414 for child in &self.children {
415 match child {
416 Node::Text(t) | Node::CData(t) => out.push_str(t),
417 Node::Element(e) if e.is(&Ns::Text, "s") => {
418 let n = e.attr_usize(&Ns::Text, "c").unwrap_or(1);
419 for _ in 0..n {
420 out.push(' ');
421 }
422 }
423 Node::Element(e) if e.is(&Ns::Text, "tab") => out.push('\t'),
424 Node::Element(e) if e.is(&Ns::Text, "line-break") => out.push('\n'),
425 Node::Element(e) if e.is(&Ns::Text, "note") => {}
428 Node::Element(e) => e.write_plain_text(out),
429 Node::Comment(_) | Node::ProcessingInstruction(_) => {}
430 }
431 }
432 }
433}
434
435pub use read::parse;
436pub use write::serialize;