├── .gitignore ├── .vscode └── settings.json ├── src └── xml │ ├── tests │ ├── mod.rs │ ├── documents │ │ ├── sample_6.xml │ │ ├── sample_5.xml │ │ ├── sample_6_full.txt │ │ ├── sample_5_short.txt │ │ ├── sample_3.xml │ │ ├── sample_4.xml │ │ ├── sample_4_full.txt │ │ ├── sample_4_short.txt │ │ ├── sample_2.xml │ │ ├── sample_3_full.txt │ │ ├── sample_3_short.txt │ │ ├── sample_1.xml │ │ ├── sample_2_full.txt │ │ ├── sample_2_short.txt │ │ ├── sample_1_full.txt │ │ └── sample_1_short.txt │ ├── streaming.rs │ ├── event_writer.rs │ └── event_reader.rs │ ├── reader │ ├── parser │ │ ├── inside_doctype.rs │ │ ├── inside_comment.rs │ │ ├── inside_cdata.rs │ │ ├── inside_closing_tag_name.rs │ │ ├── inside_reference.rs │ │ ├── inside_processing_instruction.rs │ │ ├── outside_tag.rs │ │ ├── inside_opening_tag.rs │ │ ├── inside_declaration.rs │ │ └── mod.rs │ ├── config.rs │ ├── mod.rs │ ├── error.rs │ └── events.rs │ ├── mod.rs │ ├── macros.rs │ ├── writer │ ├── config.rs │ ├── mod.rs │ ├── events.rs │ └── emitter.rs │ ├── attribute.rs │ ├── util.rs │ ├── escape.rs │ ├── common.rs │ ├── name.rs │ └── namespace.rs ├── examples ├── test.xml └── basic.rs ├── CHANGELOG.md ├── .github └── workflows │ ├── clippy.yml │ ├── rustfmt.yml │ └── tests.yml ├── Cargo.toml ├── Makefile ├── LICENSE ├── README.md └── tests └── test_basic.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.checkOnSave.command": "clippy" 3 | } -------------------------------------------------------------------------------- /src/xml/tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod event_reader; 2 | mod event_writer; 3 | mod streaming; 4 | -------------------------------------------------------------------------------- /src/xml/tests/documents/sample_6.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello 5 | -------------------------------------------------------------------------------- /src/xml/tests/documents/sample_5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | test ©≂̸ 5 |

6 | 7 | 8 | -------------------------------------------------------------------------------- /src/xml/tests/documents/sample_6_full.txt: -------------------------------------------------------------------------------- 1 | StartDocument(1.0, UTF-8) 2 | ProcessingInstruction(xml-stylesheet="href=\"doc.xsl\"") 3 | StartElement(doc) 4 | Characters("Hello") 5 | EndElement(doc) 6 | EndDocument 7 | -------------------------------------------------------------------------------- /src/xml/tests/documents/sample_5_short.txt: -------------------------------------------------------------------------------- 1 | StartDocument(1.0, utf-8) 2 | StartElement(p) 3 | Characters("\n ") 4 | StartElement(a) 5 | Characters("test ©≂̸") 6 | EndElement(a) 7 | Characters("\n") 8 | EndElement(p) 9 | EndDocument 10 | -------------------------------------------------------------------------------- /examples/test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test 1Tail1 5 | Test 2Tail2 6 | 7 | 8 | Test 3Tail3 9 | Test 4Tail4 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/xml/tests/documents/sample_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | test 5 | kkss" = ddd' > 6 | ddddd!e3--> 5 | test 6 | kkss" = ddd' > 7 | ddddd!e3--> 195 | 196 | 197 | " 198 | ); 199 | } 200 | 201 | #[test] 202 | fn issue_112_overriding_namepace_prefix() { 203 | use crate::xml::writer::XmlEvent; 204 | 205 | let mut b = Vec::new(); 206 | 207 | { 208 | let mut w = EmitterConfig::new() 209 | .write_document_declaration(false) 210 | .create_writer(&mut b); 211 | 212 | unwrap_all! { 213 | w.write(XmlEvent::start_element("iq").ns("", "jabber:client").ns("a", "urn:A")); 214 | w.write(XmlEvent::start_element("bind").ns("", "urn:ietf:params:xml:ns:xmpp-bind")); 215 | w.write(XmlEvent::end_element()); 216 | w.write(XmlEvent::start_element("whatever").ns("a", "urn:X")); 217 | w.write(XmlEvent::end_element()); 218 | w.write(XmlEvent::end_element()) 219 | } 220 | } 221 | 222 | assert_eq!( 223 | str::from_utf8(&b).unwrap(), 224 | r#""# 225 | ) 226 | } 227 | 228 | #[test] 229 | fn attribute_escaping() { 230 | use crate::xml::writer::XmlEvent; 231 | 232 | let mut b = Vec::new(); 233 | 234 | { 235 | let mut w = EmitterConfig::new() 236 | .write_document_declaration(false) 237 | .perform_indent(true) 238 | .create_writer(&mut b); 239 | 240 | unwrap_all! { 241 | w.write( 242 | XmlEvent::start_element("hello") 243 | .attr("testLt", "<") 244 | .attr("testGt", ">") 245 | ); 246 | w.write(XmlEvent::end_element()); 247 | w.write( 248 | XmlEvent::start_element("hello") 249 | .attr("testQuot", "\"") 250 | .attr("testApos", "\'") 251 | ); 252 | w.write(XmlEvent::end_element()); 253 | w.write( 254 | XmlEvent::start_element("hello") 255 | .attr("testAmp", "&") 256 | ); 257 | w.write(XmlEvent::end_element()); 258 | w.write( 259 | XmlEvent::start_element("hello") 260 | .attr("testNl", "\n") 261 | .attr("testCr", "\r") 262 | ); 263 | w.write(XmlEvent::end_element()); 264 | w.write( 265 | XmlEvent::start_element("hello") 266 | .attr("testNl", "\\n") 267 | .attr("testCr", "\\r") 268 | ); 269 | w.write(XmlEvent::end_element()) 270 | } 271 | } 272 | assert_eq!( 273 | str::from_utf8(&b).unwrap(), 274 | " 275 | 276 | 277 | 278 | " 279 | ); 280 | } 281 | -------------------------------------------------------------------------------- /src/xml/name.rs: -------------------------------------------------------------------------------- 1 | //! Contains XML qualified names manipulation types and functions. 2 | //! 3 | 4 | use std::fmt; 5 | use std::str::FromStr; 6 | 7 | use crate::xml::namespace::NS_NO_PREFIX; 8 | 9 | /// Represents a qualified XML name. 10 | /// 11 | /// A qualified name always consists at least of a local name. It can optionally contain 12 | /// a prefix; when reading an XML document, if it contains a prefix, it must also contain a 13 | /// namespace URI, but this is not enforced statically; see below. The name can contain a 14 | /// namespace without a prefix; in that case a default, empty prefix is assumed. 15 | /// 16 | /// When writing XML documents, it is possible to omit the namespace URI, leaving only 17 | /// the prefix. In this case the writer will check that the specifed prefix is bound to some 18 | /// URI in the current namespace context. If both prefix and namespace URI are specified, 19 | /// it is checked that the current namespace context contains this exact correspondence 20 | /// between prefix and namespace URI. 21 | /// 22 | /// # Prefixes and URIs 23 | /// 24 | /// A qualified name with a prefix must always contain a proper namespace URI --- names with 25 | /// a prefix but without a namespace associated with that prefix are meaningless. However, 26 | /// it is impossible to obtain proper namespace URI by a prefix without a context, and such 27 | /// context is only available when parsing a document (or it can be constructed manually 28 | /// when writing a document). Tying a name to a context statically seems impractical. This 29 | /// may change in future, though. 30 | #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] 31 | pub struct Name<'a> { 32 | /// A local name, e.g. `string` in `xsi:string`. 33 | pub local_name: &'a str, 34 | 35 | /// A namespace URI, e.g. `http://www.w3.org/2000/xmlns/`. 36 | pub namespace: Option<&'a str>, 37 | 38 | /// A name prefix, e.g. `xsi` in `xsi:string`. 39 | pub prefix: Option<&'a str>, 40 | } 41 | 42 | impl<'a> From<&'a str> for Name<'a> { 43 | fn from(s: &'a str) -> Name<'a> { 44 | let mut parts = s.splitn(2, ':').fuse(); 45 | match (parts.next(), parts.next()) { 46 | (Some(name), None) => Name::local(name), 47 | (Some(prefix), Some(name)) => Name::prefixed(name, prefix), 48 | _ => unreachable!(), 49 | } 50 | } 51 | } 52 | 53 | impl<'a> From<(&'a str, &'a str)> for Name<'a> { 54 | fn from((prefix, name): (&'a str, &'a str)) -> Name<'a> { 55 | Name::prefixed(name, prefix) 56 | } 57 | } 58 | 59 | impl<'a> fmt::Display for Name<'a> { 60 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 61 | if let Some(namespace) = self.namespace { 62 | write!(f, "{{{}}}", namespace)?; 63 | } 64 | 65 | if let Some(prefix) = self.prefix { 66 | write!(f, "{}:", prefix)?; 67 | } 68 | 69 | write!(f, "{}", self.local_name) 70 | } 71 | } 72 | 73 | impl<'a> Name<'a> { 74 | /// Returns an owned variant of the qualified name. 75 | pub fn to_owned(&self) -> OwnedName { 76 | OwnedName { 77 | local_name: self.local_name.into(), 78 | namespace: self.namespace.map(|s| s.into()), 79 | prefix: self.prefix.map(|s| s.into()), 80 | } 81 | } 82 | 83 | /// Returns a new `Name` instance representing plain local name. 84 | #[inline] 85 | pub fn local(local_name: &str) -> Name<'_> { 86 | Name { 87 | local_name, 88 | prefix: None, 89 | namespace: None, 90 | } 91 | } 92 | 93 | /// Returns a new `Name` instance with the given local name and prefix. 94 | #[inline] 95 | pub fn prefixed(local_name: &'a str, prefix: &'a str) -> Name<'a> { 96 | Name { 97 | local_name, 98 | namespace: None, 99 | prefix: Some(prefix), 100 | } 101 | } 102 | 103 | /// Returns a new `Name` instance representing a qualified name with or without a prefix and 104 | /// with a namespace URI. 105 | #[inline] 106 | #[cfg(test)] 107 | pub fn qualified(local_name: &'a str, namespace: &'a str, prefix: Option<&'a str>) -> Name<'a> { 108 | Name { 109 | local_name, 110 | namespace: Some(namespace), 111 | prefix, 112 | } 113 | } 114 | 115 | /// Returns a structure which can be displayed with `std::fmt` machinery to obtain this 116 | /// local name and prefix. 117 | /// 118 | /// This method is needed for efficiency purposes in order not to create unnecessary 119 | /// allocations. 120 | #[inline] 121 | pub fn repr_display(&self) -> ReprDisplay<'_, '_> { 122 | ReprDisplay(self) 123 | } 124 | 125 | /// Returns either a prefix of this name or `namespace::NS_NO_PREFIX` constant. 126 | #[inline] 127 | pub fn prefix_repr(&self) -> &str { 128 | self.prefix.unwrap_or(NS_NO_PREFIX) 129 | } 130 | } 131 | 132 | /// A wrapper around `Name` whose `Display` implementation prints the wrapped name as it is 133 | /// displayed in an XML document. 134 | pub struct ReprDisplay<'a, 'b>(&'a Name<'b>); 135 | 136 | impl<'a, 'b: 'a> fmt::Display for ReprDisplay<'a, 'b> { 137 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 138 | match self.0.prefix { 139 | Some(prefix) => write!(f, "{}:{}", prefix, self.0.local_name), 140 | None => write!(f, "{}", self.0.local_name), 141 | } 142 | } 143 | } 144 | 145 | /// An owned variant of `Name`. 146 | /// 147 | /// Everything about `Name` applies to this structure as well. 148 | #[derive(Clone, PartialEq, Eq, Hash, Debug)] 149 | pub struct OwnedName { 150 | /// A local name, e.g. `string` in `xsi:string`. 151 | pub local_name: String, 152 | 153 | /// A namespace URI, e.g. `http://www.w3.org/2000/xmlns/`. 154 | pub namespace: Option, 155 | 156 | /// A name prefix, e.g. `xsi` in `xsi:string`. 157 | pub prefix: Option, 158 | } 159 | 160 | impl fmt::Display for OwnedName { 161 | #[inline] 162 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 163 | fmt::Display::fmt(&self.borrow(), f) 164 | } 165 | } 166 | 167 | impl OwnedName { 168 | /// Constructs a borrowed `Name` based on this owned name. 169 | pub fn borrow(&self) -> Name<'_> { 170 | Name { 171 | local_name: &self.local_name, 172 | namespace: self.namespace.as_deref(), 173 | prefix: self.prefix.as_deref(), 174 | } 175 | } 176 | 177 | /// Returns a new `OwnedName` instance representing a plain local name. 178 | #[inline] 179 | #[cfg(test)] 180 | pub fn local(local_name: S) -> OwnedName 181 | where 182 | S: Into, 183 | { 184 | OwnedName { 185 | local_name: local_name.into(), 186 | namespace: None, 187 | prefix: None, 188 | } 189 | } 190 | 191 | /// Returns an optional prefix by reference, equivalent to `self.borrow().prefix` 192 | /// but avoids extra work. 193 | #[inline] 194 | pub fn prefix_ref(&self) -> Option<&str> { 195 | self.prefix.as_deref() 196 | } 197 | } 198 | 199 | impl<'a> From> for OwnedName { 200 | #[inline] 201 | fn from(n: Name<'a>) -> OwnedName { 202 | n.to_owned() 203 | } 204 | } 205 | 206 | impl FromStr for OwnedName { 207 | type Err = (); 208 | 209 | /// Parses the given string slice into a qualified name. 210 | /// 211 | /// This function, when finishes sucessfully, always return a qualified 212 | /// name without a namespace (`name.namespace == None`). It should be filled later 213 | /// using proper `NamespaceStack`. 214 | /// 215 | /// It is supposed that all characters in the argument string are correct 216 | /// as defined by the XML specification. No additional checks except a check 217 | /// for emptiness are done. 218 | fn from_str(s: &str) -> Result { 219 | let mut it = s.split(':'); 220 | 221 | let r = match (it.next(), it.next(), it.next()) { 222 | (Some(prefix), Some(local_name), None) 223 | if !prefix.is_empty() && !local_name.is_empty() => 224 | { 225 | Some((local_name.into(), Some(prefix.into()))) 226 | } 227 | (Some(local_name), None, None) if !local_name.is_empty() => { 228 | Some((local_name.into(), None)) 229 | } 230 | (_, _, _) => None, 231 | }; 232 | r.map(|(local_name, prefix)| OwnedName { 233 | local_name, 234 | namespace: None, 235 | prefix, 236 | }) 237 | .ok_or(()) 238 | } 239 | } 240 | 241 | #[cfg(test)] 242 | mod tests { 243 | use super::{Name, OwnedName}; 244 | 245 | #[test] 246 | fn test_name_from() { 247 | let n1: Name = "p:some-name".into(); 248 | let n2: Name = ("p", "some-name").into(); 249 | 250 | assert_eq!(n1, n2); 251 | assert_eq!(n1.local_name, "some-name"); 252 | assert_eq!(n1.prefix, Some("p")); 253 | assert!(n1.namespace.is_none()); 254 | } 255 | 256 | #[test] 257 | fn test_owned_name_from_str() { 258 | assert_eq!( 259 | "prefix:name".parse(), 260 | Ok(OwnedName { 261 | local_name: "name".into(), 262 | namespace: None, 263 | prefix: Some("prefix".into()) 264 | }) 265 | ); 266 | 267 | assert_eq!( 268 | "name".parse(), 269 | Ok(OwnedName { 270 | local_name: "name".into(), 271 | namespace: None, 272 | prefix: None 273 | }) 274 | ); 275 | 276 | assert_eq!("".parse(), Err::(())); 277 | assert_eq!(":".parse(), Err::(())); 278 | assert_eq!(":a".parse(), Err::(())); 279 | assert_eq!("a:".parse(), Err::(())); 280 | assert_eq!("a:b:c".parse(), Err::(())); 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /tests/test_basic.rs: -------------------------------------------------------------------------------- 1 | use elementtree::{Element, QName, WriteOptions, XmlProlog}; 2 | use std::str; 3 | 4 | #[test] 5 | fn test_basics() { 6 | let root = Element::from_reader( 7 | r#" 8 | 9 | 10 | Item 1Tail 1 11 | Item 2Tail 2 12 | Item 3Tail 3 13 | 14 | 15 | "# 16 | .as_bytes(), 17 | ) 18 | .unwrap(); 19 | 20 | let list = root.find("list").unwrap(); 21 | assert_eq!(list.tag(), &QName::from("list")); 22 | 23 | let items: Vec<_> = list.children().map(|x| x.text()).collect(); 24 | assert_eq!(items.as_slice(), &["Item 1", "Item 2", "Item 3"]); 25 | 26 | let tails: Vec<_> = list.children().map(|x| x.tail().trim()).collect(); 27 | assert_eq!(tails.as_slice(), &["Tail 1", "Tail 2", "Tail 3"]); 28 | } 29 | 30 | #[test] 31 | fn test_attributes() { 32 | let root = Element::from_reader( 33 | r#" 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | "# 42 | .as_bytes(), 43 | ) 44 | .unwrap(); 45 | 46 | let list = root.find("list").unwrap(); 47 | assert_eq!(list.tag(), &QName::from("list")); 48 | 49 | let items: Vec<_> = list 50 | .children() 51 | .map(|x| x.get_attr("attr").unwrap_or("")) 52 | .collect(); 53 | assert_eq!(items.as_slice(), &["foo1", "foo2", "foo3"]); 54 | 55 | let mut attrs: Vec<_> = list.attrs().map(|(k, v)| format!("{}={}", k, v)).collect(); 56 | attrs.sort(); 57 | assert_eq!( 58 | attrs.iter().map(|x| x.as_str()).collect::>(), 59 | vec!["a=1", "b=2", "c=3"] 60 | ); 61 | 62 | assert_eq!(list.attr_count(), 3); 63 | } 64 | 65 | #[test] 66 | fn test_namespaces() { 67 | let root = Element::from_reader( 68 | r#" 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | "# 77 | .as_bytes(), 78 | ) 79 | .unwrap(); 80 | 81 | let list = root.find("{root}list").unwrap(); 82 | assert_eq!(list.tag(), &QName::from("{root}list")); 83 | 84 | let items: Vec<_> = list 85 | .children() 86 | .map(|x| x.get_attr("{child}attr").unwrap_or("")) 87 | .collect(); 88 | assert_eq!(items.as_slice(), &["foo1", "foo2", "foo3"]); 89 | 90 | let mut attrs: Vec<_> = list.attrs().map(|(k, v)| format!("{}={}", k, v)).collect(); 91 | attrs.sort(); 92 | assert_eq!( 93 | attrs.iter().map(|x| x.as_str()).collect::>(), 94 | vec!["a=1", "b=2", "c=3"] 95 | ); 96 | 97 | assert_eq!(list.attr_count(), 3); 98 | 99 | assert_eq!( 100 | root.get_namespace_prefix("http://www.w3.org/2000/xmlns/"), 101 | Some("xmlns") 102 | ); 103 | 104 | assert_eq!(root.get_namespace_prefix("child"), Some("foo")); 105 | assert_eq!(root.get_namespace_prefix("root"), Some("")); 106 | assert_eq!(root.get_namespace_prefix("missing"), None); 107 | 108 | assert_eq!(list.get_namespace_prefix("child"), Some("foo")); 109 | assert_eq!(list.get_namespace_prefix("root"), Some("")); 110 | assert_eq!(list.get_namespace_prefix("missing"), None); 111 | } 112 | 113 | #[test] 114 | fn test_entities() { 115 | let root = Element::from_reader( 116 | r#" 117 | S 118 | "# 119 | .as_bytes(), 120 | ) 121 | .unwrap(); 122 | 123 | assert_eq!(root.text(), "S"); 124 | } 125 | 126 | #[test] 127 | fn test_write_stuff() { 128 | let mut root = Element::new(&QName::from("{myns}root")); 129 | root.set_namespace_prefix("myns", "x").unwrap(); 130 | let mut out: Vec = Vec::new(); 131 | root.to_writer(&mut out).unwrap(); 132 | let out = String::from_utf8(out).unwrap(); 133 | assert_eq!( 134 | &out, 135 | "" 136 | ); 137 | } 138 | 139 | #[test] 140 | fn test_basic_creation() { 141 | let mut root = Element::new("{demo}mydoc"); 142 | root.set_namespace_prefix("demo", "").unwrap(); 143 | 144 | let mut list = Element::new_with_namespaces("{demo}list", &root); 145 | 146 | for x in 0..3 { 147 | let mut child = Element::new_with_namespaces("{demo}item", &root); 148 | child.set_text(format!("Item {}", x)); 149 | list.append_child(child); 150 | } 151 | 152 | root.append_child(list); 153 | assert_eq!( 154 | &root.to_string().unwrap(), 155 | "\ 156 | \ 157 | \ 158 | \ 159 | Item 0\ 160 | Item 1\ 161 | Item 2\ 162 | \ 163 | " 164 | ); 165 | } 166 | 167 | #[test] 168 | fn test_alternative_creation() { 169 | let mut root = Element::new("{demo}mydoc"); 170 | root.set_namespace_prefix("demo", "").unwrap(); 171 | 172 | { 173 | let list = root.append_new_child("{demo}list"); 174 | for x in 0..3 { 175 | let child = list.append_new_child("{demo}item"); 176 | child.set_text(format!("Item {}", x)); 177 | } 178 | } 179 | 180 | assert_eq!( 181 | &root.to_string().unwrap(), 182 | "\ 183 | \ 184 | \ 185 | \ 186 | Item 0\ 187 | Item 1\ 188 | Item 2\ 189 | \ 190 | " 191 | ); 192 | } 193 | 194 | #[test] 195 | fn test_whitespace() { 196 | let root = Element::from_reader( 197 | r#" 198 | 199 | 200 | Item 1 Tail 1 201 | Item 2 Tail 2 202 | Item 3 Tail 3 203 | 204 | 205 | "# 206 | .as_bytes(), 207 | ) 208 | .unwrap(); 209 | 210 | assert_eq!(root.text(), "\n "); 211 | 212 | let list = root.find("list").unwrap(); 213 | assert_eq!(list.tag(), &QName::from("list")); 214 | 215 | let items: Vec<_> = list.children().map(|x| x.text()).collect(); 216 | assert_eq!(items.as_slice(), &[" Item 1 ", " Item 2 ", " Item 3 "]); 217 | } 218 | 219 | #[test] 220 | fn test_creation_without_xml_declaration() { 221 | let mut root = Element::new("{demo}mydoc"); 222 | root.set_namespace_prefix("demo", "").unwrap(); 223 | { 224 | let list = root.append_new_child("{demo}list"); 225 | for x in 0..3 { 226 | let child = list.append_new_child("{demo}item"); 227 | child.set_text(format!("Item {}", x)); 228 | } 229 | } 230 | 231 | let mut out: Vec = Vec::new(); 232 | let options = WriteOptions::new().set_xml_prolog(None); 233 | 234 | root.to_writer_with_options(&mut out, options).unwrap(); 235 | assert_eq!( 236 | str::from_utf8(&out).unwrap(), 237 | "\ 238 | \ 239 | \ 240 | Item 0\ 241 | Item 1\ 242 | Item 2\ 243 | \ 244 | " 245 | ); 246 | } 247 | 248 | #[test] 249 | fn test_creation_with_xml_prolog_10() { 250 | let mut root = Element::new("{demo}mydoc"); 251 | root.set_namespace_prefix("demo", "").unwrap(); 252 | { 253 | let list = root.append_new_child("{demo}list"); 254 | for x in 0..3 { 255 | let child = list.append_new_child("{demo}item"); 256 | child.set_text(format!("Item {}", x)); 257 | } 258 | } 259 | 260 | let mut out: Vec = Vec::new(); 261 | let options = WriteOptions::new().set_xml_prolog(Some(XmlProlog::Version10)); 262 | 263 | root.to_writer_with_options(&mut out, options).unwrap(); 264 | assert_eq!( 265 | str::from_utf8(&out).unwrap(), 266 | "\ 267 | \ 268 | \ 269 | \ 270 | Item 0\ 271 | Item 1\ 272 | Item 2\ 273 | \ 274 | " 275 | ); 276 | } 277 | 278 | #[test] 279 | fn test_creation_with_xml_prolog_11() { 280 | let mut root = Element::new("{demo}mydoc"); 281 | root.set_namespace_prefix("demo", "").unwrap(); 282 | { 283 | let list = root.append_new_child("{demo}list"); 284 | for x in 0..3 { 285 | let child = list.append_new_child("{demo}item"); 286 | child.set_text(format!("Item {}", x)); 287 | } 288 | } 289 | 290 | let mut out: Vec = Vec::new(); 291 | let options = WriteOptions::new().set_xml_prolog(Some(XmlProlog::Version11)); 292 | 293 | root.to_writer_with_options(&mut out, options).unwrap(); 294 | assert_eq!( 295 | str::from_utf8(&out).unwrap(), 296 | "\ 297 | \ 298 | \ 299 | \ 300 | Item 0\ 301 | Item 1\ 302 | Item 2\ 303 | \ 304 | " 305 | ); 306 | } 307 | 308 | #[test] 309 | fn test_creation_with_no_xml_prolog_defined() { 310 | let mut root = Element::new("{demo}mydoc"); 311 | root.set_namespace_prefix("demo", "").unwrap(); 312 | { 313 | let list = root.append_new_child("{demo}list"); 314 | for x in 0..3 { 315 | let child = list.append_new_child("{demo}item"); 316 | child.set_text(format!("Item {}", x)); 317 | } 318 | } 319 | 320 | let mut out: Vec = Vec::new(); 321 | let options = WriteOptions::new(); 322 | 323 | root.to_writer_with_options(&mut out, options).unwrap(); 324 | assert_eq!( 325 | str::from_utf8(&out).unwrap(), 326 | "\ 327 | \ 328 | \ 329 | \ 330 | Item 0\ 331 | Item 1\ 332 | Item 2\ 333 | \ 334 | " 335 | ); 336 | } 337 | 338 | #[test] 339 | fn test_render_multiple_times() { 340 | let mut root = Element::new("{demo}mydoc"); 341 | root.set_namespace_prefix("demo", "").unwrap(); 342 | root.set_attr(("demo", "id"), "some_id".to_string()) 343 | .set_attr(("demo", "name"), "some_name".to_string()) 344 | .set_attr(("demo", "some-other-attr"), "other_attr".to_string()); 345 | 346 | let mut out: Vec = Vec::new(); 347 | 348 | root.to_writer_with_options(&mut out, WriteOptions::new()) 349 | .unwrap(); 350 | assert_eq!( 351 | str::from_utf8(&out).unwrap(), 352 | "\ 353 | \ 354 | " 355 | ); 356 | 357 | let mut out2: Vec = Vec::new(); 358 | root.to_writer_with_options(&mut out2, WriteOptions::new()) 359 | .unwrap(); 360 | assert_eq!( 361 | str::from_utf8(&out2).unwrap(), 362 | "\ 363 | \ 364 | " 365 | ); 366 | } 367 | 368 | #[test] 369 | fn test_mut_finding() { 370 | let mut root = Element::from_reader( 371 | r#" 372 | 373 | 374 | Item 1 Tail 1 375 | Item 2 Tail 2 376 | Item 3 Tail 3 377 | 378 | 379 | "# 380 | .as_bytes(), 381 | ) 382 | .unwrap(); 383 | 384 | { 385 | let list = root.find_mut("list").unwrap(); 386 | for item in list.find_all_mut("item") { 387 | item.set_text("wat"); 388 | } 389 | } 390 | 391 | let v: Vec<_> = root 392 | .find("list") 393 | .unwrap() 394 | .find_all("item") 395 | .map(|x| x.text()) 396 | .collect(); 397 | assert_eq!(&v, &["wat", "wat", "wat"]); 398 | } 399 | 400 | #[test] 401 | fn test_attr() { 402 | let root = Element::from_reader( 403 | r#" 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | "# 413 | .as_bytes(), 414 | ) 415 | .unwrap(); 416 | 417 | let list = root.find("{tag:myns}list").unwrap(); 418 | for (idx, child) in list.find_all("{tag:myns}item").enumerate() { 419 | assert_eq!( 420 | child.get_attr("{tag:otherns}attr"), 421 | Some(format!("foo{}", idx + 1)).as_deref() 422 | ); 423 | assert_eq!(child.get_attr("{tag:myns}attr"), None); 424 | } 425 | } 426 | 427 | #[test] 428 | fn test_retain() { 429 | let mut root = Element::from_reader( 430 | r#" 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | "# 441 | .as_bytes(), 442 | ) 443 | .unwrap(); 444 | 445 | let list = root.find_mut("list").unwrap(); 446 | list.retain_children(|item| item.get_attr("attr").unwrap().starts_with("foo")); 447 | 448 | assert_eq!(list.children().count(), 3); 449 | } 450 | -------------------------------------------------------------------------------- /src/xml/tests/event_reader.rs: -------------------------------------------------------------------------------- 1 | #![forbid(unsafe_code)] 2 | 3 | use std::env; 4 | use std::fmt; 5 | use std::fs::File; 6 | use std::io::{stderr, BufRead, BufReader, Write}; 7 | use std::path::Path; 8 | 9 | use crate::xml::common::Position; 10 | use crate::xml::name::OwnedName; 11 | use crate::xml::reader::{EventReader, Outcome, ParserConfig, XmlEvent}; 12 | 13 | use once_cell::sync::Lazy; 14 | 15 | /// Dummy function that opens a file, parses it, and returns a `Result`. 16 | /// There can be IO errors (from `File::open`) and XML errors (from the parser). 17 | /// Having `impl From for xml::reader::Error` allows the user to 18 | /// do this without defining their own error type. 19 | #[allow(dead_code)] 20 | fn count_event_in_file(name: &Path) -> Outcome { 21 | let mut event_count = 0; 22 | for event in EventReader::new(BufReader::new(File::open(name)?)) { 23 | event?; 24 | event_count += 1; 25 | } 26 | Ok(event_count) 27 | } 28 | 29 | #[test] 30 | fn sample_1_short() { 31 | test( 32 | include_bytes!("documents/sample_1.xml"), 33 | include_bytes!("documents/sample_1_short.txt"), 34 | ParserConfig::new(), 35 | false, 36 | ); 37 | } 38 | 39 | #[test] 40 | fn sample_1_full() { 41 | test( 42 | include_bytes!("documents/sample_1.xml"), 43 | include_bytes!("documents/sample_1_full.txt"), 44 | ParserConfig::new(), 45 | false, 46 | ); 47 | } 48 | 49 | #[test] 50 | fn sample_2_short() { 51 | test( 52 | include_bytes!("documents/sample_2.xml"), 53 | include_bytes!("documents/sample_2_short.txt"), 54 | ParserConfig::new(), 55 | false, 56 | ); 57 | } 58 | 59 | #[test] 60 | fn sample_2_full() { 61 | test( 62 | include_bytes!("documents/sample_2.xml"), 63 | include_bytes!("documents/sample_2_full.txt"), 64 | ParserConfig::new(), 65 | false, 66 | ); 67 | } 68 | 69 | #[test] 70 | fn sample_3_short() { 71 | test( 72 | include_bytes!("documents/sample_3.xml"), 73 | include_bytes!("documents/sample_3_short.txt"), 74 | ParserConfig::new(), 75 | true, 76 | ); 77 | } 78 | 79 | #[test] 80 | fn sample_3_full() { 81 | test( 82 | include_bytes!("documents/sample_3.xml"), 83 | include_bytes!("documents/sample_3_full.txt"), 84 | ParserConfig::new(), 85 | true, 86 | ); 87 | } 88 | 89 | #[test] 90 | fn sample_4_short() { 91 | test( 92 | include_bytes!("documents/sample_4.xml"), 93 | include_bytes!("documents/sample_4_short.txt"), 94 | ParserConfig::new(), 95 | false, 96 | ); 97 | } 98 | 99 | #[test] 100 | fn sample_4_full() { 101 | test( 102 | include_bytes!("documents/sample_4.xml"), 103 | include_bytes!("documents/sample_4_full.txt"), 104 | ParserConfig::new(), 105 | false, 106 | ); 107 | } 108 | 109 | #[test] 110 | fn sample_5_short() { 111 | test( 112 | include_bytes!("documents/sample_5.xml"), 113 | include_bytes!("documents/sample_5_short.txt"), 114 | ParserConfig::new() 115 | .add_entity("nbsp", " ") 116 | .add_entity("copy", "©") 117 | .add_entity("NotEqualTilde", "≂̸"), 118 | false, 119 | ); 120 | } 121 | 122 | #[test] 123 | fn sample_6_full() { 124 | test( 125 | include_bytes!("documents/sample_6.xml"), 126 | include_bytes!("documents/sample_6_full.txt"), 127 | ParserConfig::new(), 128 | false, 129 | ); 130 | } 131 | 132 | #[test] 133 | fn eof_1() { 134 | test( 135 | br#""#, 156 | br#" 157 | |1:14 Unexpected token '--' before ' ' 158 | "#, 159 | ParserConfig::new(), 160 | false, 161 | ); 162 | 163 | test( 164 | br#""#, 165 | br#" 166 | |1:14 Unexpected token '--' before '-' 167 | "#, 168 | ParserConfig::new(), 169 | false, 170 | ); 171 | } 172 | 173 | #[test] 174 | fn tabs_1() { 175 | test( 176 | b"\t\t", 177 | br#" 178 | |1:2 StartDocument(1.0, UTF-8) 179 | |1:2 StartElement(a) 180 | |1:5 Characters("\t") 181 | |1:6 StartElement(b) 182 | |1:6 EndElement(b) 183 | |1:10 EndElement(a) 184 | |1:14 EndDocument 185 | "#, 186 | ParserConfig::new(), 187 | true, 188 | ); 189 | } 190 | 191 | #[test] 192 | fn issue_32_unescaped_cdata_end() { 193 | test( 194 | br#"]]>"#, 195 | br#" 196 | |StartDocument(1.0, UTF-8) 197 | |StartElement(hello) 198 | |Characters("]]>") 199 | |EndElement(hello) 200 | |EndDocument 201 | "#, 202 | ParserConfig::new(), 203 | false, 204 | ); 205 | } 206 | 207 | #[test] 208 | fn issue_unescaped_processing_instruction_end() { 209 | test( 210 | br#"?>"#, 211 | br#" 212 | |StartDocument(1.0, UTF-8) 213 | |StartElement(hello) 214 | |Characters("?>") 215 | |EndElement(hello) 216 | |EndDocument 217 | "#, 218 | ParserConfig::new(), 219 | false, 220 | ); 221 | } 222 | 223 | #[test] 224 | fn issue_unescaped_empty_tag_end() { 225 | test( 226 | br#"/>"#, 227 | br#" 228 | |StartDocument(1.0, UTF-8) 229 | |StartElement(hello) 230 | |Characters("/>") 231 | |EndElement(hello) 232 | |EndDocument 233 | "#, 234 | ParserConfig::new(), 235 | false, 236 | ); 237 | } 238 | 239 | #[test] 240 | fn issue_83_duplicate_attributes() { 241 | test( 242 | br#""#, 243 | br#" 244 | |StartDocument(1.0, UTF-8) 245 | |StartElement(hello) 246 | |1:30 Attribute 'a' is redefined 247 | "#, 248 | ParserConfig::new(), 249 | false, 250 | ); 251 | } 252 | 253 | #[test] 254 | fn issue_93_large_characters_in_entity_references() { 255 | test( 256 | r#"&𤶼;"#.as_bytes(), 257 | r#" 258 | |StartDocument(1.0, UTF-8) 259 | |StartElement(hello) 260 | |1:10 Unexpected entity: 𤶼 261 | "# 262 | .as_bytes(), // FIXME: it shouldn't be 10, looks like indices are off slightly 263 | ParserConfig::new(), 264 | false, 265 | ) 266 | } 267 | 268 | #[test] 269 | fn issue_98_cdata_ending_with_right_bracket() { 270 | test( 271 | br#""#, 272 | br#" 273 | |StartDocument(1.0, UTF-8) 274 | |StartElement(hello) 275 | |Characters("Foo [Bar]") 276 | |EndElement(hello) 277 | |EndDocument 278 | "#, 279 | ParserConfig::new(), 280 | false, 281 | ) 282 | } 283 | 284 | #[test] 285 | fn issue_105_unexpected_double_dash() { 286 | test( 287 | br#"-- "#, 288 | br#" 289 | |StartDocument(1.0, UTF-8) 290 | |StartElement(hello) 291 | |Characters("-- ") 292 | |EndElement(hello) 293 | |EndDocument 294 | "#, 295 | ParserConfig::new(), 296 | false, 297 | ); 298 | 299 | test( 300 | br#"--"#, 301 | br#" 302 | |StartDocument(1.0, UTF-8) 303 | |StartElement(hello) 304 | |Characters("--") 305 | |EndElement(hello) 306 | |EndDocument 307 | "#, 308 | ParserConfig::new(), 309 | false, 310 | ); 311 | 312 | test( 313 | br#"-->"#, 314 | br#" 315 | |StartDocument(1.0, UTF-8) 316 | |StartElement(hello) 317 | |Characters("-->") 318 | |EndElement(hello) 319 | |EndDocument 320 | "#, 321 | ParserConfig::new(), 322 | false, 323 | ); 324 | 325 | test( 326 | br#""#, 327 | br#" 328 | |StartDocument(1.0, UTF-8) 329 | |StartElement(hello) 330 | |Characters("--") 331 | |EndElement(hello) 332 | |EndDocument 333 | "#, 334 | ParserConfig::new(), 335 | false, 336 | ); 337 | } 338 | 339 | #[test] 340 | fn issue_attribues_have_no_default_namespace() { 341 | test( 342 | br#""#, 343 | br#" 344 | |StartDocument(1.0, UTF-8) 345 | |StartElement({urn:foo}hello [x="y"]) 346 | |EndElement({urn:foo}hello) 347 | |EndDocument 348 | "#, 349 | ParserConfig::new(), 350 | false, 351 | ); 352 | } 353 | 354 | #[test] 355 | fn issue_replacement_character_entity_reference() { 356 | test( 357 | br#"��"#, 358 | br#" 359 | |StartDocument(1.0, UTF-8) 360 | |StartElement(doc) 361 | |1:13 Invalid decimal character number in an entity: #55357 362 | "#, 363 | ParserConfig::new(), 364 | false, 365 | ); 366 | 367 | test( 368 | br#"��"#, 369 | br#" 370 | |StartDocument(1.0, UTF-8) 371 | |StartElement(doc) 372 | |1:13 Invalid hexadecimal character number in an entity: #xd83d 373 | "#, 374 | ParserConfig::new(), 375 | false, 376 | ); 377 | } 378 | 379 | #[test] 380 | #[should_panic = "Unexpected event: 2:267 Unexpected end of stream"] 381 | fn test_fuzzed_doctype() { 382 | test( 383 | b"\n sample_1_full.txt 395 | static PRINT: Lazy = Lazy::new(|| { 396 | for (key, value) in env::vars() { 397 | if key == "PRINT_SPEC" && value == "1" { 398 | return true; 399 | } 400 | } 401 | false 402 | }); 403 | 404 | // clones a lot but that's fine 405 | fn trim_until_bar(s: String) -> String { 406 | match s.trim() { 407 | ts if ts.starts_with('|') => return ts[1..].to_owned(), 408 | _ => {} 409 | } 410 | s 411 | } 412 | 413 | fn test(input: &[u8], output: &[u8], config: ParserConfig, test_position: bool) { 414 | let mut reader = config.create_reader(input); 415 | let mut spec_lines = BufReader::new(output) 416 | .lines() 417 | .map(|line| line.unwrap()) 418 | .enumerate() 419 | .map(|(i, line)| (i, trim_until_bar(line))) 420 | .filter(|&(_, ref line)| !line.trim().is_empty()); 421 | 422 | loop { 423 | let e = reader.next_event(); 424 | let line = if test_position { 425 | format!("{} {}", reader.position(), Event(&e)) 426 | } else { 427 | format!("{}", Event(&e)) 428 | }; 429 | 430 | if *PRINT { 431 | writeln!(&mut stderr(), "{}", line).unwrap(); 432 | } else if let Some((n, spec)) = spec_lines.next() { 433 | if line != spec { 434 | const SPLITTER: &str = "-------------------"; 435 | panic!( 436 | "\n{}\nUnexpected event at line {}:\nExpected: {}\nFound: {}\n{}\n", 437 | SPLITTER, 438 | n + 1, 439 | spec, 440 | line, 441 | std::str::from_utf8(output).unwrap() 442 | ); 443 | } 444 | } else { 445 | panic!("Unexpected event: {}", line); 446 | } 447 | 448 | match e { 449 | Ok(XmlEvent::EndDocument) | Err(_) => break, 450 | _ => {} 451 | } 452 | } 453 | } 454 | 455 | // Here we define our own string representation of events so we don't depend 456 | // on the specifics of Display implementation for XmlEvent and OwnedName. 457 | 458 | struct Name<'a>(&'a OwnedName); 459 | 460 | impl<'a> fmt::Display for Name<'a> { 461 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 462 | if let Some(ref namespace) = self.0.namespace { 463 | write!(f, "{{{}}}", namespace)?; 464 | } 465 | 466 | if let Some(ref prefix) = self.0.prefix { 467 | write!(f, "{}:", prefix)?; 468 | } 469 | 470 | write!(f, "{}", self.0.local_name) 471 | } 472 | } 473 | 474 | struct Event<'a>(&'a Outcome); 475 | 476 | impl<'a> fmt::Display for Event<'a> { 477 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 478 | let empty = String::new(); 479 | match *self.0 { 480 | Ok(ref e) => match *e { 481 | XmlEvent::StartDocument { 482 | ref version, 483 | ref encoding, 484 | .. 485 | } => write!(f, "StartDocument({}, {})", version, encoding), 486 | XmlEvent::EndDocument => write!(f, "EndDocument"), 487 | XmlEvent::ProcessingInstruction { ref name, ref data } => write!( 488 | f, 489 | "ProcessingInstruction({}={:?})", 490 | name, 491 | data.as_ref().unwrap_or(&empty) 492 | ), 493 | XmlEvent::StartElement { 494 | ref name, 495 | ref attributes, 496 | .. 497 | } => { 498 | if attributes.is_empty() { 499 | write!(f, "StartElement({})", Name(name)) 500 | } else { 501 | let attrs: Vec<_> = attributes 502 | .iter() 503 | .map(|a| format!("{}={:?}", Name(&a.name), a.value)) 504 | .collect(); 505 | write!(f, "StartElement({} [{}])", Name(name), attrs.join(", ")) 506 | } 507 | } 508 | XmlEvent::EndElement { ref name } => write!(f, "EndElement({})", Name(name)), 509 | XmlEvent::Characters(ref data) => { 510 | write!(f, r#"Characters("{}")"#, data.escape_debug()) 511 | } 512 | }, 513 | Err(ref e) => e.fmt(f), 514 | } 515 | } 516 | } 517 | -------------------------------------------------------------------------------- /src/xml/writer/emitter.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::io; 3 | use std::io::prelude::*; 4 | use std::result; 5 | 6 | use crate::xml::attribute::Attribute; 7 | use crate::xml::common; 8 | use crate::xml::common::XmlVersion; 9 | use crate::xml::escape::{escape_str_attribute, escape_str_pcdata}; 10 | use crate::xml::name::{Name, OwnedName}; 11 | use crate::xml::namespace::{ 12 | NamespaceStack, NS_EMPTY_URI, NS_NO_PREFIX, NS_XMLNS_PREFIX, NS_XML_PREFIX, 13 | }; 14 | 15 | use crate::xml::writer::config::EmitterConfig; 16 | 17 | /// An error which may be returned by `XmlWriter` when writing XML events. 18 | #[derive(Debug)] 19 | pub enum EmitterError { 20 | /// An I/O error occured in the underlying `Write` instance. 21 | Io(io::Error), 22 | 23 | /// Document declaration has already been written to the output stream. 24 | DocumentStartAlreadyEmitted, 25 | 26 | /// The name of the last opening element is not available. 27 | LastElementNameNotAvailable, 28 | 29 | /// The name of the last opening element is not equal to the name of the provided 30 | /// closing element. 31 | EndElementNameIsNotEqualToLastStartElementName, 32 | 33 | /// End element name is not specified when it is needed, for example, when automatic 34 | /// closing is not enabled in configuration. 35 | EndElementNameIsNotSpecified, 36 | } 37 | 38 | impl From for EmitterError { 39 | fn from(err: io::Error) -> EmitterError { 40 | EmitterError::Io(err) 41 | } 42 | } 43 | 44 | impl fmt::Display for EmitterError { 45 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 46 | write!(f, "emitter error: ")?; 47 | match *self { 48 | EmitterError::Io(ref e) => write!(f, "I/O error: {}", e), 49 | EmitterError::DocumentStartAlreadyEmitted => { 50 | write!(f, "document start event has already been emitted") 51 | } 52 | EmitterError::LastElementNameNotAvailable => { 53 | write!(f, "last element name is not available") 54 | } 55 | EmitterError::EndElementNameIsNotEqualToLastStartElementName => { 56 | write!( 57 | f, 58 | "end element name is not equal to last start element name" 59 | ) 60 | } 61 | EmitterError::EndElementNameIsNotSpecified => { 62 | write!(f, "end element name is not specified and can't be inferred") 63 | } 64 | } 65 | } 66 | } 67 | 68 | impl std::error::Error for EmitterError {} 69 | 70 | /// A result type yielded by `XmlWriter`. 71 | pub type Result = result::Result; 72 | 73 | // TODO: split into a low-level fast writer without any checks and formatting logic and a 74 | // high-level indenting validating writer 75 | pub struct Emitter { 76 | config: EmitterConfig, 77 | 78 | nst: NamespaceStack, 79 | 80 | indent_level: usize, 81 | indent_stack: Vec, 82 | 83 | element_names: Vec, 84 | 85 | start_document_emitted: bool, 86 | just_wrote_start_element: bool, 87 | } 88 | 89 | impl Emitter { 90 | pub fn new(config: EmitterConfig) -> Emitter { 91 | Emitter { 92 | config, 93 | 94 | nst: NamespaceStack::empty(), 95 | 96 | indent_level: 0, 97 | indent_stack: vec![WroteIndentation::Nothing], 98 | 99 | element_names: Vec::new(), 100 | 101 | start_document_emitted: false, 102 | just_wrote_start_element: false, 103 | } 104 | } 105 | } 106 | 107 | #[derive(Copy, Clone, Eq, PartialEq, Debug)] 108 | enum WroteIndentation { 109 | Nothing, 110 | Markup, 111 | Text, 112 | } 113 | 114 | impl Emitter { 115 | /// Returns the current state of namespaces. 116 | #[inline] 117 | pub fn namespace_stack_mut(&mut self) -> &mut NamespaceStack { 118 | &mut self.nst 119 | } 120 | 121 | #[inline] 122 | fn wrote_text(&self) -> bool { 123 | *self.indent_stack.last().unwrap() == WroteIndentation::Text 124 | } 125 | 126 | #[inline] 127 | fn wrote_markup(&self) -> bool { 128 | *self.indent_stack.last().unwrap() == WroteIndentation::Markup 129 | } 130 | 131 | #[inline] 132 | fn set_wrote_text(&mut self) { 133 | *self.indent_stack.last_mut().unwrap() = WroteIndentation::Text; 134 | } 135 | 136 | #[inline] 137 | fn set_wrote_markup(&mut self) { 138 | *self.indent_stack.last_mut().unwrap() = WroteIndentation::Markup; 139 | } 140 | 141 | fn write_newline(&mut self, target: &mut W, level: usize) -> Result<()> { 142 | target.write_all(self.config.line_separator.as_bytes())?; 143 | for _ in 0..level { 144 | target.write_all(self.config.indent_string.as_bytes())?; 145 | } 146 | Ok(()) 147 | } 148 | 149 | fn before_markup(&mut self, target: &mut W) -> Result<()> { 150 | if self.config.perform_indent 151 | && !self.wrote_text() 152 | && (self.indent_level > 0 || self.wrote_markup()) 153 | { 154 | let indent_level = self.indent_level; 155 | self.write_newline(target, indent_level)?; 156 | if self.indent_level > 0 && self.config.indent_string.len() > 0 { 157 | self.after_markup(); 158 | } 159 | } 160 | Ok(()) 161 | } 162 | 163 | fn after_markup(&mut self) { 164 | self.set_wrote_markup(); 165 | } 166 | 167 | fn before_start_element(&mut self, target: &mut W) -> Result<()> { 168 | self.before_markup(target)?; 169 | self.indent_stack.push(WroteIndentation::Nothing); 170 | Ok(()) 171 | } 172 | 173 | fn after_start_element(&mut self) { 174 | self.after_markup(); 175 | self.indent_level += 1; 176 | } 177 | 178 | fn before_end_element(&mut self, target: &mut W) -> Result<()> { 179 | if self.config.perform_indent 180 | && self.indent_level > 0 181 | && self.wrote_markup() 182 | && !self.wrote_text() 183 | { 184 | let indent_level = self.indent_level; 185 | self.write_newline(target, indent_level - 1) 186 | } else { 187 | Ok(()) 188 | } 189 | } 190 | 191 | fn after_end_element(&mut self) { 192 | if self.indent_level > 0 { 193 | self.indent_level -= 1; 194 | self.indent_stack.pop(); 195 | } 196 | self.set_wrote_markup(); 197 | } 198 | 199 | fn after_text(&mut self) { 200 | self.set_wrote_text(); 201 | } 202 | 203 | pub fn emit_start_document( 204 | &mut self, 205 | target: &mut W, 206 | version: XmlVersion, 207 | encoding: &str, 208 | standalone: Option, 209 | ) -> Result<()> { 210 | if self.start_document_emitted { 211 | return Err(EmitterError::DocumentStartAlreadyEmitted); 212 | } 213 | self.start_document_emitted = true; 214 | 215 | self.before_markup(target)?; 216 | let result = { 217 | let mut write = move || { 218 | write!( 219 | target, 220 | "")?; 233 | 234 | Ok(()) 235 | }; 236 | write() 237 | }; 238 | self.after_markup(); 239 | 240 | result 241 | } 242 | 243 | fn check_document_started(&mut self, target: &mut W) -> Result<()> { 244 | if !self.start_document_emitted && self.config.write_document_declaration { 245 | self.emit_start_document(target, common::XmlVersion::Version10, "utf-8", None) 246 | } else { 247 | Ok(()) 248 | } 249 | } 250 | 251 | fn fix_non_empty_element(&mut self, target: &mut W) -> Result<()> { 252 | if self.config.normalize_empty_elements && self.just_wrote_start_element { 253 | self.just_wrote_start_element = false; 254 | target.write_all(b">").map_err(From::from) 255 | } else { 256 | Ok(()) 257 | } 258 | } 259 | 260 | #[cfg(test)] 261 | pub fn emit_processing_instruction( 262 | &mut self, 263 | target: &mut W, 264 | name: &str, 265 | data: Option<&str>, 266 | ) -> Result<()> { 267 | self.check_document_started(target)?; 268 | self.fix_non_empty_element(target)?; 269 | 270 | self.before_markup(target)?; 271 | 272 | let result = { 273 | let mut write = || { 274 | write!(target, "")?; 281 | 282 | Ok(()) 283 | }; 284 | write() 285 | }; 286 | 287 | self.after_markup(); 288 | 289 | result 290 | } 291 | 292 | fn emit_start_element_initial( 293 | &mut self, 294 | target: &mut W, 295 | name: Name<'_>, 296 | attributes: &[Attribute<'_>], 297 | ) -> Result<()> 298 | where 299 | W: Write, 300 | { 301 | self.check_document_started(target)?; 302 | self.fix_non_empty_element(target)?; 303 | self.before_start_element(target)?; 304 | write!(target, "<{}", name.repr_display())?; 305 | self.emit_current_namespace_attributes(target)?; 306 | self.emit_attributes(target, attributes)?; 307 | self.after_start_element(); 308 | Ok(()) 309 | } 310 | 311 | pub fn emit_start_element( 312 | &mut self, 313 | target: &mut W, 314 | name: Name<'_>, 315 | attributes: &[Attribute<'_>], 316 | ) -> Result<()> 317 | where 318 | W: Write, 319 | { 320 | self.element_names.push(name.to_owned()); 321 | 322 | self.emit_start_element_initial(target, name, attributes)?; 323 | self.just_wrote_start_element = true; 324 | 325 | if !self.config.normalize_empty_elements { 326 | write!(target, ">")?; 327 | } 328 | 329 | Ok(()) 330 | } 331 | 332 | pub fn emit_current_namespace_attributes(&mut self, target: &mut W) -> Result<()> 333 | where 334 | W: Write, 335 | { 336 | for (prefix, uri) in self.nst.peek() { 337 | match prefix { 338 | // internal namespaces are not emitted 339 | NS_XMLNS_PREFIX | NS_XML_PREFIX => Ok(()), 340 | //// there is already a namespace binding with this prefix in scope 341 | //prefix if self.nst.get(prefix) == Some(uri) => Ok(()), 342 | // emit xmlns only if it is overridden 343 | NS_NO_PREFIX => { 344 | if uri != NS_EMPTY_URI { 345 | write!(target, " xmlns=\"{}\"", uri) 346 | } else { 347 | Ok(()) 348 | } 349 | } 350 | // everything else 351 | prefix => write!(target, " xmlns:{}=\"{}\"", prefix, uri), 352 | }?; 353 | } 354 | Ok(()) 355 | } 356 | 357 | pub fn emit_attributes( 358 | &mut self, 359 | target: &mut W, 360 | attributes: &[Attribute<'_>], 361 | ) -> Result<()> { 362 | for attr in attributes.iter() { 363 | write!( 364 | target, 365 | " {}=\"{}\"", 366 | attr.name.repr_display(), 367 | escape_str_attribute(attr.value) 368 | )? 369 | } 370 | Ok(()) 371 | } 372 | 373 | pub fn emit_end_element( 374 | &mut self, 375 | target: &mut W, 376 | name: Option>, 377 | ) -> Result<()> { 378 | let owned_name = Some( 379 | self.element_names 380 | .pop() 381 | .ok_or(EmitterError::LastElementNameNotAvailable)?, 382 | ); 383 | 384 | // Check that last started element name equals to the provided name, if there are both 385 | if let Some(ref last_name) = owned_name { 386 | if let Some(ref name) = name { 387 | if last_name.borrow() != *name { 388 | return Err(EmitterError::EndElementNameIsNotEqualToLastStartElementName); 389 | } 390 | } 391 | } 392 | 393 | if let Some(name) = owned_name.as_ref().map(|n| n.borrow()).or(name) { 394 | if self.config.normalize_empty_elements && self.just_wrote_start_element { 395 | self.just_wrote_start_element = false; 396 | let termination = if self.config.pad_self_closing { 397 | " />" 398 | } else { 399 | "/>" 400 | }; 401 | let result = target.write_all(termination.as_bytes()).map_err(From::from); 402 | self.after_end_element(); 403 | result 404 | } else { 405 | self.just_wrote_start_element = false; 406 | 407 | self.before_end_element(target)?; 408 | let result = write!(target, "", name.repr_display()).map_err(From::from); 409 | self.after_end_element(); 410 | 411 | result 412 | } 413 | } else { 414 | Err(EmitterError::EndElementNameIsNotSpecified) 415 | } 416 | } 417 | 418 | pub fn emit_characters(&mut self, target: &mut W, content: &str) -> Result<()> { 419 | self.check_document_started(target)?; 420 | self.fix_non_empty_element(target)?; 421 | target.write_all(escape_str_pcdata(content).as_bytes())?; 422 | self.after_text(); 423 | Ok(()) 424 | } 425 | 426 | #[cfg(test)] 427 | pub fn emit_comment(&mut self, target: &mut W, content: &str) -> Result<()> { 428 | self.fix_non_empty_element(target)?; 429 | 430 | // TODO: add escaping dashes at the end of the comment 431 | 432 | let autopad_comments = self.config.autopad_comments; 433 | let write = |target: &mut W| -> Result<()> { 434 | target.write_all(b"")?; 447 | 448 | Ok(()) 449 | }; 450 | 451 | self.before_markup(target)?; 452 | let result = write(target); 453 | self.after_markup(); 454 | 455 | result 456 | } 457 | } 458 | -------------------------------------------------------------------------------- /src/xml/namespace.rs: -------------------------------------------------------------------------------- 1 | //! Contains namespace manipulation types and functions. 2 | 3 | use std::collections::btree_map::Iter as Entries; 4 | use std::collections::btree_map::{BTreeMap, Entry}; 5 | use std::collections::HashSet; 6 | use std::iter::{Map, Rev}; 7 | use std::slice::Iter; 8 | 9 | /// Designates prefix for namespace definitions. 10 | /// 11 | /// See [Namespaces in XML][namespace] spec for more information. 12 | /// 13 | /// [namespace]: http://www.w3.org/TR/xml-names/#ns-decl 14 | pub const NS_XMLNS_PREFIX: &str = "xmlns"; 15 | 16 | /// Designates the standard URI for `xmlns` prefix. 17 | /// 18 | /// See [A Namespace Name for xmlns Attributes][namespace] for more information. 19 | /// 20 | /// [namespace]: http://www.w3.org/2000/xmlns/ 21 | pub const NS_XMLNS_URI: &str = "http://www.w3.org/2000/xmlns/"; 22 | 23 | /// Designates prefix for a namespace containing several special predefined attributes. 24 | /// 25 | /// See [2.10 White Space handling][1], [2.1 Language Identification][2], 26 | /// [XML Base specification][3] and [xml:id specification][4] for more information. 27 | /// 28 | /// [1]: http://www.w3.org/TR/REC-xml/#sec-white-space 29 | /// [2]: http://www.w3.org/TR/REC-xml/#sec-lang-tag 30 | /// [3]: http://www.w3.org/TR/xmlbase/ 31 | /// [4]: http://www.w3.org/TR/xml-id/ 32 | pub const NS_XML_PREFIX: &str = "xml"; 33 | 34 | /// Designates the standard URI for `xml` prefix. 35 | /// 36 | /// See `NS_XML_PREFIX` documentation for more information. 37 | pub const NS_XML_URI: &str = "http://www.w3.org/XML/1998/namespace"; 38 | 39 | /// Designates the absence of prefix in a qualified name. 40 | /// 41 | /// This constant should be used to define or query default namespace which should be used 42 | /// for element or attribute names without prefix. For example, if a namespace mapping 43 | /// at a particular point in the document contains correspondence like 44 | /// 45 | /// ```none 46 | /// NS_NO_PREFIX --> urn:some:namespace 47 | /// ``` 48 | /// 49 | /// then all names declared without an explicit prefix `urn:some:namespace` is assumed as 50 | /// a namespace URI. 51 | /// 52 | /// By default empty prefix corresponds to absence of namespace, but this can change either 53 | /// when writing an XML document (manually) or when reading an XML document (based on namespace 54 | /// declarations). 55 | pub const NS_NO_PREFIX: &str = ""; 56 | 57 | /// Designates an empty namespace URI, which is equivalent to absence of namespace. 58 | /// 59 | /// This constant should not usually be used directly; it is used to designate that 60 | /// empty prefix corresponds to absent namespace in `NamespaceStack` instances created with 61 | /// `NamespaceStack::default()`. Therefore, it can be used to restore `NS_NO_PREFIX` mapping 62 | /// in a namespace back to its default value. 63 | pub const NS_EMPTY_URI: &str = ""; 64 | 65 | /// Namespace is a map from prefixes to namespace URIs. 66 | /// 67 | /// No prefix (i.e. default namespace) is designated by `NS_NO_PREFIX` constant. 68 | #[derive(PartialEq, Eq, Clone, Debug)] 69 | pub struct Namespace(pub BTreeMap); 70 | 71 | impl Namespace { 72 | /// Returns an empty namespace. 73 | #[inline] 74 | pub fn empty() -> Namespace { 75 | Namespace(BTreeMap::new()) 76 | } 77 | 78 | /// Checks whether this namespace is empty. 79 | #[inline] 80 | pub fn is_empty(&self) -> bool { 81 | self.0.is_empty() 82 | } 83 | 84 | /// Checks whether this namespace is essentially empty, that is, it does not contain 85 | /// anything but default mappings. 86 | pub fn is_essentially_empty(&self) -> bool { 87 | // a shortcut for a namespace which is definitely not empty 88 | if self.0.len() > 3 { 89 | return false; 90 | } 91 | 92 | self.0.iter().all(|(k, v)| { 93 | matches!( 94 | (&**k, &**v), 95 | (NS_NO_PREFIX, NS_EMPTY_URI) 96 | | (NS_XMLNS_PREFIX, NS_XMLNS_URI) 97 | | (NS_XML_PREFIX, NS_XML_URI) 98 | ) 99 | }) 100 | } 101 | 102 | /// Checks whether this namespace mapping contains the given prefix. 103 | /// 104 | /// # Parameters 105 | /// * `prefix` --- namespace prefix. 106 | /// 107 | /// # Return value 108 | /// `true` if this namespace contains the given prefix, `false` otherwise. 109 | #[inline] 110 | pub fn contains>(&self, prefix: &P) -> bool { 111 | self.0.contains_key(prefix.as_ref()) 112 | } 113 | 114 | /// Puts a mapping into this namespace. 115 | /// 116 | /// This method does not override any already existing mappings. 117 | /// 118 | /// Returns a boolean flag indicating whether the map already contained 119 | /// the given prefix. 120 | /// 121 | /// # Parameters 122 | /// * `prefix` --- namespace prefix; 123 | /// * `uri` --- namespace URI. 124 | /// 125 | /// # Return value 126 | /// `true` if `prefix` has been inserted successfully; `false` if the `prefix` 127 | /// was already present in the namespace. 128 | pub fn put(&mut self, prefix: P, uri: U) -> bool 129 | where 130 | P: Into, 131 | U: Into, 132 | { 133 | match self.0.entry(prefix.into()) { 134 | Entry::Occupied(_) => false, 135 | Entry::Vacant(ve) => { 136 | ve.insert(uri.into()); 137 | true 138 | } 139 | } 140 | } 141 | 142 | /// Puts a mapping into this namespace forcefully. 143 | /// 144 | /// This method, unlike `put()`, does replace an already existing mapping. 145 | /// 146 | /// Returns previous URI which was assigned to the given prefix, if it is present. 147 | /// 148 | /// # Parameters 149 | /// * `prefix` --- namespace prefix; 150 | /// * `uri` --- namespace URI. 151 | /// 152 | /// # Return value 153 | /// `Some(uri)` with `uri` being a previous URI assigned to the `prefix`, or 154 | /// `None` if such prefix was not present in the namespace before. 155 | pub fn force_put(&mut self, prefix: P, uri: U) -> Option 156 | where 157 | P: Into, 158 | U: Into, 159 | { 160 | self.0.insert(prefix.into(), uri.into()) 161 | } 162 | 163 | /// Queries the namespace for the given prefix. 164 | /// 165 | /// # Parameters 166 | /// * `prefix` --- namespace prefix. 167 | /// 168 | /// # Return value 169 | /// Namespace URI corresponding to the given prefix, if it is present. 170 | pub fn get<'a, P: ?Sized + AsRef>(&'a self, prefix: &P) -> Option<&'a str> { 171 | self.0.get(prefix.as_ref()).map(|s| &**s) 172 | } 173 | } 174 | 175 | /// An alias for iterator type for namespace mappings contained in a namespace. 176 | pub type NamespaceMappings<'a> = 177 | Map, for<'b> fn((&'b String, &'b String)) -> UriMapping<'b>>; 178 | 179 | impl<'a> IntoIterator for &'a Namespace { 180 | type Item = UriMapping<'a>; 181 | type IntoIter = NamespaceMappings<'a>; 182 | 183 | fn into_iter(self) -> Self::IntoIter { 184 | self.0.iter().map(|(prefix, uri)| (prefix, uri)) 185 | } 186 | } 187 | 188 | /// Namespace stack is a sequence of namespaces. 189 | /// 190 | /// Namespace stack is used to represent cumulative namespace consisting of 191 | /// combined namespaces from nested elements. 192 | #[derive(Clone, Eq, PartialEq, Debug)] 193 | pub struct NamespaceStack(pub Vec); 194 | 195 | impl NamespaceStack { 196 | /// Returns an empty namespace stack. 197 | #[inline] 198 | pub fn empty() -> NamespaceStack { 199 | NamespaceStack(Vec::with_capacity(2)) 200 | } 201 | 202 | /// Returns a namespace stack with default items in it. 203 | /// 204 | /// Default items are the following: 205 | /// 206 | /// * `xml` → `http://www.w3.org/XML/1998/namespace`; 207 | /// * `xmlns` → `http://www.w3.org/2000/xmlns/`. 208 | #[inline] 209 | pub fn default() -> NamespaceStack { 210 | let mut nst = NamespaceStack::empty(); 211 | nst.push_empty(); 212 | // xml namespace 213 | nst.put(NS_XML_PREFIX, NS_XML_URI); 214 | // xmlns namespace 215 | nst.put(NS_XMLNS_PREFIX, NS_XMLNS_URI); 216 | // empty namespace 217 | nst.put(NS_NO_PREFIX, NS_EMPTY_URI); 218 | nst 219 | } 220 | 221 | /// Adds an empty namespace to the top of this stack. 222 | #[inline] 223 | pub fn push_empty(&mut self) -> &mut NamespaceStack { 224 | self.0.push(Namespace::empty()); 225 | self 226 | } 227 | 228 | /// Removes the topmost namespace in this stack. 229 | /// 230 | /// Panics if the stack is empty. 231 | #[inline] 232 | pub fn pop(&mut self) -> Namespace { 233 | self.0.pop().unwrap() 234 | } 235 | 236 | /// Removes the topmost namespace in this stack. 237 | /// 238 | /// Returns `Some(namespace)` if this stack is not empty and `None` otherwise. 239 | #[inline] 240 | pub fn try_pop(&mut self) -> Option { 241 | self.0.pop() 242 | } 243 | 244 | /// Borrows the topmost namespace mutably, leaving the stack intact. 245 | /// 246 | /// Panics if the stack is empty. 247 | #[inline] 248 | pub fn peek_mut(&mut self) -> &mut Namespace { 249 | self.0.last_mut().unwrap() 250 | } 251 | 252 | /// Borrows the topmost namespace immutably, leaving the stack intact. 253 | /// 254 | /// Panics if the stack is empty. 255 | #[inline] 256 | pub fn peek(&self) -> &Namespace { 257 | self.0.last().unwrap() 258 | } 259 | 260 | /// Puts a mapping into the topmost namespace if this stack does not already contain one. 261 | /// 262 | /// Returns a boolean flag indicating whether the insertion has completed successfully. 263 | /// Note that both key and value are matched and the mapping is inserted if either 264 | /// namespace prefix is not already mapped, or if it is mapped, but to a different URI. 265 | /// 266 | /// # Parameters 267 | /// * `prefix` --- namespace prefix; 268 | /// * `uri` --- namespace URI. 269 | /// 270 | /// # Return value 271 | /// `true` if `prefix` has been inserted successfully; `false` if the `prefix` 272 | /// was already present in the namespace stack. 273 | pub fn put_checked(&mut self, prefix: P, uri: U) -> bool 274 | where 275 | P: Into + AsRef, 276 | U: Into + AsRef, 277 | { 278 | if self 279 | .0 280 | .iter() 281 | .any(|ns| ns.get(&prefix) == Some(uri.as_ref())) 282 | { 283 | false 284 | } else { 285 | self.put(prefix, uri); 286 | true 287 | } 288 | } 289 | 290 | /// Puts a mapping into the topmost namespace in this stack. 291 | /// 292 | /// This method does not override a mapping in the topmost namespace if it is 293 | /// already present, however, it does not depend on other namespaces in the stack, 294 | /// so it is possible to put a mapping which is present in lower namespaces. 295 | /// 296 | /// Returns a boolean flag indicating whether the insertion has completed successfully. 297 | /// 298 | /// # Parameters 299 | /// * `prefix` --- namespace prefix; 300 | /// * `uri` --- namespace URI. 301 | /// 302 | /// # Return value 303 | /// `true` if `prefix` has been inserted successfully; `false` if the `prefix` 304 | /// was already present in the namespace. 305 | #[inline] 306 | pub fn put(&mut self, prefix: P, uri: U) -> bool 307 | where 308 | P: Into, 309 | U: Into, 310 | { 311 | self.0.last_mut().unwrap().put(prefix, uri) 312 | } 313 | 314 | /// Performs a search for the given prefix in the whole stack. 315 | /// 316 | /// This method walks the stack from top to bottom, querying each namespace 317 | /// in order for the given prefix. If none of the namespaces contains the prefix, 318 | /// `None` is returned. 319 | /// 320 | /// # Parameters 321 | /// * `prefix` --- namespace prefix. 322 | #[inline] 323 | pub fn get<'a, P: ?Sized + AsRef>(&'a self, prefix: &P) -> Option<&'a str> { 324 | let prefix = prefix.as_ref(); 325 | for ns in self.0.iter().rev() { 326 | match ns.get(prefix) { 327 | None => {} 328 | r => return r, 329 | } 330 | } 331 | None 332 | } 333 | 334 | /// Combines this stack of namespaces into a single namespace. 335 | /// 336 | /// Namespaces are combined in left-to-right order, that is, rightmost namespace 337 | /// elements take priority over leftmost ones. 338 | pub fn squash(&self) -> Namespace { 339 | let mut result = BTreeMap::new(); 340 | for ns in self.0.iter() { 341 | result.extend(ns.0.iter().map(|(k, v)| (k.clone(), v.clone()))); 342 | } 343 | Namespace(result) 344 | } 345 | 346 | /// Returns an object which implements `Extend` using `put_checked()` instead of `put()`. 347 | /// 348 | /// See `CheckedTarget` for more information. 349 | #[inline] 350 | pub fn checked_target(&mut self) -> CheckedTarget<'_> { 351 | CheckedTarget(self) 352 | } 353 | 354 | /// Returns an iterator over all mappings in this namespace stack. 355 | #[inline] 356 | pub fn iter(&self) -> NamespaceStackMappings<'_> { 357 | self.into_iter() 358 | } 359 | } 360 | 361 | /// An iterator over mappings from prefixes to URIs in a namespace stack. 362 | pub struct NamespaceStackMappings<'a> { 363 | namespaces: Rev>, 364 | current_namespace: Option>, 365 | used_keys: HashSet<&'a str>, 366 | } 367 | 368 | impl<'a> NamespaceStackMappings<'a> { 369 | fn go_to_next_namespace(&mut self) -> bool { 370 | self.current_namespace = self.namespaces.next().map(|ns| ns.into_iter()); 371 | self.current_namespace.is_some() 372 | } 373 | } 374 | 375 | impl<'a> Iterator for NamespaceStackMappings<'a> { 376 | type Item = UriMapping<'a>; 377 | 378 | fn next(&mut self) -> Option> { 379 | // If there is no current namespace and no next namespace, we're finished 380 | if self.current_namespace.is_none() && !self.go_to_next_namespace() { 381 | return None; 382 | } 383 | let next_item = self.current_namespace.as_mut().unwrap().next(); 384 | 385 | match next_item { 386 | // There is an element in the current namespace 387 | Some((k, v)) => { 388 | if self.used_keys.contains(&k) { 389 | // If the current key is used, go to the next one 390 | self.next() 391 | } else { 392 | // Otherwise insert the current key to the set of used keys and 393 | // return the mapping 394 | self.used_keys.insert(k); 395 | Some((k, v)) 396 | } 397 | } 398 | // Current namespace is exhausted 399 | None => { 400 | if self.go_to_next_namespace() { 401 | // If there is next namespace, continue from it 402 | self.next() 403 | } else { 404 | // No next namespace, exiting 405 | None 406 | } 407 | } 408 | } 409 | } 410 | } 411 | 412 | impl<'a> IntoIterator for &'a NamespaceStack { 413 | type Item = UriMapping<'a>; 414 | type IntoIter = NamespaceStackMappings<'a>; 415 | 416 | fn into_iter(self) -> Self::IntoIter { 417 | NamespaceStackMappings { 418 | namespaces: self.0.iter().rev(), 419 | current_namespace: None, 420 | used_keys: HashSet::new(), 421 | } 422 | } 423 | } 424 | 425 | /// A type alias for a pair of `(prefix, uri)` values returned by namespace iterators. 426 | pub type UriMapping<'a> = (&'a str, &'a str); 427 | 428 | impl<'a> Extend> for Namespace { 429 | fn extend(&mut self, iterable: T) 430 | where 431 | T: IntoIterator>, 432 | { 433 | for (prefix, uri) in iterable { 434 | self.put(prefix, uri); 435 | } 436 | } 437 | } 438 | 439 | impl<'a> Extend> for NamespaceStack { 440 | fn extend(&mut self, iterable: T) 441 | where 442 | T: IntoIterator>, 443 | { 444 | for (prefix, uri) in iterable { 445 | self.put(prefix, uri); 446 | } 447 | } 448 | } 449 | 450 | /// A wrapper around `NamespaceStack` which implements `Extend` using `put_checked()`. 451 | pub struct CheckedTarget<'a>(&'a mut NamespaceStack); 452 | 453 | impl<'a, 'b> Extend> for CheckedTarget<'a> { 454 | fn extend(&mut self, iterable: T) 455 | where 456 | T: IntoIterator>, 457 | { 458 | for (prefix, uri) in iterable { 459 | self.0.put_checked(prefix, uri); 460 | } 461 | } 462 | } 463 | 464 | #[test] 465 | fn test_namespace_iter() { 466 | let mut nst = NamespaceStack::empty(); 467 | nst.push_empty(); 468 | nst.put("a", "urn:A"); 469 | nst.put("b", "urn:B"); 470 | nst.push_empty(); 471 | nst.put("c", "urn:C"); 472 | 473 | assert_eq!( 474 | vec![("c", "urn:C"), ("a", "urn:A"), ("b", "urn:B")], 475 | nst.iter().collect::>() 476 | ); 477 | } 478 | 479 | #[test] 480 | fn test_checked_target() { 481 | let mut nst = NamespaceStack::empty(); 482 | nst.push_empty(); 483 | nst.put("a", "urn:A"); 484 | nst.put("b", "urn:B"); 485 | nst.push_empty(); 486 | nst.put("c", "urn:C"); 487 | 488 | nst.checked_target().extend(vec![ 489 | ("a", "urn:Z"), 490 | ("b", "urn:B"), 491 | ("c", "urn:Y"), 492 | ("d", "urn:D"), 493 | ]); 494 | assert_eq!( 495 | vec![ 496 | ("a", "urn:Z"), 497 | ("c", "urn:C"), 498 | ("d", "urn:D"), 499 | ("b", "urn:B") 500 | ], 501 | nst.iter().collect::>() 502 | ); 503 | 504 | let mut nst = NamespaceStack::empty(); 505 | nst.push_empty(); 506 | nst.put("a", "urn:A"); 507 | nst.put("b", "urn:B"); 508 | nst.push_empty(); 509 | nst.put("c", "urn:C"); 510 | 511 | nst.extend(vec![ 512 | ("a", "urn:Z"), 513 | ("b", "urn:B"), 514 | ("c", "urn:Y"), 515 | ("d", "urn:D"), 516 | ]); 517 | assert_eq!( 518 | vec![ 519 | ("a", "urn:Z"), 520 | ("b", "urn:B"), 521 | ("c", "urn:C"), 522 | ("d", "urn:D") 523 | ], 524 | nst.iter().collect::>() 525 | ); 526 | } 527 | -------------------------------------------------------------------------------- /src/xml/reader/parser/mod.rs: -------------------------------------------------------------------------------- 1 | //! Contains an implementation of pull-based XML parser. 2 | 3 | use std::borrow::Cow; 4 | use std::io::prelude::*; 5 | use std::mem; 6 | 7 | use crate::xml::attribute::OwnedAttribute; 8 | use crate::xml::common::{ 9 | self, is_name_char, is_name_start_char, Position, TextPosition, XmlVersion, 10 | }; 11 | use crate::xml::name::OwnedName; 12 | use crate::xml::namespace::NamespaceStack; 13 | use crate::xml::reader::config::ParserConfig; 14 | use crate::xml::reader::events::XmlEvent; 15 | use crate::xml::reader::lexer::{Lexer, Token}; 16 | 17 | macro_rules! gen_takes( 18 | ($($field:ident -> $method:ident, $t:ty);+) => ( 19 | $( 20 | impl MarkupData { 21 | #[inline] 22 | fn $method(&mut self) -> $t { 23 | mem::take(&mut self.$field) 24 | } 25 | } 26 | )+ 27 | ) 28 | ); 29 | 30 | gen_takes!( 31 | name -> take_name, String; 32 | ref_data -> take_ref_data, String; 33 | 34 | version -> take_version, Option; 35 | encoding -> take_encoding, Option; 36 | standalone -> take_standalone, Option; 37 | 38 | element_name -> take_element_name, Option; 39 | 40 | attr_name -> take_attr_name, Option; 41 | attributes -> take_attributes, Vec 42 | ); 43 | 44 | macro_rules! self_error( 45 | ($this:ident; $msg:expr) => ($this.error($msg)); 46 | ($this:ident; $fmt:expr, $($arg:expr),+) => ($this.error(format!($fmt, $($arg),+))) 47 | ); 48 | 49 | mod inside_cdata; 50 | mod inside_closing_tag_name; 51 | mod inside_comment; 52 | mod inside_declaration; 53 | mod inside_doctype; 54 | mod inside_opening_tag; 55 | mod inside_processing_instruction; 56 | mod inside_reference; 57 | mod outside_tag; 58 | 59 | static DEFAULT_VERSION: XmlVersion = XmlVersion::Version10; 60 | static DEFAULT_ENCODING: &str = "UTF-8"; 61 | static DEFAULT_STANDALONE: Option = None; 62 | 63 | type ElementStack = Vec; 64 | pub type ParserOutcome = super::Outcome; 65 | 66 | /// Pull-based XML parser. 67 | pub struct PullParser { 68 | config: ParserConfig, 69 | lexer: Lexer, 70 | st: State, 71 | buf: String, 72 | nst: NamespaceStack, 73 | 74 | data: MarkupData, 75 | final_result: Option, 76 | next_event: Option, 77 | est: ElementStack, 78 | pos: Vec, 79 | 80 | encountered_element: bool, 81 | parsed_declaration: bool, 82 | inside_whitespace: bool, 83 | read_prefix_separator: bool, 84 | pop_namespace: bool, 85 | } 86 | 87 | impl PullParser { 88 | /// Returns a new parser using the given config. 89 | pub fn new(config: ParserConfig) -> PullParser { 90 | PullParser { 91 | config, 92 | lexer: Lexer::new(), 93 | st: State::OutsideTag, 94 | buf: String::new(), 95 | nst: NamespaceStack::default(), 96 | 97 | data: MarkupData { 98 | name: String::new(), 99 | version: None, 100 | encoding: None, 101 | standalone: None, 102 | ref_data: String::new(), 103 | element_name: None, 104 | quote: None, 105 | attr_name: None, 106 | attributes: Vec::new(), 107 | }, 108 | final_result: None, 109 | next_event: None, 110 | est: Vec::new(), 111 | pos: vec![TextPosition::new()], 112 | 113 | encountered_element: false, 114 | parsed_declaration: false, 115 | inside_whitespace: true, 116 | read_prefix_separator: false, 117 | pop_namespace: false, 118 | } 119 | } 120 | } 121 | 122 | impl Position for PullParser { 123 | /// Returns the position of the last event produced by the parser 124 | #[inline] 125 | fn position(&self) -> TextPosition { 126 | self.pos[0] 127 | } 128 | } 129 | 130 | #[derive(Clone, PartialEq)] 131 | pub enum State { 132 | OutsideTag, 133 | InsideOpeningTag(OpeningTagSubstate), 134 | InsideClosingTag(ClosingTagSubstate), 135 | InsideProcessingInstruction(ProcessingInstructionSubstate), 136 | InsideComment, 137 | InsideCData, 138 | InsideDeclaration(DeclarationSubstate), 139 | InsideDoctype, 140 | InsideReference(Box), 141 | } 142 | 143 | #[derive(Clone, Eq, PartialEq)] 144 | pub enum OpeningTagSubstate { 145 | InsideName, 146 | 147 | InsideTag, 148 | 149 | InsideAttributeName, 150 | AfterAttributeName, 151 | 152 | InsideAttributeValue, 153 | } 154 | 155 | #[derive(Clone, Eq, PartialEq)] 156 | pub enum ClosingTagSubstate { 157 | CTInsideName, 158 | CTAfterName, 159 | } 160 | 161 | #[derive(Clone, Eq, PartialEq)] 162 | pub enum ProcessingInstructionSubstate { 163 | PIInsideName, 164 | PIInsideData, 165 | } 166 | 167 | #[derive(Clone, Eq, PartialEq)] 168 | pub enum DeclarationSubstate { 169 | BeforeVersion, 170 | InsideVersion, 171 | AfterVersion, 172 | 173 | InsideVersionValue, 174 | AfterVersionValue, 175 | 176 | InsideEncoding, 177 | AfterEncoding, 178 | 179 | InsideEncodingValue, 180 | 181 | BeforeStandaloneDecl, 182 | InsideStandaloneDecl, 183 | AfterStandaloneDecl, 184 | 185 | InsideStandaloneDeclValue, 186 | AfterStandaloneDeclValue, 187 | } 188 | 189 | #[derive(PartialEq)] 190 | enum QualifiedNameTarget { 191 | Attribute, 192 | OpeningTag, 193 | ClosingTag, 194 | } 195 | 196 | #[derive(Copy, Clone, PartialEq, Eq)] 197 | enum QuoteToken { 198 | SingleQuoteToken, 199 | DoubleQuoteToken, 200 | } 201 | 202 | impl QuoteToken { 203 | fn from_token(t: &Token) -> QuoteToken { 204 | match *t { 205 | Token::SingleQuote => QuoteToken::SingleQuoteToken, 206 | Token::DoubleQuote => QuoteToken::DoubleQuoteToken, 207 | _ => panic!("Unexpected token: {}", t), 208 | } 209 | } 210 | 211 | fn as_token(self) -> Token { 212 | match self { 213 | QuoteToken::SingleQuoteToken => Token::SingleQuote, 214 | QuoteToken::DoubleQuoteToken => Token::DoubleQuote, 215 | } 216 | } 217 | } 218 | 219 | struct MarkupData { 220 | name: String, // used for processing instruction name 221 | ref_data: String, // used for reference content 222 | 223 | version: Option, // used for XML declaration version 224 | encoding: Option, // used for XML declaration encoding 225 | standalone: Option, // used for XML declaration standalone parameter 226 | 227 | element_name: Option, // used for element name 228 | 229 | quote: Option, // used to hold opening quote for attribute value 230 | attr_name: Option, // used to hold attribute name 231 | attributes: Vec, // used to hold all accumulated attributes 232 | } 233 | 234 | impl PullParser { 235 | /// Returns next event read from the given buffer. 236 | /// 237 | /// This method should be always called with the same buffer. If you call it 238 | /// providing different buffers each time, the result will be undefined. 239 | pub fn next(&mut self, r: &mut R) -> ParserOutcome { 240 | if let Some(ref ev) = self.final_result { 241 | return ev.clone(); 242 | } 243 | 244 | if let Some(ev) = self.next_event.take() { 245 | return ev; 246 | } 247 | 248 | if self.pop_namespace { 249 | self.pop_namespace = false; 250 | self.nst.pop(); 251 | } 252 | 253 | loop { 254 | // While lexer gives us Ok(maybe_token) -- we loop. 255 | // Upon having a complete XML-event -- we return from the whole function. 256 | match self.lexer.next_token(r) { 257 | Ok(maybe_token) => match maybe_token { 258 | None => break, 259 | Some(token) => match self.dispatch_token(token) { 260 | None => {} // continue 261 | Some(Ok(XmlEvent::EndDocument)) => { 262 | return { 263 | self.next_pos(); 264 | self.set_final_result(Ok(XmlEvent::EndDocument)) 265 | } 266 | } 267 | Some(Ok(xml_event)) => { 268 | return { 269 | self.next_pos(); 270 | Ok(xml_event) 271 | } 272 | } 273 | Some(Err(xml_error)) => { 274 | return { 275 | self.next_pos(); 276 | self.set_final_result(Err(xml_error)) 277 | } 278 | } 279 | }, 280 | }, 281 | Err(lexer_error) => return self.set_final_result(Err(lexer_error)), 282 | } 283 | } 284 | 285 | // Handle end of stream 286 | // Forward pos to the lexer head 287 | self.next_pos(); 288 | let ev = if self.depth() == 0 { 289 | if self.encountered_element && self.st == State::OutsideTag { 290 | // all is ok 291 | Ok(XmlEvent::EndDocument) 292 | } else if !self.encountered_element { 293 | self_error!(self; "Unexpected end of stream: no root element found") 294 | } else { 295 | // self.st != State::OutsideTag 296 | self_error!(self; "Unexpected end of stream") // TODO: add expected hint? 297 | } 298 | } else { 299 | self_error!(self; "Unexpected end of stream: still inside the root element") 300 | }; 301 | self.set_final_result(ev) 302 | } 303 | 304 | // This function is to be called when a terminal event is reached. 305 | // The function sets up the `self.final_result` into `Some(result)` and return `result`. 306 | fn set_final_result(&mut self, result: ParserOutcome) -> ParserOutcome { 307 | self.final_result = Some(result.clone()); 308 | result 309 | } 310 | 311 | #[inline] 312 | fn error>>(&self, msg: M) -> ParserOutcome { 313 | Err((&self.lexer, msg).into()) 314 | } 315 | 316 | #[inline] 317 | fn next_pos(&mut self) { 318 | if self.pos.len() > 1 { 319 | self.pos.remove(0); 320 | } else { 321 | self.pos[0] = self.lexer.position(); 322 | } 323 | } 324 | 325 | #[inline] 326 | fn push_pos(&mut self) { 327 | self.pos.push(self.lexer.position()); 328 | } 329 | 330 | fn dispatch_token(&mut self, t: Token) -> Option { 331 | match self.st.clone() { 332 | State::OutsideTag => self.outside_tag(t), 333 | State::InsideProcessingInstruction(s) => self.inside_processing_instruction(t, s), 334 | State::InsideDeclaration(s) => self.inside_declaration(t, s), 335 | State::InsideDoctype => self.inside_doctype(t), 336 | State::InsideOpeningTag(s) => self.inside_opening_tag(t, s), 337 | State::InsideClosingTag(s) => self.inside_closing_tag_name(t, s), 338 | State::InsideComment => self.inside_comment(t), 339 | State::InsideCData => self.inside_cdata(t), 340 | State::InsideReference(s) => self.inside_reference(t, *s), 341 | } 342 | } 343 | 344 | #[inline] 345 | fn depth(&self) -> usize { 346 | self.est.len() 347 | } 348 | 349 | #[inline] 350 | fn buf_has_data(&self) -> bool { 351 | !self.buf.is_empty() 352 | } 353 | 354 | #[inline] 355 | fn take_buf(&mut self) -> String { 356 | std::mem::take(&mut self.buf) 357 | } 358 | 359 | #[inline] 360 | fn append_char_continue(&mut self, c: char) -> Option { 361 | self.buf.push(c); 362 | None 363 | } 364 | 365 | #[inline] 366 | fn as_state(&mut self, st: State, ev: Option) -> Option { 367 | self.st = st; 368 | ev 369 | } 370 | 371 | #[inline] 372 | fn as_state_continue(&mut self, st: State) -> Option { 373 | self.as_state(st, None) 374 | } 375 | 376 | #[inline] 377 | fn as_state_emit(&mut self, st: State, ev: ParserOutcome) -> Option { 378 | self.as_state(st, Some(ev)) 379 | } 380 | 381 | /// Dispatches tokens in order to process qualified name. If qualified name cannot be parsed, 382 | /// an error is returned. 383 | /// 384 | /// # Parameters 385 | /// * `t` --- next token; 386 | /// * `on_name` --- a callback which is executed when whitespace is encountered. 387 | fn read_qualified_name( 388 | &mut self, 389 | t: Token, 390 | target: QualifiedNameTarget, 391 | on_name: F, 392 | ) -> Option 393 | where 394 | F: Fn(&mut PullParser, Token, OwnedName) -> Option, 395 | { 396 | // We can get here for the first time only when self.data.name contains zero or one character, 397 | // but first character cannot be a colon anyway 398 | if self.buf.len() <= 1 { 399 | self.read_prefix_separator = false; 400 | } 401 | 402 | let invoke_callback = |this: &mut PullParser, t| { 403 | let name = this.take_buf(); 404 | match name.parse() { 405 | Ok(name) => on_name(this, t, name), 406 | Err(_) => Some(self_error!(this; "Qualified name is invalid: {}", name)), 407 | } 408 | }; 409 | 410 | match t { 411 | // There can be only one colon, and not as the first character 412 | Token::Character(':') if self.buf_has_data() && !self.read_prefix_separator => { 413 | self.buf.push(':'); 414 | self.read_prefix_separator = true; 415 | None 416 | } 417 | 418 | Token::Character(c) 419 | if c != ':' 420 | && (!self.buf_has_data() && is_name_start_char(c) 421 | || self.buf_has_data() && is_name_char(c)) => 422 | { 423 | self.append_char_continue(c) 424 | } 425 | 426 | Token::EqualsSign if target == QualifiedNameTarget::Attribute => { 427 | invoke_callback(self, t) 428 | } 429 | 430 | Token::EmptyTagEnd if target == QualifiedNameTarget::OpeningTag => { 431 | invoke_callback(self, t) 432 | } 433 | 434 | Token::TagEnd 435 | if target == QualifiedNameTarget::OpeningTag 436 | || target == QualifiedNameTarget::ClosingTag => 437 | { 438 | invoke_callback(self, t) 439 | } 440 | 441 | Token::Whitespace(_) => invoke_callback(self, t), 442 | 443 | _ => Some(self_error!(self; "Unexpected token inside qualified name: {}", t)), 444 | } 445 | } 446 | 447 | /// Dispatches tokens in order to process attribute value. 448 | /// 449 | /// # Parameters 450 | /// * `t` --- next token; 451 | /// * `on_value` --- a callback which is called when terminating quote is encountered. 452 | fn read_attribute_value(&mut self, t: Token, on_value: F) -> Option 453 | where 454 | F: Fn(&mut PullParser, String) -> Option, 455 | { 456 | match t { 457 | Token::Whitespace(_) if self.data.quote.is_none() => None, // skip leading whitespace 458 | 459 | Token::DoubleQuote | Token::SingleQuote => match self.data.quote { 460 | None => { 461 | // Entered attribute value 462 | self.data.quote = Some(QuoteToken::from_token(&t)); 463 | None 464 | } 465 | Some(q) if q.as_token() == t => { 466 | self.data.quote = None; 467 | let value = self.take_buf(); 468 | on_value(self, value) 469 | } 470 | _ => { 471 | t.push_to_string(&mut self.buf); 472 | None 473 | } 474 | }, 475 | 476 | Token::ReferenceStart => { 477 | let st = Box::new(self.st.clone()); 478 | self.as_state_continue(State::InsideReference(st)) 479 | } 480 | 481 | Token::OpeningTagStart => { 482 | Some(self_error!(self; "Unexpected token inside attribute value: <")) 483 | } 484 | 485 | // Every character except " and ' and < is okay 486 | _ => { 487 | t.push_to_string(&mut self.buf); 488 | None 489 | } 490 | } 491 | } 492 | 493 | fn emit_start_element(&mut self, emit_end_element: bool) -> Option { 494 | let mut name = self.data.take_element_name().unwrap(); 495 | let mut attributes = self.data.take_attributes(); 496 | 497 | // check whether the name prefix is bound and fix its namespace 498 | match self.nst.get(name.borrow().prefix_repr()) { 499 | Some("") => name.namespace = None, // default namespace 500 | Some(ns) => name.namespace = Some(ns.into()), 501 | None => return Some(self_error!(self; "Element {} prefix is unbound", name)), 502 | } 503 | 504 | // check and fix accumulated attributes prefixes 505 | for attr in attributes.iter_mut() { 506 | if let Some(ref pfx) = attr.name.prefix { 507 | let new_ns = match self.nst.get(pfx) { 508 | Some("") => None, // default namespace 509 | Some(ns) => Some(ns.into()), 510 | None => { 511 | return Some(self_error!(self; "Attribute {} prefix is unbound", attr.name)) 512 | } 513 | }; 514 | attr.name.namespace = new_ns; 515 | } 516 | } 517 | 518 | if emit_end_element { 519 | self.pop_namespace = true; 520 | self.next_event = Some(Ok(XmlEvent::EndElement { name: name.clone() })); 521 | } else { 522 | self.est.push(name.clone()); 523 | } 524 | let namespace = self.nst.squash(); 525 | self.as_state_emit( 526 | State::OutsideTag, 527 | Ok(XmlEvent::StartElement { 528 | name, 529 | attributes, 530 | namespace, 531 | }), 532 | ) 533 | } 534 | 535 | fn emit_end_element(&mut self) -> Option { 536 | let mut name = self.data.take_element_name().unwrap(); 537 | 538 | // check whether the name prefix is bound and fix its namespace 539 | match self.nst.get(name.borrow().prefix_repr()) { 540 | Some("") => name.namespace = None, // default namespace 541 | Some(ns) => name.namespace = Some(ns.into()), 542 | None => return Some(self_error!(self; "Element {} prefix is unbound", name)), 543 | } 544 | 545 | let op_name = self.est.pop().unwrap(); 546 | 547 | if name == op_name { 548 | self.pop_namespace = true; 549 | self.as_state_emit(State::OutsideTag, Ok(XmlEvent::EndElement { name })) 550 | } else { 551 | Some(self_error!(self; "Unexpected closing tag: {}, expected {}", name, op_name)) 552 | } 553 | } 554 | } 555 | 556 | #[cfg(test)] 557 | mod tests { 558 | use std::io::BufReader; 559 | 560 | use crate::xml::attribute::OwnedAttribute; 561 | use crate::xml::common::{Position, TextPosition}; 562 | use crate::xml::name::OwnedName; 563 | use crate::xml::reader::events::XmlEvent; 564 | use crate::xml::reader::parser::PullParser; 565 | use crate::xml::reader::ParserConfig; 566 | 567 | fn new_parser() -> PullParser { 568 | PullParser::new(ParserConfig::new()) 569 | } 570 | 571 | macro_rules! expect_event( 572 | ($r:expr, $p:expr, $t:pat) => ( 573 | match $p.next(&mut $r) { 574 | $t => {} 575 | e => panic!("Unexpected event: {:?}", e) 576 | } 577 | ); 578 | ($r:expr, $p:expr, $t:pat => $c:expr ) => ( 579 | match $p.next(&mut $r) { 580 | $t if $c => {} 581 | e => panic!("Unexpected event: {:?}", e) 582 | } 583 | ) 584 | ); 585 | 586 | macro_rules! test_data( 587 | ($d:expr) => ({ 588 | static DATA: &'static str = $d; 589 | let r = BufReader::new(DATA.as_bytes()); 590 | let p = new_parser(); 591 | (r, p) 592 | }) 593 | ); 594 | 595 | #[test] 596 | fn issue_3_semicolon_in_attribute_value() { 597 | let (mut r, mut p) = test_data!( 598 | r#" 599 | 600 | "# 601 | ); 602 | 603 | expect_event!(r, p, Ok(XmlEvent::StartDocument { .. })); 604 | expect_event!(r, p, Ok(XmlEvent::StartElement { ref name, ref attributes, ref namespace }) => 605 | *name == OwnedName::local("a") && 606 | attributes.len() == 1 && 607 | attributes[0] == OwnedAttribute::new(OwnedName::local("attr"), "zzz;zzz") && 608 | namespace.is_essentially_empty() 609 | ); 610 | expect_event!(r, p, Ok(XmlEvent::EndElement { ref name }) => *name == OwnedName::local("a")); 611 | expect_event!(r, p, Ok(XmlEvent::EndDocument)); 612 | } 613 | 614 | #[test] 615 | fn issue_140_entity_reference_inside_tag() { 616 | let (mut r, mut p) = test_data!( 617 | r#" 618 | 619 | "# 620 | ); 621 | 622 | expect_event!(r, p, Ok(XmlEvent::StartDocument { .. })); 623 | expect_event!(r, p, Ok(XmlEvent::StartElement { ref name, .. }) => *name == OwnedName::local("bla")); 624 | expect_event!(r, p, Ok(XmlEvent::Characters(ref s)) => s == "\u{266b}"); 625 | expect_event!(r, p, Ok(XmlEvent::EndElement { ref name, .. }) => *name == OwnedName::local("bla")); 626 | expect_event!(r, p, Ok(XmlEvent::EndDocument)); 627 | } 628 | 629 | #[test] 630 | fn opening_tag_in_attribute_value() { 631 | let (mut r, mut p) = test_data!( 632 | r#" 633 | 634 | "# 635 | ); 636 | 637 | expect_event!(r, p, Ok(XmlEvent::StartDocument { .. })); 638 | expect_event!(r, p, Err(ref e) => 639 | e.msg() == "Unexpected token inside attribute value: <" && 640 | e.position() == TextPosition { row: 1, column: 24 } 641 | ); 642 | } 643 | } 644 | --------------------------------------------------------------------------------