From 2d2aa5196a56908704dcac101543aef0183c143f Mon Sep 17 00:00:00 2001 From: Greg Woolsey Date: Sat, 20 May 2017 04:05:32 +0000 Subject: [PATCH] #61085 support table styles Added more features - this allows a client app to fully display table styling without resorting to CT* classes, and has some helper logic for the additional processing needed for things like regional/area borders. As far as I can tell I only added stuff, didn't change any existing behavior. git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1795648 13f79535-47bb-0310-9956-ffa450edef68 --- .../HSSFConditionalFormattingRule.java | 8 + .../usermodel/DifferentialStyleProvider.java | 7 + .../org/apache/poi/ss/usermodel/Table.java | 38 ++- .../apache/poi/ss/usermodel/TableStyle.java | 17 +- .../poi/ss/usermodel/TableStyleType.java | 250 ++++++++++++++++-- .../poi/ss/util/CellRangeAddressBase.java | 42 +++ .../apache/poi/xssf/model/StylesTable.java | 13 +- .../xssf/usermodel/XSSFBuiltinTableStyle.java | 54 +++- .../XSSFConditionalFormattingRule.java | 8 + .../xssf/usermodel/XSSFDxfStyleProvider.java | 9 +- .../apache/poi/xssf/usermodel/XSSFTable.java | 112 +++++++- .../poi/xssf/usermodel/XSSFTableStyle.java | 23 +- 12 files changed, 543 insertions(+), 38 deletions(-) diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFConditionalFormattingRule.java b/src/java/org/apache/poi/hssf/usermodel/HSSFConditionalFormattingRule.java index 71920a9ff..ebbec778c 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFConditionalFormattingRule.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFConditionalFormattingRule.java @@ -314,4 +314,12 @@ public final class HSSFConditionalFormattingRule implements ConditionalFormattin } return HSSFFormulaParser.toFormulaString(workbook, parsedExpression); } + + /** + * Conditional format rules don't define stripes, so always 0 + * @see org.apache.poi.ss.usermodel.DifferentialStyleProvider#getStripeSize() + */ + public int getStripeSize() { + return 0; + } } diff --git a/src/java/org/apache/poi/ss/usermodel/DifferentialStyleProvider.java b/src/java/org/apache/poi/ss/usermodel/DifferentialStyleProvider.java index 1b488353c..ce6ad535e 100644 --- a/src/java/org/apache/poi/ss/usermodel/DifferentialStyleProvider.java +++ b/src/java/org/apache/poi/ss/usermodel/DifferentialStyleProvider.java @@ -46,4 +46,11 @@ public interface DifferentialStyleProvider { */ PatternFormatting getPatternFormatting(); + /** + * This is the number of rows or columns in a band or stripe. + * For styles that represent stripes, it must be > 1, for all others it is 0. + * Not the greatest overloading by the OOXML spec. + * @return number of rows/columns in a stripe for stripe styles, 0 for all others + */ + int getStripeSize(); } diff --git a/src/java/org/apache/poi/ss/usermodel/Table.java b/src/java/org/apache/poi/ss/usermodel/Table.java index 01e0394aa..4d8aba1d3 100644 --- a/src/java/org/apache/poi/ss/usermodel/Table.java +++ b/src/java/org/apache/poi/ss/usermodel/Table.java @@ -59,6 +59,12 @@ public interface Table { */ String getName(); + /** + * @return name of the table style, if there is one. May be a built-in name or user-defined. + * @since 3.17 beta 1 + */ + String getStyleName(); + /** * Returns the index of a given named column in the table (names are case insensitive in XSSF). * Note this list is lazily loaded and cached for performance. @@ -70,16 +76,46 @@ public interface Table { int findColumnIndex(String columnHeader); /** * Returns the sheet name that the table belongs to. + * @return sheet name */ String getSheetName(); + /** - * Returns true iff the table has a 'Totals' row + * Note: This is misleading. The OOXML spec indicates this is true if the totals row + * has ever been shown, not whether or not it is currently displayed. + * Use {@link #getTotalsRowCount()} > 0 to decide whether or not the totals row is visible. + * @return true if a totals row has ever been shown for this table + * @since 3.15 beta 2 + * @see #getTotalsRowCount() */ boolean isHasTotalsRow(); + /** + * @return 0 for no totals rows, 1 for totals row shown. + * Values > 1 are not currently used by Excel up through 2016, and the OOXML spec + * doesn't define how they would be implemented. + * @since 3.17 beta 1 + */ + int getTotalsRowCount(); + + /** + * @return 0 for no header rows, 1 for table headers shown. + * Values > 1 might be used by Excel for pivot tables? + * @since 3.17 beta 1 + */ + int getHeaderRowCount(); + /** * @return TableStyleInfo for this instance * @since 3.17 beta 1 */ TableStyleInfo getStyle(); + + /** + * checks if the given cell is part of the table. Includes checking that they are on the same sheet. + * @param cell + * @return true if the table and cell are on the same sheet and the cell is within the table range. + * @since 3.17 beta 1 + */ + boolean contains(Cell cell); } diff --git a/src/java/org/apache/poi/ss/usermodel/TableStyle.java b/src/java/org/apache/poi/ss/usermodel/TableStyle.java index e80e0ced7..b4e5e1983 100644 --- a/src/java/org/apache/poi/ss/usermodel/TableStyle.java +++ b/src/java/org/apache/poi/ss/usermodel/TableStyle.java @@ -27,10 +27,25 @@ package org.apache.poi.ss.usermodel; public interface TableStyle { /** - * @return name (may be a builtin name) + * @return name (may be a built-in name) */ String getName(); + /** + * Some clients may care where in the table style list this definition came from, so we'll track it. + * The spec only references these by name, unlike Dxf records, which these definitions reference by index + * (XML definition order). Nice of MS to be consistent when defining the ECMA standard. + * Use org.apache.poi.xssf.usermodel.XSSFBuiltinTableStyle.isBuiltinStyle(TableStyle) to determine whether the index is for a built-in style or explicit user style + * @return index from org.apache.poi.xssf.model.StylesTable.getExplicitTableStyle(String) or org.apache.poi.xssf.usermodel.XSSFBuiltinTableStyle.ordinal() + */ + int getIndex(); + + /** + * + * @return true if this is a built-in style defined in the OOXML specification, false if it is a user style + */ + boolean isBuiltin(); + /** * * @param type diff --git a/src/java/org/apache/poi/ss/usermodel/TableStyleType.java b/src/java/org/apache/poi/ss/usermodel/TableStyleType.java index 04bfb4ce5..e6fa9dce2 100644 --- a/src/java/org/apache/poi/ss/usermodel/TableStyleType.java +++ b/src/java/org/apache/poi/ss/usermodel/TableStyleType.java @@ -17,6 +17,11 @@ package org.apache.poi.ss.usermodel; +import java.util.EnumSet; + +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.ss.util.CellRangeAddressBase; + /** * Ordered list of table style elements, for both data tables and pivot tables. * Some elements only apply to pivot tables, but any style definition can omit any number, @@ -33,34 +38,243 @@ package org.apache.poi.ss.usermodel; * @since 3.17 beta 1 */ public enum TableStyleType { + /***/ + wholeTable { + CellRangeAddressBase getRange(Table table, Cell cell) { + return new CellRangeAddress(table.getStartRowIndex(), table.getEndRowIndex(), table.getStartColIndex(), table.getEndColIndex()); + } + }, + /***/ + pageFieldLabels, // pivot only + /***/ + pageFieldValues, // pivot only + /***/ + firstColumnStripe{ + CellRangeAddressBase getRange(Table table, Cell cell) { + TableStyleInfo info = table.getStyle(); + if (! info.isShowColumnStripes()) return null; + DifferentialStyleProvider c1Style = info.getStyle().getStyle(firstColumnStripe); + DifferentialStyleProvider c2Style = info.getStyle().getStyle(secondColumnStripe); + int c1Stripe = c1Style == null ? 1 : Math.max(1, c1Style.getStripeSize()); + int c2Stripe = c2Style == null ? 1 : Math.max(1, c2Style.getStripeSize()); + + int firstStart = table.getStartColIndex(); + int secondStart = firstStart + c1Stripe; + int c = cell.getColumnIndex(); + + // look for the stripe containing c, accounting for the style element stripe size + // could do fancy math, but tables can't be that wide, a simple loop is fine + // if not in this type of stripe, return null + while (true) { + if (firstStart > c) break; + if (c >= firstStart && c <= secondStart -1) return new CellRangeAddress(table.getStartRowIndex(), table.getEndRowIndex(), firstStart, secondStart - 1); + firstStart = secondStart + c2Stripe; + secondStart = firstStart + c1Stripe; + } + return null; + } + }, + /***/ + secondColumnStripe{ + CellRangeAddressBase getRange(Table table, Cell cell) { + TableStyleInfo info = table.getStyle(); + if (! info.isShowColumnStripes()) return null; + + DifferentialStyleProvider c1Style = info.getStyle().getStyle(firstColumnStripe); + DifferentialStyleProvider c2Style = info.getStyle().getStyle(secondColumnStripe); + int c1Stripe = c1Style == null ? 1 : Math.max(1, c1Style.getStripeSize()); + int c2Stripe = c2Style == null ? 1 : Math.max(1, c2Style.getStripeSize()); - wholeTable, - headerRow, - totalRow, - firstColumn, - lastColumn, - firstRowStripe, - secondRowStripe, - firstColumnStripe, - secondColumnStripe, - firstHeaderCell, - lastHeaderCell, - firstTotalCell, - lastTotalCell, + int firstStart = table.getStartColIndex(); + int secondStart = firstStart + c1Stripe; + int c = cell.getColumnIndex(); + + // look for the stripe containing c, accounting for the style element stripe size + // could do fancy math, but tables can't be that wide, a simple loop is fine + // if not in this type of stripe, return null + while (true) { + if (firstStart > c) break; + if (c >= secondStart && c <= secondStart + c2Stripe -1) return new CellRangeAddress(table.getStartRowIndex(), table.getEndRowIndex(), secondStart, secondStart + c2Stripe - 1); + firstStart = secondStart + c2Stripe; + secondStart = firstStart + c1Stripe; + } + return null; + } + }, + /***/ + firstRowStripe { + CellRangeAddressBase getRange(Table table, Cell cell) { + TableStyleInfo info = table.getStyle(); + if (! info.isShowRowStripes()) return null; + + DifferentialStyleProvider c1Style = info.getStyle().getStyle(firstRowStripe); + DifferentialStyleProvider c2Style = info.getStyle().getStyle(secondRowStripe); + int c1Stripe = c1Style == null ? 1 : Math.max(1, c1Style.getStripeSize()); + int c2Stripe = c2Style == null ? 1 : Math.max(1, c2Style.getStripeSize()); + + int firstStart = table.getStartRowIndex() + table.getHeaderRowCount(); + int secondStart = firstStart + c1Stripe; + int c = cell.getRowIndex(); + + // look for the stripe containing c, accounting for the style element stripe size + // could do fancy math, but tables can't be that wide, a simple loop is fine + // if not in this type of stripe, return null + while (true) { + if (firstStart > c) break; + if (c >= firstStart && c <= secondStart -1) return new CellRangeAddress(firstStart, secondStart - 1, table.getStartColIndex(), table.getEndColIndex()); + firstStart = secondStart + c2Stripe; + secondStart = firstStart + c1Stripe; + } + return null; + } + }, + /***/ + secondRowStripe{ + CellRangeAddressBase getRange(Table table, Cell cell) { + TableStyleInfo info = table.getStyle(); + if (! info.isShowRowStripes()) return null; + + DifferentialStyleProvider c1Style = info.getStyle().getStyle(firstRowStripe); + DifferentialStyleProvider c2Style = info.getStyle().getStyle(secondRowStripe); + int c1Stripe = c1Style == null ? 1 : Math.max(1, c1Style.getStripeSize()); + int c2Stripe = c2Style == null ? 1 : Math.max(1, c2Style.getStripeSize()); + + int firstStart = table.getStartRowIndex() + table.getHeaderRowCount(); + int secondStart = firstStart + c1Stripe; + int c = cell.getRowIndex(); + + // look for the stripe containing c, accounting for the style element stripe size + // could do fancy math, but tables can't be that wide, a simple loop is fine + // if not in this type of stripe, return null + while (true) { + if (firstStart > c) break; + if (c >= secondStart && c <= secondStart +c2Stripe -1) return new CellRangeAddress(secondStart, secondStart + c2Stripe - 1, table.getStartColIndex(), table.getEndColIndex()); + firstStart = secondStart + c2Stripe; + secondStart = firstStart + c1Stripe; + } + return null; + } + }, + /***/ + lastColumn { + CellRangeAddressBase getRange(Table table, Cell cell) { + if (! table.getStyle().isShowLastColumn()) return null; + return new CellRangeAddress(table.getStartRowIndex(), table.getEndRowIndex(), table.getEndColIndex(), table.getEndColIndex()); + } + }, + /***/ + firstColumn { + CellRangeAddressBase getRange(Table table, Cell cell) { + if (! table.getStyle().isShowFirstColumn()) return null; + return new CellRangeAddress(table.getStartRowIndex(), table.getEndRowIndex(), table.getStartColIndex(), table.getStartColIndex()); + } + }, + /***/ + headerRow { + CellRangeAddressBase getRange(Table table, Cell cell) { + if (table.getHeaderRowCount() < 1) return null; + return new CellRangeAddress(table.getStartRowIndex(), table.getStartRowIndex() + table.getHeaderRowCount() -1, table.getStartColIndex(), table.getEndColIndex()); + } + }, + /***/ + totalRow { + CellRangeAddressBase getRange(Table table, Cell cell) { + if (table.getTotalsRowCount() < 1) return null; + return new CellRangeAddress(table.getEndRowIndex() - table.getTotalsRowCount() +1, table.getEndRowIndex(), table.getStartColIndex(), table.getEndColIndex()); + } + }, + /***/ + firstHeaderCell { + CellRangeAddressBase getRange(Table table, Cell cell) { + if (table.getHeaderRowCount() < 1) return null; + return new CellRangeAddress(table.getStartRowIndex(), table.getStartRowIndex(), table.getStartColIndex(), table.getStartColIndex()); + } + }, + /***/ + lastHeaderCell { + CellRangeAddressBase getRange(Table table, Cell cell) { + if (table.getHeaderRowCount() < 1) return null; + return new CellRangeAddress(table.getStartRowIndex(), table.getStartRowIndex(), table.getEndColIndex(), table.getEndColIndex()); + } + }, + /***/ + firstTotalCell { + CellRangeAddressBase getRange(Table table, Cell cell) { + if (table.getTotalsRowCount() < 1) return null; + return new CellRangeAddress(table.getEndRowIndex() - table.getTotalsRowCount() +1, table.getEndRowIndex(), table.getStartColIndex(), table.getStartColIndex()); + } + }, + /***/ + lastTotalCell { + CellRangeAddressBase getRange(Table table, Cell cell) { + if (table.getTotalsRowCount() < 1) return null; + return new CellRangeAddress(table.getEndRowIndex() - table.getTotalsRowCount() +1, table.getEndRowIndex(), table.getEndColIndex(), table.getEndColIndex()); + } + }, + /* these are for pivot tables only */ + /***/ firstSubtotalColumn, + /***/ secondSubtotalColumn, + /***/ thirdSubtotalColumn, - firstSubtotalRow, - secondSubtotalRow, - thirdSubtotalRow, + /***/ blankRow, + /***/ + firstSubtotalRow, + /***/ + secondSubtotalRow, + /***/ + thirdSubtotalRow, + /***/ firstColumnSubheading, + /***/ secondColumnSubheading, + /***/ thirdColumnSubheading, + /***/ firstRowSubheading, + /***/ secondRowSubheading, + /***/ thirdRowSubheading, - pageFieldLabels, - pageFieldValues, ; + + /** + * A range is returned only for the part of the table matching this enum instance and containing the given cell. + * Null is returned for all other cases, such as: + * + * The returned range can be used to determine how style options may or may not apply to this cell. + * For example, {@link #wholeTable} borders only apply to the outer boundary of a table, while the + * rest of the styling, such as font and color, could apply to all the interior cells as well. + * + * @param table table to evaluate + * @param cell to evaluate + * @return range in the table representing this class of cells, if it contains the given cell, or null if not applicable. + * Stripe style types return only the stripe range containing the given cell, or null. + */ + public CellRangeAddressBase appliesTo(Table table, Cell cell) { + if (table == null || cell == null) return null; + if ( ! cell.getSheet().getSheetName().equals(table.getSheetName())) return null; + if ( ! table.contains(cell)) return null; + + final CellRangeAddressBase range = getRange(table, cell); + if (range != null && range.isInRange(cell.getRowIndex(), cell.getColumnIndex())) return range; + // else + return null; + } + + /** + * @param table + * @param cell + * @return default is unimplemented/null + */ + CellRangeAddressBase getRange(Table table, Cell cell) { + return null; + } } diff --git a/src/java/org/apache/poi/ss/util/CellRangeAddressBase.java b/src/java/org/apache/poi/ss/util/CellRangeAddressBase.java index e4dc4aeee..cd1052ef2 100644 --- a/src/java/org/apache/poi/ss/util/CellRangeAddressBase.java +++ b/src/java/org/apache/poi/ss/util/CellRangeAddressBase.java @@ -17,6 +17,9 @@ package org.apache.poi.ss.util; +import java.util.EnumSet; +import java.util.Set; + import org.apache.poi.ss.SpreadsheetVersion; import org.apache.poi.ss.usermodel.Cell; @@ -28,6 +31,23 @@ import org.apache.poi.ss.usermodel.Cell; */ public abstract class CellRangeAddressBase { + /** + * Indicates a cell or range is in the given relative position in a range. + * More than one of these may apply at once. + */ + public static enum CellPosition { + /** range starting rows are equal */ + TOP, + /** range ending rows are equal */ + BOTTOM, + /** range starting columns are equal */ + LEFT, + /** range ending columns are equal */ + RIGHT, + /** a cell or range is completely inside another range, without touching any edges (a cell in this position can't be in any others) */ + INSIDE, + ; + } private int _firstRow; private int _firstCol; private int _lastRow; @@ -188,6 +208,28 @@ public abstract class CellRangeAddressBase { other._firstCol <= this._lastCol; } + /** + * Useful for logic like table/range styling, where some elements apply based on relative position in a range. + * @param rowInd + * @param colInd + * @return set of {@link CellPosition}s occupied by the given coordinates. Empty if the coordinates are not in the range, never null. + * @since 3.17 beta 1 + */ + public Set getPosition(int rowInd, int colInd) { + Set positions = EnumSet.noneOf(CellPosition.class); + if (rowInd > getFirstRow() && rowInd < getLastRow() && colInd > getFirstColumn() && colInd < getLastColumn()) { + positions.add(CellPosition.INSIDE); + return positions; // entirely inside, matches no boundaries + } + // check edges + if (rowInd == getFirstRow()) positions.add(CellPosition.TOP); + if (rowInd == getLastRow()) positions.add(CellPosition.BOTTOM); + if (colInd == getFirstColumn()) positions.add(CellPosition.LEFT); + if (colInd == getLastColumn()) positions.add(CellPosition.RIGHT); + + return positions; + } + /** * @param firstCol column number for the upper left hand corner */ diff --git a/src/ooxml/java/org/apache/poi/xssf/model/StylesTable.java b/src/ooxml/java/org/apache/poi/xssf/model/StylesTable.java index 901757582..8582cf398 100644 --- a/src/ooxml/java/org/apache/poi/xssf/model/StylesTable.java +++ b/src/ooxml/java/org/apache/poi/xssf/model/StylesTable.java @@ -29,6 +29,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; @@ -246,8 +247,10 @@ public class StylesTable extends POIXMLDocumentPart { CTTableStyles ctTableStyles = styleSheet.getTableStyles(); if (ctTableStyles != null) { + int idx = 0; for (CTTableStyle style : Arrays.asList(ctTableStyles.getTableStyleArray())) { - tableStyles.put(style.getName(), new XSSFTableStyle(styleDxfs, style)); + tableStyles.put(style.getName(), new XSSFTableStyle(idx, styleDxfs, style)); + idx++; } } @@ -792,6 +795,14 @@ public class StylesTable extends POIXMLDocumentPart { return tableStyles.get(name); } + /** + * @return names of all explicitly defined table styles in the workbook + * @since 3.17 beta 1 + */ + public Set getExplicitTableStyleNames() { + return tableStyles.keySet(); + } + /** * @param name of the table style * @return defined style, either explicit or built-in, or null if not found diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFBuiltinTableStyle.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFBuiltinTableStyle.java index bf7431b59..aab86abca 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFBuiltinTableStyle.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFBuiltinTableStyle.java @@ -20,16 +20,15 @@ package org.apache.poi.xssf.usermodel; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.nio.charset.Charset; -import java.util.HashMap; +import java.util.EnumMap; import java.util.Map; +import org.apache.poi.ss.usermodel.DifferentialStyleProvider; import org.apache.poi.ss.usermodel.TableStyle; +import org.apache.poi.ss.usermodel.TableStyleType; import org.apache.poi.util.DocumentHelper; import org.apache.poi.util.IOUtils; import org.apache.poi.xssf.model.StylesTable; -import org.apache.xmlbeans.XmlOptions; -import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTDxfs; -import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableStyle; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -332,7 +331,10 @@ public enum XSSFBuiltinTableStyle { PivotStyleDark28, ; - private static final Map styleMap = new HashMap(60); + /** + * Interestingly, this is initialized after the enum instances, so using an {@link EnumMap} works. + */ + private static final Map styleMap = new EnumMap(XSSFBuiltinTableStyle.class); private XSSFBuiltinTableStyle() { } @@ -399,7 +401,8 @@ public enum XSSFBuiltinTableStyle { // - build a fake styles.xml file with just this built-in StylesTable styles = new StylesTable(); styles.readFrom(new ByteArrayInputStream(styleXML(dxfsNode, tableStyleNode).getBytes(Charset.forName("UTF-8")))); - styleMap.put(XSSFBuiltinTableStyle.valueOf(styleName), styles.getExplicitTableStyle(styleName)); + XSSFBuiltinTableStyle builtIn = XSSFBuiltinTableStyle.valueOf(styleName); + styleMap.put(builtIn, new XSSFBuiltinTypeStyleStyle(builtIn, styles.getExplicitTableStyle(styleName))); } } finally { IOUtils.closeQuietly(is); @@ -410,6 +413,10 @@ public enum XSSFBuiltinTableStyle { } private static String styleXML(Node dxfsNode, Node tableStyleNode) { + // built-ins doc uses 1-based dxf indexing, Excel uses 0 based. + // add a dummy node to adjust properly. + dxfsNode.insertBefore(dxfsNode.getOwnerDocument().createElement("dxf"), dxfsNode.getFirstChild()); + DOMImplementationLS lsImpl = (DOMImplementationLS)dxfsNode.getOwnerDocument().getImplementation().getFeature("LS", "3.0"); LSSerializer lsSerializer = lsImpl.createLSSerializer(); lsSerializer.getDomConfig().setParameter("xml-declaration", false); @@ -425,4 +432,39 @@ public enum XSSFBuiltinTableStyle { sb.append(""); return sb.toString(); } + + /** + * implementation for built-in styles + */ + protected static class XSSFBuiltinTypeStyleStyle implements TableStyle { + + private final XSSFBuiltinTableStyle builtIn; + private final TableStyle style; + + /** + * @param builtIn + * @param style + */ + protected XSSFBuiltinTypeStyleStyle(XSSFBuiltinTableStyle builtIn, TableStyle style) { + this.builtIn = builtIn; + this.style = style; + } + + public String getName() { + return style.getName(); + } + + public int getIndex() { + return builtIn.ordinal(); + } + + public boolean isBuiltin() { + return true; + } + + public DifferentialStyleProvider getStyle(TableStyleType type) { + return style.getStyle(type); + } + + } } diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFConditionalFormattingRule.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFConditionalFormattingRule.java index 5ab6c299a..ec72a5da7 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFConditionalFormattingRule.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFConditionalFormattingRule.java @@ -412,4 +412,12 @@ public class XSSFConditionalFormattingRule implements ConditionalFormattingRule public String getFormula2(){ return _cfRule.sizeOfFormulaArray() == 2 ? _cfRule.getFormulaArray(1) : null; } + + /** + * Conditional format rules don't define stripes, so always 0 + * @see org.apache.poi.ss.usermodel.DifferentialStyleProvider#getStripeSize() + */ + public int getStripeSize() { + return 0; + } } diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFDxfStyleProvider.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFDxfStyleProvider.java index 8e03712a0..b725258fa 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFDxfStyleProvider.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFDxfStyleProvider.java @@ -34,11 +34,14 @@ public class XSSFDxfStyleProvider implements DifferentialStyleProvider { private final FontFormatting font; private final ExcelNumberFormat number; private final PatternFormatting fill; + private final int stripeSize; /** * @param dxf + * @param stripeSize 0 for non-stripe styles, > 1 for stripes */ - public XSSFDxfStyleProvider(CTDxf dxf) { + public XSSFDxfStyleProvider(CTDxf dxf, int stripeSize) { + this.stripeSize = stripeSize; if (dxf == null) { border = null; font = null; @@ -72,5 +75,9 @@ public class XSSFDxfStyleProvider implements DifferentialStyleProvider { public PatternFormatting getPatternFormatting() { return fill; } + + public int getStripeSize() { + return stripeSize; + } } diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFTable.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFTable.java index f1a31b0f9..ad9e09bef 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFTable.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFTable.java @@ -30,6 +30,7 @@ import java.util.Locale; import org.apache.poi.POIXMLDocumentPart; import org.apache.poi.openxml4j.opc.PackagePart; +import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Table; import org.apache.poi.ss.usermodel.TableStyleInfo; import org.apache.poi.ss.util.CellReference; @@ -62,14 +63,20 @@ public class XSSFTable extends POIXMLDocumentPart implements Table { private transient CellReference startCellReference; private transient CellReference endCellReference; private transient String commonXPath; + private transient String name; + private transient String styleName; - + /** + * empty implementation, not attached to a workbook/worksheet yet + */ public XSSFTable() { super(); ctTable = CTTable.Factory.newInstance(); } /** + * @param part + * @throws IOException * @since POI 3.14-Beta1 */ public XSSFTable(PackagePart part) throws IOException { @@ -77,6 +84,11 @@ public class XSSFTable extends POIXMLDocumentPart implements Table { readFrom(part.getInputStream()); } + /** + * read table XML + * @param is + * @throws IOException + */ public void readFrom(InputStream is) throws IOException { try { TableDocument doc = TableDocument.Factory.parse(is, DEFAULT_XML_OPTIONS); @@ -86,10 +98,18 @@ public class XSSFTable extends POIXMLDocumentPart implements Table { } } + /** + * @return owning sheet + */ public XSSFSheet getXSSFSheet(){ return (XSSFSheet) getParent(); } + /** + * write table XML to stream + * @param out + * @throws IOException + */ public void writeTo(OutputStream out) throws IOException { updateHeaders(); @@ -108,6 +128,7 @@ public class XSSFTable extends POIXMLDocumentPart implements Table { /** * get the underlying CTTable XML bean + * @return underlying OOXML object */ @Internal(since="POI 3.15 beta 3") public CTTable getCTTable() { @@ -209,20 +230,57 @@ public class XSSFTable extends POIXMLDocumentPart implements Table { * @return the name of the Table, if set */ public String getName() { - return ctTable.getName(); + if (name == null) { + setName(ctTable.getName()); + } + return name; } /** * Changes the name of the Table + * @param newName */ - public void setName(String name) { - if (name == null) { + public void setName(String newName) { + if (newName == null) { ctTable.unsetName(); + name = null; return; } - ctTable.setName(name); + ctTable.setName(newName); + name = newName; } + /** + * @return the table style name, if set + * @since 3.17 beta 1 + */ + public String getStyleName() { + if (styleName == null && ctTable.isSetTableStyleInfo()) { + setStyleName(ctTable.getTableStyleInfo().getName()); + } + return styleName; + } + + /** + * Changes the name of the Table + * @param newStyleName + * @since 3.17 beta 1 + */ + public void setStyleName(String newStyleName) { + if (newStyleName == null) { + if (ctTable.isSetTableStyleInfo()) { + ctTable.getTableStyleInfo().unsetName(); + } + styleName = null; + return; + } + if (! ctTable.isSetTableStyleInfo()) { + ctTable.addNewTableStyleInfo(); + } + ctTable.getTableStyleInfo().setName(newStyleName); + styleName = newStyleName; + } + /** * @return the display name of the Table, if set */ @@ -232,6 +290,7 @@ public class XSSFTable extends POIXMLDocumentPart implements Table { /** * Changes the display name of the Table + * @param name to use */ public void setDisplayName(String name) { ctTable.setDisplayName(name); @@ -412,12 +471,35 @@ public class XSSFTable extends POIXMLDocumentPart implements Table { } /** + * Note: This is misleading. The Spec indicates this is true if the totals row + * has ever been shown, not whether or not it is currently displayed. + * Use {@link #getTotalsRowCount()} > 0 to decide whether or not the totals row is visible. * @since 3.15 beta 2 + * @see #getTotalsRowCount() */ public boolean isHasTotalsRow() { return ctTable.getTotalsRowShown(); } + + /** + * @return 0 for no totals rows, 1 for totals row shown. + * Values > 1 are not currently used by Excel up through 2016, and the OOXML spec + * doesn't define how they would be implemented. + * @since 3.17 beta 1 + */ + public int getTotalsRowCount() { + return (int) ctTable.getTotalsRowCount(); + } + /** + * @return 0 for no header rows, 1 for table headers shown. + * Values > 1 might be used by Excel for pivot tables? + * @since 3.17 beta 1 + */ + public int getHeaderRowCount() { + return (int) ctTable.getHeaderRowCount(); + } + /** * @since 3.15 beta 2 */ @@ -447,11 +529,27 @@ public class XSSFTable extends POIXMLDocumentPart implements Table { } /** - * * @since 3.17 beta 1 */ public TableStyleInfo getStyle() { if (! ctTable.isSetTableStyleInfo()) return null; return new XSSFTableStyleInfo(((XSSFSheet) getParent()).getWorkbook().getStylesSource(), ctTable.getTableStyleInfo()); } -} + + /** + * @see org.apache.poi.ss.usermodel.Table#contains(org.apache.poi.ss.usermodel.Cell) + * @since 3.17 beta 1 + */ + public boolean contains(Cell cell) { + if (cell == null) return false; + // check if cell is on the same sheet as the table + if ( ! getSheetName().equals(cell.getSheet().getSheetName())) return false; + // check if the cell is inside the table + if (cell.getRowIndex() >= getStartRowIndex() + && cell.getRowIndex() <= getEndRowIndex() + && cell.getColumnIndex() >= getStartColIndex() + && cell.getColumnIndex() <= getEndColIndex()) { + return true; + } + return false; + }} diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFTableStyle.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFTableStyle.java index a3333dce7..a46ab2d5b 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFTableStyle.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFTableStyle.java @@ -35,26 +35,32 @@ import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableStyleElement; public class XSSFTableStyle implements TableStyle { private final String name; + private final int index; private final Map elementMap = new EnumMap(TableStyleType.class); /** + * @param index style definition index or built-in ordinal depending on use * @param dxfs * @param tableStyle + * @see TableStyle#getIndex() */ - public XSSFTableStyle(CTDxfs dxfs, CTTableStyle tableStyle) { + public XSSFTableStyle(int index, CTDxfs dxfs, CTTableStyle tableStyle) { this.name = tableStyle.getName(); + this.index = index; for (CTTableStyleElement element : tableStyle.getTableStyleElementList()) { TableStyleType type = TableStyleType.valueOf(element.getType().toString()); DifferentialStyleProvider dstyle = null; if (element.isSetDxfId()) { - int idx = (int) element.getDxfId() -1; + int idx = (int) element.getDxfId(); CTDxf dxf; if (idx >= 0 && idx < dxfs.getCount()) { dxf = dxfs.getDxfArray(idx); } else { dxf = null; } - if (dxf != null) dstyle = new XSSFDxfStyleProvider(dxf); + int stripeSize = 0; + if (element.isSetSize()) stripeSize = (int) element.getSize(); + if (dxf != null) dstyle = new XSSFDxfStyleProvider(dxf, stripeSize); } elementMap.put(type, dstyle); } @@ -64,6 +70,17 @@ public class XSSFTableStyle implements TableStyle { return name; } + public int getIndex() { + return index; + } + + /** + * Always false for these, these are user defined styles + */ + public boolean isBuiltin() { + return false; + } + public DifferentialStyleProvider getStyle(TableStyleType type) { return elementMap.get(type); }