BrowseJasperReportTemplate.java
/*
** Module : BrowseJasperReportTemplate.java
** Abstract : Template for a browse Jasper report design, used by a browse.
**
** Copyright (c) 2018-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------Description----------------------------------
** 001 SVL 20180207 Created initial version.
** 002 SVL 20181004 Support for arbitrary fonts and colors.
** 003 OM 20181005 Improve reports in small page space.
** SVL 20181006 Implemented arbitrary colors and fonts for column labels.
** 004 SVL 20200514 Added center column alignment.
** 005 RNC 20250131 If there are too many columns to be displayed in the given page width,
* prepare the cells for the process of splitting the pages instead of just
* shrinking the cells.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.reporting;
import com.goldencode.ast.*;
import com.goldencode.p2j.ui.*;
import com.goldencode.p2j.ui.client.*;
import com.goldencode.p2j.xml.*;
import com.goldencode.util.*;
import net.sf.jasperreports.engine.*;
import org.w3c.dom.*;
import javax.xml.parsers.*;
import java.io.*;
import java.util.*;
import static com.goldencode.p2j.reporting.BrowseJasperDataSource.*;
import static com.goldencode.p2j.xml.XmlAst.*;
/**
* Template for a Jasper report design, used by a browse. First, user creates a Jasper report
* design. This design is called "template" because basing on this design a design applicable to
* the given browse is created (design should have the matching number of columns, same width,
* alignment and data type of columns). The following transformations is used to transform a
* design template into the design for the given browse:
* <ul>
* <li>Only the first column in the design template is taken into consideration. It is
* duplicated to form other columns.</li>
* <li>Column headers and details bands are widened to the page width (excluding margins)</li>
* <li>Widths of columns are set in the design in the same proportion as in the browse.</li>
* <li>Text alignment of a column (including column label) is set as in the browse.</li>
* <li>Label of a column is set as in the browse.</li>
* </ul>
* Transformations are performed in two steps: {@link #prepare(String)} function which loads
* template into XML tree and performs preparation steps, and
* {@link #generateReport(BrowseWidget)} function which transforms the XML tree into report
* applicable to the specific browse and compiles the report.
*/
class BrowseJasperReportTemplate
{
/** Default column label bgcolor. */
private static final String DEFAULT_COLUMN_LABEL_BGCOLOR = "#ADADAD";
/** Root node of the prepared template. */
private XmlAst templateRoot;
/** Sub-tree representing template for a column header. */
private XmlAst columnHeaderTemplate;
/** Sub-tree representing template for a split table header. */
private XmlAst headersRootTemplate;
/** Sub-tree representing template for a split table details. */
private XmlAst detailsRootTemplate;
/** Sub-tree representing template for a column data area. */
private XmlAst columnDataTemplate;
/** Name of the design template file. */
private String designTemplateFile;
/** Page width specified in the report (assumed to be Portrait, width < height). */
private int templatePageWidth;
/** Page height specified in the report (assumed to be Portrait, width < height). */
private int templatePageHeight;
/** The left margin of the page, as read from the template file. */
private int leftMargin;
/** The right margin of the page, as read from the template file. */
private int rightMargin;
/** The top margin of the page, as read from the template file. */
private int topMargin;
/** The bottom margin of the page, as read from the template file. */
private int bottomMargin;
/**
* Load given design template into XML tree and perform preparation steps, which in the future
* allow to quickly transform this tree into a report suitable for the specific browse.
*
* @param designTemplateFile
* Name of the design template file.
*
* @throws JasperReportException
* if design template file could not be read or parsed or has invalid structure.
*/
void prepare(String designTemplateFile)
throws JasperReportException
{
InputStream stream =
FwdJasperExtensionRegistry.getRepositoryService().getInputStream(designTemplateFile);
Document dom;
try
{
dom = XmlHelper.parse(stream);
}
catch (Exception e)
{
throw new JasperReportException("Could not parse browse report template file",
designTemplateFile, e);
}
templateRoot = domToAst(dom);
this.designTemplateFile = designTemplateFile;
XmlAst jasperReportsRoot = findChild(templateRoot, "jasperReport");
cleanUUID(jasperReportsRoot);
// get page width
templatePageWidth = getAttributeInt(jasperReportsRoot, "pageWidth", 0);
leftMargin = getAttributeInt(jasperReportsRoot, "leftMargin", 0);
rightMargin = getAttributeInt(jasperReportsRoot, "rightMargin", 0);
// get page height
templatePageHeight = getAttributeInt(jasperReportsRoot, "pageHeight", 0);
topMargin = getAttributeInt(jasperReportsRoot, "topMargin", 0);
bottomMargin = getAttributeInt(jasperReportsRoot, "bottomMargin", 0);
// Remove FIELD nodes
List<XmlAst> fields = findChildren(jasperReportsRoot, "field");
fields.forEach(AnnotatedAst::remove);
// Handle column headers (columnHeader/band nodes)
XmlAst columnHeadersRoot = findChildPath(jasperReportsRoot, "columnHeader/band");
List<XmlAst> headers = findChildren(columnHeadersRoot, "textField");
// Create template for column headers
columnHeaderTemplate = (XmlAst) headers.get(0).duplicateFresh();
// Remove original header nodes
headers.forEach(AnnotatedAst::remove);
// Handle column data (detail/band nodes)
XmlAst columnsDataRoot = findChildPath(jasperReportsRoot, "detail/band");
List<XmlAst> columns = findChildren(columnsDataRoot, "textField");
// Create template for column data
columnDataTemplate = (XmlAst) columns.get(0).duplicateFresh();
// Remove original column data nodes
columns.forEach(AnnotatedAst::remove);
// just in case the table does not fit on page width
headersRootTemplate = (XmlAst) findChildPath(jasperReportsRoot, "columnHeader").duplicateFresh();
detailsRootTemplate = (XmlAst) findChildPath(jasperReportsRoot, "detail").duplicateFresh();
}
/**
* Transform the XML tree representing prepared template ({@link #templateRoot} field) into the
* report applicable to the specific browse and compile it.
*
* What to do when "Bands are larger than one page":
* * use landscape page,
* * https://community.jaspersoft.com/wiki/bands-larger-one-page
*
* @param browse
* Browse for which the report is generated.
*
* @return compiled report applicable to the specific browse.
*
* @throws JasperReportException
* if there was an error during creation of the browse report or the report could not
* be compiled.
*/
JasperReport generateReport(BrowseWidget browse)
throws JasperReportException
{
XmlAst resultRoot = (XmlAst) templateRoot.duplicateFresh();
XmlAst fieldsRoot = findChild(resultRoot, "jasperReport");
XmlAst fieldsAnchor = findChild(fieldsRoot, "queryString");
// calc total columns width
List<BrowseColumnWidget> visibleColumns = browse.getVisibleColumns();
int colNo = visibleColumns.size();
if (colNo == 0)
{
throw new JasperReportException("Browse has no visible columns", designTemplateFile);
}
// calculate report columns' widths: widths should be in the same proportion as the browse
// columns' widths
int totalBrowseColumnsWidth = 0;
int[] columnWidths = new int[colNo];
for (int i = 0; i < colNo; i++)
{
BrowseColumnWidget column = visibleColumns.get(i);
int columnWidth = column.getWidthPixels().toJavaIntegerType();
columnWidths[i] = columnWidth;
totalBrowseColumnsWidth += columnWidth;
}
boolean longBand = false;
int pageWidth = templatePageWidth;
int pageHeight = templatePageHeight;
if (pageWidth > 0)
{
if (pageHeight <= 0)
{
// auto-fix to An paper:
pageHeight = (int) (pageWidth * Math.sqrt(2));
}
if (totalBrowseColumnsWidth + leftMargin + rightMargin > pageWidth)
{
// well, the report does not fit. Assuming this is portrait, try landscape:
if (totalBrowseColumnsWidth + leftMargin + rightMargin > pageHeight)
{
// The columns will not fit even in landscape. Prepare for splitting the pages.
longBand = true;
}
int tmp = pageWidth;
pageWidth = pageHeight;
pageHeight = tmp;
XmlAst footerNode = findChildPath(resultRoot, "jasperReport/pageFooter/band");
List<XmlAst> footerReportElements = new ArrayList<>();
for (XmlAst footerElement : findChildren(footerNode, "textField"))
{
footerReportElements.add(findChild(footerElement, "reportElement"));
}
// re-center the footer because of the landscape mode
setAttribute(footerReportElements.get(0), "width", String.valueOf(pageWidth));
setAttribute(footerReportElements.get(1), "width", String.valueOf(pageWidth));
// special case when we have a single column that is larger than the usable width
if (colNo == 1)
{
longBand = false;
columnWidths[0] = pageWidth - leftMargin - rightMargin;
totalBrowseColumnsWidth = columnWidths[0];
}
if (longBand)
{
// compensate for the longer format (Page I of N, Rows A to B). After splitting,
// there will be only one footer element instead of 2
int oldXCoordinate = Integer.parseInt(getAttribute(footerReportElements.get(0), "x"));
setAttribute(footerReportElements.get(0), "x", String.valueOf(oldXCoordinate - 18));
}
}
}
else
{
// template does not care about width (like SSV, XLS or XLSX)
pageWidth = totalBrowseColumnsWidth + leftMargin + rightMargin;
pageHeight = 10000; // quite large; it will be ignored, anyway
}
int usableWidth = pageWidth - leftMargin - rightMargin;
List<Integer> splits = new ArrayList<>();
int counter = 0;
int nextSplit = -1;
if (longBand)
{
int offset = 0;
int i;
for (i = 0; i < colNo - 1; i++)
{
if (columnWidths[i] > usableWidth)
{
// shrink column width only to usable width, there will be only one column on this page
columnWidths[i] = usableWidth;
if (i > 0 && columnWidths[i - 1] != usableWidth)
{
// if columnWidths[i - 1] == usableWidth, it means the last column occupied the whole
// page. In this case, avoid adding duplicate splits
// Furthermore, there is no point of setting the last column width one more time if we
// already know that it uses the whole page width
splits.add(i);
columnWidths[i - 1] += usableWidth - offset;
}
splits.add(i + 1);
offset = 0;
continue;
}
if (offset + columnWidths[i] <= usableWidth)
{
// add the whole column to the page
offset += columnWidths[i];
}
else
{
// the column doesn't fit anymore, so "move it" to the next page
columnWidths[i - 1] += usableWidth - offset;
splits.add(i);
i--;
offset = 0;
}
}
if (offset + columnWidths[i] > usableWidth)
{
if (columnWidths[i] > usableWidth)
{
columnWidths[i] = usableWidth;
}
if (columnWidths[i - 1] != usableWidth)
{
columnWidths[i - 1] += usableWidth - offset;
splits.add(i);
}
}
totalBrowseColumnsWidth = usableWidth;
nextSplit = splits.get(0);
}
int tableX = (usableWidth - totalBrowseColumnsWidth) / 2;
// center the title:
XmlAst titleNode = findChildPath(resultRoot,
"jasperReport/title/band/textField/reportElement");
if (titleNode != null)
{
setAttribute(titleNode, "x", "0");
setAttribute(titleNode, "width", Integer.toString(usableWidth));
}
// now set the page sizes to [fieldsRoot]
setAttribute(fieldsRoot, "pageWidth", Integer.toString(pageWidth));
setAttribute(fieldsRoot, "pageHeight", Integer.toString(pageHeight));
setAttribute(fieldsRoot, "columnWidth", Integer.toString(usableWidth));
XmlAst columnHeadersRoot = findChildPath(resultRoot, "jasperReport/columnHeader/band");
XmlAst columnDataRoot = findChildPath(resultRoot, "jasperReport/detail/band");
int xOffset = tableX;
int splitSize = splits.size();
for (int i = 0; i < colNo; i++)
{
BrowseColumnWidget column = visibleColumns.get(i);
int columnWidth = columnWidths[i];
// Create nodes for the field and its attributes
String columnVarName = "column" + column.config().ordinal;
XmlAst node = createFieldNode(columnVarName, fieldsRoot, fieldsAnchor);
node = createFieldNode(columnVarName + ".forecolor", fieldsRoot, node);
node = createFieldNode(columnVarName + ".fontName", fieldsRoot, node);
node = createFieldNode(columnVarName + ".fontSize", fieldsRoot, node);
node = createFieldNode(columnVarName + ".isBold", fieldsRoot, node);
node = createFieldNode(columnVarName + ".isItalic", fieldsRoot, node);
fieldsAnchor = createFieldNode(columnVarName + ".isUnderline", fieldsRoot, node);
BrowseTextAlignment alignment = Browse.getTextAlign(column.config());
// Create column header
XmlAst columnHeaderNode = (XmlAst) columnHeaderTemplate.duplicateFresh();
columnHeadersRoot.graft(columnHeaderNode);
// Set label text
String[] labelLines = Browse.getLabelVar(column.config());
StringBuilder sb = new StringBuilder();
for (int j = 0; j < labelLines.length; j++)
{
if (j != 0)
{
sb.append("\\n"); // do not add the NL char, use the 2-char escape instead
}
sb.append(labelLines[j]);
}
node = findChild(columnHeaderNode, "textFieldExpression");
node = (XmlAst) node.getImmediateChild(CDATA_SECTION_NODE, null);
node = (XmlAst) node.getImmediateChild(CONTENT, null);
node.setText("\"" + sb.toString() + "\"");
XmlAst headerReportElement = findChild(columnHeaderNode, "reportElement");
// label foreground color
node = null;
String fgColor = getElementColor(browse,
column.config().ehLabelFgColor,
column.config().labelFgColor,
browse.config().ehLabelFgColor,
browse.config().labelFgColor);
if (fgColor == null)
{
fgColor = getElementColor(browse,
column.config().ehFgColor,
column.config().columnFgColor,
browse.config().ehFgColor,
browse.config().fgcolor);
}
if (fgColor != null)
{
node = createPropertyNode("net.sf.jasperreports.style.forecolor",
fgColor,
headerReportElement,
node);
}
// label bg color
String bgColor = getElementColor(browse,
column.config().ehLabelBgColor,
column.config().labelBgColor,
browse.config().ehLabelBgColor,
browse.config().labelBgColor);
if (bgColor == null)
{
bgColor = DEFAULT_COLUMN_LABEL_BGCOLOR;
}
node = createPropertyNode("net.sf.jasperreports.style.backcolor",
bgColor,
headerReportElement,
node);
// font attributes
FontDetails font = getColumnFont(column, true);
if (isFontFamilyAvailable(font))
{
node = createPropertyNode("net.sf.jasperreports.style.fontName",
font.fontName,
headerReportElement,
node);
}
node = createPropertyNode("net.sf.jasperreports.style.fontSize",
Integer.toString(font.pointSize),
headerReportElement,
node);
node = createPropertyNode("net.sf.jasperreports.style.isBold",
Boolean.toString(font.style.isBold()),
headerReportElement,
node);
node = createPropertyNode("net.sf.jasperreports.style.isItalic",
Boolean.toString(font.style.isItalic()),
headerReportElement,
node);
node = createPropertyNode("net.sf.jasperreports.style.isUnderline",
Boolean.toString(font.style.isUnderline()),
headerReportElement,
node);
setPositionAndWidth(columnHeaderNode, xOffset, columnWidth);
setTextAlignment(columnHeaderNode, alignment);
// Create column data
XmlAst columnDataNode = (XmlAst) columnDataTemplate.duplicateFresh();
columnDataRoot.graft(columnDataNode);
String content = String.format("$F{%s}", columnVarName);
node = findChild(columnDataNode, "textFieldExpression");
node = (XmlAst) node.getImmediateChild(CDATA_SECTION_NODE, null);
node = (XmlAst) node.getImmediateChild(CONTENT, null);
node.setText(content);
setPositionAndWidth(columnDataNode, xOffset, columnWidth);
setTextAlignment(columnDataNode, alignment);
// process attribute names
node = findChild(columnDataNode, "reportElement");
List<XmlAst> nodes = findChildren(node, "propertyExpression");
for (XmlAst propNode : nodes)
{
XmlAst n = (XmlAst) propNode.getImmediateChild(CDATA_SECTION_NODE, null);
n = (XmlAst) n.getImmediateChild(CONTENT, null);
n.setText(n.getText().replace("dummy", columnVarName));
}
if (longBand && nextSplit == i + 1)
{
nextSplit = ++counter == splitSize ? -1 : splits.get(counter);
xOffset = tableX;
}
else
{
xOffset += columnWidth;
}
}
ByteArrayInputStream inputStream;
try
{
String namespaceURI = getAttribute(fieldsRoot, "xmlns");
Document doc = XmlAst.astToDom(resultRoot, namespaceURI);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
XmlHelper.write(doc, outputStream);
//DEBUG: XmlHelper.write(doc, "/tmp/generated-report.jrxml");
inputStream = new ByteArrayInputStream(outputStream.toByteArray());
}
catch (ParserConfigurationException | IOException e)
{
throw new JasperReportException("Cannot create DOM from XML", designTemplateFile, e);
}
try
{
JasperReport report = JasperCompileManager.compileReport(inputStream);
if (longBand)
{
// attach the required properties to the report for splitting the pages
report.setProperty("colNo", String.valueOf(colNo));
report.setProperty("splitsPerPage", String.valueOf(splitSize));
}
return report;
}
catch (JRException e)
{
throw new JasperReportException("Cannot compile generated browse report",
designTemplateFile, e);
}
}
/**
* Set position and width of the single <code>reportElement</code> element under the given
* parent node.
*
* @param parent
* Parent node.
* @param xPos
* Element X position.
* @param columnWidth
* Element width.
*/
private void setPositionAndWidth(XmlAst parent, int xPos, int columnWidth)
{
XmlAst element = findChild(parent, "reportElement");
setAttribute(element, "width", Integer.toString(columnWidth));
setAttribute(element, "x", Integer.toString(xPos));
}
/**
* Set text alignment of the single <code>textElement</code> element under the given
* parent node.
*
* @param parent
* Parent node.
* @param alignment
* Text alignment.
*/
private void setTextAlignment(XmlAst parent, BrowseTextAlignment alignment)
{
XmlAst element = findChild(parent, "textElement");
String alignmentStr = null;
switch (alignment)
{
case LEFT:
alignmentStr = "Left";
break;
case RIGHT:
alignmentStr = "Right";
break;
case CENTER:
alignmentStr = "Center";
break;
}
setAttribute(element, "textAlignment", alignmentStr);
}
/**
* Find the single available path under the given parent node, and return the last node in this
* path.
*
* @param parent
* Parent node.
* @param path
* "/"-separated down path, e.g. "jasperReport/columnHeader/band". Elements of the path
* are XML tags.
*
* @return the last node in the path, if such path exists, or <code>null</code> if there is no
* path or there are multiple matching paths.
*/
private XmlAst findChildPath(XmlAst parent, String path)
{
String[] tags = path.split("/");
XmlAst res = parent;
for (String tag : tags)
{
res = findChild(res, tag);
if (res == null)
{
break;
}
}
return res;
}
/**
* Find the single available child with the specified XML tag under the given parent node.
*
* @param parent
* Parent node.
* @param tag
* XML tag.
*
* @return the single available child with the specified XML tag.
*
* @throws JasperReportXmlSearchException
* if there is no available node, or there are multiple matching nodes.
*/
private XmlAst findChild(XmlAst parent, String tag)
{
return findChild(parent, tag, false);
}
/**
* Find the single available child with the specified XML tag under the given parent node.
*
* @param parent
* Parent node.
* @param tag
* XML tag.
*
* @return the single available child with the specified XML tag. If <code>noError</code> is
* <code>true</code>: returns <code>null</code> if there is no available node, or
* there are multiple matching nodes.
*
* @throws JasperReportXmlSearchException
* if <code>noError</code> is <code>false</code>: there is no available node, or
* there are multiple matching nodes.
*/
private XmlAst findChild(XmlAst parent, String tag, boolean noError)
{
return findChild(parent, ELEMENT_NODE, tag, noError);
}
/**
* Find the single available child with the specified node type and text under the given parent
* node.
*
* @param parent
* Parent node.
* @param nodeType
* Node type (see constants in {@link XmlTokenTypes}).
* @param nodeText
* Node text (e.g. XML tag or attribute name).
*
* @return the single available child with the specified node type and text. If
* <code>noError</code> is <code>true</code>: returns <code>null</code> if there are no
* available node, or there are multiple matching nodes.
*
* @throws JasperReportXmlSearchException
* if <code>noError</code> is <code>false</code>: there are no available node, or
* there are multiple matching nodes.
*/
private XmlAst findChild(XmlAst parent, int nodeType, String nodeText, boolean noError)
{
List<XmlAst> children = findChildren(parent, nodeType, nodeText, true);
if (children == null || children.size() != 1)
{
if (noError)
{
return null;
}
else
{
throw new JasperReportXmlSearchException(parent, nodeText, designTemplateFile);
}
}
return children.get(0);
}
/**
* Find the child nodes with the specified XML tag under the given parent node.
*
* @param parent
* Parent node.
* @param tag
* XML tag.
*
* @return available child nodes with the specified XML tag.
*
* @throws JasperReportXmlSearchException
* there are no available node.
*/
private List<XmlAst> findChildren(XmlAst parent, String tag)
{
return findChildren(parent, ELEMENT_NODE, tag);
}
/**
* Find the child nodes with the specified node type and text under the given parent node.
*
* @param parent
* Parent node.
* @param nodeType
* Type of the target nodes (see constants in {@link XmlTokenTypes}).
* @param nodeText
* Node text (e.g. XML tag or attribute name).
*
* @return available child nodes with the specified node type and text.
*
* @throws JasperReportXmlSearchException
* there are no available node.
*/
private List<XmlAst> findChildren(XmlAst parent, int nodeType, String nodeText)
{
return findChildren(parent, nodeType, nodeText, false);
}
/**
* Find the child nodes with the specified node type and text under the given parent node.
*
* @param parent
* Parent node.
* @param nodeType
* Type of the target nodes (see constants in {@link XmlTokenTypes}).
* @param nodeText
* Node text (e.g. XML tag or attribute name).
*
* @return available child nodes with the specified node type and text. If
* <code>noError</code> is <code>true</code>: returns <code>null</code> if there are no
* available nodes.
*
* @throws JasperReportXmlSearchException
* if <code>noError</code> is <code>false</code>: there are no available nodes.
*/
private List<XmlAst> findChildren(XmlAst parent,
int nodeType,
String nodeText,
boolean noError)
{
List<XmlAst> children = null;
XmlAst child = null;
while (true)
{
child = (XmlAst) parent.getImmediateChild(nodeType, child);
if (child == null)
{
break;
}
if (Objects.equals(nodeText, child.getText()))
{
if (children == null)
{
children = new ArrayList<>();
}
children.add(child);
}
}
if (children == null && !noError)
{
throw new JasperReportXmlSearchException(parent, nodeText, designTemplateFile);
}
return children;
}
/**
* Remove all "uuid" attributes in the given tree.
*
* @param root
* Root of the tree.
*/
private void cleanUUID(XmlAst root)
{
removeAttribute(root, "uuid");
XmlAst child = null;
while (true)
{
child = (XmlAst) root.getImmediateChild(ELEMENT_NODE, child);
if (child == null)
{
return;
}
cleanUUID(child);
}
}
/**
* Create FIELD node for Jasper report template.
*
* @param name
* Field name.
* @param parent
* Parent node.
* @param anchor
* Anchor node: created node will be positioned after the anchor node.
*
* @return created FIELD node.
*/
private XmlAst createFieldNode(String name, XmlAst parent, XmlAst anchor)
{
XmlAst fieldNode = new XmlAst(ELEMENT_NODE, "field");
setAttribute(fieldNode, "name", name);
setAttribute(fieldNode, "class", "java.lang.String");
parent.graftAt(fieldNode, anchor.getIndexPos() + 1);
return fieldNode;
}
/**
* Create PROPERTY node for Jasper report template.
*
* @param name
* Property name.
* @param value
* Property value.
* @param parent
* Parent node.
* @param anchor
* Anchor node: created node will be positioned after the anchor node. Can be
* <code>null</code>.
*
* @return created PROPERTY node.
*/
private XmlAst createPropertyNode(String name, String value, XmlAst parent, XmlAst anchor)
{
XmlAst node = new XmlAst(ELEMENT_NODE, "property");
setAttribute(node, "name", name);
setAttribute(node, "value", value);
if (anchor == null)
{
parent.graft(node);
}
else
{
parent.graftAt(node, anchor.getIndexPos() + 1);
}
return node;
}
/**
* Get value of the specified integer attribute of the specified node.
*
* @param node
* Parent node.
* @param name
* Attribute name.
* @param defaultValue
* Default value of the attribute.
*
* @return value of the attribute or the default value if there is no such attribute.
*/
private int getAttributeInt(XmlAst node, String name, int defaultValue)
{
String val = getAttribute(node, name);
if (val != null)
{
try
{
return Integer.valueOf(val);
}
catch (NumberFormatException e)
{
return defaultValue;
}
}
else
{
return defaultValue;
}
}
/**
* Get value of the specified attribute of the specified node.
*
* @param node
* Parent node.
* @param name
* Attribute name.
*
* @return value of the attribute or <code>null</code> if there is no such attribute.
*/
private String getAttribute(XmlAst node, String name)
{
// Structure of an attributed node:
//
// <node> [ELEMENT_NODE]
// attributes [ATTR_SET]
// <name> [ATTRIBUTE_NODE]
// #text [TEXT_NODE]
// <value> [CONTENT]
XmlAst attrSet = (XmlAst) node.getImmediateChild(ATTR_SET, null);
if (attrSet == null)
{
return null;
}
XmlAst attrNode = findChild(attrSet, ATTRIBUTE_NODE, name, true);
if (attrNode == null)
{
return null;
}
XmlAst textNode = (XmlAst) attrNode.getImmediateChild(TEXT_NODE, null);
if (textNode == null)
{
return null;
}
XmlAst contentNode = (XmlAst) textNode.getImmediateChild(CONTENT, null);
if (contentNode == null)
{
return null;
}
else
{
return contentNode.getText();
}
}
/**
* Delete the specified attribute of the specified node.
*
* @param node
* Parent node.
* @param name
* Attribute name.
*/
private void removeAttribute(XmlAst node, String name)
{
// Structure of an attributed node:
//
// <node> [ELEMENT_NODE]
// attributes [ATTR_SET]
// <name> [ATTRIBUTE_NODE]
// #text [TEXT_NODE]
// <value> [CONTENT]
XmlAst attrSet = (XmlAst) node.getImmediateChild(ATTR_SET, null);
if (attrSet == null)
{
return;
}
XmlAst attrNode = findChild(attrSet, ATTRIBUTE_NODE, name, true);
if (attrNode != null)
{
attrNode.remove();
}
}
/**
* Set the value of the specified attribute.
*
* @param node
* Parent node.
* @param name
* Attribute name.
* @param value
* Attribute value to set.
*/
private void setAttribute(XmlAst node, String name, String value)
{
// Structure of an attributed node:
//
// <node> [ELEMENT_NODE]
// attributes [ATTR_SET]
// <name> [ATTRIBUTE_NODE]
// #text [TEXT_NODE]
// <value> [CONTENT]
XmlAst attrSet = (XmlAst) node.getImmediateChild(ATTR_SET, null);
if (attrSet == null)
{
attrSet = new XmlAst(ATTR_SET, "attributes");
node.graft(attrSet);
}
XmlAst attrNode = findChild(attrSet, ATTRIBUTE_NODE, name, true);
if (attrNode == null)
{
attrNode = new XmlAst(ATTRIBUTE_NODE, name);
attrSet.graft(attrNode);
}
XmlAst textNode = (XmlAst) attrNode.getImmediateChild(TEXT_NODE, null);
if (textNode == null)
{
textNode = new XmlAst(TEXT_NODE, "#text");
attrNode.graft(textNode);
}
XmlAst contentNode = (XmlAst) textNode.getImmediateChild(CONTENT, null);
if (contentNode == null)
{
contentNode = new XmlAst(CONTENT, value);
textNode.graft(contentNode);
}
else
{
contentNode.setText(value);
}
}
}