use super::WmlComparerSettings;
use crate::namespaces::W;
use crate::xmllinq::{Dom, NodeId, XName};
fn set_id(dom: &mut Dom, e: NodeId, v: &str) {
dom.set_attribute_value(e, &W::id(), Some(v));
}
pub fn change_footnote_endnote_references_to_unique_range(
dom: &mut Dom,
main_root: NodeId,
fn_root: Option<NodeId>,
en_root: Option<NodeId>,
start: i32,
has_log: bool,
) -> Result<Vec<String>, String> {
let fn_ref = W::name("footnoteReference");
let en_ref = W::name("endnoteReference");
let refs: Vec<NodeId> = dom
.descendants(main_root, None)
.into_iter()
.filter(|&d| dom.name(d).is_some_and(|n| n == fn_ref || n == en_ref))
.collect();
let mut warnings = Vec::new();
let mut orphans = Vec::new();
for (id, r) in (start..).zip(refs) {
let old = dom.attribute(r, &W::id()).unwrap_or("").to_string();
let new = id.to_string();
let is_fn = dom.name(r).unwrap() == fn_ref;
let (notes_root, def_name) = if is_fn {
(fn_root, W::footnote())
} else {
(en_root, W::endnote())
};
let def = notes_root.and_then(|nr| {
dom.elements(nr, Some(&def_name))
.into_iter()
.find(|&e| dom.attribute(e, &W::id()) == Some(old.as_str()))
});
match def {
Some(def) => {
set_id(dom, r, &new);
set_id(dom, def, &new);
}
None => {
if has_log {
warnings.push(format!(
"orphaned {} reference id '{old}'",
def_name.local_name()
));
orphans.push(r);
} else {
return Err(format!(
"Invalid document: {} reference id '{old}' has no definition",
def_name.local_name()
));
}
}
}
}
for o in orphans {
dom.remove(o);
}
Ok(warnings)
}
pub fn mandatory_separator_notes(dom: &mut Dom, is_footnote: bool) -> Vec<NodeId> {
let note_name = if is_footnote {
W::footnote()
} else {
W::endnote()
};
let sep_inner = "separator"; let mk = |dom: &mut Dom, ty: &str, id: &str, inner: &str| -> NodeId {
let note = dom.new_element(note_name.clone());
dom.set_attribute_value(note, &W::name("type"), Some(ty));
dom.set_attribute_value(note, &W::id(), Some(id));
let p = dom.new_element(W::p());
let ppr = dom.new_element(W::p_pr());
let spacing = dom.new_element(W::name("spacing"));
dom.set_attribute_value(spacing, &W::name("after"), Some("0"));
dom.set_attribute_value(spacing, &W::name("line"), Some("240"));
dom.set_attribute_value(spacing, &W::name("lineRule"), Some("auto"));
dom.add(ppr, spacing);
dom.add(p, ppr);
let r = dom.new_element(W::r());
let s = dom.new_element(W::name(inner));
dom.add(r, s);
dom.add(p, r);
dom.add(note, p);
note
};
vec![
mk(dom, "separator", "-1", sep_inner),
mk(dom, "continuationSeparator", "0", "continuationSeparator"),
]
}
pub fn fill_in_empty_footnotes_endnotes(dom: &mut Dom, notes_root: NodeId, is_footnote: bool) {
let (note_name, p_style, r_style, ref_el) = if is_footnote {
(
W::footnote(),
"FootnoteText",
"FootnoteReference",
"footnoteRef",
)
} else {
(
W::endnote(),
"EndnoteText",
"EndnoteReference",
"endnoteRef",
)
};
let notes: Vec<NodeId> = dom.elements(notes_root, Some(¬e_name));
for note in notes {
if dom.has_elements(note) {
continue;
}
let p = dom.new_element(W::p());
let ppr = dom.new_element(W::p_pr());
let pstyle = dom.new_element(W::name("pStyle"));
dom.set_attribute_value(pstyle, &W::val(), Some(p_style));
dom.add(ppr, pstyle);
dom.add(p, ppr);
let r = dom.new_element(W::r());
let rpr = dom.new_element(W::r_pr());
let rstyle = dom.new_element(W::name("rStyle"));
dom.set_attribute_value(rstyle, &W::val(), Some(r_style));
dom.add(rpr, rstyle);
dom.add(r, rpr);
let refe = dom.new_element(W::name(ref_el));
dom.add(r, refe);
dom.add(p, r);
dom.add(note, p);
}
}
pub fn content_contains_footnote_endnote_references_that_have_revisions(
dom: &Dom,
element: NodeId,
fn_root: Option<NodeId>,
en_root: Option<NodeId>,
) -> bool {
let fn_ref = W::name("footnoteReference");
let en_ref = W::name("endnoteReference");
let ins = W::ins();
let del = W::del();
for d in dom.descendants(element, None) {
let Some(n) = dom.name(d) else { continue };
let (notes_root, def_name) = if n == fn_ref {
(fn_root, W::footnote())
} else if n == en_ref {
(en_root, W::endnote())
} else {
continue;
};
let id = dom.attribute(d, &W::id());
if let (Some(nr), Some(id)) = (notes_root, id)
&& let Some(def) = dom
.elements(nr, Some(&def_name))
.into_iter()
.find(|&e| dom.attribute(e, &W::id()) == Some(id))
{
let has_rev = dom
.descendants(def, None)
.into_iter()
.any(|x| dom.name(x).is_some_and(|xn| xn == ins || xn == del));
if has_rev {
return true;
}
}
}
false
}
pub fn defined_style_ids(dom: &Dom, styles_root: NodeId) -> std::collections::HashSet<String> {
let style = W::name("style");
let style_id = W::name("styleId");
dom.elements(styles_root, Some(&style))
.into_iter()
.filter_map(|st| dom.attribute(st, &style_id).map(|s| s.to_string()))
.filter(|s| !s.is_empty())
.collect()
}
pub fn strip_unresolved_style_refs(
dom: &mut Dom,
root: NodeId,
defined: &std::collections::HashSet<String>,
) -> usize {
let mut removed = 0;
for local in ["pStyle", "rStyle"] {
let nm = W::name(local);
let victims: Vec<NodeId> = dom
.descendants(root, Some(&nm))
.into_iter()
.filter(|&e| {
matches!(dom.attribute(e, &W::val()),
Some(v) if !v.is_empty() && !defined.contains(v))
})
.collect();
for e in victims {
dom.remove(e);
removed += 1;
}
}
removed
}
pub fn copy_missing_styles(dom: &mut Dom, to_root: NodeId, from_root: NodeId) {
let style = W::name("style");
let type_a = W::name("type");
let style_id = W::name("styleId");
let mut existing: std::collections::HashSet<(String, String)> = dom
.elements(to_root, Some(&style))
.into_iter()
.map(|st| {
(
dom.attribute(st, &type_a).unwrap_or("").to_string(),
dom.attribute(st, &style_id).unwrap_or("").to_string(),
)
})
.collect();
let from_styles = dom.elements(from_root, Some(&style));
for s in from_styles {
let key = (
dom.attribute(s, &type_a).unwrap_or("").to_string(),
dom.attribute(s, &style_id).unwrap_or("").to_string(),
);
if !existing.insert(key) {
continue;
}
let cloned = dom.clone_subtree(s);
dom.set_attribute_value(cloned, &W::name("default"), None);
dom.add(to_root, cloned);
}
}
fn add_numbering_child_in_schema_order(dom: &mut Dom, numbering: NodeId, child: NodeId) {
fn rank(dom: &Dom, e: NodeId) -> i32 {
match dom
.name(e)
.map(|n| n.local_name().to_string())
.unwrap_or_default()
.as_str()
{
"numPicBullet" => 0,
"abstractNum" => 1,
"num" => 2,
_ => 3, }
}
let r = rank(dom, child);
let first_later = dom
.elements(numbering, None)
.into_iter()
.find(|&e| rank(dom, e) > r);
match first_later {
Some(fl) => dom.add_before_self(fl, child),
None => dom.add(numbering, child),
}
}
fn get_int_attribute(dom: &Dom, e: NodeId, name: &XName) -> Option<i32> {
dom.attribute(e, name).and_then(|v| v.parse().ok())
}
fn normalized_abstract_num_signature(dom: &mut Dom, abstract_num: NodeId) -> String {
let c = dom.clone_subtree(abstract_num);
dom.set_attribute_value(c, &W::name("abstractNumId"), None);
dom.set_attribute_value(
c,
&crate::namespaces::W15::name("restartNumberingAfterBreak"),
None,
);
for name in ["nsid", "tmpl"] {
if let Some(e) = dom.element(c, &W::name(name)) {
dom.remove(e);
}
}
dom.serialize_element(c)
}
pub fn copy_missing_numbering(
dom: &mut Dom,
to_root: NodeId,
from_root: NodeId,
) -> std::collections::HashMap<String, String> {
let abstract_num = W::name("abstractNum");
let num = W::name("num");
let abstract_num_id = W::name("abstractNumId");
let num_id_attr = W::name("numId");
let mut num_remap: std::collections::HashMap<String, String> = std::collections::HashMap::new();
let mut max_abstract_num_id = dom
.elements(to_root, Some(&abstract_num))
.into_iter()
.map(|e| get_int_attribute(dom, e, &abstract_num_id).unwrap_or(0))
.max()
.unwrap_or(0);
let mut max_num_id = dom
.elements(to_root, Some(&num))
.into_iter()
.map(|e| get_int_attribute(dom, e, &num_id_attr).unwrap_or(0))
.max()
.unwrap_or(0);
let mut abstract_map: std::collections::HashMap<i32, i32> = std::collections::HashMap::new();
for an in dom.elements(from_root, Some(&abstract_num)) {
let Some(from_id) = get_int_attribute(dom, an, &abstract_num_id) else {
continue;
};
let normalized_from = normalized_abstract_num_signature(dom, an);
let to_ans: Vec<NodeId> = dom.elements(to_root, Some(&abstract_num));
let mut matching: Option<NodeId> = None;
for e in to_ans {
if normalized_abstract_num_signature(dom, e) == normalized_from {
matching = Some(e);
break;
}
}
if let Some(m) = matching
&& let Some(existing_id) = get_int_attribute(dom, m, &abstract_num_id)
{
abstract_map.insert(from_id, existing_id);
continue;
}
let same_id_taken = dom
.elements(to_root, Some(&abstract_num))
.into_iter()
.any(|e| get_int_attribute(dom, e, &abstract_num_id) == Some(from_id));
let target_id = if same_id_taken {
max_abstract_num_id += 1;
max_abstract_num_id
} else {
max_abstract_num_id = max_abstract_num_id.max(from_id);
from_id
};
let cloned = dom.clone_subtree(an);
dom.set_attribute_value(cloned, &abstract_num_id, Some(&target_id.to_string()));
abstract_map.insert(from_id, target_id);
add_numbering_child_in_schema_order(dom, to_root, cloned);
}
for n in dom.elements(from_root, Some(&num)) {
let Some(from_num_id) = get_int_attribute(dom, n, &num_id_attr) else {
continue;
};
let Some(from_ref) = dom
.element(n, &abstract_num_id)
.and_then(|e| get_int_attribute(dom, e, &W::val()))
else {
continue;
};
let mapped = *abstract_map.get(&from_ref).unwrap_or(&from_ref);
let existing = dom
.elements(to_root, Some(&num))
.into_iter()
.find(|&e| get_int_attribute(dom, e, &num_id_attr) == Some(from_num_id));
if let Some(existing) = existing {
let existing_ref = dom
.element(existing, &abstract_num_id)
.and_then(|e| get_int_attribute(dom, e, &W::val()));
if existing_ref == Some(mapped) {
continue; }
max_num_id += 1;
let cloned = dom.clone_subtree(n);
dom.set_attribute_value(cloned, &num_id_attr, Some(&max_num_id.to_string()));
if let Some(e) = dom.element(cloned, &abstract_num_id) {
dom.set_attribute_value(e, &W::val(), Some(&mapped.to_string()));
}
num_remap.insert(from_num_id.to_string(), max_num_id.to_string());
add_numbering_child_in_schema_order(dom, to_root, cloned);
} else {
max_num_id = max_num_id.max(from_num_id);
let cloned = dom.clone_subtree(n);
if mapped != from_ref
&& let Some(e) = dom.element(cloned, &abstract_num_id)
{
dom.set_attribute_value(e, &W::val(), Some(&mapped.to_string()));
}
add_numbering_child_in_schema_order(dom, to_root, cloned);
}
}
num_remap
}
pub fn synthesize_dangling_numbering(dom: &mut Dom, numbering_root: NodeId, dangling: &[String]) {
if dangling.is_empty() {
return;
}
let fresh_aid = dom
.elements(numbering_root, Some(&W::name("abstractNum")))
.into_iter()
.filter_map(|e| get_int_attribute(dom, e, &W::name("abstractNumId")))
.max()
.map_or(0, |m| m + 1);
let mut lvls = String::new();
for ilvl in 0..9 {
lvls.push_str(&format!(
"<w:lvl w:ilvl=\"{ilvl}\"><w:start w:val=\"1\"/><w:numFmt w:val=\"decimal\"/>\
<w:lvlText w:val=\"%{n}.\"/><w:lvlJc w:val=\"left\"/>\
<w:pPr><w:ind w:left=\"{left}\" w:hanging=\"360\"/></w:pPr></w:lvl>",
n = ilvl + 1,
left = 720 * (ilvl + 1),
));
}
let xml = format!(
"<w:abstractNum xmlns:w=\"{w}\" w:abstractNumId=\"{fresh_aid}\">\
<w:multiLevelType w:val=\"multilevel\"/>{lvls}</w:abstractNum>",
w = W::URI
);
let d = dom.parse_xdocument(&xml);
if let Some(an) = dom.root(d) {
add_numbering_child_in_schema_order(dom, numbering_root, an);
}
for id in dangling {
let nxml = format!(
"<w:num xmlns:w=\"{w}\" w:numId=\"{id}\">\
<w:abstractNumId w:val=\"{fresh_aid}\"/></w:num>",
w = W::URI
);
let nd = dom.parse_xdocument(&nxml);
if let Some(ne) = dom.root(nd) {
add_numbering_child_in_schema_order(dom, numbering_root, ne);
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RectifyError {
MissingNoteDef {
id: String,
},
MissingTargetPart {
kind: &'static str,
},
}
impl std::fmt::Display for RectifyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RectifyError::MissingNoteDef { id } => {
write!(
f,
"Internal error: note definition id '{id}' not found in before ∪ after parts"
)
}
RectifyError::MissingTargetPart { kind } => {
write!(
f,
"Internal error: withRevisions {kind} part is absent, cannot write renumbered note definitions"
)
}
}
}
}
impl std::error::Error for RectifyError {}
#[derive(Debug, Clone, Copy, Default)]
pub struct NotesSet {
pub before: Option<NodeId>,
pub after: Option<NodeId>,
pub with_revisions: Option<NodeId>,
}
pub fn rectify_footnote_endnote_ids(
dom: &mut Dom,
main_root: NodeId,
footnotes: NotesSet,
endnotes: NotesSet,
settings: &WmlComparerSettings,
id_gen: &mut u32,
) -> Result<(), RectifyError> {
let fn_plan = plan_rectify(
dom,
main_root,
&W::name("footnoteReference"),
&W::footnote(),
&footnotes,
)?;
let en_plan = plan_rectify(
dom,
main_root,
&W::name("endnoteReference"),
&W::endnote(),
&endnotes,
)?;
apply_rectify(dom, &W::footnote(), &footnotes, &fn_plan);
apply_rectify(dom, &W::endnote(), &endnotes, &en_plan);
for wr in [footnotes.with_revisions, endnotes.with_revisions]
.into_iter()
.flatten()
{
finalize_notes_part(dom, wr, settings, id_gen);
}
Ok(())
}
fn finalize_notes_part(
dom: &mut Dom,
root: NodeId,
settings: &WmlComparerSettings,
id_gen: &mut u32,
) {
let rebuilt = super::finalize::mark_content_as_deleted_or_inserted(dom, root, settings, id_gen);
if rebuilt != root {
for c in dom.nodes(root) {
dom.remove(c);
}
for c in dom.nodes(rebuilt) {
dom.add(root, c);
}
}
super::finalize::coalesce_all_paragraphs(dom, root);
super::finalize::move_paragraph_properties_first(dom, root);
super::finalize::fix_paragraph_mark_revision_order(dom, root);
super::finalize::remove_powertools_scratch_markup(dom, root);
super::finalize::ignore_pt14_namespace(dom, root);
}
struct RectifyPlan {
refs: Vec<NodeId>,
defs: Vec<NodeId>,
}
fn plan_rectify(
dom: &Dom,
main_root: NodeId,
ref_name: &XName,
def_name: &XName,
notes: &NotesSet,
) -> Result<RectifyPlan, RectifyError> {
let refs = dom.descendants(main_root, Some(ref_name));
let mut defs = Vec::with_capacity(refs.len());
for r in &refs {
let old = dom.attribute(*r, &W::id()).unwrap_or("").to_string();
let def = lookup_def(dom, notes.after, def_name, &old)
.or_else(|| lookup_def(dom, notes.before, def_name, &old));
let def = def.ok_or(RectifyError::MissingNoteDef { id: old })?;
defs.push(def);
}
if !refs.is_empty() && notes.with_revisions.is_none() {
let kind = if def_name == &W::footnote() {
"footnotes"
} else {
"endnotes"
};
return Err(RectifyError::MissingTargetPart { kind });
}
Ok(RectifyPlan { refs, defs })
}
fn lookup_def(dom: &Dom, root: Option<NodeId>, def_name: &XName, old: &str) -> Option<NodeId> {
root.and_then(|rt| {
dom.elements(rt, Some(def_name))
.into_iter()
.find(|&e| dom.attribute(e, &W::id()) == Some(old))
})
}
pub(crate) fn is_structural_note(dom: &Dom, note: NodeId) -> bool {
match dom.attribute(note, &W::id()) {
Some("-1") | Some("0") => return true,
_ => {}
}
matches!(
dom.attribute(note, &W::name("type")),
Some("separator") | Some("continuationSeparator") | Some("continuationNotice")
)
}
fn apply_rectify(dom: &mut Dom, def_name: &XName, notes: &NotesSet, plan: &RectifyPlan) {
if let Some(wr) = notes.with_revisions {
for note in dom.elements(wr, Some(def_name)) {
if !is_structural_note(dom, note) {
dom.remove(note);
}
}
}
let reserved: std::collections::HashSet<String> = notes
.with_revisions
.map(|wr| {
dom.elements(wr, Some(def_name))
.into_iter()
.filter_map(|n| dom.attribute(n, &W::id()).map(str::to_string))
.collect()
})
.unwrap_or_default();
let mut next = 1u32;
let mut assigned: Vec<String> = Vec::with_capacity(plan.refs.len());
for _ in 0..plan.refs.len() {
while reserved.contains(&next.to_string()) {
next += 1;
}
assigned.push(next.to_string());
next += 1;
}
for (idx, r) in plan.refs.iter().enumerate() {
dom.set_attribute_value(*r, &W::id(), Some(&assigned[idx]));
}
if let Some(wr) = notes.with_revisions {
for (idx, def) in plan.defs.iter().enumerate() {
let cloned = dom.clone_subtree(*def);
dom.set_attribute_value(cloned, &W::id(), Some(&assigned[idx]));
dom.add(wr, cloned);
}
}
}
pub fn sync_settings_special_note_ids(
settings_dom: &mut Dom,
settings_root: NodeId,
footnote_ids: &std::collections::HashSet<String>,
endnote_ids: &std::collections::HashSet<String>,
) {
for (pr_name, child_name, existing) in [
("footnotePr", "footnote", footnote_ids),
("endnotePr", "endnote", endnote_ids),
] {
let pr = W::name(pr_name);
let child = W::name(child_name);
for pr_el in settings_dom.descendants(settings_root, Some(&pr)) {
let doomed: Vec<_> = settings_dom
.elements(pr_el, Some(&child))
.into_iter()
.filter(|&e| {
let id = settings_dom.attribute(e, &W::id()).unwrap_or("");
!id.is_empty() && !existing.contains(id)
})
.collect();
for e in doomed {
settings_dom.remove(e);
}
}
}
}
pub fn fix_up_footnotes_endnotes_with_custom_markers(dom: &mut Dom, root: NodeId) {
let cmf = W::name("customMarkFollows");
let refs: Vec<NodeId> = [W::name("footnoteReference"), W::name("endnoteReference")]
.iter()
.flat_map(|n| dom.descendants(root, Some(n)))
.filter(|&r| dom.attribute(r, &cmf).is_some())
.collect();
for fnenr in refs {
let Some(par) = dom.parent(fnenr) else {
continue;
};
let Some(gp) = dom.parent(par) else { continue };
if dom.name(par) != Some(W::r()) {
continue;
}
let gpn = dom.name(gp);
let (is_del, leaf) = if gpn == Some(W::del()) {
(true, W::name("delText"))
} else if gpn == Some(W::ins()) {
(false, W::t())
} else {
continue;
};
if dom.element(par, &leaf).is_some() {
continue;
}
let Some(after_gp) = dom.next_element(gp) else {
continue;
};
let expected_gp = if is_del { W::del() } else { W::ins() };
if dom.name(after_gp) != Some(expected_gp) {
continue;
}
let Some(lf) = dom
.elements(after_gp, Some(&W::r()))
.into_iter()
.find_map(|r| dom.element(r, &leaf))
else {
continue;
};
let cloned = dom.clone_subtree(lf);
dom.add(par, cloned);
dom.remove(lf);
}
}
fn note_def_by_id(dom: &Dom, root: NodeId, id: Option<&str>) -> Option<NodeId> {
dom.elements(root, None)
.into_iter()
.find(|&e| dom.attribute(e, &W::id()) == id)
}
fn replace_nodes(dom: &mut Dom, target: NodeId, source: NodeId) {
for c in dom.nodes(target) {
dom.remove(c);
}
for c in dom.nodes(source) {
dom.add(target, c);
}
}
fn ensure_reference_marker(dom: &mut Dom, temp: NodeId, is_footnote: bool, direct_child_run: bool) {
let r_name = W::r();
let has_marker = dom.descendants(temp, Some(&r_name)).into_iter().any(|run| {
let style = dom
.elements(run, Some(&W::r_pr()))
.into_iter()
.flat_map(|rpr| dom.elements(rpr, Some(&W::name("rStyle"))))
.find_map(|st| dom.attribute(st, &W::val()));
style == Some("FootnoteReference")
|| !dom
.descendants(run, Some(&W::name("footnoteRef")))
.is_empty()
});
if has_marker {
return;
}
let Some(first_para) = dom.descendants(temp, Some(&W::p())).first().copied() else {
return;
};
let first_run = if direct_child_run {
dom.element(first_para, &r_name)
} else {
dom.descendants(first_para, Some(&r_name)).first().copied()
};
let Some(first_run) = first_run else {
return;
};
let (style_val, ref_el) = if is_footnote {
("FootnoteReference", "footnoteRef")
} else {
("EndnoteReference", "endnoteRef")
};
let marker = dom.new_element(W::r());
let rpr = dom.new_element(W::r_pr());
let rstyle = dom.new_element(W::name("rStyle"));
dom.set_attribute_value(rstyle, &W::val(), Some(style_val));
dom.add(rpr, rstyle);
dom.add(marker, rpr);
let re = dom.new_element(W::name(ref_el));
dom.add(marker, re);
dom.add_before_self(first_run, marker);
}
fn produce_note_redline(
dom: &mut Dom,
flat: &mut [super::atoms::ComparisonUnitAtom],
is_footnote: bool,
direct_child_run: bool,
settings: &WmlComparerSettings,
id_gen: &mut u32,
) -> Option<NodeId> {
super::produce::assemble_ancestor_unids(dom, flat);
let children = super::produce::produce_new_wml_markup_from_correlated_sequence(
dom, flat, settings, id_gen,
);
let temp = dom.new_element(W::body());
for c in children {
dom.add(temp, c);
}
ensure_reference_marker(dom, temp, is_footnote, direct_child_run);
super::finalize::move_paragraph_properties_first(dom, temp);
super::finalize::fix_paragraph_mark_revision_order(dom, temp);
let fn_name = W::footnote();
let en_name = W::endnote();
dom.descendants(temp, None)
.into_iter()
.find(|&d| dom.name(d).is_some_and(|n| n == fn_name || n == en_name))
}
pub fn process_footnote_endnote(
dom: &mut Dom,
atoms: &[super::atoms::ComparisonUnitAtom],
notes: &mut super::NotesContext,
settings: &WmlComparerSettings,
id_gen: &mut u32,
) {
use super::CorrelationStatus;
use super::atoms::CorrelatedSequence;
use super::{atomize, lcs, lcs_table, preprocess, produce, units};
let fn_ref = W::name("footnoteReference");
let en_ref = W::name("endnoteReference");
let candidates: Vec<(NodeId, Option<NodeId>, CorrelationStatus)> = atoms
.iter()
.filter(|a| {
dom.name(a.content_element)
.is_some_and(|n| n == fn_ref || n == en_ref)
})
.map(|a| {
(
a.content_element,
a.content_element_before,
a.correlation_status,
)
})
.collect();
for (content, content_before, status) in candidates {
let before_id = content_before
.and_then(|e| dom.attribute(e, &W::id()))
.map(str::to_string);
let after_id = dom.attribute(content, &W::id()).map(str::to_string);
let is_footnote = dom.name(content).is_some_and(|n| n == fn_ref);
match status {
CorrelationStatus::Equal => {
let (before_root, after_root) = if is_footnote {
(
notes
.fn_before
.expect("footnotes part missing in before document (C# NRE)"),
notes
.fn_after
.expect("footnotes part missing in after document (C# NRE)"),
)
} else {
(
notes
.en_before
.expect("endnotes part missing in before document (C# NRE)"),
notes
.en_after
.expect("endnotes part missing in after document (C# NRE)"),
)
};
let def_before = note_def_by_id(dom, before_root, before_id.as_deref())
.expect("before note definition not found (C# NRE in AddSha1Hash)");
let def_after = note_def_by_id(dom, after_root, after_id.as_deref())
.expect("after note definition not found (C# NRE in AddSha1Hash)");
preprocess::add_sha1_hash_to_block_level_content(
dom,
def_before,
settings,
&preprocess::null_rel_resolver,
);
preprocess::add_sha1_hash_to_block_level_content(
dom,
def_after,
settings,
&preprocess::null_rel_resolver,
);
let fncal1 = atomize::create_comparison_unit_atom_list(dom, def_before, settings);
let fncus1 = units::get_comparison_unit_list(dom, &fncal1, settings);
let fncal2 = atomize::create_comparison_unit_atom_list(dom, def_after, settings);
let fncus2 = units::get_comparison_unit_list(dom, &fncal2, settings);
if !(fncus1.is_empty() && fncus2.is_empty()) {
let seqs = lcs::lcs(dom, fncus1, fncus2, settings);
lcs_table::mark_rows_as_deleted_or_inserted(dom, settings, &seqs, id_gen);
let mut flat = produce::flatten_to_comparison_unit_atom_list(dom, &seqs);
let new_content =
produce_note_redline(dom, &mut flat, is_footnote, true, settings, id_gen)
.expect("Internal error");
replace_nodes(dom, def_after, new_content);
}
}
CorrelationStatus::Inserted => {
let after_root = if is_footnote {
notes
.fn_after
.expect("footnotes part missing in after document (C# NRE)")
} else {
notes
.en_after
.expect("endnotes part missing in after document (C# NRE)")
};
let def_after = note_def_by_id(dom, after_root, after_id.as_deref())
.expect("after note definition not found (C# NRE in AddSha1Hash)");
preprocess::add_sha1_hash_to_block_level_content(
dom,
def_after,
settings,
&preprocess::null_rel_resolver,
);
let fncal2 = atomize::create_comparison_unit_atom_list(dom, def_after, settings);
let fncus2 = units::get_comparison_unit_list(dom, &fncal2, settings);
let seqs = vec![CorrelatedSequence::inserted(fncus2)];
lcs_table::mark_rows_as_deleted_or_inserted(dom, settings, &seqs, id_gen);
let mut flat = produce::flatten_to_comparison_unit_atom_list(dom, &seqs);
if let Some(new_content) =
produce_note_redline(dom, &mut flat, is_footnote, false, settings, id_gen)
{
replace_nodes(dom, def_after, new_content);
}
}
CorrelationStatus::Deleted => {
let before_root = if is_footnote {
notes
.fn_before
.expect("footnotes part missing in before document (C# NRE)")
} else {
notes
.en_before
.expect("endnotes part missing in before document (C# NRE)")
};
let def_before = note_def_by_id(dom, before_root, after_id.as_deref())
.expect("before note definition not found (C# NRE in AddSha1Hash)");
preprocess::add_sha1_hash_to_block_level_content(
dom,
def_before,
settings,
&preprocess::null_rel_resolver,
);
let fncal2 = atomize::create_comparison_unit_atom_list(dom, def_before, settings);
let fncus2 = units::get_comparison_unit_list(dom, &fncal2, settings);
let seqs = vec![CorrelatedSequence::deleted(fncus2)];
lcs_table::mark_rows_as_deleted_or_inserted(dom, settings, &seqs, id_gen);
let mut flat = produce::flatten_to_comparison_unit_atom_list(dom, &seqs);
if !flat.is_empty()
&& let Some(new_content) =
produce_note_redline(dom, &mut flat, is_footnote, false, settings, id_gen)
{
replace_nodes(dom, def_before, new_content);
}
}
_ => panic!("Internal error"),
}
}
}