xmlwriter-0.1.0/.gitignore010064400017500001750000000000361352417414300137610ustar0000000000000000/target **/*.rs.bk Cargo.lock xmlwriter-0.1.0/.travis.yml010064400017500001750000000000531352417414300141010ustar0000000000000000language: rust rust: - 1.32.0 - stable xmlwriter-0.1.0/Cargo.toml.orig010064400017500001750000000005011352417352200146550ustar0000000000000000[package] name = "xmlwriter" version = "0.1.0" authors = ["Evgeniy Reizner "] description = "A simple, streaming XML writer." repository = "https://github.com/RazrFalcon/xmlwriter" documentation = "https://docs.rs/xmlwriter/" keywords = ["xml"] license = "MIT" readme = "README.md" edition = "2018" xmlwriter-0.1.0/Cargo.toml0000644000000015160000000000000111270ustar00# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "xmlwriter" version = "0.1.0" authors = ["Evgeniy Reizner "] description = "A simple, streaming XML writer." documentation = "https://docs.rs/xmlwriter/" readme = "README.md" keywords = ["xml"] license = "MIT" repository = "https://github.com/RazrFalcon/xmlwriter" xmlwriter-0.1.0/LICENSE010064400017500001750000000020721352417414300130000ustar0000000000000000The MIT License (MIT) Copyright (c) 2019 Reizner Evgeniy Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. xmlwriter-0.1.0/README.md010064400017500001750000000023761352417414300132610ustar0000000000000000## xmlwriter [![Build Status](https://travis-ci.org/RazrFalcon/xmlwriter.svg?branch=master)](https://travis-ci.org/RazrFalcon/xmlwriter) [![Crates.io](https://img.shields.io/crates/v/xmlwriter.svg)](https://crates.io/crates/xmlwriter) [![Documentation](https://docs.rs/xmlwriter/badge.svg)](https://docs.rs/xmlwriter) A simple, streaming, partially-validating XML writer that writes XML data into an internal buffer. ### Features - A simple, bare-minimum, panic-based API. - Non-allocating API. All methods are accepting either `fmt::Display` or `fmt::Arguments`. - Nodes auto-closing. ### Example ```rust use xmlwriter::*; let opt = Options { use_single_quote: true, ..Options::default() }; let mut w = XmlWriter::new(opt); w.start_element("svg"); w.write_attribute("xmlns", "http://www.w3.org/2000/svg"); w.write_attribute_fmt("viewBox", format_args!("{} {} {} {}", 0, 0, 128, 128)); w.start_element("text"); // We can write any object that implements `fmt::Display`. w.write_attribute("x", &10); w.write_attribute("y", &20); w.write_text_fmt(format_args!("length is {}", 5)); assert_eq!(w.end_document(), " length is 5 "); ``` ### License MIT xmlwriter-0.1.0/README.tpl010064400017500001750000000005341352417414300134520ustar0000000000000000## {{crate}} [![Build Status](https://travis-ci.org/RazrFalcon/{{crate}}.svg?branch=master)](https://travis-ci.org/RazrFalcon/{{crate}}) [![Crates.io](https://img.shields.io/crates/v/{{crate}}.svg)](https://crates.io/crates/{{crate}}) [![Documentation](https://docs.rs/{{crate}}/badge.svg)](https://docs.rs/{{crate}}) {{readme}} ### License MIT xmlwriter-0.1.0/src/lib.rs010064400017500001750000000355131351403540200136750ustar0000000000000000/*! A simple, streaming, partially-validating XML writer that writes XML data into an internal buffer. ## Features - A simple, bare-minimum, panic-based API. - Non-allocating API. All methods are accepting either `fmt::Display` or `fmt::Arguments`. - Nodes auto-closing. ## Example ```rust use xmlwriter::*; let opt = Options { use_single_quote: true, ..Options::default() }; let mut w = XmlWriter::new(opt); w.start_element("svg"); w.write_attribute("xmlns", "http://www.w3.org/2000/svg"); w.write_attribute_fmt("viewBox", format_args!("{} {} {} {}", 0, 0, 128, 128)); w.start_element("text"); // We can write any object that implements `fmt::Display`. w.write_attribute("x", &10); w.write_attribute("y", &20); w.write_text_fmt(format_args!("length is {}", 5)); assert_eq!(w.end_document(), " length is 5 "); ``` */ #![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; /// An XML node indention. #[derive(Clone, Copy, PartialEq, Debug)] pub enum Indent { /// Disable indention and new lines. None, /// Indent with spaces. Preferred range is 0..4. Spaces(u8), /// Indent with tabs. Tabs, } /// An XML writing options. #[derive(Clone, Copy, Debug)] pub struct Options { /// Use single quote marks instead of double quote. /// /// # Examples /// /// Before: /// /// ```text /// /// ``` /// /// After: /// /// ```text /// /// ``` /// /// Default: disabled pub use_single_quote: bool, /// Set XML nodes indention. /// /// # Examples /// /// `Indent::None` /// Before: /// /// ```text /// /// /// /// ``` /// /// After: /// /// ```text /// /// ``` /// /// Default: 4 spaces pub indent: Indent, /// Set XML attributes indention. /// /// # Examples /// /// `Indent::Spaces(2)` /// /// Before: /// /// ```text /// /// /// /// ``` /// /// After: /// /// ```text /// /// /// /// ``` /// /// Default: `None` 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, has_children: bool, } /// An XML writer. pub struct XmlWriter { buf: Vec, state: State, preserve_whitespaces: bool, depth_stack: Vec, opt: Options, } impl XmlWriter { #[inline] fn from_vec(buf: Vec, opt: Options) -> Self { XmlWriter { buf, state: State::Empty, preserve_whitespaces: false, depth_stack: Vec::new(), opt, } } /// Creates a new `XmlWriter`. #[inline] pub fn new(opt: Options) -> Self { Self::from_vec(Vec::new(), opt) } /// Creates a new `XmlWriter` with a specified capacity. #[inline] pub fn with_capacity(capacity: usize, opt: Options) -> Self { Self::from_vec(Vec::with_capacity(capacity), opt) } /// Writes an XML declaration. /// /// `` /// /// # Panics /// /// - When called twice. #[inline(never)] pub fn write_declaration(&mut self) { if self.state != State::Empty { panic!("declaration was already written"); } // Pretend that we are writing an element. self.state = State::Attributes; // self.push_str(""); self.state = State::Document; } /// Writes a comment string. pub fn write_comment(&mut self, text: &str) { self.write_comment_fmt(format_args!("{}", text)); } /// Writes a formatted comment. #[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(""); if self.state == State::Attributes { self.depth_stack.push(DepthData { range: 0..0, has_children: false, }); } self.state = State::Document; } /// Starts writing a new element. /// /// This method writes only the `\n"); /// ``` pub fn write_attribute(&mut self, name: &str, value: &V) { self.write_attribute_fmt(name, format_args!("{}", value)); } /// Writes a formatted attribute value. /// /// Quotes in the value will be escaped. /// /// # Panics /// /// - When called before `start_element()`. /// - When called after `close_element()`. /// /// # Example /// /// ``` /// use xmlwriter::*; /// /// let mut w = XmlWriter::new(Options::default()); /// w.start_element("rect"); /// w.write_attribute_fmt("fill", format_args!("url(#{})", "gradient")); /// assert_eq!(w.end_document(), "\n"); /// ``` #[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(); } /// Writes a raw attribute value. /// /// Closure provides a mutable reference to an internal buffer. /// /// **Warning:** this method is an escape hatch for cases when you need to write /// a lot of data very fast. /// /// # Panics /// /// - When called before `start_element()`. /// - When called after `close_element()`. /// /// # Example /// /// ``` /// use xmlwriter::*; /// /// let mut w = XmlWriter::new(Options::default()); /// w.start_element("path"); /// w.write_attribute_raw("d", |buf| buf.extend_from_slice(b"M 10 20 L 30 40")); /// assert_eq!(w.end_document(), "\n"); /// ``` #[inline(never)] pub fn write_attribute_raw(&mut self, name: &str, f: F) where F: FnOnce(&mut Vec) { 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(); } /// Escapes the attribute value string. /// /// - " -> " /// - ' -> ' #[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; } } /// Sets the preserve whitespaces flag. /// /// - If set, text nodes will be written as is. /// - If not set, text nodes will be indented. /// /// Can be set at any moment. /// /// # Example /// /// ``` /// use xmlwriter::*; /// /// let mut w = XmlWriter::new(Options::default()); /// w.start_element("html"); /// w.start_element("p"); /// w.write_text("text"); /// w.end_element(); /// w.start_element("p"); /// w.set_preserve_whitespaces(true); /// w.write_text("text"); /// w.end_element(); /// w.set_preserve_whitespaces(false); /// assert_eq!(w.end_document(), /// " ///

/// text ///

///

text

/// /// "); /// ``` pub fn set_preserve_whitespaces(&mut self, preserve: bool) { self.preserve_whitespaces = preserve; } /// Writes a text node. /// /// See `write_text_fmt()` for details. pub fn write_text(&mut self, text: &str) { self.write_text_fmt(format_args!("{}", text)); } /// Writes a formatted text node. /// /// `<` will be escaped. /// /// # Panics /// /// - When called not after `start_element()`. #[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; } } /// Closes an open element. #[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("'); } else { self.push_str("/>"); } } self.state = State::Document; } /// Closes all open elements and returns an internal XML buffer. /// /// # Example /// /// ``` /// use xmlwriter::*; /// /// let mut w = XmlWriter::new(Options::default()); /// w.start_element("svg"); /// w.start_element("g"); /// w.start_element("rect"); /// assert_eq!(w.end_document(), /// " /// /// /// /// /// "); /// ``` pub fn end_document(mut self) -> String { while !self.depth_stack.is_empty() { self.end_element(); } self.write_new_line(); // The only way it can fail is if an invalid data // was written via `write_attribute_raw()`. 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'); } } } xmlwriter-0.1.0/tests/tests.rs010064400017500001750000000241111352417353300146450ustar0000000000000000use std::str; use xmlwriter::{XmlWriter, Options}; #[derive(Clone, Copy, PartialEq)] struct TStr<'a>(pub &'a str); macro_rules! text_eq { ($result:expr, $expected:expr) => { assert_eq!($result, $expected) }; } #[test] fn write_element_01() { let mut w = XmlWriter::new(Options::default()); w.start_element("svg"); w.end_element(); text_eq!(w.end_document(), "\n"); } #[test] fn write_element_02() { let mut w = XmlWriter::new(Options::default()); w.start_element("svg"); w.start_element("rect"); w.end_element(); w.end_element(); text_eq!(w.end_document(), " "); } #[test] fn write_element_03() { let mut w = XmlWriter::new(Options::default()); w.start_element("svg"); w.end_element(); w.end_element(); // Should not panic. text_eq!(w.end_document(), "\n"); } #[test] fn write_element_05() { let mut w = XmlWriter::new(Options::default()); w.start_element("svg"); // end_document() will call `close_element` automatically. text_eq!(w.end_document(), "\n"); } #[test] fn write_element_06() { let mut w = XmlWriter::new(Options::default()); w.start_element("svg"); w.start_element("rect"); w.start_element("rect"); w.start_element("rect"); w.start_element("rect"); w.start_element("rect"); text_eq!(w.end_document(), " "); } #[test] #[should_panic] fn write_attribute_01() { let mut w = XmlWriter::new(Options::default()); // must be used only after write_element w.write_attribute("id", "q"); } #[test] fn write_attribute_02() { let mut w = XmlWriter::new(Options::default()); w.start_element("svg"); w.write_attribute("id", "q"); w.end_element(); text_eq!(w.end_document(), "\n"); } #[test] fn write_attribute_03() { let mut w = XmlWriter::new(Options::default()); w.start_element("svg"); w.write_attribute("id", "\""); w.end_element(); text_eq!(w.end_document(), "\n"); } #[test] fn write_attribute_04() { let opt = Options { use_single_quote: true, .. Options::default() }; let mut w = XmlWriter::new(opt); w.start_element("svg"); w.write_attribute("id", "'"); w.end_element(); text_eq!(w.end_document(), "\n"); } #[test] fn write_attribute_05() { let opt = Options { use_single_quote: true, .. Options::default() }; let mut w = XmlWriter::new(opt); w.start_element("svg"); w.write_attribute("id", "'''''"); w.end_element(); text_eq!(w.end_document(), "\n"); } #[test] fn write_attribute_06() { let opt = Options { use_single_quote: true, .. Options::default() }; let mut w = XmlWriter::new(opt); w.start_element("svg"); w.write_attribute("id", "'text'"); w.end_element(); text_eq!(w.end_document(), "\n"); } #[test] fn write_attribute_07() { let mut w = XmlWriter::new(Options::default()); w.start_element("svg"); // TODO: looks we need specialization to remove & w.write_attribute("x", &5); w.end_element(); text_eq!(w.end_document(), "\n"); } #[test] fn write_declaration_01() { let mut w = XmlWriter::new(Options::default()); w.write_declaration(); text_eq!(w.end_document(), "\n"); } #[test] #[should_panic] fn write_declaration_02() { let mut w = XmlWriter::new(Options::default()); w.write_declaration(); w.write_declaration(); // declaration must be written once } #[test] #[should_panic] fn write_declaration_03() { let mut w = XmlWriter::new(Options::default()); w.write_comment("test"); w.write_declaration(); // declaration must be written first } #[test] fn write_single_quote_01() { let opt = Options { use_single_quote: true, .. Options::default() }; let mut w = XmlWriter::new(opt); w.write_declaration(); text_eq!(w.end_document(), "\n"); } #[test] fn write_single_quote_02() { let opt = Options { use_single_quote: true, .. Options::default() }; let mut w = XmlWriter::new(opt); w.start_element("p"); w.write_attribute("a", "b"); w.end_element(); text_eq!(w.end_document(), "

\n"); } #[test] fn write_comment_01() { let mut w = XmlWriter::new(Options::default()); w.write_comment("test"); w.start_element("svg"); text_eq!(w.end_document(), " "); } #[test] fn write_comment_02() { let mut w = XmlWriter::new(Options::default()); w.start_element("svg"); w.write_comment("test"); text_eq!(w.end_document(), " "); } #[test] fn write_comment_03() { let mut w = XmlWriter::new(Options::default()); w.write_comment("test"); w.start_element("svg"); w.write_comment("test"); text_eq!(w.end_document(), " "); } #[test] fn write_comment_04() { let mut w = XmlWriter::new(Options::default()); w.write_comment("test"); w.start_element("svg"); w.start_element("rect"); w.write_comment("test"); text_eq!(w.end_document(), " "); } #[test] fn write_comment_05() { let mut w = XmlWriter::new(Options::default()); w.write_comment("test"); w.start_element("svg"); w.write_comment("test"); w.start_element("rect"); w.end_element(); text_eq!(w.end_document(), " "); } #[test] fn write_comment_06() { let mut w = XmlWriter::new(Options::default()); w.write_comment("test"); w.start_element("svg"); w.start_element("rect"); w.end_element(); w.write_comment("test"); text_eq!(w.end_document(), " "); } #[test] fn write_comment_07() { let mut w = XmlWriter::new(Options::default()); w.start_element("svg"); w.end_element(); w.write_comment("test"); text_eq!(w.end_document(), " "); } #[test] fn write_comment_08() { let mut w = XmlWriter::new(Options::default()); w.write_comment("test"); w.write_comment("test"); w.write_comment("test"); text_eq!(w.end_document(), " "); } #[test] #[should_panic] fn write_text_01() { let mut w = XmlWriter::new(Options::default()); w.write_text("text"); // Should be called after start_element() } #[test] #[should_panic] fn write_text_02() { let mut w = XmlWriter::new(Options::default()); w.write_text("text"); // Should be called after start_element() } #[test] #[should_panic] fn write_text_03() { let mut w = XmlWriter::new(Options::default()); w.start_element("p"); w.end_element(); w.write_text("text"); // Should be called after start_element() } #[test] fn write_text_04() { let mut w = XmlWriter::new(Options::default()); w.start_element("p"); w.write_text("text"); w.write_text("text"); text_eq!(w.end_document(), "

text text

"); } #[test] fn write_text_05() { let mut w = XmlWriter::new(Options::default()); w.start_element("p"); w.write_text("text"); text_eq!(w.end_document(), "

text

"); } #[test] fn write_text_06() { let mut w = XmlWriter::new(Options::default()); w.start_element("p"); w.write_text("text"); w.start_element("p"); w.write_text("text"); text_eq!(w.end_document(), "

text

text

"); } #[test] fn write_text_07() { let mut w = XmlWriter::new(Options::default()); w.start_element("div"); w.start_element("p"); w.write_text("text"); w.start_element("p"); w.write_text("text"); text_eq!(w.end_document(), "

text

text

"); } #[test] fn write_text_08() { let mut w = XmlWriter::new(Options::default()); w.start_element("p"); w.write_text("<"); text_eq!(w.end_document(), "

<

"); } #[test] fn write_text_09() { let mut w = XmlWriter::new(Options::default()); w.start_element("p"); w.write_text("<&>"); text_eq!(w.end_document(), "

<&>

"); } #[test] fn write_text_10() { let mut w = XmlWriter::new(Options::default()); w.start_element("p"); w.write_text("<"); text_eq!(w.end_document(), "

<

"); } #[test] fn write_text_11() { let mut w = XmlWriter::new(Options::default()); w.start_element("p"); w.write_text("text"); w.start_element("p"); w.end_element(); w.write_text("text"); text_eq!(w.end_document(), "

text

text

"); } #[test] fn write_preserve_text_01() { let mut w = XmlWriter::new(Options::default()); w.set_preserve_whitespaces(true); w.start_element("p"); w.write_text("text"); w.start_element("p"); w.end_element(); w.write_text("text"); text_eq!(w.end_document(), "

text

text

"); } #[test] fn write_preserve_text_02() { let mut w = XmlWriter::new(Options::default()); w.start_element("p"); w.start_element("p"); w.set_preserve_whitespaces(true); w.write_text("text"); w.start_element("p"); w.end_element(); w.write_text("text"); w.end_element(); w.set_preserve_whitespaces(false); text_eq!(w.end_document(), "

text

text

"); } #[test] fn attrs_indent_01() { let opt = Options { attributes_indent: xmlwriter::Indent::Spaces(2), .. Options::default() }; let mut w = XmlWriter::new(opt); w.start_element("rect"); w.write_attribute("x", "5"); w.start_element("rect"); w.write_attribute("x", "10"); w.write_attribute("y", "15"); text_eq!(w.end_document(), " "); } xmlwriter-0.1.0/.cargo_vcs_info.json0000644000000001120000000000000131200ustar00{ "git": { "sha1": "5fdcff47da7d71896b55a1bc0e4b561171e232d6" } }