use crate::model::{Adf, Prospect};
use crate::{ParseOptions, Result, validate};
use std::borrow::Cow;
use std::cell::OnceCell;
use std::io::Write;
use std::ops::Range;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Span {
pub start: usize,
pub end: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Attribute<'a> {
pub name: Cow<'a, str>,
pub value: Cow<'a, str>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct XmlElement<'a> {
pub name: Cow<'a, str>,
pub attributes: Vec<Attribute<'a>>,
pub children: Vec<XmlNode<'a>>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum XmlNode<'a> {
Element(XmlElement<'a>),
Text(Cow<'a, str>),
CData(Cow<'a, str>),
EntityRef(Cow<'a, str>),
Comment(Cow<'a, str>),
ProcessingInstruction(Cow<'a, str>),
Declaration(Cow<'a, str>),
DocType(Cow<'a, str>),
}
#[derive(Debug, Clone)]
pub struct AdfDocument<'a> {
pub(crate) original: &'a str,
pub(crate) parse_options: ParseOptions,
pub(crate) raw_root: OnceCell<XmlElement<'a>>,
pub(crate) adf: Adf<'a>,
pub(crate) prospect_spans: Vec<Range<usize>>,
pub(crate) dirty_prospects: Vec<bool>,
pub(crate) dirty_all: bool,
}
impl<'a> AdfDocument<'a> {
pub(crate) fn new(
original: &'a str,
parse_options: ParseOptions,
adf: Adf<'a>,
prospect_spans: Vec<Range<usize>>,
) -> Self {
let dirty_prospects = vec![false; prospect_spans.len()];
Self {
original,
parse_options,
raw_root: OnceCell::new(),
adf,
prospect_spans,
dirty_prospects,
dirty_all: false,
}
}
pub fn original(&self) -> &'a str {
self.original
}
pub fn root(&self) -> &XmlElement<'a> {
if self.raw_root.get().is_none() {
tracing::trace!(
input_bytes = self.original.len(),
"parsing lazy raw XML tree"
);
}
self.raw_root.get_or_init(|| {
crate::parse::parse_tree(self.original, &self.parse_options)
.expect("original input was already parsed successfully")
})
}
pub fn adf(&self) -> &Adf<'a> {
&self.adf
}
pub fn adf_mut(&mut self) -> &mut Adf<'a> {
self.dirty_all = true;
tracing::trace!("marked full ADF document dirty");
&mut self.adf
}
pub fn prospect_mut(&mut self, index: usize) -> Option<&mut Prospect<'a>> {
let found = index < self.adf.prospects.len();
if let Some(dirty) = self.dirty_prospects.get_mut(index) {
*dirty = true;
}
tracing::trace!(prospect_index = index, found, "requested mutable prospect");
self.adf.prospects.get_mut(index)
}
pub fn is_dirty(&self) -> bool {
self.dirty_all || self.dirty_prospects.iter().any(|dirty| *dirty)
}
pub fn validate(&self) -> validate::ValidationReport<'a> {
validate::validate(&self.adf)
}
pub fn validate_strict(&self) -> validate::ValidationReport<'a> {
validate::validate_with(&self.adf, validate::ValidationOptions { strict: true })
}
pub fn write_original_preserving<W: Write>(&self, writer: W) -> Result<()> {
let span = tracing::debug_span!(
"adf.write.original_preserving",
input_bytes = self.original.len(),
dirty_all = self.dirty_all
);
let _span_guard = span.enter();
match crate::write::write_original_preserving(writer, self) {
Ok(()) => {
if tracing::enabled!(tracing::Level::DEBUG) {
let stats = crate::trace::DocumentStats::from_adf(&self.adf);
let dirty_prospects = crate::trace::dirty_prospect_count(&self.dirty_prospects);
tracing::debug!(
prospects = stats.prospects,
vehicles = stats.vehicles,
contacts = stats.contacts,
addresses = stats.addresses,
extensions = stats.extensions,
dirty_prospects,
mode = self.write_preservation_mode(),
"ADF write complete"
);
}
Ok(())
}
Err(error) => {
crate::trace::record_error("write_original_preserving", &error);
Err(error)
}
}
}
pub fn write_typed<W: Write>(&self, writer: W) -> Result<()> {
let span = tracing::debug_span!("adf.write.typed");
let _span_guard = span.enter();
match crate::write::write_adf(writer, &self.adf) {
Ok(()) => {
if tracing::enabled!(tracing::Level::DEBUG) {
let stats = crate::trace::DocumentStats::from_adf(&self.adf);
tracing::debug!(
prospects = stats.prospects,
vehicles = stats.vehicles,
contacts = stats.contacts,
addresses = stats.addresses,
extensions = stats.extensions,
"ADF write complete"
);
}
Ok(())
}
Err(error) => {
crate::trace::record_error("write_typed", &error);
Err(error)
}
}
}
pub fn to_original_preserving_string(&self) -> Result<String> {
let mut bytes = Vec::new();
self.write_original_preserving(&mut bytes)?;
tracing::trace!(
output_bytes = bytes.len(),
"original-preserving string created"
);
Ok(String::from_utf8(bytes).expect("ADF writer only emits UTF-8"))
}
pub fn to_typed_string(&self) -> Result<String> {
let mut bytes = Vec::new();
self.write_typed(&mut bytes)?;
tracing::trace!(output_bytes = bytes.len(), "typed string created");
Ok(String::from_utf8(bytes).expect("ADF writer only emits UTF-8"))
}
fn write_preservation_mode(&self) -> &'static str {
if self.dirty_all {
"typed"
} else if self.dirty_prospects.iter().any(|dirty| *dirty) {
"localized"
} else {
"copy"
}
}
}