#[must_use]
pub fn insert_after_nth(xml: &str, pattern: &str, n: usize, content: &str) -> String {
let mut offset = 0usize;
let mut remaining = xml;
let mut count = 0usize;
while let Some(pos) = remaining.find(pattern) {
if count == n {
let insert_at = offset + pos + pattern.len();
let mut result = String::with_capacity(xml.len() + content.len());
result.push_str(&xml[..insert_at]);
result.push_str(content);
result.push_str(&xml[insert_at..]);
return result;
}
count += 1;
let advance = pos + pattern.len();
offset += advance;
remaining = &remaining[advance..];
}
xml.to_string()
}
#[must_use]
pub fn insert_many_after_nth(xml: &str, pattern: &str, contents: &[String]) -> String {
if contents.is_empty() {
return xml.to_owned();
}
let mut result =
String::with_capacity(xml.len() + contents.iter().map(String::len).sum::<usize>());
let mut search_from = 0usize;
let mut occurrence = 0usize;
while occurrence < contents.len() {
let Some(rel) = xml[search_from..].find(pattern) else {
result.push_str(&xml[search_from..]);
return result;
};
let abs = search_from + rel;
result.push_str(&xml[search_from..abs + pattern.len()]);
result.push_str(&contents[occurrence]);
search_from = abs + pattern.len();
occurrence += 1;
}
result.push_str(&xml[search_from..]);
result
}
#[must_use]
pub fn insert_no_wrap(xml: &str, cell_index: usize) -> String {
if xml.matches("<w:tcW").count() > cell_index {
return insert_after_nth(xml, "<w:tcW", cell_index, "<w:noWrap/>");
}
if xml.matches("<w:tcPr").count() > cell_index {
return insert_after_nth(xml, "<w:tcPr", cell_index, "<w:noWrap/>");
}
xml.to_string()
}
#[must_use]
pub fn insert_num_fmt(xml: &str, cell_index: usize, format: &str) -> String {
let pattern = "<w:pPr><w:rPr";
if xml.matches(pattern).count() > cell_index {
let num_fmt = format!("<w:numFmt w:val=\"{format}\"/>");
return insert_after_nth(xml, pattern, cell_index, &num_fmt);
}
xml.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn insert_after_nth_first() {
let out = insert_after_nth("AAxBBxAA", "AA", 0, "!");
assert_eq!(out, "AA!xBBxAA");
}
#[test]
fn insert_after_nth_second() {
let out = insert_after_nth("AAxBBxAA", "AA", 1, "!");
assert_eq!(out, "AAxBBxAA!");
}
#[test]
fn insert_after_nth_not_found() {
let out = insert_after_nth("xBBx", "AA", 0, "!");
assert_eq!(out, "xBBx");
}
#[test]
fn insert_after_nth_out_of_bounds() {
let out = insert_after_nth("AA", "AA", 5, "!");
assert_eq!(out, "AA");
}
#[test]
fn insert_many_basic() {
let out = insert_many_after_nth("<a/><b/><a/><b/><a/>", "<b/>", &["!".into(), "?".into()]);
assert_eq!(out, "<a/><b/>!<a/><b/>?<a/>");
}
#[test]
fn insert_many_empty_contents_returns_original() {
let xml = "<a/><b/><a/>";
let out = insert_many_after_nth(xml, "<b/>", &[]);
assert_eq!(out, xml);
}
#[test]
fn insert_many_more_contents_than_matches() {
let out = insert_many_after_nth("<a/><b/>", "<b/>", &["!".into(), "?".into()]);
assert_eq!(out, "<a/><b/>!");
}
#[test]
fn insert_many_no_match_returns_original() {
let xml = "<a/><a/>";
let out = insert_many_after_nth(xml, "<b/>", &["!".into()]);
assert_eq!(out, xml);
}
#[test]
fn insert_many_preserves_remaining_tail() {
let out = insert_many_after_nth("<a/><b/>TAIL", "<b/>", &["!".into()]);
assert_eq!(out, "<a/><b/>!TAIL");
}
}