Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
538 changes: 343 additions & 195 deletions scripts/patches/xmloxide-0.5.0.patch

Large diffs are not rendered by default.

11 changes: 6 additions & 5 deletions src/c14n/ns_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::xml::dom::storage::Storage as DomStorage;
use super::C14nError;
use super::NodeVisibility;
use super::prefix::has_in_scope_default_namespace;
use super::scratch::{List, NamespaceMap, NamespaceRendering, Scratch};
use super::scratch::{List, NamespaceMap, NamespaceRendering, Scratch, changed};

/// Collect, filter, and sort namespace declarations for a single element.
///
Expand All @@ -33,7 +33,7 @@ use super::scratch::{List, NamespaceMap, NamespaceRendering, Scratch};
/// # Returns
///
/// `(sorted_ns_decls, updated_rendered_map)` — the declarations to emit and
/// the binding map to pass to child elements.
/// the binding map for child elements, `None` when it is `parent_rendered`.
pub(super) fn collect_ns_declarations<'a, S: Scratch, D: DomStorage>(
node: Node<'a, '_, D>,
parent_rendered: &S::Namespaces<'a>,
Expand All @@ -42,7 +42,7 @@ pub(super) fn collect_ns_declarations<'a, S: Scratch, D: DomStorage>(
include_prefix: impl Fn(&str, &str) -> Result<bool, C14nError>,
scratch: &S,
) -> Result<NamespaceRendering<'a, S>, C14nError> {
let mut rendered = parent_rendered.copy()?;
let mut rendered = None;
let mut decls = scratch.values();

for (ordinal, ns) in node.namespaces().enumerate() {
Expand Down Expand Up @@ -70,7 +70,7 @@ pub(super) fn collect_ns_declarations<'a, S: Scratch, D: DomStorage>(
{
decls.push(("", ""))?;
}
rendered.remove(prefix)?;
changed(&mut rendered, parent_rendered)?.remove(prefix)?;
}
continue;
}
Expand Down Expand Up @@ -114,7 +114,7 @@ pub(super) fn collect_ns_declarations<'a, S: Scratch, D: DomStorage>(

// Update the rendered map for child elements.
for &(prefix, uri) in decls.iter() {
rendered.insert(prefix, false, Some(uri))?;
changed(&mut rendered, parent_rendered)?.insert(prefix, false, Some(uri))?;
}

Ok((decls, rendered))
Expand Down Expand Up @@ -156,6 +156,7 @@ mod tests {
.map(|(p, u)| (p.to_owned(), u.to_owned()))
.collect(),
rendered
.unwrap_or(namespaces)
.into_pairs()
.map(|(p, u)| (p.to_owned(), u.to_owned()))
.collect(),
Expand Down
10 changes: 7 additions & 3 deletions src/c14n/ns_exclusive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use super::C14nError;
use super::NodeVisibility;
use super::ns_common::collect_ns_declarations;
use super::prefix::{attribute_prefix, element_prefix};
use super::scratch::{List, NamespaceMap, NamespaceRendering, Scratch};
use super::scratch::{List, NamespaceMap, NamespaceRendering, NamespaceState, Scratch, changed};
use super::serialize::NsRenderer;

/// Exclusive C14N namespace renderer.
Expand Down Expand Up @@ -123,7 +123,7 @@ impl<S: Scratch, D: DomStorage> NsRenderer<S, D> for ExclusiveNsRenderer<'_> {
&& declarations.iter().any(|(declared, _)| *declared == prefix))
);
let nearest_utilizer_omitted_namespace =
parent_rendered.get(prefix, true)? == super::scratch::NamespaceState::Excluded;
parent_rendered.get(prefix, true)? == NamespaceState::Excluded;
let uses_exclusive_rendering = utilized.get(prefix, false)?.is_present()
&& !self.inclusive_prefixes.contains(prefix);

Expand All @@ -143,7 +143,11 @@ impl<S: Scratch, D: DomStorage> NsRenderer<S, D> for ExclusiveNsRenderer<'_> {
// nearest output ancestor that visibly utilized its prefix,
// not merely with the physically effective output binding.
if uses_exclusive_rendering {
rendered.insert(prefix, true, selected_here.then_some(uri))?;
let value = selected_here.then_some(uri);
let current = rendered.as_ref().unwrap_or(parent_rendered);
if current.get(prefix, true)? != NamespaceState::binding(value) {
changed(&mut rendered, parent_rendered)?.insert(prefix, true, value)?;
}
}
}
}
Expand Down
17 changes: 15 additions & 2 deletions src/c14n/scratch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub(crate) enum NamespaceState<'a> {
}

impl<'a> NamespaceState<'a> {
fn binding(entry: Option<&'a str>) -> Self {
pub(crate) fn binding(entry: Option<&'a str>) -> Self {
match entry {
None => Self::Excluded,
Some(uri) => Self::Bound(uri),
Expand Down Expand Up @@ -163,11 +163,24 @@ pub(crate) trait Scratch {
fn text(&self, parts: &[&str]) -> Result<Self::Text, C14nError>;
}

/// An element's declarations, and the map its children read when it differs
/// from the parent's. Most elements change nothing, so most copy nothing.
pub(crate) type NamespaceRendering<'a, S> = (
<S as Scratch>::Values<(&'a str, &'a str)>,
<S as Scratch>::Namespaces<'a>,
Option<<S as Scratch>::Namespaces<'a>>,
);

/// `rendered`, copied from `parent` before its first change.
pub(crate) fn changed<'m, 'a, M: NamespaceMap<'a>>(
rendered: &'m mut Option<M>,
parent: &M,
) -> Result<&'m mut M, C14nError> {
if rendered.is_none() {
*rendered = Some(parent.copy()?);
}
Ok(rendered.as_mut().expect("assigned above"))
}

pub(crate) struct Native;

impl<T> List<T> for Vec<T> {
Expand Down
47 changes: 31 additions & 16 deletions src/c14n/serialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,15 @@ const XML_NS: &str = "http://www.w3.org/XML/1998/namespace";

type TrackedPositions = Vec<(NodeId, usize)>;

struct CanonicalOutput<'a, W: ByteSink + ?Sized, S: Scratch = Native> {
/// One attribute of a start tag: namespace URI, local name, prefix and value.
type StartTagAttribute<'n, S> = (
&'n str,
&'n str,
&'n str,
AttributeValue<'n, <S as Scratch>::Text>,
);

struct CanonicalOutput<'a, 'n, W: ByteSink + ?Sized, S: Scratch = Native> {
writer: &'a mut W,
position: usize,
max_len: usize,
Expand All @@ -46,6 +54,9 @@ struct CanonicalOutput<'a, W: ByteSink + ?Sized, S: Scratch = Native> {
tracked_positions: Vec<(NodeId, usize)>,
xml_base_resolution: &'a XmlBaseResolutionBudget,
scratch: &'a S,
/// Start-tag working space, empty between tags. One start tag finishes
/// before the next begins, so one capacity serves the whole walk.
attributes: S::Values<StartTagAttribute<'n, S>>,
}

enum AttributeValue<'a, T: Text> {
Expand All @@ -61,13 +72,13 @@ impl<T: Text> AttributeValue<'_, T> {
}
}

impl<W: ByteSink + ?Sized, S: Scratch> ByteSink for CanonicalOutput<'_, W, S> {
impl<W: ByteSink + ?Sized, S: Scratch> ByteSink for CanonicalOutput<'_, '_, W, S> {
fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), C14nError> {
self.write_all(bytes)
}
}

impl<W: ByteSink + ?Sized, S: Scratch> CanonicalOutput<'_, W, S> {
impl<W: ByteSink + ?Sized, S: Scratch> CanonicalOutput<'_, '_, W, S> {
fn write_all(&mut self, bytes: &[u8]) -> Result<(), C14nError> {
debug_assert!(self.position <= self.max_len);
if bytes.len() > self.max_len - self.position {
Expand Down Expand Up @@ -174,7 +185,8 @@ impl<'a> CanonicalOutputOptions<'a> {
/// Trait for namespace rendering strategies (inclusive vs exclusive).
pub(crate) trait NsRenderer<S: Scratch = Native, D: DomStorage = NativeDom> {
/// Compute namespace declarations to emit for this element.
/// Returns `(sorted_ns_decls, updated_rendered_map)`.
/// Returns `(sorted_ns_decls, updated_rendered_map)`, the map `None` when
/// children read `parent_rendered` unchanged.
///
/// `parent_rendered` maps prefix → URI for what the nearest output ancestor
/// already declared in the canonical form.
Expand Down Expand Up @@ -389,6 +401,7 @@ pub(super) fn serialize_with_scratch<W: ByteSink + ?Sized, S: Scratch, D: DomSto
tracked_positions: Vec::new(),
xml_base_resolution: options.xml_base_resolution,
scratch,
attributes: scratch.values(),
};
let top = scratch.namespaces();
// No node outside a whole subtree can contribute, so the walk starts there.
Expand Down Expand Up @@ -427,7 +440,7 @@ fn serialize_children<'n, W: ByteSink + ?Sized, S: Scratch, D: DomStorage>(
ns_renderer: &dyn NsRenderer<S, D>,
config: C14nConfig,
parent_rendered: &S::Namespaces<'n>,
output: &mut CanonicalOutput<'_, W, S>,
output: &mut CanonicalOutput<'_, 'n, W, S>,
) -> Result<(), C14nError> {
let is_doc_root = parent.node_type() == NodeType::Root;

Expand Down Expand Up @@ -537,7 +550,7 @@ fn serialize_selected_axes_of_omitted_element<
visibility: Option<&dyn NodeVisibility<D>>,
ns_renderer: &dyn NsRenderer<S, D>,
parent_rendered: &S::Namespaces<'n>,
output: &mut CanonicalOutput<'_, W, S>,
output: &mut CanonicalOutput<'_, 'n, W, S>,
) -> Result<(), C14nError> {
let Some(visibility) = visibility else {
return Ok(());
Expand Down Expand Up @@ -621,7 +634,7 @@ fn serialize_element<'n, W: ByteSink + ?Sized, S: Scratch, D: DomStorage>(
ns_renderer: &dyn NsRenderer<S, D>,
config: C14nConfig,
parent_rendered: &S::Namespaces<'n>,
output: &mut CanonicalOutput<'_, W, S>,
output: &mut CanonicalOutput<'_, 'n, W, S>,
) -> Result<(), C14nError> {
let rendered = serialize_start_tag(
node,
Expand All @@ -637,7 +650,7 @@ fn serialize_element<'n, W: ByteSink + ?Sized, S: Scratch, D: DomStorage>(
with_comments,
ns_renderer,
config,
&rendered,
rendered.as_ref().unwrap_or(parent_rendered),
output,
)?;
output.write_all(b"</")?;
Expand All @@ -652,8 +665,8 @@ fn serialize_start_tag<'n, W: ByteSink + ?Sized, S: Scratch, D: DomStorage>(
ns_renderer: &dyn NsRenderer<S, D>,
config: C14nConfig,
parent_rendered: &S::Namespaces<'n>,
output: &mut CanonicalOutput<'_, W, S>,
) -> Result<S::Namespaces<'n>, C14nError> {
output: &mut CanonicalOutput<'_, 'n, W, S>,
) -> Result<Option<S::Namespaces<'n>>, C14nError> {
let (ns_decls, rendered) =
ns_renderer.render_namespaces(node, parent_rendered, visibility, output.scratch)?;

Expand Down Expand Up @@ -702,7 +715,7 @@ fn serialize_start_tag<'n, W: ByteSink + ?Sized, S: Scratch, D: DomStorage>(

// Build unified list: (ns_uri, local_name, prefix, value)
// Using Cow to avoid allocations when no fixup is needed.
let mut all_attrs = output.scratch.values();
let mut all_attrs = core::mem::replace(&mut output.attributes, output.scratch.values());
for (ordinal, attr) in node.attributes().enumerate() {
output.scratch.charge(attr.name().len().saturating_add(1))?;
if let Some(set) = visibility
Expand Down Expand Up @@ -743,7 +756,7 @@ fn serialize_start_tag<'n, W: ByteSink + ?Sized, S: Scratch, D: DomStorage>(
let resolved_value = if config.fixup_xml_base && name == "base" {
// C14N 1.1: inherited xml:base uses the resolved effective value
match effective_parent_base.as_ref() {
Some(base) => AttributeValue::Borrowed(base.view()?),
Some(base) => AttributeValue::Owned(output.scratch.text(&[base.view()?])?),
None => AttributeValue::Borrowed(value),
}
} else {
Expand Down Expand Up @@ -778,14 +791,16 @@ fn serialize_start_tag<'n, W: ByteSink + ?Sized, S: Scratch, D: DomStorage>(
// Always use <tag></tag> form, never self-closing.
output.write_all(b">")?;

while all_attrs.pop().is_some() {}
output.attributes = all_attrs;
Ok(rendered)
}

fn effective_parent_base<W: ByteSink + ?Sized, S: Scratch, D: DomStorage>(
node: Node<'_, '_, D>,
visibility: Option<&dyn NodeVisibility<D>>,
config: C14nConfig,
output: &mut CanonicalOutput<'_, W, S>,
output: &mut CanonicalOutput<'_, '_, W, S>,
) -> Result<Option<S::Text>, C14nError> {
let parent_not_in_set = if let Some(set) = visibility {
match node.parent().filter(Node::is_element) {
Expand Down Expand Up @@ -829,7 +844,7 @@ pub(super) fn map_xml_base_error(error: XmlBaseResolutionError) -> C14nError {
fn write_doc_level_prefix<W: ByteSink + ?Sized, S: Scratch>(
is_doc_root: bool,
follows_document_element: bool,
output: &mut CanonicalOutput<'_, W, S>,
output: &mut CanonicalOutput<'_, '_, W, S>,
) -> Result<(), C14nError> {
if is_doc_root && follows_document_element {
output.write_all(b"\n")?;
Expand All @@ -841,7 +856,7 @@ fn write_doc_level_prefix<W: ByteSink + ?Sized, S: Scratch>(
fn write_doc_level_suffix<W: ByteSink + ?Sized, S: Scratch>(
is_doc_root: bool,
follows_document_element: bool,
output: &mut CanonicalOutput<'_, W, S>,
output: &mut CanonicalOutput<'_, '_, W, S>,
) -> Result<(), C14nError> {
if is_doc_root && !follows_document_element {
output.write_all(b"\n")?;
Expand Down Expand Up @@ -961,7 +976,7 @@ fn collect_inherited_xml_attrs<'a, S: Scratch, D: DomStorage>(
/// avoiding ambiguity when multiple prefixes bind the same namespace URI.
fn write_qualified_name<W: ByteSink + ?Sized, S: Scratch, D: DomStorage>(
node: Node<'_, '_, D>,
output: &mut CanonicalOutput<'_, W, S>,
output: &mut CanonicalOutput<'_, '_, W, S>,
) -> Result<(), C14nError> {
let prefix = element_prefix(node);
if !prefix.is_empty() {
Expand Down
Loading
Loading