Compare commits

..
4 changed files with 39 additions and 29 deletions
+2 -2
View File
File diff suppressed because one or more lines are too long
+5 -25
View File
@@ -353,11 +353,11 @@ impl super::Writer<'_> {
};
self.embeds.markup_bytes = left;
let mut root = xml::parse(src.xml.as_bytes()).ok()?;
let mut used = HashSet::new();
let mut used = Vec::new();
rel_attr_values(&root, &mut used, 0);
// Every relationship the markup uses must be writable, or the object would be broken.
let writable: HashSet<&str> = src.refs.iter().filter(|r| r.external || self.embeds.available(&r.target)).map(|r| r.id.as_str()).collect();
if !used.iter().all(|id| writable.contains(id.as_str())) {
let writable = |id: &String| src.refs.iter().any(|r| r.id == *id && (r.external || self.embeds.available(&r.target)));
if !used.iter().all(writable) {
log::warn!("docx: an embedded object's parts are missing; it is left out");
return None;
}
@@ -637,15 +637,13 @@ fn set_attr(e: &mut El, k: &str, v: &str) {
}
}
/// The distinct relationship ids `e` uses: a set, so a large tree (a native document's markup
/// never went through the reader's id limit) costs no scan per attribute.
fn rel_attr_values(e: &El, out: &mut HashSet<String>, depth: usize) {
fn rel_attr_values(e: &El, out: &mut Vec<String>, depth: usize) {
if depth > MAX_DEPTH {
return;
}
for (k, v) in &e.attrs {
if xml::is_rel_attr(&e.name, k) && !out.contains(v) {
out.insert(v.clone());
out.push(v.clone());
}
}
for c in e.els() {
@@ -853,24 +851,6 @@ fn valid_part_name(name: &str) -> String {
mod tests {
use super::*;
#[test]
fn relationship_ids_are_collected_once_each() {
// A native document's embedded markup skips the reader's id limit: 40,000 distinct ids
// (each named twice) were a scan of every id collected so far, per attribute.
let mut xml = String::from(
r#"<w:p xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">"#,
);
for i in 0..40_000 {
xml.push_str(&format!(r#"<w:r r:id="rId{i}"/><w:r r:id="rId{i}"/>"#));
}
xml.push_str("</w:p>");
let root = xml::parse(xml.as_bytes()).unwrap();
let mut used = HashSet::new();
rel_attr_values(&root, &mut used, 0);
assert_eq!(used.len(), 40_000);
assert!(used.contains("rId39999"));
}
#[test]
fn charts_cut_to_the_limits_on_save_say_so_once() {
// A model deserialised as is can hold more than a chart part keeps; the save left the rest
+31 -1
View File
@@ -179,9 +179,18 @@ fn char_prop(w: &mut W, name: &str, c: Option<char>) {
val(w, name, &s);
}
/// Most cells a matrix is padded out to (rows × its widest row). A ragged matrix past it (a
/// native document's, or one parsed from a few bytes of text) writes each row as it is, rather
/// than output that grows with the square of its size.
const MAX_PADDED_CELLS: usize = 1 << 20;
fn write_matrix(w: &mut W, n: &MNode, depth: usize) {
let MNode::Matrix { rows, col_jc } = n else { return };
let cols = rows.iter().map(|r| r.len()).max().unwrap_or(0).max(1);
let pad = rows.len().saturating_mul(cols) <= MAX_PADDED_CELLS;
if !pad {
log::warn!("docx: a {} × {cols} matrix is too large to pad; its rows are written as they are", rows.len());
}
w.open("m:m", &[]);
w.open("m:mPr", &[]);
w.open("m:mcs", &[]);
@@ -203,7 +212,7 @@ fn write_matrix(w: &mut W, n: &MNode, depth: usize) {
w.close("m:mPr");
for row in rows {
w.open("m:mr", &[]);
for c in 0..cols {
for c in 0..if pad { cols } else { row.len().max(1) } {
arg(w, "m:e", row.get(c).map(|v| v.as_slice()).unwrap_or(&[]), depth);
}
w.close("m:mr");
@@ -480,3 +489,24 @@ fn run(w: &mut W, r: &MRun) {
w.leaf("m:t", &[("xml:space", "preserve")], &r.text);
w.close("m:r");
}
#[cfg(test)]
mod tests {
use super::*;
fn cells(rows: Vec<Vec<Arg>>) -> usize {
let mut w = W::new();
write_matrix(&mut w, &MNode::Matrix { rows, col_jc: Vec::new() }, 0);
w.s.matches("<m:e/>").count() + w.s.matches("<m:e>").count()
}
#[test]
fn a_ragged_matrix_is_padded_within_a_cell_budget() {
// A small ragged matrix is padded to a rectangle, as before.
assert_eq!(cells(vec![vec![Vec::new(); 3], vec![Vec::new()]]), 6);
// 2,000 one-cell rows and one 2,000-cell row: 4 million cells padded, past the budget.
let mut rows = vec![vec![Vec::new()]; 2000];
rows.push(vec![Vec::new(); 2000]);
assert_eq!(cells(rows), 4000);
}
}
+1 -1
View File
File diff suppressed because one or more lines are too long