From 6d8f1901dcf6cd5f6261e93c293b4383858e1b1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacobo=20Aragunde=20P=C3=A9rez?= Date: Wed, 9 Jul 2025 21:00:14 +0200 Subject: [PATCH] Allow null values in XWPFParagraph.get/setAlignment(). (#829) * Allow null values in XWPFParagraph.get/setAlignment(). A null value in this field would indicate that the paragraph should follow the alignment provided by the style hierarchy. Fixes: https://bz.apache.org/bugzilla/show_bug.cgi?id=69720 * Revert getAlignment() and implement isAlignmentSet() instead. * Replace test case file. * Implement XWPFParagraph.getTCPPr(create). It lets the caller choose if a new PPr should be created in case it doesn't exist. * use getCTPPr(boolean) * compile issue --------- Co-authored-by: PJ Fanning --- .../poi/xwpf/usermodel/XWPFParagraph.java | 77 ++++++++++++++---- .../org/apache/poi/xwpf/TestXWPFBugs.java | 23 ++++-- .../poi/xwpf/usermodel/TestXWPFParagraph.java | 7 ++ .../document/bug-paragraph-alignment.docx | Bin 0 -> 11916 bytes 4 files changed, 83 insertions(+), 24 deletions(-) create mode 100644 test-data/document/bug-paragraph-alignment.docx diff --git a/poi-ooxml/src/main/java/org/apache/poi/xwpf/usermodel/XWPFParagraph.java b/poi-ooxml/src/main/java/org/apache/poi/xwpf/usermodel/XWPFParagraph.java index 7f3ab37ff6..44bc523096 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xwpf/usermodel/XWPFParagraph.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xwpf/usermodel/XWPFParagraph.java @@ -482,16 +482,19 @@ public class XWPFParagraph implements IBodyElement, IRunBody, ISDTContents, Para * Returns the paragraph alignment which shall be applied to text in this * paragraph. *

- * If this element is not set on a given paragraph, its value is determined + * If this element is not set on a given paragraph, this function returns + * ParagraphAlignment.LEFT as a placeholder value, and isAlignmentSet() + * returns false. In such case, the alignment value must be determined * by the setting previously set at any level of the style hierarchy (i.e. * that previous setting remains unchanged). If this setting is never * specified in the style hierarchy, then no alignment is applied to the * paragraph. * + * @see #isAlignmentSet() * @return the paragraph alignment of this paragraph. */ public ParagraphAlignment getAlignment() { - CTPPr pr = getCTPPr(); + CTPPr pr = getCTPPr(false); return pr == null || !pr.isSetJc() ? ParagraphAlignment.LEFT : ParagraphAlignment.valueOf(pr.getJc().getVal().intValue()); } @@ -506,13 +509,32 @@ public class XWPFParagraph implements IBodyElement, IRunBody, ISDTContents, Para * specified in the style hierarchy, then no alignment is applied to the * paragraph. * - * @param align the paragraph alignment to apply to this paragraph. + * @param align the paragraph alignment to apply to this paragraph. It can + * be null to unset it and fall back to the style hierarchy. */ public void setAlignment(ParagraphAlignment align) { - CTPPr pr = getCTPPr(); - CTJc jc = pr.isSetJc() ? pr.getJc() : pr.addNewJc(); - STJc.Enum en = STJc.Enum.forInt(align.getValue()); - jc.setVal(en); + if (align == null) { + CTPPr pr = getCTPPr(false); + if (pr != null) + pr.unsetJc(); + } else { + CTPPr pr = getCTPPr(true); + CTJc jc = pr.isSetJc() ? pr.getJc() : pr.addNewJc(); + STJc.Enum en = STJc.Enum.forInt(align.getValue()); + jc.setVal(en); + } + } + + /** + * Returns true if the paragraph has a paragraph alignment value of its own + * or false in case it should fall back to the alignment value set by the + * paragraph style. + * + * @return boolean + */ + public boolean isAlignmentSet() { + CTPPr pr = getCTPPr(false); + return pr != null && pr.isSetJc(); } /** @@ -548,7 +570,7 @@ public class XWPFParagraph implements IBodyElement, IRunBody, ISDTContents, Para * @return the vertical alignment of this paragraph. */ public TextAlignment getVerticalAlignment() { - CTPPr pr = getCTPPr(); + CTPPr pr = getCTPPr(false); return (pr == null || !pr.isSetTextAlignment()) ? TextAlignment.AUTO : TextAlignment.valueOf(pr.getTextAlignment().getVal() .intValue()); @@ -864,7 +886,10 @@ public class XWPFParagraph implements IBodyElement, IRunBody, ISDTContents, Para * @return boolean - if page break is set */ public boolean isPageBreak() { - final CTPPr ppr = getCTPPr(); + final CTPPr ppr = getCTPPr(false); + if (ppr == null) { + return false; + } final CTOnOff ctPageBreak = ppr.isSetPageBreakBefore() ? ppr.getPageBreakBefore() : null; if (ctPageBreak == null) { return false; @@ -1353,7 +1378,8 @@ public class XWPFParagraph implements IBodyElement, IRunBody, ISDTContents, Para */ @Override public boolean isWordWrapped() { - return getCTPPr().isSetWordWrap() && POIXMLUnits.parseOnOff(getCTPPr().getWordWrap()); + CTPPr ppr = getCTPPr(false); + return ppr != null && ppr.isSetWordWrap() && POIXMLUnits.parseOnOff(ppr.getWordWrap()); } /** @@ -1390,7 +1416,10 @@ public class XWPFParagraph implements IBodyElement, IRunBody, ISDTContents, Para * @return the style of the paragraph */ public String getStyle() { - CTPPr pr = getCTPPr(); + CTPPr pr = getCTPPr(false); + if (pr == null) { + return null; + } CTString style = pr.isSetPStyle() ? pr.getPStyle() : null; return style != null ? style.getVal() : null; } @@ -1411,7 +1440,10 @@ public class XWPFParagraph implements IBodyElement, IRunBody, ISDTContents, Para * a new instance. */ private CTPBdr getCTPBrd(boolean create) { - CTPPr pr = getCTPPr(); + CTPPr pr = getCTPPr(create); + if (pr == null) { + return null; + } CTPBdr ct = pr.isSetPBdr() ? pr.getPBdr() : null; if (create && ct == null) { ct = pr.addNewPBdr(); @@ -1424,7 +1456,7 @@ public class XWPFParagraph implements IBodyElement, IRunBody, ISDTContents, Para * return a new instance. */ private CTSpacing getCTSpacing(boolean create) { - CTPPr pr = getCTPPr(); + CTPPr pr = getCTPPr(create); CTSpacing ct = pr.getSpacing(); if (create && ct == null) { ct = pr.addNewSpacing(); @@ -1437,7 +1469,10 @@ public class XWPFParagraph implements IBodyElement, IRunBody, ISDTContents, Para * a new instance. */ private CTInd getCTInd(boolean create) { - CTPPr pr = getCTPPr(); + CTPPr pr = getCTPPr(create); + if (pr == null) { + return null; + } CTInd ct = pr.getInd(); if (create && ct == null) { ct = pr.addNewInd(); @@ -1451,8 +1486,18 @@ public class XWPFParagraph implements IBodyElement, IRunBody, ISDTContents, Para */ @Internal public CTPPr getCTPPr() { - return paragraph.getPPr() == null ? paragraph.addNewPPr() - : paragraph.getPPr(); + return getCTPPr(true); + } + + /** + * Get a copy of the currently used CTPPr. If none is used, return + * a new instance when create is true, or null when create is false. + * + * @param create create a new instance if none exists. + */ + private CTPPr getCTPPr(final boolean create) { + return (paragraph.isSetPPr() || !create) ? paragraph.getPPr() + : paragraph.addNewPPr(); } diff --git a/poi-ooxml/src/test/java/org/apache/poi/xwpf/TestXWPFBugs.java b/poi-ooxml/src/test/java/org/apache/poi/xwpf/TestXWPFBugs.java index ecf9d13601..4eefc3517a 100644 --- a/poi-ooxml/src/test/java/org/apache/poi/xwpf/TestXWPFBugs.java +++ b/poi-ooxml/src/test/java/org/apache/poi/xwpf/TestXWPFBugs.java @@ -16,10 +16,7 @@ ==================================================================== */ package org.apache.poi.xwpf; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.io.File; @@ -42,10 +39,7 @@ import org.apache.poi.poifs.crypt.HashAlgorithm; import org.apache.poi.poifs.filesystem.Ole10Native; import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.xwpf.extractor.XWPFWordExtractor; -import org.apache.poi.xwpf.usermodel.XWPFDocument; -import org.apache.poi.xwpf.usermodel.XWPFParagraph; -import org.apache.poi.xwpf.usermodel.XWPFTable; -import org.apache.poi.xwpf.usermodel.XWPFTableCell; +import org.apache.poi.xwpf.usermodel.*; import org.apache.xmlbeans.XmlCursor; import org.apache.xmlbeans.XmlException; import org.junit.jupiter.api.Test; @@ -241,4 +235,17 @@ class TestXWPFBugs { XWPFTable xwpfTable = document.insertNewTbl(xmlCursor); xwpfTable.getRow(0).getCell(0).setText("Hello"); } + + @Test + void correctParagraphAlignment() throws IOException { + try (XWPFDocument document = new XWPFDocument(samples.openResourceAsStream("bug-paragraph-alignment.docx"))) { + XWPFParagraph centeredParagraph = document.getParagraphArray(0); + assertFalse(centeredParagraph.isAlignmentSet()); + assertEquals(ParagraphAlignment.LEFT, centeredParagraph.getAlignment()); // LEFT is a fallback value here. + + XWPFParagraph leftParagraph = document.getParagraphArray(1); + assertTrue(leftParagraph.isAlignmentSet()); + assertEquals(ParagraphAlignment.LEFT, leftParagraph.getAlignment()); // LEFT is the real alignment value. + } + } } diff --git a/poi-ooxml/src/test/java/org/apache/poi/xwpf/usermodel/TestXWPFParagraph.java b/poi-ooxml/src/test/java/org/apache/poi/xwpf/usermodel/TestXWPFParagraph.java index 518734f4f6..2aaf6b1072 100644 --- a/poi-ooxml/src/test/java/org/apache/poi/xwpf/usermodel/TestXWPFParagraph.java +++ b/poi-ooxml/src/test/java/org/apache/poi/xwpf/usermodel/TestXWPFParagraph.java @@ -132,6 +132,7 @@ public final class TestXWPFParagraph { XWPFParagraph p = doc.createParagraph(); assertEquals(STJc.LEFT.intValue(), p.getAlignment().getValue()); + assertFalse(p.isAlignmentSet()); CTP ctp = p.getCTP(); CTPPr ppr = ctp.getPPr() == null ? ctp.addNewPPr() : ctp.getPPr(); @@ -139,9 +140,15 @@ public final class TestXWPFParagraph { CTJc align = ppr.addNewJc(); align.setVal(STJc.CENTER); assertEquals(ParagraphAlignment.CENTER, p.getAlignment()); + assertTrue(p.isAlignmentSet()); p.setAlignment(ParagraphAlignment.BOTH); assertEquals(STJc.BOTH, ppr.getJc().getVal()); + assertTrue(p.isAlignmentSet()); + + p.setAlignment(null); + assertEquals(STJc.LEFT.intValue(), p.getAlignment().getValue()); + assertFalse(p.isAlignmentSet()); } } diff --git a/test-data/document/bug-paragraph-alignment.docx b/test-data/document/bug-paragraph-alignment.docx new file mode 100644 index 0000000000000000000000000000000000000000..de4a468552ff002cc6519843489416f6c730abeb GIT binary patch literal 11916 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 w75<+57x