package org.fenixedu.oddjet.table; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.fenixedu.oddjet.exception.IllegalTableParameterRepresentationException; import org.fenixedu.oddjet.exception.UnknownParameterTypeException; import org.odftoolkit.simple.style.StyleTypeDefinitions.CellBordersType; /** * Contains the parameters used to configure a table's generation from a data collection within a template. * * @author Gil Lacerda (gil.lacerda@tecnico.ulisboa.pt) * */ public class TableConfiguration { /** * Specifies the behavior used when filling the cells of a dynamic table with the corresponding data content. * * @author Gil Lacerda (gil.lacerda@tecnico.ulisboa.pt) * */ public static enum FillBehavior { /** If any paragraph has content keep the data for the next cell. */ SKIP, /** If any paragraph has content throw away the data. */ STEP, /** Always write the data to the cell. */ WRITE } /** * Specifies the behavior used when writing in the cells of a dynamic table with the corresponding data content. * * @author Gil Lacerda (gil.lacerda@tecnico.ulisboa.pt) * */ public static enum WriteBehavior { /** Append data to last paragraph or place it in a new paragraph if there is none. */ APPEND, /** Prepend data to first paragraph or place it in a new paragraph if there is none. */ PREPEND, /** Write over the cell contents. */ OVERWRITE; } /** * Specifies the way the table's data content is to be used to generate the table. * * @author Gil Lacerda (gil.lacerda@tecnico.ulisboa.pt) * */ public static enum ContentStructure { /** * Use content as structured by position, data content will be used as a whole disregarding categorical structure and * order. */ CATEGORICAL, /** Use content as structured by categories, the categories to be used are specified within the table in the proper order. */ POSITIONAL } /** * In case of a categorical content structure, this enumeration specifies the direction in which data categories will be * expanded in the dynamic table. * <p> * In case of a positional content structure, it specifies whether the coordinates of the positional data are to be inverted, * flipping the table. * * @author Gil Lacerda (gil.lacerda@tecnico.ulisboa.pt) * */ public static enum ContentDirection { /** Categorical data is expanded in the columns. Positional data remains unaffected. */ VERTICAL, /** Categorical data is expanded in the rows. Positional data is flipped. */ HORIZONTAL; } /** * Specifies the source section of the border to be copied into the last border of the table. This parameter is to be used in * conjunction with a border type. * <p> * The type of border is specified by the class <code>org.odftoolkit.simple.style.StyleTypeDefinitions.CellBordersType</code>, * but only the four basic types are recognized to use with this parameter: <code>TOP</code>, <code>BOTTOM</code>, * <code>LEFT</code> and <code>RIGHT</code>. * <p> * The concrete border to be copied is expected to be picked from the table section's top left (for left and top border types) * and bottom right (for bottom and right border types) cells. Any exceptions to this behavior should be documented in its * implementing class. The {@link org.fenixedu.oddjet.Template Template} class implements this exact behavior. * <p> * The last border will be dependent of the table's Content Direction. If it is vertical then the last border will be the * bottom border of the cells in the last row. If it is horizontal then it will be the right border of the cells in the last * column. * <p> * This parameter is necessary because there may be a need for the last border of the table to be different from the * corresponding borders along the table body. As the tables are now generated by replicating the table body's rows/columns * present in the template it is not possible to specify this formatting parameter directly within the template document. * * @author Gil Lacerda (gil.lacerda@tecnico.ulisboa.pt) * */ public static enum LastBorderSourceSection { /** The table's last border will be set to the empty border. */ NONE, /** The table's last border will be copied from the header. */ HEADER, /** The table's last border will be copied from the body. */ BODY; /** * Gets the <code>LastBorderSourceSection</code> matching a string representation. The allowed representations include the * standard ones accepted by valueOf and shorthands to be used to match table parameters in the template. * * @param string the string representation of the LastBorderSourceSection to be obtained * @return the <code>LastBorderSourceSection</code> matching the given string or null if there is no match. * @throws NullPointerException if the provided string is null. */ private static LastBorderSourceSection getLastBorderSourceSection(String string) { try { return LastBorderSourceSection.valueOf(string.toUpperCase()); } catch (IllegalArgumentException re) { switch (string) { case "h": return HEADER; case "b": return BODY; case "n": return NONE; default: return null; } } } /** * Gets the <code>CellBordersType</code> matching a string representation. Only a subset of the CellBorderTypes are * contemplated since this method is used for those that are compatible with the <code>LastBorderSource</code> parameter. * The allowed representations include the standard ones accepted by valueOf and shorthands to be used to match table * parameters in the template. * * @param string the string representation of the CellBordersType to be obtained * @return the <code>CellBordersType</code> matching the given string or null if there is no match. * @throws NullPointerException if the provided string is null. */ private static CellBordersType getLastBorderSourceType(String string) { try { CellBordersType type = CellBordersType.valueOf(string.toUpperCase()); if (type == CellBordersType.LEFT || type == CellBordersType.RIGHT || type == CellBordersType.BOTTOM || type == CellBordersType.TOP) { return type; } else { return null; } } catch (IllegalArgumentException re) { switch (string) { case "l": return CellBordersType.LEFT; case "r": return CellBordersType.RIGHT; case "b": return CellBordersType.BOTTOM; case "t": return CellBordersType.TOP; default: return null; } } } } /** * Contains the regex patterns and logic necessary for translating the different table parameters type's string * representations present in the templates into their internal representation. * * @author Gil Lacerda (gil.lacerda@tecnico.ulisboa.pt) * */ public static enum ParameterType { /** * A generic parameter type, consisting of a sequence of one or more alphanumeric or underscore characters. All other * parameter type's representations should match this pattern. */ GENERIC("([a-zA-Z_0-9]+)"), /** * The table header parameter type, specifying the table's header section limits via the {@link TableCoordinate} of the * first non-header cell. It matches the following values: * <ul> * <li>nhr or noheader - the table has no headers. The first non-header cell's column and row indexes are 0.</li> * <li>hdr[col idx]_[row idx] or header[col idx]_[row idx] - the table header has [col idx] columns and [row idx] rows. * The first non-header cell's column index is [col idx] and row index is [row idx].</li> * </ul> * * */ HEADER("^(nhr|noheader)|(?:hdr|header)(\\d+)_(\\d+)$"), /** * The table content structure parameter type, see the {@link ContentStructure} enum for details. It matches the following * values: * <ul> * <li>pos or positional - content structure is positional, corresponding to {@link ContentStructure#POSITIONAL * ContentStructure.POSITIONAL}</li> * <li>cat or categorical - content structure is categorical, corresponding to {@link ContentStructure#CATEGORICAL * ContentStructure.CATEGORICAL}</li> * </ul> */ CONTENT_STRUCTURE("^(pos|positional)|(cat|categorical)$"), /** * The table fill behavior parameter type, see the {@link FillBehavior} enum for details. It matches the following * values: * <ul> * <li>skp or skip - fill behavior is to skip the cell, corresponding to {@link FillBehavior#SKIP * FillBehavior.SKIP}</li> * <li>stp or step - fill behavior is to step to the next cell, corresponding to {@link FillBehavior#STEP * FillBehavior.STEP}</li> * <li>wrt or write - fill behavior is to write to the cell, corresponding to {@link FillBehavior#WRITE * FillBehavior.WRITE}</li> * </ul> */ FILL_BEHAVIOR("^(skp|skip)|(stp|step)|(wrt|write)$"), /** * The table write behavior parameter type, see the {@link WriteBehavior} enum for details. It matches the following * values: * <ul> * <li>apd or append - write behavior is to append the data to the cell content, corresponding to * {@link WriteBehavior#APPEND WriteBehavior.APPEND}</li> * <li>ppd or prepend - write behavior is to prepend the data to the cell content, corresponding to * {@link WriteBehavior#PREPEND WriteBehavior.PREPEND}</li> * <li>ovw or overwrite - write behavior is to overwrite the cell content with the data, corresponding to * {@link WriteBehavior#OVERWRITE WriteBehavior.OVERWRITE}</li> * </ul> */ WRITE_BEHAVIOR("^(apd|append)|(ppd|prepend)|(ovw|overwrite)$"), /** * The table content direction parameter type, see the {@link ContentDirection} enum for details. It matches the following * values: * <ul> * <li>ver or vert or vertical - content is vertical, corresponding to {@link ContentDirection#VERTICAL * ContentDirection.VERTICAL}</li> * <li>hor or horz or horizontal - content is horizontal, corresponding to {@link ContentDirection#HORIZONTAL * ContentDirection.HORIZONTAL}</li> * </ul> */ CONTENT_DIRECTION("^(ver|vert|vertical)|(hor|horz|horizontal)$"), /** * The table style source parameter type, specifying a relative {@link TableCoordinate} to calculate from which existing * cell a given cell's style should be copied from during table generation. It matches the following * values: * <ul> * <li>pre or prest or prestyled - the table is already styled as intended.</li> * <li>vst or vertst or verticalstyle - the table is to be styled vertically, meaning styles vary only between columns. * The corresponding relative coordinates are (0,1) so the style is copied from the previous cell in the column.</li> * <li>hst or horzst or horizontalstyle - the table is to be styled horizontally, meaning styles vary only between * columns. The corresponding relative coordinates are (1,0) so the style is copied from the previous cell in the row.</li> * <li>pst[col idx]_[row idx] or perdst[col idx]_[row idx] or periodicstyle[col idx]_[row idx] - the table is periodically * styled, meaning styles vary periodically between columns, rows or a combination of both. The corresponding relative * coordinates are specified by [col idx] and [row idx] so the style is copied from the cell that is [col idx] columns * previous and [row idx] rows previous to the target one.</li> * </ul> */ STYLE_SOURCE("^(pre|prest|prestyled)|" // don't copy any style + "(vst|vertst|verticalstyle)|" // copy style from top cell + "(hst|horzst|horizontalstyle)|" // copy style from left cell + "((?:pst|perdst|periodicstyle)(\\d+)_(\\d+))$"), // copy style from cell at specific distance /** * The table last border parameter type, specifies both the {@link LastBorderSourceSection} and the * <code>org.odftoolkit.simple.style.StyleTypeDefinitions.CellBordersType</code> necessary for obtaining the concrete * border to replace the last border of the table. It matches the following * values: * <ul> * <li>nlb or nolborder or nolastborder - the table's last border is to be replaced by the empty border, corresponding to * {@link LastBorderSourceSection#NONE LastBorderSourceSection.NONE}.</li> * <li>lb[border reference] or lborder[border reference] or lastborder[border reference] - the table's last border is to * be replaced by the referenced border. The border reference notation is _[section][type], where [section] matches: * <ul> * <li>h or header - The referenced border is in the header section of the table, corresponding to * {@link LastBorderSourceSection#HEADER LastBorderSourceSection.HEADER}.</li> * <li>b or body - The referenced border is in the body section of the table, corresponding to * {@link LastBorderSourceSection#BODY LastBorderSourceSection.BODY}.</li> * </ul> * and [type] matches: * <ul> * <li>l or left - The referenced border is the left border of the section, corresponding to * <code>CellBordersType.LEFT</code>.</li> * <li>r or right - The referenced border is the right border of the section, corresponding to * <code>CellBordersType.RIGHT</code>.</li> * <li>t or top - The referenced border is the top border of the section, corresponding to * <code>CellBordersType.TOP</code>.</li> * <li>b or bottom - The referenced border is the bottom border of the section, corresponding to * <code>CellBordersType.BOTTOM</code>.</li> * </ul> * </li> * </ul> */ LAST_BORDER( "^(?:(lb|lborder|lastborder)(?:_(h|header|b|body)(l|left|r|right|b|bottom|t|top))?)|(nlb|nolborder|nolastborder)$"); /** The pattern that matches the parameter type's string representations. */ private Pattern pattern; /** * Constructs a ParameterType from a regex pattern that matches that type's string representations. * * @param pattern a String containing a regex pattern */ private ParameterType(String pattern) { this.pattern = Pattern.compile(pattern); } /** * @return the ParameterType's pattern. */ public Pattern getPattern() { return pattern; } /** * Gets a matcher that matches the given parameter string to this ParameterType's pattern. * * @param parameter the parameter string to be matched to this ParameterType's pattern. * @return a matcher that matches a string to this ParameterType's pattern. */ public Matcher getMatcher(String parameter) { return pattern.matcher(parameter); } /** * Reads a parameter string representation and sets its matching parameter in a {@link TableConfiguration} instance. The * parameter strings are case-insensitive. * * @param parameter the parameter string representation to be read. * @param tableConfig the TableConfiguration instance to be affected. * @throws {@link UnknownParameterTypeException} if the given string fulfills the generic parameter restrictions but not a * specific parameter type. * @throws {@link IllegalTableParameterRepresentationException} if the given string does not fulfill the generic parameter * restrictions. */ public static void readInto(String parameter, TableConfiguration tableConfig) throws UnknownParameterTypeException, IllegalTableParameterRepresentationException { parameter = parameter.toLowerCase(); Matcher matcher = null; if ((matcher = ParameterType.HEADER.getMatcher(parameter)).find()) { tableConfig.header = matcher.group(1) != null ? new TableCoordinate() : new TableCoordinate( Integer.parseInt(matcher.group(2)), Integer.parseInt(matcher.group(3))); } else if ((matcher = ParameterType.CONTENT_STRUCTURE.getMatcher(parameter)).find()) { tableConfig.contentStructure = matcher.group(1) != null ? ContentStructure.POSITIONAL : ContentStructure.CATEGORICAL; } else if ((matcher = ParameterType.FILL_BEHAVIOR.getMatcher(parameter)).find()) { tableConfig.fillBehavior = matcher.group(1) != null ? FillBehavior.SKIP : matcher.group(2) != null ? FillBehavior.STEP : FillBehavior.WRITE; } else if ((matcher = ParameterType.WRITE_BEHAVIOR.getMatcher(parameter)).find()) { tableConfig.writeBehavior = matcher.group(1) != null ? WriteBehavior.APPEND : matcher.group(2) != null ? WriteBehavior.PREPEND : WriteBehavior.OVERWRITE; } else if ((matcher = ParameterType.CONTENT_DIRECTION.getMatcher(parameter)).find()) { tableConfig.contentDirection = matcher.group(1) != null ? ContentDirection.VERTICAL : ContentDirection.HORIZONTAL; } else if ((matcher = ParameterType.STYLE_SOURCE.getMatcher(parameter)).find()) { if (matcher.group(4) != null && matcher.group(5) != null) { int col = Integer.parseInt(matcher.group(4)); int row = Integer.parseInt(matcher.group(5)); if (row == 0 && col == 0) { // Style source (0,0) is the same as prestyled tableConfig.styleRelativeCoord = null; } else { tableConfig.styleRelativeCoord = new TableCoordinate(col, row); } } else { tableConfig.styleRelativeCoord = matcher.group(2) != null ? new TableCoordinate(0, 1) : matcher.group(3) != null ? new TableCoordinate( 1, 0) : null; } } else if ((matcher = ParameterType.LAST_BORDER.getMatcher(parameter)).find()) { if (matcher.group(4) != null) { tableConfig.lastBorderSourceSection = LastBorderSourceSection.NONE; } else if (matcher.group(2) != null) { tableConfig.lastBorderSourceSection = LastBorderSourceSection.getLastBorderSourceSection(matcher.group(2)); tableConfig.lastBorderSourceType = LastBorderSourceSection.getLastBorderSourceType(matcher.group(3)); } else { tableConfig.lastBorderSourceSection = LastBorderSourceSection.HEADER; tableConfig.lastBorderSourceType = CellBordersType.BOTTOM; } } else if ((ParameterType.GENERIC.getMatcher(parameter)).find()) { throw new UnknownParameterTypeException(parameter); } else { throw new IllegalTableParameterRepresentationException(parameter); } } } private ContentStructure contentStructure = ContentStructure.CATEGORICAL; private FillBehavior fillBehavior = FillBehavior.WRITE; private WriteBehavior writeBehavior = WriteBehavior.APPEND; private ContentDirection contentDirection = ContentDirection.VERTICAL; private LastBorderSourceSection lastBorderSourceSection = null; private CellBordersType lastBorderSourceType = null; private TableCoordinate styleRelativeCoord = new TableCoordinate(0, 1); private TableCoordinate header = new TableCoordinate(0, 1); /** * @return the current content structure parameter's value. The default value is {@link ContentStructure#CATEGORICAL * CATEGORICAL}. */ public ContentStructure getContentStructure() { return contentStructure; } /** * @return the current fill behavior parameter's value. The default value is {@link FillBehavior#WRITE WRITE}. */ public FillBehavior getFillBehavior() { return fillBehavior; } /** * @return the current write behavior parameter's value. The default value is {@link WriteBehavior#APPEND APPEND}. */ public WriteBehavior getWriteBehavior() { return writeBehavior; } /** * @return the current content direction parameter's value. The default value is {@link ContentDirection#VERTICAL VERTICAL}. */ public ContentDirection getContentDirection() { return contentDirection; } /** * @return the current last border source section parameter's value. The default value is <code>null</code>, meaning the last * border is to remain unchanged. */ public LastBorderSourceSection getLastBorderSourceSection() { return lastBorderSourceSection; } /** * @return the current last border source type parameter's value. This parameter should only be important if the last border * source section parameter is set to either {@link LastBorderSourceSection#HEADER HEADER} or * {@link LastBorderSourceSection#BODY BODY}. Matching the connected section parameter, the default value is * <code>null</code>. */ public CellBordersType getLastBorderSourceType() { return lastBorderSourceType; } /** * @return the current style source parameter's value. The default value is (0,1), meaning the style of a cell is to be copied * from the cell above, forming a column, or vertical, styled table. */ public TableCoordinate getStyleRelativeCoord() { return styleRelativeCoord; } /** * @return the current header parameter's value. The default value is (0,1), meaning the header section of the table is * comprised of a single row. */ public TableCoordinate getHeader() { return header; } }