Respect on-valued default paragraph styles

This commit is contained in:
Dylan M. Taylor committed 2026-11-14 13:25:25 +00:00
1 parent 15740f3083
commit a94c1a386a
2 files changed
+19 -5

No files matched your search

+5 -4
View File
@@ -11,6 +11,9 @@ fn numbering_level_index(value: Option<&str>) -> Option<usize> {
None => Some(0),
Some(value) => int(value).filter(|level| (0..=8).contains(level)).map(|level| level as usize),
}
fn style_is_default(style: &El) -> bool {
style.attr("w:default").is_some_and(|value| !matches!(value.trim(), "0" | "false" | "off" | "False" | "FALSE"))
}
/// Built-in style names Word stores in lower case, and how the UI shows them.
@@ -67,9 +70,7 @@ impl Reader<'_> {
// The default paragraph style must be called `Normal` in our model.
let has_normal = root.children("w:style").any(|s| s.attr("w:styleId") == Some("Normal"));
if !has_normal
&& let Some(def) = root
.children("w:style")
.find(|s| s.attr("w:type") == Some("paragraph") && s.attr("w:default").is_some_and(|v| v == "1" || v == "true"))
&& let Some(def) = root.children("w:style").find(|s| s.attr("w:type") == Some("paragraph") && style_is_default(s))
&& let Some(id) = def.attr("w:styleId")
{
self.pc.alias.insert(id.to_string(), "Normal".into());
@@ -143,7 +144,7 @@ impl Reader<'_> {
builtin: !s.attr("w:customStyle").is_some_and(|v| matches!(v.trim(), "1" | "true" | "on")),
locked: flag(s, "w:locked").unwrap_or(false),
// Only where the writer couldn't tell from the id (it flags the English ids).
default: s.attr("w:default").is_some_and(|v| v == "1" || v == "true")
default: style_is_default(s)
&& !matches!(
(kind, id.as_str()),
(StyleKind::Paragraph, "Normal")
+14 -1
View File
@@ -13,6 +13,10 @@ fn package(normal_id: &str) -> Vec<u8> {
}
fn package_with_default(normal_id: &str, is_default: bool) -> Vec<u8> {
package_with_body_default(normal_id, is_default, false)
}
fn package_with_body_default(normal_id: &str, is_default: bool, has_body_default: bool) -> Vec<u8> {
let root = r#"<?xml version="1.0" encoding="UTF-8"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/></Relationships>"#;
let doc = format!(
r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:document xmlns:w="{W}" xmlns:r="{R}"><w:body><w:p><w:pPr><w:pStyle w:val="Footer"/></w:pPr><w:r><w:t>body</w:t></w:r></w:p><w:p><w:r><w:t>normal</w:t></w:r></w:p><w:sectPr><w:footerReference w:type="default" r:id="rId2"/></w:sectPr></w:body></w:document>"#
@@ -21,8 +25,10 @@ fn package_with_default(normal_id: &str, is_default: bool) -> Vec<u8> {
r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:ftr xmlns:w="{W}"><w:p><w:pPr><w:pStyle w:val="Footer"/><w:jc w:val="center"/></w:pPr><w:r><w:t>1</w:t></w:r></w:p></w:ftr>"#
);
let default_attr = if is_default { r#" w:default="1""# } else { "" };
let body_style =
if has_body_default { r#"<w:style w:type="paragraph" w:default="on" w:styleId="Body"><w:name w:val="Body"/></w:style>"# } else { "" };
let styles = format!(
r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:styles xmlns:w="{W}"><w:docDefaults><w:rPrDefault><w:rPr><w:sz w:val="24"/></w:rPr></w:rPrDefault><w:pPrDefault><w:pPr><w:spacing w:after="200"/></w:pPr></w:pPrDefault></w:docDefaults><w:style w:type="paragraph"{default_attr} w:styleId="{normal_id}"><w:name w:val="Normal"/><w:pPr><w:spacing w:before="20" w:after="40" w:line="259" w:lineRule="auto"/></w:pPr><w:rPr><w:sz w:val="26"/></w:rPr></w:style></w:styles>"#
r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:styles xmlns:w="{W}"><w:docDefaults><w:rPrDefault><w:rPr><w:sz w:val="24"/></w:rPr></w:rPrDefault><w:pPrDefault><w:pPr><w:spacing w:after="200"/></w:pPr></w:pPrDefault></w:docDefaults><w:style w:type="paragraph"{default_attr} w:styleId="{normal_id}"><w:name w:val="Normal"/><w:pPr><w:spacing w:before="20" w:after="40" w:line="259" w:lineRule="auto"/></w:pPr><w:rPr><w:sz w:val="26"/></w:rPr></w:style>{body_style}</w:styles>"#
);
let rels = r#"<?xml version="1.0" encoding="UTF-8"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/><Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer" Target="footer1.xml"/></Relationships>"#;
let mut zw = zip::ZipWriter::new(std::io::Cursor::new(Vec::new()));
@@ -46,6 +52,13 @@ fn an_explicit_normal_style_is_the_default_even_without_w_default() {
assert!(normal.default, "Word treats the explicitly declared built-in Normal style as the default");
}
#[test]
fn an_explicit_body_default_on_prevents_normal_inference() {
let doc = craft_docx::read(&package_with_body_default("Normal", false, true)).unwrap();
assert!(doc.styles.styles.iter().any(|style| style.id == "Body" && style.default));
assert!(!doc.styles.styles.iter().any(|style| style.id == "Normal" && style.default));
}
fn footer_para(d: &Document) -> &craft_wordmodel::Paragraph {
let part = d.parts.values().find(|p| p.kind == PartKind::Footer).expect("a footer");
match part.blocks.first().map(|b| &**b) {