#![doc(html_root_url = "https://docs.rs/xmlwriter/0.1.0")]
#![forbid(unsafe_code)]
#![warn(missing_docs)]
#![warn(missing_copy_implementations)]
use std::fmt::{self, Display};
use std::io::Write;
use std::ops::Range;
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum Indent {
None,
Spaces(u8),
Tabs,
}
#[derive(Clone, Copy, Debug)]
pub struct Options {
pub use_single_quote: bool,
pub indent: Indent,
pub attributes_indent: Indent,
}
impl Default for Options {
#[inline]
fn default() -> Self {
Options {
use_single_quote: false,
indent: Indent::Spaces(4),
attributes_indent: Indent::None,
}
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
enum State {
Empty,
Document,
Attributes,
}
struct DepthData {
range: Range<usize>,
has_children: bool,
}
pub struct XmlWriter {
buf: Vec<u8>,
state: State,
preserve_whitespaces: bool,
depth_stack: Vec<DepthData>,
opt: Options,
}
impl XmlWriter {
#[inline]
fn from_vec(buf: Vec<u8>, opt: Options) -> Self {
XmlWriter {
buf,
state: State::Empty,
preserve_whitespaces: false,
depth_stack: Vec::new(),
opt,
}
}
#[inline]
pub fn new(opt: Options) -> Self {
Self::from_vec(Vec::new(), opt)
}
#[inline]
pub fn with_capacity(capacity: usize, opt: Options) -> Self {
Self::from_vec(Vec::with_capacity(capacity), opt)
}
#[inline(never)]
pub fn write_declaration(&mut self) {
if self.state != State::Empty {
panic!("declaration was already written");
}
self.state = State::Attributes;
self.push_str("<?xml");
self.write_attribute("version", "1.0");
self.write_attribute("encoding", "UTF-8");
self.write_attribute("standalone", "no");
self.push_str("?>");
self.state = State::Document;
}
pub fn write_comment(&mut self, text: &str) {
self.write_comment_fmt(format_args!("{}", text));
}
#[inline(never)]
pub fn write_comment_fmt(&mut self, fmt: fmt::Arguments) {
if self.state == State::Attributes {
self.write_open_element();
}
if self.state != State::Empty {
self.write_new_line();
}
self.write_node_indent();
self.push_str("<!--");
self.buf.write_fmt(fmt).unwrap(); self.push_str("-->");
if self.state == State::Attributes {
self.depth_stack.push(DepthData {
range: 0..0,
has_children: false,
});
}
self.state = State::Document;
}
#[inline(never)]
pub fn start_element(&mut self, name: &str) {
if self.state == State::Attributes {
self.write_open_element();
}
if self.state != State::Empty {
self.write_new_line();
}
if !self.preserve_whitespaces {
self.write_node_indent();
}
self.push_byte(b'<');
let start = self.buf.len();
self.push_str(name);
self.depth_stack.push(DepthData {
range: start..self.buf.len(),
has_children: false,
});
self.state = State::Attributes;
}
pub fn write_attribute<V: Display + ?Sized>(&mut self, name: &str, value: &V) {
self.write_attribute_fmt(name, format_args!("{}", value));
}
#[inline(never)]
pub fn write_attribute_fmt(&mut self, name: &str, fmt: fmt::Arguments) {
if self.state != State::Attributes {
panic!("must be called after start_element()");
}
self.write_attribute_prefix(name);
let start = self.buf.len();
self.buf.write_fmt(fmt).unwrap();
self.escape_attribute_value(start);
self.write_quote();
}
#[inline(never)]
pub fn write_attribute_raw<F>(&mut self, name: &str, f: F)
where F: FnOnce(&mut Vec<u8>)
{
if self.state != State::Attributes {
panic!("must be called after start_element()");
}
self.write_attribute_prefix(name);
let start = self.buf.len();
f(&mut self.buf);
self.escape_attribute_value(start);
self.write_quote();
}
#[inline(never)]
fn write_attribute_prefix(&mut self, name: &str) {
if self.opt.attributes_indent == Indent::None {
self.push_byte(b' ');
} else {
self.push_byte(b'\n');
let depth = self.depth_stack.len();
if depth > 0 {
self.write_indent(depth - 1, self.opt.indent);
}
self.write_indent(1, self.opt.attributes_indent);
}
self.push_str(name);
self.push_byte(b'=');
self.write_quote();
}
#[inline(never)]
fn escape_attribute_value(&mut self, mut start: usize) {
let quote = if self.opt.use_single_quote { b'\'' } else { b'"' };
while let Some(idx) = self.buf[start..].iter().position(|c| *c == quote) {
let i = start + idx;
let s = if self.opt.use_single_quote { b"'" } else { b""" };
self.buf.splice(i..i+1, s.iter().cloned());
start = i + 6;
}
}
pub fn set_preserve_whitespaces(&mut self, preserve: bool) {
self.preserve_whitespaces = preserve;
}
pub fn write_text(&mut self, text: &str) {
self.write_text_fmt(format_args!("{}", text));
}
#[inline(never)]
pub fn write_text_fmt(&mut self, fmt: fmt::Arguments) {
if self.state == State::Empty || self.depth_stack.is_empty() {
panic!("must be called after start_element()");
}
if self.state == State::Attributes {
self.write_open_element();
}
if self.state != State::Empty {
self.write_new_line();
}
self.write_node_indent();
let start = self.buf.len();
self.buf.write_fmt(fmt).unwrap();
self.escape_text(start);
if self.state == State::Attributes {
self.depth_stack.push(DepthData {
range: 0..0,
has_children: false,
});
}
self.state = State::Document;
}
fn escape_text(&mut self, mut start: usize) {
while let Some(idx) = self.buf[start..].iter().position(|c| *c == b'<') {
let i = start + idx;
self.buf.splice(i..i+1, b"<".iter().cloned());
start = i + 4;
}
}
#[inline(never)]
pub fn end_element(&mut self) {
if let Some(depth) = self.depth_stack.pop() {
if depth.has_children {
if !self.preserve_whitespaces {
self.write_new_line();
self.write_node_indent();
}
self.push_str("</");
for i in depth.range {
self.push_byte(self.buf[i]);
}
self.push_byte(b'>');
} else {
self.push_str("/>");
}
}
self.state = State::Document;
}
pub fn end_document(mut self) -> String {
while !self.depth_stack.is_empty() {
self.end_element();
}
self.write_new_line();
String::from_utf8(self.buf).unwrap()
}
#[inline]
fn push_byte(&mut self, c: u8) {
self.buf.push(c);
}
#[inline]
fn push_str(&mut self, text: &str) {
self.buf.extend_from_slice(text.as_bytes());
}
#[inline]
fn get_quote_char(&self) -> u8 {
if self.opt.use_single_quote { b'\'' } else { b'"' }
}
#[inline]
fn write_quote(&mut self) {
self.push_byte(self.get_quote_char());
}
fn write_open_element(&mut self) {
if let Some(depth) = self.depth_stack.last_mut() {
depth.has_children = true;
self.push_byte(b'>');
self.state = State::Document;
}
}
fn write_node_indent(&mut self) {
self.write_indent(self.depth_stack.len(), self.opt.indent);
}
fn write_indent(&mut self, depth: usize, indent: Indent) {
if indent == Indent::None || self.preserve_whitespaces {
return;
}
for _ in 0..depth {
match indent {
Indent::None => {}
Indent::Spaces(n) => {
for _ in 0..n {
self.push_byte(b' ');
}
}
Indent::Tabs => self.push_byte(b'\t'),
}
}
}
fn write_new_line(&mut self) {
if self.opt.indent != Indent::None && !self.preserve_whitespaces {
self.push_byte(b'\n');
}
}
}