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;
}
}