From 1a3032f2533efef0d4dc942acf52bad61545e3f4 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Mon, 20 Aug 2018 18:12:37 +0000 Subject: [PATCH] [github-122] XWPF: Set table and table cell widths to percentage, twips, or auto. This closes #122 git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1838449 13f79535-47bb-0310-9956-ffa450edef68 --- .../poi/xwpf/usermodel/TableWidthType.java | 54 ++++++ .../apache/poi/xwpf/usermodel/XWPFTable.java | 181 +++++++++++++++++- .../poi/xwpf/usermodel/XWPFTableCell.java | 75 +++++++- .../poi/xwpf/usermodel/TestXWPFTable.java | 60 +++++- .../poi/xwpf/usermodel/TestXWPFTableCell.java | 14 ++ 5 files changed, 371 insertions(+), 13 deletions(-) create mode 100644 src/ooxml/java/org/apache/poi/xwpf/usermodel/TableWidthType.java diff --git a/src/ooxml/java/org/apache/poi/xwpf/usermodel/TableWidthType.java b/src/ooxml/java/org/apache/poi/xwpf/usermodel/TableWidthType.java new file mode 100644 index 0000000000..34a1f2af9b --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/TableWidthType.java @@ -0,0 +1,54 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ +package org.apache.poi.xwpf.usermodel; + +import org.apache.poi.util.Internal; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.STTblWidth; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.STTblWidth.Enum; + +/** + * The width types for tables and table cells. Table width can be specified as "auto" (AUTO), + * an absolute value in 20ths of a point (DXA), or as a percentage (PCT). + * @since 4.0.0 + */ +public enum TableWidthType { + AUTO(STTblWidth.AUTO), /* Width is determined by content. */ + DXA(STTblWidth.DXA), /* Width is an integer number of 20ths of a point (twips) */ + NIL(STTblWidth.NIL), /* No width value set */ + PCT(STTblWidth.PCT); /* Width is a percentage, e.g. "33.3%" or 50 times percentage value, rounded to an integer, */ + /* e.g. 2500 for 50% */ + + private Enum type = STTblWidth.NIL; + + TableWidthType(STTblWidth.Enum type) { + this.type = type; + } + + protected STTblWidth.Enum getSTWidthType() { + return this.type; + } + + /** + * Get the underlying STTblWidth enum value. + * + * @return STTblWidth.Enum value + */ + @Internal + public STTblWidth.Enum getStWidthType() { + return this.type; + } +} diff --git a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFTable.java b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFTable.java index ad93ed375b..0caca8c6dd 100644 --- a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFTable.java +++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFTable.java @@ -18,13 +18,12 @@ package org.apache.poi.xwpf.usermodel; import java.math.BigInteger; import java.util.ArrayList; +import java.util.Collections; import java.util.EnumMap; import java.util.HashMap; import java.util.List; -import java.util.Collections; import java.util.function.Consumer; import java.util.function.Function; -import java.util.function.Supplier; import org.apache.poi.ooxml.POIXMLDocumentPart; import org.apache.poi.util.Internal; @@ -54,6 +53,11 @@ import org.openxmlformats.schemas.wordprocessingml.x2006.main.STTblWidth; @SuppressWarnings("WeakerAccess") public class XWPFTable implements IBodyElement, ISDTContents { + public static final String REGEX_PERCENTAGE = "[0-9]+(\\.[0-9]+)?%"; + public static final String DEFAULT_PERCENTAGE_WIDTH = "100%"; + static final String NS_OOXML_WP_MAIN = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"; + public static final String REGEX_WIDTH_VALUE = "auto|[0-9]+|" + REGEX_PERCENTAGE; + // Create a map from this XWPF-level enum to the STBorder.Enum values public enum XWPFBorderType { NIL, NONE, SINGLE, THICK, DOUBLE, DOTTED, DASHED, DOT_DASH, DOT_DOT_DASH, TRIPLE, @@ -282,7 +286,10 @@ public class XWPFTable implements IBodyElement, ISDTContents { } /** - * @return width value + * Get the width value as an integer. + *

If the width type is AUTO, DXA, or NIL, the value is 20ths of a point. If + * the width type is PCT, the value is the percentage times 50 (e.g., 2500 for 50%).

+ * @return width value as an integer */ public int getWidth() { CTTblPr tblPr = getTblPr(); @@ -290,12 +297,14 @@ public class XWPFTable implements IBodyElement, ISDTContents { } /** - * @param width + * Set the width in 20ths of a point (twips). + * @param width Width value (20ths of a point) */ public void setWidth(int width) { CTTblPr tblPr = getTblPr(); CTTblWidth tblWidth = tblPr.isSetTblW() ? tblPr.getTblW() : tblPr.addNewTblW(); tblWidth.setW(new BigInteger(Integer.toString(width))); + tblWidth.setType(STTblWidth.DXA); } /** @@ -1125,4 +1134,168 @@ public class XWPFTable implements IBodyElement, ISDTContents { } return null; } + + /** + * Get the table width as a decimal value. + *

If the width type is DXA or AUTO, then the value will always have + * a fractional part of zero (because these values are really integers). + * If the with type is percentage, then value may have a non-zero fractional + * part. + * + * @return Width value as a double-precision decimal. + * @since 4.0.0 + */ + public double getWidthDecimal() { + return getWidthDecimal(getTblPr().getTblW()); + } + + /** + * Get the width as a decimal value. + *

This method is also used by table cells. + * @param ctWidth Width value to evaluate. + * @return Width value as a decimal + * @since 4.0.0 + */ + protected static double getWidthDecimal(CTTblWidth ctWidth) { + double result = 0.0; + STTblWidth.Enum typeValue = ctWidth.getType(); + if (typeValue == STTblWidth.DXA + || typeValue == STTblWidth.AUTO + || typeValue == STTblWidth.NIL) { + result = 0.0 + ctWidth.getW().intValue(); + } else if (typeValue == STTblWidth.PCT) { + // Percentage values are stored as integers that are 50 times + // percentage. + result = ctWidth.getW().intValue() / 50.0; + } else { + // Should never get here + } + return result; + } + + /** + * Get the width type for the table, as an {@link STTblWidth.Enum} value. + * A table width can be specified as an absolute measurement (an integer + * number of twips), a percentage, or the value "AUTO". + * + * @return The width type. + * @since 4.0.0 + */ + public TableWidthType getWidthType() { + return getWidthType(getTblPr().getTblW()); + } + + /** + * Get the width type from the width value + * @param ctWidth CTTblWidth to evalute + * @return The table width type + * @since 4.0.0 + */ + protected static TableWidthType getWidthType(CTTblWidth ctWidth) { + STTblWidth.Enum typeValue = ctWidth.getType(); + if (typeValue == null) { + typeValue = STTblWidth.NIL; + ctWidth.setType(typeValue); + } + switch (typeValue.intValue()) { + case STTblWidth.INT_NIL: + return TableWidthType.NIL; + case STTblWidth.INT_AUTO: + return TableWidthType.AUTO; + case STTblWidth.INT_DXA: + return TableWidthType.DXA; + case STTblWidth.INT_PCT: + return TableWidthType.PCT; + default: + // Should never get here + return TableWidthType.AUTO; + } + } + + /** + * Set the width to the value "auto", an integer value (20ths of a point), or a percentage ("nn.nn%"). + * + * @param widthValue String matching one of "auto", [0-9]+, or [0-9]+(\.[0-9]+)%. + * @since 4.0.0 + */ + public void setWidth(String widthValue) { + setWidthValue(widthValue, getTblPr().getTblW()); + } + + /** + * Set the width value from a string + * @param widthValue The width value string + * @param ctWidth CTTblWidth to set the value on. + */ + protected static void setWidthValue(String widthValue, CTTblWidth ctWidth) { + if (!widthValue.matches(REGEX_WIDTH_VALUE)) { + throw new RuntimeException("Table width value \"" + widthValue + "\" " + + "must match regular expression \"" + REGEX_WIDTH_VALUE + "\"."); + } + if (widthValue.matches("auto")) { + ctWidth.setType(STTblWidth.AUTO); + ctWidth.setW(BigInteger.ZERO); + } else if (widthValue.matches(REGEX_PERCENTAGE)) { + setWidthPercentage(ctWidth, widthValue); + } else { + // Must be an integer + ctWidth.setW(new BigInteger(widthValue)); + ctWidth.setType(STTblWidth.DXA); + } + } + + /** + * Set the underlying table width value to a percentage value. + * @param ctWidth The CTTblWidth to set the value on + * @param widthValue String width value in form "33.3%" or an integer that is 50 times desired percentage value (e.g, + * 2500 for 50%) + * @since 4.0.0 + */ + protected static void setWidthPercentage(CTTblWidth ctWidth, String widthValue) { + ctWidth.setType(STTblWidth.PCT); + if (widthValue.matches(REGEX_PERCENTAGE)) { + String numberPart = widthValue.substring(0, widthValue.length() - 1); + double percentage = Double.parseDouble(numberPart) * 50; + long intValue = Math.round(percentage); + ctWidth.setW(BigInteger.valueOf(intValue)); + } else if (widthValue.matches("[0-9]+")) { + ctWidth.setW(new BigInteger(widthValue)); + } else { + throw new RuntimeException("setWidthPercentage(): Width value must be a percentage (\"33.3%\" or an integer, was \"" + widthValue + "\""); + } + } + + /** + * Set the width value type for the table. + *

If the width type is changed from the current type and the currently-set value + * is not consistent with the new width type, the value is reset to the default value + * for the specified width type.

+ * + * @param widthType Width type + * @since 4.0.0 + */ + public void setWidthType(TableWidthType widthType) { + setWidthType(widthType, getTblPr().getTblW()); + } + + /** + * Set the width type if different from current width type + * @param widthType The new width type + * @param ctWidth CTTblWidth to set the type on + * @since 4.0.0 + */ + protected static void setWidthType(TableWidthType widthType, CTTblWidth ctWidth) { + TableWidthType currentType = getWidthType(ctWidth); + if (!currentType.equals(widthType)) { + STTblWidth.Enum stWidthType = widthType.getSTWidthType(); + ctWidth.setType(stWidthType); + switch (stWidthType.intValue()) { + case STTblWidth.INT_PCT: + setWidthPercentage(ctWidth, DEFAULT_PERCENTAGE_WIDTH); + break; + default: + ctWidth.setW(BigInteger.ZERO); + } + } + } } diff --git a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFTableCell.java b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFTableCell.java index 169aef21d6..745f5b2879 100644 --- a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFTableCell.java +++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFTableCell.java @@ -32,10 +32,12 @@ import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSdtBlock; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSdtRun; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTShd; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblWidth; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTc; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTVerticalJc; import org.openxmlformats.schemas.wordprocessingml.x2006.main.STShd; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.STTblWidth; import org.openxmlformats.schemas.wordprocessingml.x2006.main.STVerticalJc; /** @@ -216,7 +218,7 @@ public class XWPFTableCell implements IBody, ICell { * @param rgbStr - the desired cell color, in the hex form "RRGGBB". */ public void setColor(String rgbStr) { - CTTcPr tcpr = ctTc.isSetTcPr() ? ctTc.getTcPr() : ctTc.addNewTcPr(); + CTTcPr tcpr = getTcPr(); CTShd ctshd = tcpr.isSetShd() ? tcpr.getShd() : tcpr.addNewShd(); ctshd.setColor("auto"); ctshd.setVal(STShd.CLEAR); @@ -247,7 +249,7 @@ public class XWPFTableCell implements IBody, ICell { * @param vAlign - the desired alignment enum value */ public void setVerticalAlignment(XWPFVertAlign vAlign) { - CTTcPr tcpr = ctTc.isSetTcPr() ? ctTc.getTcPr() : ctTc.addNewTcPr(); + CTTcPr tcpr = getTcPr(); CTVerticalJc va = tcpr.addNewVAlign(); va.setVal(alignMap.get(vAlign)); } @@ -407,7 +409,7 @@ public class XWPFTableCell implements IBody, ICell { public void insertTable(int pos, XWPFTable table) { bodyElements.add(pos, table); int i = 0; - for (CTTbl tbl : ctTc.getTblArray()) { + for (CTTbl tbl : ctTc.getTblList()) { if (tbl == table.getCTTbl()) { break; } @@ -510,4 +512,71 @@ public class XWPFTableCell implements IBody, ICell { public enum XWPFVertAlign { TOP, CENTER, BOTH, BOTTOM } + + /** + * Get the table width as a decimal value. + *

If the width type is DXA or AUTO, then the value will always have + * a fractional part of zero (because these values are really integers). + * If the with type is percentage, then value may have a non-zero fractional + * part. + * + * @return Width value as a double-precision decimal. + * @since 4.0.0 + */ + public double getWidthDecimal() { + return XWPFTable.getWidthDecimal(getTcWidth()); + } + + /** + * Get the width type for the table, as an {@link STTblWidth.Enum} value. + * A table width can be specified as an absolute measurement (an integer + * number of twips), a percentage, or the value "AUTO". + * + * @return The width type. + * @since 4.0.0 + */ + public TableWidthType getWidthType() { + return XWPFTable.getWidthType(getTcWidth()); + } + + /** + * Set the width to the value "auto", an integer value (20ths of a point), or a percentage ("nn.nn%"). + * + * @param widthValue String matching one of "auto", [0-9]+, or [0-9]+(\.[0-9]+)%. + * @since 4.0.0 + */ + public void setWidth(String widthValue) { + XWPFTable.setWidthValue(widthValue, getTcWidth()); + } + + private CTTblWidth getTcWidth() { + CTTcPr tcPr = getTcPr(); + return tcPr.isSetTcW() ? tcPr.getTcW() : tcPr.addNewTcW(); + } + + /** + * Get the cell properties for the cell. + * @return The cell properties + * @since 4.0.0 + */ + protected CTTcPr getTcPr() { + return ctTc.isSetTcPr() ? ctTc.getTcPr() : ctTc.addNewTcPr(); + } + + /** + * Set the width value type for the table. + *

If the width type is changed from the current type and the currently-set value + * is not consistent with the new width type, the value is reset to the default value + * for the specified width type.

+ * + * @param widthType Width type + * @since 4.0.0 + */ + public void setWidthType(TableWidthType widthType) { + XWPFTable.setWidthType(widthType, getTcWidth()); + } + + public int getWidth() { + return getTcWidth().getW().intValue(); + } } diff --git a/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFTable.java b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFTable.java index 96e7b22717..77ba00e0f2 100644 --- a/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFTable.java +++ b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFTable.java @@ -146,15 +146,63 @@ public class TestXWPFTable { public void testSetGetWidth() { XWPFDocument doc = new XWPFDocument(); - CTTbl table = CTTbl.Factory.newInstance(); - table.addNewTblPr().addNewTblW().setW(new BigInteger("1000")); - - XWPFTable xtab = new XWPFTable(table, doc); + XWPFTable xtab = doc.createTable(); + assertEquals(0, xtab.getWidth()); + assertEquals(TableWidthType.AUTO, xtab.getWidthType()); + + xtab.setWidth(1000); + assertEquals(TableWidthType.DXA, xtab.getWidthType()); assertEquals(1000, xtab.getWidth()); + + xtab.setWidth("auto"); + assertEquals(TableWidthType.AUTO, xtab.getWidthType()); + assertEquals(0, xtab.getWidth()); + assertEquals(0.0, xtab.getWidthDecimal(), 0.01); + + xtab.setWidth("999"); + assertEquals(TableWidthType.DXA, xtab.getWidthType()); + assertEquals(999, xtab.getWidth()); + + xtab.setWidth("50.5%"); + assertEquals(TableWidthType.PCT, xtab.getWidthType()); + assertEquals(50.5, xtab.getWidthDecimal(), 0.01); + + // Test effect of setting width type to a new value + + // From PCT to NIL: + xtab.setWidthType(TableWidthType.NIL); + assertEquals(TableWidthType.NIL, xtab.getWidthType()); + assertEquals(0, xtab.getWidth()); + + xtab.setWidth("999"); // Sets type to DXA + assertEquals(TableWidthType.DXA, xtab.getWidthType()); + + // From DXA to AUTO: + xtab.setWidthType(TableWidthType.AUTO); + assertEquals(TableWidthType.AUTO, xtab.getWidthType()); + assertEquals(0, xtab.getWidth()); + + xtab.setWidthType(TableWidthType.PCT); + assertEquals(TableWidthType.PCT, xtab.getWidthType()); + + // From PCT to DXA: + xtab.setWidth("33.3%"); + xtab.setWidthType(TableWidthType.DXA); + assertEquals(TableWidthType.DXA, xtab.getWidthType()); + assertEquals(0, xtab.getWidth()); + + // From DXA to DXA: (value should be unchanged) + xtab.setWidth("999"); + xtab.setWidthType(TableWidthType.DXA); + assertEquals(TableWidthType.DXA, xtab.getWidthType()); + assertEquals(999, xtab.getWidth()); + + // From DXA to PCT: + xtab.setWidthType(TableWidthType.PCT); + assertEquals(TableWidthType.PCT, xtab.getWidthType()); + assertEquals(100.0, xtab.getWidthDecimal(), 0.0); - xtab.setWidth(100); - assertEquals(100, table.getTblPr().getTblW().getW().intValue()); try { doc.close(); } catch (IOException e) { diff --git a/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFTableCell.java b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFTableCell.java index d1cf5b2e90..4b711c6a77 100644 --- a/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFTableCell.java +++ b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFTableCell.java @@ -133,4 +133,18 @@ public class TestXWPFTableCell { } } } + + @Test + public void testCellGetSetWidth() throws Exception { + XWPFDocument doc = new XWPFDocument(); + XWPFTable table = doc.createTable(); + XWPFTableRow tr = table.createRow(); + XWPFTableCell cell = tr.addNewTableCell(); + + cell.setWidth("50%"); + assertEquals(TableWidthType.PCT, cell.getWidthType()); + assertEquals(50.0, cell.getWidthDecimal(), 0.0); + assertEquals(2500, cell.getWidth()); + doc.close(); + } } -- 2.39.5