mod read;
mod write;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Ns {
Office,
Text,
Style,
Table,
Draw,
Presentation,
Chart,
Fo,
Svg,
Number,
Meta,
Manifest,
Config,
Of,
Xlink,
Dc,
Loext,
Calcext,
Xmlns,
Other(Box<str>),
None,
}
impl Ns {
pub fn from_uri(uri: &str) -> Self {
match uri {
"urn:oasis:names:tc:opendocument:xmlns:office:1.0" => Self::Office,
"urn:oasis:names:tc:opendocument:xmlns:text:1.0" => Self::Text,
"urn:oasis:names:tc:opendocument:xmlns:style:1.0" => Self::Style,
"urn:oasis:names:tc:opendocument:xmlns:table:1.0" => Self::Table,
"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" => Self::Draw,
"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0" => Self::Presentation,
"urn:oasis:names:tc:opendocument:xmlns:chart:1.0" => Self::Chart,
"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" => Self::Fo,
"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" => Self::Svg,
"urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" => Self::Number,
"urn:oasis:names:tc:opendocument:xmlns:meta:1.0" => Self::Meta,
"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0" => Self::Manifest,
"urn:oasis:names:tc:opendocument:xmlns:config:1.0" => Self::Config,
"urn:oasis:names:tc:opendocument:xmlns:of:1.2" => Self::Of,
"http://www.w3.org/1999/xlink" => Self::Xlink,
"http://purl.org/dc/elements/1.1/" => Self::Dc,
"urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" => Self::Loext,
"urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" => Self::Calcext,
other => Self::Other(other.into()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Name {
pub prefix: Option<Box<str>>,
pub local: Box<str>,
pub ns: Ns,
}
impl Name {
pub fn is(&self, ns: &Ns, local: &str) -> bool {
self.ns == *ns && &*self.local == local
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Attribute {
pub name: Name,
pub value: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Node {
Element(Element),
Text(String),
CData(String),
Comment(String),
ProcessingInstruction(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Element {
pub name: Name,
pub attrs: Vec<Attribute>,
pub children: Vec<Node>,
pub self_closing: bool,
}
impl Element {
pub fn new(prefix: &str, local: &str, ns: Ns) -> Self {
Self {
name: Name {
prefix: if prefix.is_empty() {
None
} else {
Some(prefix.into())
},
local: local.into(),
ns,
},
attrs: Vec::new(),
children: Vec::new(),
self_closing: true,
}
}
pub fn is(&self, ns: &Ns, local: &str) -> bool {
self.name.is(ns, local)
}
pub fn attr(&self, ns: &Ns, local: &str) -> Option<&str> {
self.attrs
.iter()
.find(|a| a.name.is(ns, local))
.map(|a| a.value.as_str())
}
pub fn attr_usize(&self, ns: &Ns, local: &str) -> Option<usize> {
self.attr(ns, local)?.trim().parse().ok()
}
pub fn elements(&self) -> impl Iterator<Item = &Element> {
self.children.iter().filter_map(|n| match n {
Node::Element(e) => Some(e),
_ => None,
})
}
pub fn child(&self, ns: &Ns, local: &str) -> Option<&Element> {
self.elements().find(|e| e.is(ns, local))
}
pub fn descendant(&self, ns: &Ns, local: &str) -> Option<&Element> {
for e in self.elements() {
if e.is(ns, local) {
return Some(e);
}
if let Some(found) = e.descendant(ns, local) {
return Some(found);
}
}
None
}
pub fn plain_text(&self) -> String {
let mut out = String::new();
self.write_plain_text(&mut out);
out
}
fn write_plain_text(&self, out: &mut String) {
for child in &self.children {
match child {
Node::Text(t) | Node::CData(t) => out.push_str(t),
Node::Element(e) if e.is(&Ns::Text, "s") => {
let n = e.attr_usize(&Ns::Text, "c").unwrap_or(1);
for _ in 0..n {
out.push(' ');
}
}
Node::Element(e) if e.is(&Ns::Text, "tab") => out.push('\t'),
Node::Element(e) if e.is(&Ns::Text, "line-break") => out.push('\n'),
Node::Element(e) if e.is(&Ns::Text, "note") => {}
Node::Element(e) => e.write_plain_text(out),
Node::Comment(_) | Node::ProcessingInstruction(_) => {}
}
}
}
}
pub use read::parse;
pub use write::serialize;