use std::{borrow::Cow, error::Error, fmt, io::Cursor, sync::Arc};
use quick_xml::{
events::{
attributes::Attribute as XmlAttribute, BytesCData, BytesDecl, BytesEnd, BytesPI,
BytesStart, BytesText, Event,
},
name::QName,
Writer,
};
use crate::parser::{parse_document, ParseError, ParseOptions};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Span {
pub(crate) start: u64,
pub(crate) end: u64,
}
impl Span {
pub(crate) const fn new(start: u64, end: u64) -> Self {
Self { start, end }
}
pub(crate) const fn union(self, other: Self) -> Self {
Self {
start: if self.start < other.start {
self.start
} else {
other.start
},
end: if self.end > other.end {
self.end
} else {
other.end
},
}
}
fn slice(self, source: &str) -> Option<&str> {
let start = usize::try_from(self.start).ok()?;
let end = usize::try_from(self.end).ok()?;
source.get(start..end)
}
}
#[derive(Debug, Clone, Default, Eq)]
pub(crate) struct NodeList {
nodes: Vec<Node>,
spans: Vec<Option<Span>>,
}
impl PartialEq for NodeList {
fn eq(&self, other: &Self) -> bool {
self.nodes == other.nodes
}
}
impl NodeList {
pub(crate) const fn new() -> Self {
Self {
nodes: Vec::new(),
spans: Vec::new(),
}
}
pub(crate) fn nodes(&self) -> &[Node] {
&self.nodes
}
fn spans(&self) -> &[Option<Span>] {
&self.spans
}
fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
pub(crate) fn push(&mut self, node: Node, span: Option<Span>) {
if let Node::Text(value) = node {
if let Some(Node::Text(existing)) = self.nodes.last_mut() {
existing.push_str(&value);
let last = self.spans.len() - 1;
self.spans[last] = match (self.spans[last], span) {
(Some(previous), Some(next)) => Some(previous.union(next)),
_ => None,
};
return;
}
self.nodes.push(Node::Text(value));
} else {
self.nodes.push(node);
}
self.spans.push(span);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct XmlDeclaration {
pub version: String,
pub encoding: Option<String>,
pub standalone: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Attribute {
qname: String,
prefix: Option<String>,
local_name: String,
namespace_uri: Option<Arc<str>>,
value: String,
}
impl Attribute {
pub(crate) fn parsed(
qname: String,
prefix: Option<String>,
local_name: String,
namespace_uri: Option<Arc<str>>,
value: String,
) -> Self {
Self {
qname,
prefix,
local_name,
namespace_uri,
value,
}
}
#[must_use]
pub fn qname(&self) -> &str {
&self.qname
}
#[must_use]
pub fn prefix(&self) -> Option<&str> {
self.prefix.as_deref()
}
#[must_use]
pub fn local_name(&self) -> &str {
&self.local_name
}
#[must_use]
pub fn namespace_uri(&self) -> Option<&str> {
self.namespace_uri.as_deref()
}
#[must_use]
pub fn value(&self) -> &str {
&self.value
}
}
#[derive(Debug, Clone, Eq)]
pub struct Element {
qname: String,
prefix: Option<String>,
local_name: String,
namespace_uri: Option<Arc<str>>,
attributes: Vec<Attribute>,
nodes: NodeList,
empty_style: bool,
open_span: Option<Span>,
close_span: Option<Span>,
}
impl PartialEq for Element {
fn eq(&self, other: &Self) -> bool {
self.qname == other.qname
&& self.prefix == other.prefix
&& self.local_name == other.local_name
&& self.namespace_uri == other.namespace_uri
&& self.attributes == other.attributes
&& self.nodes == other.nodes
}
}
impl Element {
pub(crate) fn parsed(
qname: String,
prefix: Option<String>,
local_name: String,
namespace_uri: Option<Arc<str>>,
attributes: Vec<Attribute>,
empty_style: bool,
open_span: Option<Span>,
) -> Self {
Self {
qname,
prefix,
local_name,
namespace_uri,
attributes,
nodes: NodeList::new(),
empty_style,
open_span,
close_span: None,
}
}
pub(crate) fn set_close_span(&mut self, span: Option<Span>) {
self.close_span = span;
}
pub(crate) fn full_span(&self) -> Option<Span> {
let open = self.open_span?;
match self.close_span {
Some(close) => Some(open.union(close)),
None if self.empty_style => Some(open),
None => None,
}
}
#[must_use]
pub fn qname(&self) -> &str {
&self.qname
}
#[must_use]
pub fn prefix(&self) -> Option<&str> {
self.prefix.as_deref()
}
#[must_use]
pub fn local_name(&self) -> &str {
&self.local_name
}
#[must_use]
pub fn namespace_uri(&self) -> Option<&str> {
self.namespace_uri.as_deref()
}
#[must_use]
pub fn attributes(&self) -> &[Attribute] {
&self.attributes
}
#[must_use]
pub fn nodes(&self) -> &[Node] {
self.nodes.nodes()
}
#[must_use]
pub const fn was_empty_element(&self) -> bool {
self.empty_style
}
pub fn children(&self) -> impl Iterator<Item = &Element> {
self.nodes.nodes().iter().filter_map(Node::as_element)
}
#[must_use]
pub fn attribute_ns(&self, namespace_uri: Option<&str>, local_name: &str) -> Option<&str> {
self.attributes
.iter()
.find(|attribute| {
attribute.namespace_uri() == namespace_uri && attribute.local_name() == local_name
})
.map(Attribute::value)
}
#[must_use]
pub fn direct_text(&self) -> String {
let mut result = String::new();
for node in self.nodes.nodes() {
match node {
Node::Text(value) | Node::CData(value) => result.push_str(value),
_ => {}
}
}
result
}
pub(crate) fn push(&mut self, node: Node, span: Option<Span>) {
self.nodes.push(node, span);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Node {
Element(Element),
Text(String),
CData(String),
Comment(String),
ProcessingInstruction(String),
}
impl Node {
#[must_use]
pub fn as_element(&self) -> Option<&Element> {
match self {
Self::Element(element) => Some(element),
_ => None,
}
}
}
#[derive(Debug, Clone, Eq)]
pub struct Document {
declaration: Option<XmlDeclaration>,
prolog: NodeList,
root: Element,
epilog: NodeList,
source: Option<Arc<str>>,
declaration_span: Option<Span>,
}
impl PartialEq for Document {
fn eq(&self, other: &Self) -> bool {
self.declaration == other.declaration
&& self.prolog == other.prolog
&& self.root == other.root
&& self.epilog == other.epilog
}
}
impl Document {
pub fn parse(xml: &str) -> Result<Self, ParseError> {
Self::parse_with_options(xml, ParseOptions::default())
}
pub fn parse_with_options(xml: &str, options: ParseOptions) -> Result<Self, ParseError> {
parse_document(xml, options)
}
pub(crate) fn parsed(
declaration: Option<XmlDeclaration>,
prolog: NodeList,
root: Element,
epilog: NodeList,
source: Option<Arc<str>>,
declaration_span: Option<Span>,
) -> Self {
Self {
declaration,
prolog,
root,
epilog,
source,
declaration_span,
}
}
#[must_use]
pub const fn declaration(&self) -> Option<&XmlDeclaration> {
self.declaration.as_ref()
}
#[must_use]
pub fn prolog(&self) -> &[Node] {
self.prolog.nodes()
}
#[must_use]
pub const fn root(&self) -> &Element {
&self.root
}
#[must_use]
pub fn epilog(&self) -> &[Node] {
self.epilog.nodes()
}
#[must_use]
pub fn source(&self) -> Option<&str> {
self.source.as_deref()
}
#[must_use]
pub fn without_source(mut self) -> Self {
self.source = None;
self
}
pub fn to_xml_string(&self) -> Result<String, WriteError> {
let mut writer = Writer::new(Cursor::new(Vec::new()));
if let Some(declaration) = &self.declaration {
writer.write_event(Event::Decl(BytesDecl::new(
&declaration.version,
declaration.encoding.as_deref(),
declaration.standalone.as_deref(),
)))?;
}
for node in self.prolog.nodes() {
write_node(&mut writer, node)?;
}
write_element(&mut writer, &self.root)?;
for node in self.epilog.nodes() {
write_node(&mut writer, node)?;
}
String::from_utf8(writer.into_inner().into_inner()).map_err(WriteError::Utf8)
}
pub fn to_xml_string_exact(&self) -> Result<String, ExactWriteError> {
let source = self.source.as_deref().ok_or(ExactWriteError::NoSource)?;
let mut output = String::with_capacity(source.len());
if self.declaration.is_some() {
push_span(&mut output, source, self.declaration_span)?;
}
push_nodes(&mut output, source, &self.prolog)?;
push_element(&mut output, source, &self.root)?;
push_nodes(&mut output, source, &self.epilog)?;
Ok(output)
}
pub(crate) fn take_root(self) -> Element {
self.root
}
}
fn push_span(output: &mut String, source: &str, span: Option<Span>) -> Result<(), ExactWriteError> {
let span = span.ok_or(ExactWriteError::MissingSpan)?;
output.push_str(span.slice(source).ok_or(ExactWriteError::SpanOutOfRange)?);
Ok(())
}
fn push_nodes(output: &mut String, source: &str, list: &NodeList) -> Result<(), ExactWriteError> {
for (node, span) in list.nodes().iter().zip(list.spans()) {
match node {
Node::Element(element) => push_element(output, source, element)?,
_ => push_span(output, source, *span)?,
}
}
Ok(())
}
fn push_element(
output: &mut String,
source: &str,
element: &Element,
) -> Result<(), ExactWriteError> {
push_span(output, source, element.open_span)?;
if element.close_span.is_none() {
if element.nodes.is_empty() {
return Ok(());
}
return Err(ExactWriteError::MissingSpan);
}
push_nodes(output, source, &element.nodes)?;
push_span(output, source, element.close_span)
}
fn write_element(
writer: &mut Writer<Cursor<Vec<u8>>>,
element: &Element,
) -> Result<(), WriteError> {
let mut start = BytesStart::new(element.qname());
for attribute in element.attributes() {
start.push_attribute(XmlAttribute {
key: QName(attribute.qname().as_bytes()),
value: Cow::Owned(escape_attribute_value(attribute.value())),
});
}
if element.empty_style && element.nodes.is_empty() {
writer.write_event(Event::Empty(start))?;
return Ok(());
}
writer.write_event(Event::Start(start))?;
for node in element.nodes() {
write_node(writer, node)?;
}
writer.write_event(Event::End(BytesEnd::new(element.qname())))?;
Ok(())
}
fn escape_attribute_value(value: &str) -> Vec<u8> {
let mut output = String::with_capacity(value.len());
for character in value.chars() {
match character {
'&' => output.push_str("&"),
'<' => output.push_str("<"),
'"' => output.push_str("""),
'\t' => output.push_str("	"),
'\n' => output.push_str("
"),
'\r' => output.push_str("
"),
_ => output.push(character),
}
}
output.into_bytes()
}
fn escape_text_value(value: &str) -> String {
let mut output = String::with_capacity(value.len());
for character in value.chars() {
match character {
'&' => output.push_str("&"),
'<' => output.push_str("<"),
'>' => output.push_str(">"),
'\r' => output.push_str(" "),
_ => output.push(character),
}
}
output
}
fn write_node(writer: &mut Writer<Cursor<Vec<u8>>>, node: &Node) -> Result<(), WriteError> {
match node {
Node::Element(element) => write_element(writer, element),
Node::Text(value) => writer
.write_event(Event::Text(BytesText::from_escaped(escape_text_value(
value,
))))
.map_err(Into::into),
Node::CData(value) => writer
.write_event(Event::CData(BytesCData::new(value)))
.map_err(Into::into),
Node::Comment(value) => writer
.write_event(Event::Comment(BytesText::from_escaped(value.as_str())))
.map_err(Into::into),
Node::ProcessingInstruction(value) => writer
.write_event(Event::PI(BytesPI::new(value)))
.map_err(Into::into),
}
}
#[derive(Debug)]
pub enum WriteError {
Xml(std::io::Error),
Utf8(std::string::FromUtf8Error),
}
impl fmt::Display for WriteError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Xml(error) => write!(formatter, "could not write XML: {error}"),
Self::Utf8(error) => write!(formatter, "XML writer produced invalid UTF-8: {error}"),
}
}
}
impl Error for WriteError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::Xml(error) => Some(error),
Self::Utf8(error) => Some(error),
}
}
}
impl From<std::io::Error> for WriteError {
fn from(value: std::io::Error) -> Self {
Self::Xml(value)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExactWriteError {
NoSource,
MissingSpan,
SpanOutOfRange,
}
impl fmt::Display for ExactWriteError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let message = match self {
Self::NoSource => "document does not retain its parse input",
Self::MissingSpan => "a retained node has no recorded source span",
Self::SpanOutOfRange => "a recorded source span is not a character boundary",
};
formatter.write_str(message)
}
}
impl Error for ExactWriteError {}