mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-28 04:17:06 -06:00
DOCX: a style ID defined twice is its last definition (#2031)
Some generators write a placeholder Heading 1 and then their own, under the same w:styleId. The reader kept the first definition and skipped the rest, so headings came out in the placeholder's font, size and colour. Word uses the last definition, whole: a synthetic document with an italic red 20 pt first definition and a bold blue 12 pt second one shows bold blue 12 pt, with nothing of the first (no italic, no centring), for a custom style and for Heading 1 alike. The reader now replaces an earlier definition in place instead of skipping the later one. Co-authored-by: Daniele Mori <Dinasty@hotmail.it> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Daniele Mori
Claude Opus 5.5
parent
f6115ea7fa
commit
ba8dc7bf1c
@@ -818,9 +818,6 @@ impl Reader<'_> {
|
||||
for s in root.children("w:style").take(10_000) {
|
||||
let Some(raw_id) = s.attr("w:styleId").filter(|i| !i.is_empty()) else { continue };
|
||||
let id = self.pc.style_id(raw_id);
|
||||
if sheet.get(&id).is_some() {
|
||||
continue;
|
||||
}
|
||||
let kind = match s.attr("w:type").unwrap_or("paragraph") {
|
||||
"character" => StyleKind::Character,
|
||||
"table" => StyleKind::Table,
|
||||
@@ -862,7 +859,13 @@ impl Reader<'_> {
|
||||
let parts = self.table_style_parts(s, &mut st);
|
||||
st.table = (parts != TableStyleParts::default()).then_some(parts);
|
||||
}
|
||||
sheet.styles.push(st);
|
||||
// A style defined twice (some generators write their own Heading 1 after a
|
||||
// placeholder one) is the last definition, whole: Word drops the earlier one rather
|
||||
// than merging the two (observed with synthetic documents).
|
||||
match sheet.styles.iter_mut().find(|s| s.id == id) {
|
||||
Some(earlier) => *earlier = st,
|
||||
None => sheet.styles.push(st),
|
||||
}
|
||||
}
|
||||
// Styles that give only a list level take the list from the style they are based on
|
||||
// (Heading 3: level 3 of Heading 2's outline list).
|
||||
|
||||
@@ -595,6 +595,26 @@ fn empty_tbl_borders_elements_leave_sides_unset() {
|
||||
assert!(b.top.is_some() && b.between.is_some() && b.inside_v.is_some());
|
||||
}
|
||||
|
||||
/// A style ID defined twice is its last definition, whole (Word drops the earlier one rather
|
||||
/// than merging: observed with synthetic documents). Generators that write a placeholder
|
||||
/// Heading 1 and then their own made headings come out in the placeholder's font and size.
|
||||
#[test]
|
||||
fn a_style_defined_twice_is_its_last_definition() {
|
||||
let styles = format!(
|
||||
r#"<w:styles {W_NS}>
|
||||
<w:style w:type="paragraph" w:styleId="Heading1"><w:name w:val="heading 1"/><w:pPr><w:jc w:val="center"/></w:pPr><w:rPr><w:i/><w:color w:val="FF0000"/><w:sz w:val="40"/></w:rPr></w:style>
|
||||
<w:style w:type="paragraph" w:styleId="Heading1"><w:name w:val="heading 1"/><w:rPr><w:b/><w:color w:val="0000FF"/><w:sz w:val="24"/></w:rPr></w:style>
|
||||
</w:styles>"#
|
||||
);
|
||||
let body = r#"<w:p><w:pPr><w:pStyle w:val="Heading1"/></w:pPr><w:r><w:t>Probe</w:t></w:r></w:p>"#;
|
||||
let d = wordcraft_docx::read(&docx(body, &[("rId1", "styles", "styles.xml")], &[("word/styles.xml", &styles)])).unwrap();
|
||||
assert_eq!(d.styles.styles.iter().filter(|s| s.id == "Heading1").count(), 1);
|
||||
let h = d.styles.get("Heading1").unwrap();
|
||||
assert_eq!((h.chr.bold, h.chr.italic, h.chr.size), (Some(true), None, Some(12.0)));
|
||||
assert_eq!(h.chr.color, Some(TextColor::Rgb(Rgb(0, 0, 0xFF))));
|
||||
assert_eq!(h.para.align, None, "nothing kept from the first definition");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vml_image_and_inline_drawing() {
|
||||
let png: &[u8] = b"\x89PNG\r\n\x1a\nnot really a png";
|
||||
|
||||
Reference in New Issue
Block a user