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 0000000000..de4a468552 Binary files /dev/null and b/test-data/document/bug-paragraph-alignment.docx differ