xmltree-0.10.3/.cargo_vcs_info.json0000644000000001120000000000000125660ustar { "git": { "sha1": "5d856838919cfdf31fbdc318780532b437d4e3a3" } } xmltree-0.10.3/.gitignore000064400000000000000000000000220000000000000133240ustar 00000000000000target Cargo.lock xmltree-0.10.3/Cargo.toml0000644000000020630000000000000105730ustar # 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 = "xmltree" version = "0.10.3" authors = ["Andrew Chin "] description = "Parse an XML file into a simple tree-like structure" documentation = "https://docs.rs/xmltree/" readme = "README.md" keywords = ["xml", "parse", "xmltree"] categories = ["parsing", "data-structures"] license = "MIT" repository = "https://github.com/eminence/xmltree-rs" [dependencies.indexmap] version = "1.4.0" optional = true [dependencies.xml-rs] version = "0.8" [features] attribute-order = ["indexmap"] default = [] xmltree-0.10.3/Cargo.toml.orig000064400000000000000000000010260000000000000142300ustar 00000000000000[package] name = "xmltree" version = "0.10.3" authors = ["Andrew Chin "] description = "Parse an XML file into a simple tree-like structure" documentation = "https://docs.rs/xmltree/" repository = "https://github.com/eminence/xmltree-rs" keywords = ["xml", "parse", "xmltree"] license = "MIT" readme = "README.md" categories = ["parsing", "data-structures"] edition = "2018" [dependencies] xml-rs = "0.8" indexmap = { version = "1.4.0", optional = true } [features] default = [] attribute-order = ["indexmap"] xmltree-0.10.3/LICENSE000064400000000000000000000020530000000000000123470ustar 00000000000000MIT License Copyright (c) 2015 Andrew Chin 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. xmltree-0.10.3/README.md000064400000000000000000000022760000000000000126300ustar 00000000000000xmltree-rs ========== [Documention](https://docs.rs/xmltree/) A small library for parsing an XML file into an in-memory tree structure. Not recommended for large XML files, as it will load the entire file into memory. https://crates.io/crates/xmltree ## Usage Add the following to your `Cargo.toml` file: ```toml [dependencies] xmltree = "0.10" ``` ### Feature-flags * `attribute-order` - change the data structure that stores attributes into one that keeps a consistent order. This changes the type definition and adds another dependency. ## Compatability with xml-rs This crate will export some types from the xml-rs crate. If your own crate also uses the xml-rs crate, but with a different version, the types may be incompatible. One way to solve this is to only use the exported types, but sometimes that is not always possible. In those cases you should use a version of xmltree that matches the version of xml-rs you are using: | xml-rs version | xmltree version | |----------------|-----------------| | 0.8 | 0.10 | | 0.7 | 0.8 | | 0.6 | 0.6 | ## Example See the documentation for some examples: https://docs.rs/xmltree/ xmltree-0.10.3/benches/suite.rs000064400000000000000000000115300000000000000144500ustar 00000000000000#![feature(test)] extern crate test; extern crate xmltree; use test::Bencher; use std::fs::File; use std::io::Cursor; use std::io::Read; use xmltree::Element; use xmltree::ParseError; fn _parse_file(filename: &str) -> std::result::Result { let mut file = File::open(filename).unwrap(); let mut data = String::new(); file.read_to_string(&mut data).unwrap(); Element::parse(data.as_bytes()) } #[bench] fn bench_01(b: &mut Bencher) { b.iter(|| { let filename = "tests/data/01.xml"; let e: Element = _parse_file(filename).unwrap(); assert_eq!(e.name, "project"); let e2: &Element = e .get_child("libraries") .expect("Missing libraries child element"); assert_eq!(e2.name, "libraries"); assert!(e.get_child("doesnotexist").is_none()); let mut buf = Vec::new(); e.write(&mut buf).unwrap(); let e2 = Element::parse(Cursor::new(buf)).unwrap(); assert_eq!(e, e2); }) } #[bench] fn bench_02(b: &mut Bencher) { let filename = "tests/data/02.xml"; b.iter(|| { let _ = _parse_file(filename).unwrap(); }); } #[bench] fn bench_03(b: &mut Bencher) { let filename = "tests/data/03.xml"; b.iter(|| { let _ = _parse_file(filename).unwrap(); }); } #[bench] fn bench_04(b: &mut Bencher) { let filename = "tests/data/04.xml"; b.iter(|| { let _ = _parse_file(filename).unwrap(); }); } #[bench] fn bench_rw(b: &mut Bencher) { let filename = "tests/data/rw.xml"; b.iter(|| { let e = _parse_file(filename).unwrap(); let mut buf = Vec::new(); e.write(&mut buf).unwrap(); let e2 = Element::parse(Cursor::new(buf)).unwrap(); assert_eq!(e, e2); }) } #[bench] fn bench_mal_01(b: &mut Bencher) { let data = r##" "##; b.iter(|| { let names_element = Element::parse(data.as_bytes()); if let Err(ParseError::MalformedXml(..)) = names_element { // OK } else { panic!("unexpected parse result"); } }); } #[bench] fn bench_mal_02(b: &mut Bencher) { let data = r##" this is not even close to XML "##; b.iter(|| { let names_element = Element::parse(data.as_bytes()); if let Err(ParseError::MalformedXml(..)) = names_element { // OK } else { panic!("unexpected parse result"); } }); } #[bench] fn bench_mal_03(b: &mut Bencher) { let data = r##" "##; b.iter(|| { let names_element = Element::parse(data.as_bytes()); if let Err(ParseError::MalformedXml(..)) = names_element { // OK } else { panic!("unexpected parse result"); } }); } #[bench] fn bench_new(b: &mut Bencher) { b.iter(|| { let e = Element::new("foo"); assert_eq!(e.name.as_str(), "foo"); assert_eq!(e.attributes.len(), 0); assert_eq!(e.children.len(), 0); assert_eq!(e.get_text(), None); }); } #[bench] fn bench_take(b: &mut Bencher) { let data_xml_1 = r##" "##; let data_xml_2 = r##" "##; b.iter(|| { let mut data_1 = Element::parse(data_xml_1.as_bytes()).unwrap(); let data_2 = Element::parse(data_xml_2.as_bytes()).unwrap(); if let Some(removed) = data_1.take_child("remove_me") { assert_eq!(removed.children.len(), 1); } else { panic!("take_child failed"); } assert_eq!(data_1, data_2); }); } #[bench] fn bench_ns1_rw(b: &mut Bencher) { let filename = "tests/data/ns2.xml"; b.iter(|| { let e = _parse_file(filename).unwrap(); let mut buf = Vec::new(); e.write(&mut buf).unwrap(); }); } #[bench] fn bench_ns2_rw(b: &mut Bencher) { let filename = "tests/data/ns2.xml"; b.iter(|| { let e = _parse_file(filename).unwrap(); let mut buf = Vec::new(); e.write(&mut buf).unwrap(); }); } xmltree-0.10.3/src/lib.rs000064400000000000000000000404510000000000000132510ustar 00000000000000//! A simple library for parsing an XML file into an in-memory tree structure //! //! Not recommended for large XML files, as it will load the entire file into memory. //! //! # Example //! //! ```no_run //! use xmltree::Element; //! use std::fs::File; //! //! let data: &'static str = r##" //! //! //! //! //! //! "##; //! //! let mut names_element = Element::parse(data.as_bytes()).unwrap(); //! //! println!("{:#?}", names_element); //! { //! // get first `name` element //! let name = names_element.get_mut_child("name").expect("Can't find name element"); //! name.attributes.insert("suffix".to_owned(), "mr".to_owned()); //! } //! names_element.write(File::create("result.xml").unwrap()); //! //! //! ``` extern crate xml; use std::borrow::Cow; #[cfg(not(feature = "attribute-order"))] use std::collections::HashMap as AttributeMap; use std::fmt; use std::io::{Read, Write}; pub use xml::namespace::Namespace; use xml::reader::{EventReader, ParserConfig, XmlEvent}; pub use xml::writer::{EmitterConfig, Error}; #[cfg(feature = "attribute-order")] use indexmap::map::IndexMap as AttributeMap; #[derive(Debug, Clone, PartialEq, Eq)] pub enum XMLNode { Element(Element), Comment(String), CData(String), Text(String), ProcessingInstruction(String, Option), } impl XMLNode { pub fn as_element(&self) -> Option<&Element> { if let XMLNode::Element(e) = self { Some(e) } else { None } } pub fn as_mut_element(&mut self) -> Option<&mut Element> { if let XMLNode::Element(e) = self { Some(e) } else { None } } pub fn as_comment(&self) -> Option<&str> { if let XMLNode::Comment(c) = self { Some(c) } else { None } } pub fn as_cdata(&self) -> Option<&str> { if let XMLNode::CData(c) = self { Some(c) } else { None } } pub fn as_text(&self) -> Option<&str> { if let XMLNode::Text(c) = self { Some(c) } else { None } } pub fn as_processing_instruction(&self) -> Option<(&str, Option<&str>)> { if let XMLNode::ProcessingInstruction(s, o) = self { Some((s, o.as_ref().map(|s| s.as_str()))) } else { None } } } /// Represents an XML element. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Element { /// This elements prefix, if any pub prefix: Option, /// This elements namespace, if any pub namespace: Option, /// The full list of namespaces, if any /// /// The `Namespace` type is exported from the `xml-rs` crate. pub namespaces: Option, /// The name of the Element. Does not include any namespace info pub name: String, /// The Element attributes /// /// By default, this is a `HashMap`, but if the optional "attribute-order" feature is enabled, /// this is an [IndexMap](https://docs.rs/indexmap/1.4.0/indexmap/), which will retain /// item insertion order. pub attributes: AttributeMap, /// Children pub children: Vec, } /// Errors that can occur parsing XML #[derive(Debug)] pub enum ParseError { /// The XML is invalid MalformedXml(xml::reader::Error), /// This library is unable to process this XML. This can occur if, for /// example, the XML contains processing instructions. CannotParse, } impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { ParseError::MalformedXml(ref e) => write!(f, "Malformed XML. {}", e), ParseError::CannotParse => write!(f, "Cannot parse"), } } } impl std::error::Error for ParseError { fn description(&self) -> &str { match *self { ParseError::MalformedXml(..) => "Malformed XML", ParseError::CannotParse => "Cannot parse", } } fn cause(&self) -> Option<&dyn std::error::Error> { match *self { ParseError::MalformedXml(ref e) => Some(e), ParseError::CannotParse => None, } } } fn build(reader: &mut EventReader, mut elem: Element) -> Result { loop { match reader.next() { Ok(XmlEvent::EndElement { ref name }) => { if name.local_name == elem.name { return Ok(elem); } else { return Err(ParseError::CannotParse); } } Ok(XmlEvent::StartElement { name, attributes, namespace, }) => { let mut attr_map = AttributeMap::new(); for attr in attributes { attr_map.insert(attr.name.local_name, attr.value); } let new_elem = Element { prefix: name.prefix, namespace: name.namespace, namespaces: if namespace.is_essentially_empty() { None } else { Some(namespace) }, name: name.local_name, attributes: attr_map, children: Vec::new(), }; elem.children .push(XMLNode::Element(build(reader, new_elem)?)); } Ok(XmlEvent::Characters(s)) => elem.children.push(XMLNode::Text(s)), Ok(XmlEvent::Whitespace(..)) => (), Ok(XmlEvent::Comment(s)) => elem.children.push(XMLNode::Comment(s)), Ok(XmlEvent::CData(s)) => elem.children.push(XMLNode::Text(s)), Ok(XmlEvent::ProcessingInstruction { name, data }) => elem .children .push(XMLNode::ProcessingInstruction(name, data)), Ok(XmlEvent::StartDocument { .. }) | Ok(XmlEvent::EndDocument) => { return Err(ParseError::CannotParse) } Err(e) => return Err(ParseError::MalformedXml(e)), } } } impl Element { /// Create a new empty element with given name /// /// All other fields are empty pub fn new(name: &str) -> Element { Element { name: String::from(name), prefix: None, namespace: None, namespaces: None, attributes: AttributeMap::new(), children: Vec::new(), } } /// Parses some data into a list of `XMLNode`s /// /// This is useful when you want to capture comments or processing instructions that appear /// before or after the root node pub fn parse_all(r: R) -> Result, ParseError> { let parser_config = ParserConfig::new().ignore_comments(false); let mut reader = EventReader::new_with_config(r, parser_config); let mut root_nodes = Vec::new(); loop { match reader.next() { Ok(XmlEvent::StartElement { name, attributes, namespace, }) => { let mut attr_map = AttributeMap::with_capacity(attributes.len()); for attr in attributes { attr_map.insert(attr.name.local_name, attr.value); } let root = Element { prefix: name.prefix, namespace: name.namespace, namespaces: if namespace.is_essentially_empty() { None } else { Some(namespace) }, name: name.local_name, attributes: attr_map, children: Vec::new(), }; root_nodes.push(XMLNode::Element(build(&mut reader, root)?)); } Ok(XmlEvent::Comment(comment_string)) => { root_nodes.push(XMLNode::Comment(comment_string)) } Ok(XmlEvent::Characters(text_string)) => { root_nodes.push(XMLNode::Text(text_string)) } Ok(XmlEvent::CData(cdata_string)) => root_nodes.push(XMLNode::CData(cdata_string)), Ok(XmlEvent::Whitespace(..)) | Ok(XmlEvent::StartDocument { .. }) => continue, Ok(XmlEvent::ProcessingInstruction { name, data }) => { root_nodes.push(XMLNode::ProcessingInstruction(name, data)) } Ok(XmlEvent::EndElement { .. }) => (), Ok(XmlEvent::EndDocument) => return Ok(root_nodes), Err(e) => return Err(ParseError::MalformedXml(e)), } } } /// Parses some data into an Element pub fn parse(r: R) -> Result { let nodes = Element::parse_all(r)?; for node in nodes { match node { XMLNode::Element(elem) => return Ok(elem), _ => (), } } // This assume the underlying xml library throws an error on no root element unreachable!(); } fn _write(&self, emitter: &mut xml::writer::EventWriter) -> Result<(), Error> { use xml::attribute::Attribute; use xml::name::Name; use xml::writer::events::XmlEvent; let mut name = Name::local(&self.name); if let Some(ref ns) = self.namespace { name.namespace = Some(ns); } if let Some(ref p) = self.prefix { name.prefix = Some(p); } let mut attributes = Vec::with_capacity(self.attributes.len()); for (k, v) in &self.attributes { attributes.push(Attribute { name: Name::local(k), value: v, }); } let empty_ns = Namespace::empty(); let namespace = if let Some(ref ns) = self.namespaces { Cow::Borrowed(ns) } else { Cow::Borrowed(&empty_ns) }; emitter.write(XmlEvent::StartElement { name, attributes: Cow::Owned(attributes), namespace, })?; for node in &self.children { match node { XMLNode::Element(elem) => elem._write(emitter)?, XMLNode::Text(text) => emitter.write(XmlEvent::Characters(text))?, XMLNode::Comment(comment) => emitter.write(XmlEvent::Comment(comment))?, XMLNode::CData(comment) => emitter.write(XmlEvent::CData(comment))?, XMLNode::ProcessingInstruction(name, data) => match data.to_owned() { Some(string) => emitter.write(XmlEvent::ProcessingInstruction { name, data: Some(&string), })?, None => emitter.write(XmlEvent::ProcessingInstruction { name, data: None })?, }, } // elem._write(emitter)?; } emitter.write(XmlEvent::EndElement { name: Some(name) })?; Ok(()) } /// Writes out this element as the root element in an new XML document pub fn write(&self, w: W) -> Result<(), Error> { self.write_with_config(w, EmitterConfig::new()) } /// Writes out this element as the root element in a new XML document using the provided configuration pub fn write_with_config(&self, w: W, config: EmitterConfig) -> Result<(), Error> { use xml::common::XmlVersion; use xml::writer::events::XmlEvent; use xml::writer::EventWriter; let write_document_declaration = config.write_document_declaration; let mut emitter = EventWriter::new_with_config(w, config); if write_document_declaration { emitter.write(XmlEvent::StartDocument { version: XmlVersion::Version10, encoding: None, standalone: None, })?; } self._write(&mut emitter) } /// Find a child element with the given name and return a reference to it. /// /// Both `&str` and `String` implement `ElementPredicate` and can be used to search for child /// elements that match the given element name with `.get_child("element_name")`. You can also /// search by `("element_name", "tag_name")` tuple. /// /// /// Note: this will only return Elements. To get other nodes (like comments), iterate through /// the `children` field. pub fn get_child(&self, k: P) -> Option<&Element> { self.children .iter() .filter_map(|e| match e { XMLNode::Element(elem) => Some(elem), _ => None, }) .find(|e| k.match_element(e)) } /// Find a child element with the given name and return a mutable reference to it. pub fn get_mut_child(&mut self, k: P) -> Option<&mut Element> { self.children .iter_mut() .filter_map(|e| match e { XMLNode::Element(elem) => Some(elem), _ => None, }) .find(|e| k.match_element(e)) } /// Find a child element with the given name, remove and return it. pub fn take_child(&mut self, k: P) -> Option { let index = self.children.iter().position(|e| match e { XMLNode::Element(elem) => k.match_element(elem), _ => false, }); match index { Some(index) => match self.children.remove(index) { XMLNode::Element(elem) => Some(elem), _ => None, }, None => None, } } /// Returns the inner text/cdata of this element, if any. /// /// If there are multiple text/cdata nodes, they will be all concatenated into one string. pub fn get_text<'a>(&'a self) -> Option> { let text_nodes: Vec<&'a str> = self .children .iter() .filter_map(|node| node.as_text().or_else(|| node.as_cdata())) .collect(); if text_nodes.is_empty() { None } else if text_nodes.len() == 1 { Some(Cow::Borrowed(text_nodes[0])) } else { let mut full_text = String::new(); for text in text_nodes { full_text.push_str(text); } Some(Cow::Owned(full_text)) } } } /// A predicate for matching elements. /// /// The default implementations allow you to match by tag name or a tuple of /// tag name and namespace. pub trait ElementPredicate { fn match_element(&self, e: &Element) -> bool; } // Unfortunately, // `impl ElementPredicate for TN where String: PartialEq` and // `impl ElementPredicate for (TN, NS) where String: PartialEq, String: PartialEq` // are conflicting implementations, even though we know that there is no // implementation for tuples. We just manually implement `ElementPredicate` for // all `PartialEq` impls of `String` and forward them to the 1-tuple version. // // This can probably be fixed once specialization is stable. impl ElementPredicate for (TN,) where String: PartialEq, { fn match_element(&self, e: &Element) -> bool { e.name == self.0 } } impl<'a> ElementPredicate for &'a str { /// Search by tag name fn match_element(&self, e: &Element) -> bool { (*self,).match_element(e) } } impl<'a> ElementPredicate for Cow<'a, str> { /// Search by tag name fn match_element(&self, e: &Element) -> bool { (&**self,).match_element(e) } } impl ElementPredicate for String { /// Search by tag name fn match_element(&self, e: &Element) -> bool { (&**self,).match_element(e) } } impl ElementPredicate for (TN, NS) where String: PartialEq, String: PartialEq, { /// Search by a tuple of (tagname, namespace) fn match_element(&self, e: &Element) -> bool { e.name == self.0 && e.namespace .as_ref() .map(|ns| ns == &self.1) .unwrap_or(false) } } xmltree-0.10.3/tests/data/01.xml000064400000000000000000000022010000000000000143520ustar 00000000000000 Some <java> class Another "java" class Weird 'XML' config JavaScript & program Cascading style sheet: © - ҉ xmltree-0.10.3/tests/data/02.xml000064400000000000000000000007160000000000000143640ustar 00000000000000 Name Another name 0.3 0.2 0.1 0.01 header 1 value Some bigger value xmltree-0.10.3/tests/data/03.xml000064400000000000000000000004270000000000000143640ustar 00000000000000 test kkss" = ddd' > ddddd!e3--> test kkss" = ddd' > ddddd!e3--> ddddd!e3--> ddddd!e3-->