diff options
Diffstat (limited to 'third_party/rust/xml-rs/src/writer')
-rw-r--r-- | third_party/rust/xml-rs/src/writer/config.rs | 157 | ||||
-rw-r--r-- | third_party/rust/xml-rs/src/writer/emitter.rs | 447 | ||||
-rw-r--r-- | third_party/rust/xml-rs/src/writer/events.rs | 241 | ||||
-rw-r--r-- | third_party/rust/xml-rs/src/writer/mod.rs | 93 |
4 files changed, 938 insertions, 0 deletions
diff --git a/third_party/rust/xml-rs/src/writer/config.rs b/third_party/rust/xml-rs/src/writer/config.rs new file mode 100644 index 0000000000..ebabf181f0 --- /dev/null +++ b/third_party/rust/xml-rs/src/writer/config.rs @@ -0,0 +1,157 @@ +//! Contains emitter configuration structure. + +use std::io::Write; +use std::borrow::Cow; + +use writer::EventWriter; + +/// Emitter configuration structure. +/// +/// This structure contains various options which control XML document emitter behavior. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct EmitterConfig { + /// Line separator used to separate lines in formatted output. Default is `"\n"`. + pub line_separator: Cow<'static, str>, + + /// A string which will be used for a single level of indentation. Default is `" "` + /// (two spaces). + pub indent_string: Cow<'static, str>, + + /// Whether or not the emitted document should be indented. Default is false. + /// + /// The emitter is capable to perform automatic indentation of the emitted XML document. + /// It is done in stream-like fashion and does not require the knowledge of the whole + /// document in advance. + /// + /// Sometimes, however, automatic indentation is undesirable, e.g. when you want to keep + /// existing layout when processing an existing XML document. Also the indentiation algorithm + /// is not thoroughly tested. Hence by default it is disabled. + pub perform_indent: bool, + + /// Whether or not characters in output events will be escaped. Default is true. + /// + /// The emitter can automatically escape characters which can't appear in PCDATA sections + /// or element attributes of an XML document, like `<` or `"` (in attributes). This may + /// introduce some overhead because then every corresponding piece of character data + /// should be scanned for invalid characters. + /// + /// If this option is disabled, the XML writer may produce non-well-formed documents, so + /// use `false` value for this option with care. + pub perform_escaping: bool, + + /// Whether or not to write XML document declaration at the beginning of a document. + /// Default is true. + /// + /// This option controls whether the document declaration should be emitted automatically + /// before a root element is written if it was not emitted explicitly by the user. + pub write_document_declaration: bool, + + /// Whether or not to convert elements with empty content to empty elements. Default is true. + /// + /// This option allows turning elements like `<a></a>` (an element with empty content) + /// into `<a />` (an empty element). + pub normalize_empty_elements: bool, + + /// Whether or not to emit CDATA events as plain characters. Default is false. + /// + /// This option forces the emitter to convert CDATA events into regular character events, + /// performing all the necessary escaping beforehand. This may be occasionally useful + /// for feeding the document into incorrect parsers which do not support CDATA. + pub cdata_to_characters: bool, + + /// Whether or not to keep element names to support `EndElement` events without explicit names. + /// Default is true. + /// + /// This option makes the emitter to keep names of written elements in order to allow + /// omitting names when writing closing element tags. This could incur some memory overhead. + pub keep_element_names_stack: bool, + + /// Whether or not to automatically insert leading and trailing spaces in emitted comments, + /// if necessary. Default is true. + /// + /// This is a convenience option in order for the user not to append spaces before and after + /// comments text in order to get more pretty comments: `<!-- something -->` instead of + /// `<!--something-->`. + pub autopad_comments: bool, + + /// Whether or not to automatically insert spaces before the trailing `/>` in self-closing + /// elements. Default is true. + /// + /// This option is only meaningful if `normalize_empty_elements` is true. For example, the + /// element `<a></a>` would be unaffected. When `normalize_empty_elements` is true, then when + /// this option is also true, the same element would appear `<a />`. If this option is false, + /// then the same element would appear `<a/>`. + pub pad_self_closing: bool, +} + +impl EmitterConfig { + /// Creates an emitter configuration with default values. + /// + /// You can tweak default options with builder-like pattern: + /// + /// ```rust + /// use xml::writer::EmitterConfig; + /// + /// let config = EmitterConfig::new() + /// .line_separator("\r\n") + /// .perform_indent(true) + /// .normalize_empty_elements(false); + /// ``` + #[inline] + pub fn new() -> EmitterConfig { + EmitterConfig { + line_separator: "\n".into(), + indent_string: " ".into(), // two spaces + perform_indent: false, + perform_escaping: true, + write_document_declaration: true, + normalize_empty_elements: true, + cdata_to_characters: false, + keep_element_names_stack: true, + autopad_comments: true, + pad_self_closing: true + } + } + + /// Creates an XML writer with this configuration. + /// + /// This is a convenience method for configuring and creating a writer at the same time: + /// + /// ```rust + /// use xml::writer::EmitterConfig; + /// + /// let mut target: Vec<u8> = Vec::new(); + /// + /// let writer = EmitterConfig::new() + /// .line_separator("\r\n") + /// .perform_indent(true) + /// .normalize_empty_elements(false) + /// .create_writer(&mut target); + /// ``` + /// + /// This method is exactly equivalent to calling `EventWriter::new_with_config()` with + /// this configuration object. + #[inline] + pub fn create_writer<W: Write>(self, sink: W) -> EventWriter<W> { + EventWriter::new_with_config(sink, self) + } +} + +impl Default for EmitterConfig { + #[inline] + fn default() -> EmitterConfig { + EmitterConfig::new() + } +} + +gen_setters!(EmitterConfig, + line_separator: into Cow<'static, str>, + indent_string: into Cow<'static, str>, + perform_indent: val bool, + write_document_declaration: val bool, + normalize_empty_elements: val bool, + cdata_to_characters: val bool, + keep_element_names_stack: val bool, + autopad_comments: val bool, + pad_self_closing: val bool +); diff --git a/third_party/rust/xml-rs/src/writer/emitter.rs b/third_party/rust/xml-rs/src/writer/emitter.rs new file mode 100644 index 0000000000..ba80f66781 --- /dev/null +++ b/third_party/rust/xml-rs/src/writer/emitter.rs @@ -0,0 +1,447 @@ +use std::io; +use std::io::prelude::*; +use std::fmt; +use std::result; +use std::borrow::Cow; +use std::error::Error; + +use common; +use name::{Name, OwnedName}; +use attribute::Attribute; +use escape::{escape_str_attribute, escape_str_pcdata}; +use common::XmlVersion; +use namespace::{NamespaceStack, NS_NO_PREFIX, NS_EMPTY_URI, NS_XMLNS_PREFIX, NS_XML_PREFIX}; + +use writer::config::EmitterConfig; + +/// An error which may be returned by `XmlWriter` when writing XML events. +#[derive(Debug)] +pub enum EmitterError { + /// An I/O error occured in the underlying `Write` instance. + Io(io::Error), + + /// Document declaration has already been written to the output stream. + DocumentStartAlreadyEmitted, + + /// The name of the last opening element is not available. + LastElementNameNotAvailable, + + /// The name of the last opening element is not equal to the name of the provided + /// closing element. + EndElementNameIsNotEqualToLastStartElementName, + + /// End element name is not specified when it is needed, for example, when automatic + /// closing is not enabled in configuration. + EndElementNameIsNotSpecified +} + +impl From<io::Error> for EmitterError { + fn from(err: io::Error) -> EmitterError { + EmitterError::Io(err) + } +} + +impl fmt::Display for EmitterError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + + write!(f, "emitter error: ")?; + match *self { + EmitterError::Io(ref e) => + write!(f, "I/O error: {}", e), + ref other => + write!(f, "{}", other.description()), + } + } +} + +impl Error for EmitterError { + fn description(&self) -> &str { + match *self { + EmitterError::Io(_) => + "I/O error", + EmitterError::DocumentStartAlreadyEmitted => + "document start event has already been emitted", + EmitterError::LastElementNameNotAvailable => + "last element name is not available", + EmitterError::EndElementNameIsNotEqualToLastStartElementName => + "end element name is not equal to last start element name", + EmitterError::EndElementNameIsNotSpecified => + "end element name is not specified and can't be inferred", + } + } +} + +/// A result type yielded by `XmlWriter`. +pub type Result<T> = result::Result<T, EmitterError>; + +// TODO: split into a low-level fast writer without any checks and formatting logic and a +// high-level indenting validating writer +pub struct Emitter { + config: EmitterConfig, + + nst: NamespaceStack, + + indent_level: usize, + indent_stack: Vec<IndentFlags>, + + element_names: Vec<OwnedName>, + + start_document_emitted: bool, + just_wrote_start_element: bool +} + +impl Emitter { + pub fn new(config: EmitterConfig) -> Emitter { + Emitter { + config, + + nst: NamespaceStack::empty(), + + indent_level: 0, + indent_stack: vec![IndentFlags::WroteNothing], + + element_names: Vec::new(), + + start_document_emitted: false, + just_wrote_start_element: false + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +enum IndentFlags { + WroteNothing, + WroteMarkup, + WroteText, +} + +impl Emitter { + /// Returns the current state of namespaces. + #[inline] + pub fn namespace_stack_mut(&mut self) -> &mut NamespaceStack { + &mut self.nst + } + + #[inline] + fn wrote_text(&self) -> bool { + *self.indent_stack.last().unwrap() == IndentFlags::WroteText + } + + #[inline] + fn wrote_markup(&self) -> bool { + *self.indent_stack.last().unwrap() == IndentFlags::WroteMarkup + } + + #[inline] + fn set_wrote_text(&mut self) { + *self.indent_stack.last_mut().unwrap() = IndentFlags::WroteText; + } + + #[inline] + fn set_wrote_markup(&mut self) { + *self.indent_stack.last_mut().unwrap() = IndentFlags::WroteMarkup; + } + + #[inline] + fn reset_state(&mut self) { + *self.indent_stack.last_mut().unwrap() = IndentFlags::WroteNothing; + } + + fn write_newline<W: Write>(&mut self, target: &mut W, level: usize) -> Result<()> { + target.write_all(self.config.line_separator.as_bytes())?; + for _ in 0..level { + target.write_all(self.config.indent_string.as_bytes())?; + } + Ok(()) + } + + fn before_markup<W: Write>(&mut self, target: &mut W) -> Result<()> { + if self.config.perform_indent && !self.wrote_text() && + (self.indent_level > 0 || self.wrote_markup()) { + let indent_level = self.indent_level; + self.write_newline(target, indent_level)?; + if self.indent_level > 0 && self.config.indent_string.len() > 0 { + self.after_markup(); + } + } + Ok(()) + } + + fn after_markup(&mut self) { + self.set_wrote_markup(); + } + + fn before_start_element<W: Write>(&mut self, target: &mut W) -> Result<()> { + self.before_markup(target)?; + self.indent_stack.push(IndentFlags::WroteNothing); + Ok(()) + } + + fn after_start_element(&mut self) { + self.after_markup(); + self.indent_level += 1; + } + + fn before_end_element<W: Write>(&mut self, target: &mut W) -> Result<()> { + if self.config.perform_indent && self.indent_level > 0 && self.wrote_markup() && + !self.wrote_text() { + let indent_level = self.indent_level; + self.write_newline(target, indent_level - 1) + } else { + Ok(()) + } + } + + fn after_end_element(&mut self) { + if self.indent_level > 0 { + self.indent_level -= 1; + self.indent_stack.pop(); + } + self.set_wrote_markup(); + } + + fn after_text(&mut self) { + self.set_wrote_text(); + } + + pub fn emit_start_document<W: Write>(&mut self, target: &mut W, + version: XmlVersion, + encoding: &str, + standalone: Option<bool>) -> Result<()> { + if self.start_document_emitted { + return Err(EmitterError::DocumentStartAlreadyEmitted); + } + self.start_document_emitted = true; + + self.before_markup(target)?; + let result = { + let mut write = move || { + write!(target, "<?xml version=\"{}\" encoding=\"{}\"", version, encoding)?; + + if let Some(standalone) = standalone { + write!(target, " standalone=\"{}\"", if standalone { "yes" } else { "no" })?; + } + + write!(target, "?>")?; + + Ok(()) + }; + write() + }; + self.after_markup(); + + result + } + + fn check_document_started<W: Write>(&mut self, target: &mut W) -> Result<()> { + if !self.start_document_emitted && self.config.write_document_declaration { + self.emit_start_document(target, common::XmlVersion::Version10, "utf-8", None) + } else { + Ok(()) + } + } + + fn fix_non_empty_element<W: Write>(&mut self, target: &mut W) -> Result<()> { + if self.config.normalize_empty_elements && self.just_wrote_start_element { + self.just_wrote_start_element = false; + target.write_all(b">").map_err(From::from) + } else { + Ok(()) + } + } + + pub fn emit_processing_instruction<W: Write>(&mut self, + target: &mut W, + name: &str, + data: Option<&str>) -> Result<()> { + self.check_document_started(target)?; + self.fix_non_empty_element(target)?; + + self.before_markup(target)?; + + let result = { + let mut write = || { + write!(target, "<?{}", name)?; + + if let Some(data) = data { + write!(target, " {}", data)?; + } + + write!(target, "?>")?; + + Ok(()) + }; + write() + }; + + self.after_markup(); + + result + } + + fn emit_start_element_initial<W>(&mut self, target: &mut W, + name: Name, + attributes: &[Attribute]) -> Result<()> + where W: Write + { + self.check_document_started(target)?; + self.fix_non_empty_element(target)?; + self.before_start_element(target)?; + write!(target, "<{}", name.repr_display())?; + self.emit_current_namespace_attributes(target)?; + self.emit_attributes(target, attributes)?; + self.after_start_element(); + Ok(()) + } + + pub fn emit_start_element<W>(&mut self, target: &mut W, + name: Name, + attributes: &[Attribute]) -> Result<()> + where W: Write + { + if self.config.keep_element_names_stack { + self.element_names.push(name.to_owned()); + } + + self.emit_start_element_initial(target, name, attributes)?; + self.just_wrote_start_element = true; + + if !self.config.normalize_empty_elements { + write!(target, ">")?; + } + + Ok(()) + } + + pub fn emit_current_namespace_attributes<W>(&mut self, target: &mut W) -> Result<()> + where W: Write + { + for (prefix, uri) in self.nst.peek() { + match prefix { + // internal namespaces are not emitted + NS_XMLNS_PREFIX | NS_XML_PREFIX => Ok(()), + //// there is already a namespace binding with this prefix in scope + //prefix if self.nst.get(prefix) == Some(uri) => Ok(()), + // emit xmlns only if it is overridden + NS_NO_PREFIX => if uri != NS_EMPTY_URI { + write!(target, " xmlns=\"{}\"", uri) + } else { Ok(()) }, + // everything else + prefix => write!(target, " xmlns:{}=\"{}\"", prefix, uri) + }?; + } + Ok(()) + } + + pub fn emit_attributes<W: Write>(&mut self, target: &mut W, + attributes: &[Attribute]) -> Result<()> { + for attr in attributes.iter() { + write!( + target, " {}=\"{}\"", + attr.name.repr_display(), + if self.config.perform_escaping { escape_str_attribute(attr.value) } else { Cow::Borrowed(attr.value) } + )? + } + Ok(()) + } + + pub fn emit_end_element<W: Write>(&mut self, target: &mut W, + name: Option<Name>) -> Result<()> { + let owned_name = if self.config.keep_element_names_stack { + Some(self.element_names.pop().ok_or(EmitterError::LastElementNameNotAvailable)?) + } else { + None + }; + + // Check that last started element name equals to the provided name, if there are both + if let Some(ref last_name) = owned_name { + if let Some(ref name) = name { + if last_name.borrow() != *name { + return Err(EmitterError::EndElementNameIsNotEqualToLastStartElementName); + } + } + } + + if let Some(name) = owned_name.as_ref().map(|n| n.borrow()).or(name) { + if self.config.normalize_empty_elements && self.just_wrote_start_element { + self.just_wrote_start_element = false; + let termination = if self.config.pad_self_closing { " />" } else { "/>" }; + let result = target.write_all(termination.as_bytes()).map_err(From::from); + self.after_end_element(); + result + } else { + self.just_wrote_start_element = false; + + self.before_end_element(target)?; + let result = write!(target, "</{}>", name.repr_display()).map_err(From::from); + self.after_end_element(); + + result + } + } else { + Err(EmitterError::EndElementNameIsNotSpecified) + } + } + + pub fn emit_cdata<W: Write>(&mut self, target: &mut W, content: &str) -> Result<()> { + self.fix_non_empty_element(target)?; + if self.config.cdata_to_characters { + self.emit_characters(target, content) + } else { + // TODO: escape ']]>' characters in CDATA as two adjacent CDATA blocks + target.write_all(b"<![CDATA[")?; + target.write_all(content.as_bytes())?; + target.write_all(b"]]>")?; + + self.after_text(); + + Ok(()) + } + } + + pub fn emit_characters<W: Write>(&mut self, target: &mut W, + content: &str) -> Result<()> { + self.check_document_started(target)?; + self.fix_non_empty_element(target)?; + target.write_all( + (if self.config.perform_escaping { + escape_str_pcdata(content) + } else { + Cow::Borrowed(content) + }).as_bytes() + )?; + self.after_text(); + Ok(()) + } + + pub fn emit_comment<W: Write>(&mut self, target: &mut W, content: &str) -> Result<()> { + self.fix_non_empty_element(target)?; + + // TODO: add escaping dashes at the end of the comment + + let autopad_comments = self.config.autopad_comments; + let write = |target: &mut W| -> Result<()> { + target.write_all(b"<!--")?; + + if autopad_comments && !content.starts_with(char::is_whitespace) { + target.write_all(b" ")?; + } + + target.write_all(content.as_bytes())?; + + if autopad_comments && !content.ends_with(char::is_whitespace) { + target.write_all(b" ")?; + } + + target.write_all(b"-->")?; + + Ok(()) + }; + + self.before_markup(target)?; + let result = write(target); + self.after_markup(); + + result + } +} diff --git a/third_party/rust/xml-rs/src/writer/events.rs b/third_party/rust/xml-rs/src/writer/events.rs new file mode 100644 index 0000000000..1f7040f66a --- /dev/null +++ b/third_party/rust/xml-rs/src/writer/events.rs @@ -0,0 +1,241 @@ +//! Contains `XmlEvent` datatype, instances of which are consumed by the writer. + +use std::borrow::Cow; + +use name::Name; +use attribute::Attribute; +use common::XmlVersion; +use namespace::{Namespace, NS_NO_PREFIX}; + +/// A part of an XML output stream. +/// +/// Objects of this enum are consumed by `EventWriter`. They correspond to different parts of +/// an XML document. +#[derive(Debug)] +pub enum XmlEvent<'a> { + /// Corresponds to XML document declaration. + /// + /// This event should always be written before any other event. If it is not written + /// at all, a default XML declaration will be outputted if the corresponding option + /// is set in the configuration. Otherwise an error will be returned. + StartDocument { + /// XML version. + /// + /// Defaults to `XmlVersion::Version10`. + version: XmlVersion, + + /// XML document encoding. + /// + /// Defaults to `Some("UTF-8")`. + encoding: Option<&'a str>, + + /// XML standalone declaration. + /// + /// Defaults to `None`. + standalone: Option<bool> + }, + + /// Denotes an XML processing instruction. + ProcessingInstruction { + /// Processing instruction target. + name: &'a str, + + /// Processing instruction content. + data: Option<&'a str> + }, + + /// Denotes a beginning of an XML element. + StartElement { + /// Qualified name of the element. + name: Name<'a>, + + /// A list of attributes associated with the element. + /// + /// Currently attributes are not checked for duplicates (TODO). Attribute values + /// will be escaped, and all characters invalid for attribute values like `"` or `<` + /// will be changed into character entities. + attributes: Cow<'a, [Attribute<'a>]>, + + /// Contents of the namespace mapping at this point of the document. + /// + /// This mapping will be inspected for "new" entries, and if at this point of the document + /// a particular pair of prefix and namespace URI is already defined, no namespace + /// attributes will be emitted. + namespace: Cow<'a, Namespace>, + }, + + /// Denotes an end of an XML element. + EndElement { + /// Optional qualified name of the element. + /// + /// If `None`, then it is assumed that the element name should be the last valid one. + /// If `Some` and element names tracking is enabled, then the writer will check it for + /// correctness. + name: Option<Name<'a>> + }, + + /// Denotes CDATA content. + /// + /// This event contains unparsed data, and no escaping will be performed when writing it + /// to the output stream. + CData(&'a str), + + /// Denotes a comment. + /// + /// The string will be checked for invalid sequences and error will be returned by the + /// write operation + Comment(&'a str), + + /// Denotes character data outside of tags. + /// + /// Contents of this event will be escaped if `perform_escaping` option is enabled, + /// that is, every character invalid for PCDATA will appear as a character entity. + Characters(&'a str) +} + +impl<'a> XmlEvent<'a> { + /// Returns an writer event for a processing instruction. + #[inline] + pub fn processing_instruction(name: &'a str, data: Option<&'a str>) -> XmlEvent<'a> { + XmlEvent::ProcessingInstruction { name: name, data: data } + } + + /// Returns a builder for a starting element. + /// + /// This builder can then be used to tweak attributes and namespace starting at + /// this element. + #[inline] + pub fn start_element<S>(name: S) -> StartElementBuilder<'a> where S: Into<Name<'a>> { + StartElementBuilder { + name: name.into(), + attributes: Vec::new(), + namespace: Namespace::empty().into() + } + } + + /// Returns a builder for an closing element. + /// + /// This method, unline `start_element()`, does not accept a name because by default + /// the writer is able to determine it automatically. However, when this functionality + /// is disabled, it is possible to specify the name with `name()` method on the builder. + #[inline] + pub fn end_element() -> EndElementBuilder<'a> { + EndElementBuilder { name: None } + } + + /// Returns a CDATA event. + /// + /// Naturally, the provided string won't be escaped, except for closing CDATA token `]]>` + /// (depending on the configuration). + #[inline] + pub fn cdata(data: &'a str) -> XmlEvent<'a> { XmlEvent::CData(data) } + + /// Returns a regular characters (PCDATA) event. + /// + /// All offending symbols, in particular, `&` and `<`, will be escaped by the writer. + #[inline] + pub fn characters(data: &'a str) -> XmlEvent<'a> { XmlEvent::Characters(data) } + + /// Returns a comment event. + #[inline] + pub fn comment(data: &'a str) -> XmlEvent<'a> { XmlEvent::Comment(data) } +} + +impl<'a> From<&'a str> for XmlEvent<'a> { + #[inline] + fn from(s: &'a str) -> XmlEvent<'a> { XmlEvent::Characters(s) } +} + +pub struct EndElementBuilder<'a> { + name: Option<Name<'a>> +} + +/// A builder for a closing element event. +impl<'a> EndElementBuilder<'a> { + /// Sets the name of this closing element. + /// + /// Usually the writer is able to determine closing element names automatically. If + /// this functionality is enabled (by default it is), then this name is checked for correctness. + /// It is possible, however, to disable such behavior; then the user must ensure that + /// closing element name is correct manually. + #[inline] + pub fn name<N>(mut self, name: N) -> EndElementBuilder<'a> where N: Into<Name<'a>> { + self.name = Some(name.into()); + self + } +} + +impl<'a> From<EndElementBuilder<'a>> for XmlEvent<'a> { + fn from(b: EndElementBuilder<'a>) -> XmlEvent<'a> { + XmlEvent::EndElement { name: b.name } + } +} + +/// A builder for a starting element event. +pub struct StartElementBuilder<'a> { + name: Name<'a>, + attributes: Vec<Attribute<'a>>, + namespace: Namespace +} + +impl<'a> StartElementBuilder<'a> { + /// Sets an attribute value of this element to the given string. + /// + /// This method can be used to add attributes to the starting element. Name is a qualified + /// name; its namespace is ignored, but its prefix is checked for correctness, that is, + /// it is checked that the prefix is bound to some namespace in the current context. + /// + /// Currently attributes are not checked for duplicates. Note that duplicate attributes + /// are a violation of XML document well-formedness. + /// + /// The writer checks that you don't specify reserved prefix names, for example `xmlns`. + #[inline] + pub fn attr<N>(mut self, name: N, value: &'a str) -> StartElementBuilder<'a> + where N: Into<Name<'a>> + { + self.attributes.push(Attribute::new(name.into(), value)); + self + } + + /// Adds a namespace to the current namespace context. + /// + /// If no namespace URI was bound to the provided prefix at this point of the document, + /// then the mapping from the prefix to the provided namespace URI will be written as + /// a part of this element attribute set. + /// + /// If the same namespace URI was bound to the provided prefix at this point of the document, + /// then no namespace attributes will be emitted. + /// + /// If some other namespace URI was bound to the provided prefix at this point of the document, + /// then another binding will be added as a part of this element attribute set, shadowing + /// the outer binding. + #[inline] + pub fn ns<S1, S2>(mut self, prefix: S1, uri: S2) -> StartElementBuilder<'a> + where S1: Into<String>, S2: Into<String> + { + self.namespace.put(prefix, uri); + self + } + + /// Adds a default namespace mapping to the current namespace context. + /// + /// Same rules as for `ns()` are also valid for the default namespace mapping. + #[inline] + pub fn default_ns<S>(mut self, uri: S) -> StartElementBuilder<'a> + where S: Into<String> + { + self.namespace.put(NS_NO_PREFIX, uri); + self + } +} + +impl<'a> From<StartElementBuilder<'a>> for XmlEvent<'a> { + #[inline] + fn from(b: StartElementBuilder<'a>) -> XmlEvent<'a> { + XmlEvent::StartElement { + name: b.name, + attributes: Cow::Owned(b.attributes), + namespace: Cow::Owned(b.namespace) + } + } +} diff --git a/third_party/rust/xml-rs/src/writer/mod.rs b/third_party/rust/xml-rs/src/writer/mod.rs new file mode 100644 index 0000000000..ea1b24266f --- /dev/null +++ b/third_party/rust/xml-rs/src/writer/mod.rs @@ -0,0 +1,93 @@ +//! Contains high-level interface for an events-based XML emitter. +//! +//! The most important type in this module is `EventWriter` which allows writing an XML document +//! to some output stream. + +pub use self::emitter::Result; +pub use self::emitter::EmitterError as Error; +pub use self::config::EmitterConfig; +pub use self::events::XmlEvent; + +use self::emitter::Emitter; + +use std::io::prelude::*; + +mod emitter; +mod config; +pub mod events; + +/// A wrapper around an `std::io::Write` instance which emits XML document according to provided +/// events. +pub struct EventWriter<W> { + sink: W, + emitter: Emitter +} + +impl<W: Write> EventWriter<W> { + /// Creates a new `EventWriter` out of an `std::io::Write` instance using the default + /// configuration. + #[inline] + pub fn new(sink: W) -> EventWriter<W> { + EventWriter::new_with_config(sink, EmitterConfig::new()) + } + + /// Creates a new `EventWriter` out of an `std::io::Write` instance using the provided + /// configuration. + #[inline] + pub fn new_with_config(sink: W, config: EmitterConfig) -> EventWriter<W> { + EventWriter { + sink, + emitter: Emitter::new(config) + } + } + + /// Writes the next piece of XML document according to the provided event. + /// + /// Note that output data may not exactly correspond to the written event because + /// of various configuration options. For example, `XmlEvent::EndElement` may + /// correspond to a separate closing element or it may cause writing an empty element. + /// Another example is that `XmlEvent::CData` may be represented as characters in + /// the output stream. + pub fn write<'a, E>(&mut self, event: E) -> Result<()> where E: Into<XmlEvent<'a>> { + match event.into() { + XmlEvent::StartDocument { version, encoding, standalone } => + self.emitter.emit_start_document(&mut self.sink, version, encoding.unwrap_or("UTF-8"), standalone), + XmlEvent::ProcessingInstruction { name, data } => + self.emitter.emit_processing_instruction(&mut self.sink, name, data), + XmlEvent::StartElement { name, attributes, namespace } => { + self.emitter.namespace_stack_mut().push_empty().checked_target().extend(namespace.as_ref()); + self.emitter.emit_start_element(&mut self.sink, name, &attributes) + } + XmlEvent::EndElement { name } => { + let r = self.emitter.emit_end_element(&mut self.sink, name); + self.emitter.namespace_stack_mut().try_pop(); + r + } + XmlEvent::Comment(content) => + self.emitter.emit_comment(&mut self.sink, content), + XmlEvent::CData(content) => + self.emitter.emit_cdata(&mut self.sink, content), + XmlEvent::Characters(content) => + self.emitter.emit_characters(&mut self.sink, content) + } + } + + /// Returns a mutable reference to the underlying `Writer`. + /// + /// Note that having a reference to the underlying sink makes it very easy to emit invalid XML + /// documents. Use this method with care. Valid use cases for this method include accessing + /// methods like `Write::flush`, which do not emit new data but rather change the state + /// of the stream itself. + pub fn inner_mut(&mut self) -> &mut W { + &mut self.sink + } + + /// Unwraps this `EventWriter`, returning the underlying writer. + /// + /// Note that this is a destructive operation: unwrapping a writer and then wrapping + /// it again with `EventWriter::new()` will create a fresh writer whose state will be + /// blank; for example, accumulated namespaces will be reset. + pub fn into_inner(self) -> W { + self.sink + } +} |