use crate::element::{Element, ElementData};
use crate::error::{EditXMLError, Result};
use crate::parser::{DocumentParser, ReadOptions};
use crate::types::StandaloneValue;
use crate::ElementBuilder;
use quick_xml::events::{BytesCData, BytesDecl, BytesEnd, BytesPI, BytesStart, BytesText, Event};
use quick_xml::Writer;
use std::fs::File;
use std::io::{Read, Write};
use std::path::Path;
use std::str::FromStr;
#[cfg(feature = "document-breakdown")]
mod breakdown;
#[cfg(feature = "document-breakdown")]
pub use breakdown::*;
mod node;
pub use node::*;
#[derive(Debug)]
pub struct Document {
pub(crate) counter: usize, pub(crate) store: Vec<ElementData>,
container: Element,
pub(crate) version: String,
pub(crate) standalone: Option<StandaloneValue>,
}
impl Default for Document {
fn default() -> Self {
let (container, container_data) = Element::container();
Document {
counter: 1, store: vec![container_data],
container,
version: String::from("1.0"),
standalone: None,
}
}
}
impl Document {
pub fn new() -> Document {
Document::default()
}
pub fn new_with_root<N, F>(root_name: N, f: F) -> Document
where
N: Into<String>,
F: FnOnce(ElementBuilder) -> ElementBuilder,
{
let mut doc = Document::new();
let root = f(ElementBuilder::new(root_name)).finish(&mut doc);
doc.push_root_node(root).unwrap();
doc
}
pub fn container(&self) -> Element {
self.container
}
pub fn is_empty(&self) -> bool {
self.store.len() == 1
}
pub fn root_nodes(&self) -> &Vec<Node> {
self.container.children(self)
}
pub fn root_element(&self) -> Option<Element> {
self.container.child_elements(self).first().copied()
}
pub fn push_root_node(&mut self, node: impl Into<Node>) -> Result<()> {
let node = node.into();
let elem = self.container;
elem.push_child(self, node)
}
}
impl Document {
pub fn parse_str(str: &str) -> Result<Document> {
DocumentParser::parse_reader(str.as_bytes(), ReadOptions::default())
}
pub fn parse_str_with_opts(str: &str, opts: ReadOptions) -> Result<Document> {
DocumentParser::parse_reader(str.as_bytes(), opts)
}
pub fn parse_file<P: AsRef<Path>>(path: P) -> Result<Document> {
let file = File::open(path)?;
DocumentParser::parse_reader(file, ReadOptions::default())
}
pub fn parse_file_with_opts<P: AsRef<Path>>(path: P, opts: ReadOptions) -> Result<Document> {
let file = File::open(path)?;
DocumentParser::parse_reader(file, opts)
}
pub fn parse_reader<R: Read>(reader: R) -> Result<Document> {
DocumentParser::parse_reader(reader, ReadOptions::default())
}
pub fn parse_reader_with_opts<R: Read>(reader: R, opts: ReadOptions) -> Result<Document> {
DocumentParser::parse_reader(reader, opts)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WriteOptions {
pub indent_char: u8,
pub indent_size: usize,
pub write_decl: bool,
}
impl Default for WriteOptions {
fn default() -> Self {
Self {
indent_char: b' ',
indent_size: 2,
write_decl: true,
}
}
}
impl Document {
pub fn write_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
self.write_file_with_opts(path, WriteOptions::default())
}
pub fn write_file_with_opts<P: AsRef<Path>>(&self, path: P, opts: WriteOptions) -> Result<()> {
let mut file = File::open(path)?;
self.write_with_opts(&mut file, opts)
}
pub fn write_str(&self) -> Result<String> {
self.write_str_with_opts(WriteOptions::default())
}
pub fn write_str_with_opts(&self, opts: WriteOptions) -> Result<String> {
let mut buf: Vec<u8> = Vec::with_capacity(200);
self.write_with_opts(&mut buf, opts)?;
Ok(String::from_utf8(buf)?)
}
pub fn write(&self, writer: &mut impl Write) -> Result<()> {
self.write_with_opts(writer, WriteOptions::default())
}
pub fn write_with_opts(&self, writer: &mut impl Write, opts: WriteOptions) -> Result<()> {
let container = self.container();
let mut writer = Writer::new_with_indent(writer, opts.indent_char, opts.indent_size);
if opts.write_decl {
self.write_decl(&mut writer)?;
}
self.write_nodes(&mut writer, container.children(self))?;
writer.write_event(Event::Eof)?;
Ok(())
}
fn write_decl(&self, writer: &mut Writer<impl Write>) -> Result<()> {
let standalone = self.standalone.map(|v| v.as_str());
writer.write_event(Event::Decl(BytesDecl::new(
&self.version,
Some("UTF-8"),
standalone,
)))?;
Ok(())
}
fn write_nodes(&self, writer: &mut Writer<impl Write>, nodes: &[Node]) -> Result<()> {
for node in nodes {
match node {
Node::Element(eid) => self.write_element(writer, *eid)?,
Node::Text(text) => writer.write_event(Event::Text(BytesText::new(text)))?,
Node::DocType(text) => writer.write_event(Event::DocType(
BytesText::new(&format!(" {}", text)), ))?,
Node::Comment(text) => {
writer.write_event(Event::Comment(BytesText::new(text)))?
}
Node::CData(text) => {
writer.write_event(Event::CData(BytesCData::new(text)))?
}
Node::PI(text) => writer.write_event(Event::PI(BytesPI::new(text)))?,
};
}
Ok(())
}
fn write_element(&self, writer: &mut Writer<impl Write>, element: Element) -> Result<()> {
let name_bytes = element.full_name(self);
let mut start = BytesStart::new(name_bytes);
for (key, val) in element.attributes(self) {
let val = quick_xml::escape::escape(val.as_str());
start.push_attribute((key.as_str(), val.as_ref()));
}
for (prefix, val) in element.namespace_decls(self) {
let attr_name = if prefix.is_empty() {
"xmlns".to_string()
} else {
format!("xmlns:{}", prefix)
};
let val = quick_xml::escape::escape(val.as_str());
start.push_attribute((attr_name.as_str(), val.as_ref()));
}
if element.has_children(self) {
writer.write_event(Event::Start(start))?;
self.write_nodes(writer, element.children(self))?;
writer.write_event(Event::End(BytesEnd::new(name_bytes)))?;
} else {
writer.write_event(Event::Empty(start))?;
}
Ok(())
}
}
impl FromStr for Document {
type Err = EditXMLError;
fn from_str(s: &str) -> Result<Document> {
Document::parse_str(s)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add_element() {
let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
<basic>
Text
<c />
</basic>
"#;
let mut doc = Document::from_str(xml).unwrap();
let basic = doc.container().children(&doc)[0].as_element().unwrap();
let p = Element::new(&mut doc, "p");
basic.push_child(&mut doc, Node::Element(p)).unwrap();
assert_eq!(p.parent(&doc).unwrap(), basic);
assert_eq!(
p,
basic.children(&doc).last().unwrap().as_element().unwrap()
)
}
}