/* * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software * Foundation. * * You should have received a copy of the GNU Lesser General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * 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 Lesser General Public License for more details. * * Copyright (c) 2001 - 2013 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved. */ package org.pentaho.reporting.engine.classic.core.modules.output.table.xml.internal; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.pentaho.reporting.engine.classic.core.AttributeNames; import org.pentaho.reporting.engine.classic.core.InvalidReportStateException; import org.pentaho.reporting.engine.classic.core.ReportAttributeMap; import org.pentaho.reporting.engine.classic.core.filter.types.AutoLayoutBoxType; import org.pentaho.reporting.engine.classic.core.layout.model.BlockRenderBox; import org.pentaho.reporting.engine.classic.core.layout.model.Border; import org.pentaho.reporting.engine.classic.core.layout.model.BorderCorner; import org.pentaho.reporting.engine.classic.core.layout.model.BorderEdge; import org.pentaho.reporting.engine.classic.core.layout.model.CanvasRenderBox; import org.pentaho.reporting.engine.classic.core.layout.model.InlineRenderBox; import org.pentaho.reporting.engine.classic.core.layout.model.LayoutNodeTypes; import org.pentaho.reporting.engine.classic.core.layout.model.LogicalPageBox; import org.pentaho.reporting.engine.classic.core.layout.model.ParagraphRenderBox; import org.pentaho.reporting.engine.classic.core.layout.model.RenderBox; import org.pentaho.reporting.engine.classic.core.layout.model.RenderLength; import org.pentaho.reporting.engine.classic.core.layout.model.RenderNode; import org.pentaho.reporting.engine.classic.core.layout.model.RenderableComplexText; import org.pentaho.reporting.engine.classic.core.layout.model.RenderableReplacedContent; import org.pentaho.reporting.engine.classic.core.layout.model.RenderableReplacedContentBox; import org.pentaho.reporting.engine.classic.core.layout.model.RenderableText; import org.pentaho.reporting.engine.classic.core.layout.model.SpacerRenderNode; import org.pentaho.reporting.engine.classic.core.layout.model.context.BoxDefinition; import org.pentaho.reporting.engine.classic.core.layout.model.context.StaticBoxLayoutProperties; import org.pentaho.reporting.engine.classic.core.layout.model.table.TableCellRenderBox; import org.pentaho.reporting.engine.classic.core.layout.model.table.TableColumnGroupNode; import org.pentaho.reporting.engine.classic.core.layout.model.table.TableRenderBox; import org.pentaho.reporting.engine.classic.core.layout.model.table.TableRowRenderBox; import org.pentaho.reporting.engine.classic.core.layout.model.table.TableSectionRenderBox; import org.pentaho.reporting.engine.classic.core.layout.output.OutputProcessorFeature; import org.pentaho.reporting.engine.classic.core.layout.output.OutputProcessorMetaData; import org.pentaho.reporting.engine.classic.core.layout.process.IterateStructuralProcessStep; import org.pentaho.reporting.engine.classic.core.metadata.AttributeMetaData; import org.pentaho.reporting.engine.classic.core.metadata.ElementType; import org.pentaho.reporting.engine.classic.core.modules.output.table.base.AbstractTableOutputProcessor; import org.pentaho.reporting.engine.classic.core.modules.output.table.base.CellBackground; import org.pentaho.reporting.engine.classic.core.modules.output.table.base.CellBackgroundProducer; import org.pentaho.reporting.engine.classic.core.modules.output.table.base.CellMarker; import org.pentaho.reporting.engine.classic.core.modules.output.table.base.SheetLayout; import org.pentaho.reporting.engine.classic.core.modules.output.table.base.TableContentProducer; import org.pentaho.reporting.engine.classic.core.modules.output.table.base.TableRectangle; import org.pentaho.reporting.engine.classic.core.style.ElementStyleKeys; import org.pentaho.reporting.engine.classic.core.style.TextStyleKeys; import org.pentaho.reporting.engine.classic.core.util.beans.BeanException; import org.pentaho.reporting.engine.classic.core.util.beans.ColorValueConverter; import org.pentaho.reporting.engine.classic.core.util.beans.ConverterRegistry; import org.pentaho.reporting.engine.classic.core.util.geom.StrictBounds; import org.pentaho.reporting.engine.classic.core.util.geom.StrictGeomUtility; import org.pentaho.reporting.libraries.base.util.StringUtils; import org.pentaho.reporting.libraries.fonts.encoding.EncodingRegistry; import org.pentaho.reporting.libraries.formatting.FastDecimalFormat; import org.pentaho.reporting.libraries.resourceloader.ResourceKey; import org.pentaho.reporting.libraries.xmlns.common.AttributeList; import org.pentaho.reporting.libraries.xmlns.common.AttributeMap; import org.pentaho.reporting.libraries.xmlns.writer.DefaultTagDescription; import org.pentaho.reporting.libraries.xmlns.writer.XmlWriter; import java.awt.Color; import java.beans.PropertyEditor; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.Arrays; import java.util.Comparator; import java.util.Locale; import java.util.Set; /** * @noinspection HardCodedStringLiteral */ public class XmlDocumentWriter extends IterateStructuralProcessStep { private static final String LAYOUT_OUTPUT_NAMESPACE = "http://reporting.pentaho.org/namespaces/output/layout-output/table/1.0"; private static final Log logger = LogFactory.getLog( XmlDocumentWriter.class ); private OutputStream outputStream; private StrictBounds drawArea; private XmlWriter xmlWriter; private FastDecimalFormat pointIntConverter; private FastDecimalFormat pointConverter; private FastDecimalFormat pointShortConverter; private boolean ignoreEmptyBorders; private CellBackgroundProducer cellBackgroundProducer; private OutputProcessorMetaData metaData; private boolean tableOpen; public XmlDocumentWriter( final OutputStream outputStream, final OutputProcessorMetaData metaData ) { this.metaData = metaData; if ( outputStream == null ) { throw new NullPointerException(); } this.outputStream = outputStream; this.pointConverter = new FastDecimalFormat( "0.0####", Locale.US ); this.pointShortConverter = new FastDecimalFormat( "0.#####", Locale.US ); this.pointIntConverter = new FastDecimalFormat( "0", Locale.US ); this.ignoreEmptyBorders = true; } public void open() throws IOException { final DefaultTagDescription td = new DefaultTagDescription(); td.setNamespaceHasCData( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, false ); td.setElementHasCData( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "text", true ); // prepare anything that might needed to be prepared .. final String encoding = metaData.getConfiguration().getConfigProperty( "org.pentaho.reporting.engine.classic.core.modules.output.table.xml.Encoding", EncodingRegistry.getPlatformDefaultEncoding() ); final Writer writer = new BufferedWriter( new OutputStreamWriter( outputStream, encoding ) ); this.xmlWriter = new XmlWriter( writer, td ); this.xmlWriter.writeXmlDeclaration( null ); final AttributeList attrs = new AttributeList(); attrs.addNamespaceDeclaration( "", XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE ); xmlWriter.writeTag( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "layout-output", attrs, XmlWriter.OPEN ); } public void close() throws IOException { // close all .. xmlWriter.writeCloseTag(); xmlWriter.close(); } public void processTableContent( final LogicalPageBox logicalPageBox, final OutputProcessorMetaData metaData, final TableContentProducer contentProducer, final boolean incremental ) throws IOException { // Start a new page. final SheetLayout sheetLayout = contentProducer.getSheetLayout(); final int columnCount = contentProducer.getColumnCount(); final int rowCount = contentProducer.getRowCount(); final int startRow = contentProducer.getFinishedRows(); final int finishRow = contentProducer.getFilledRows(); if ( incremental && startRow == finishRow ) { return; } if ( cellBackgroundProducer == null ) { this.cellBackgroundProducer = new CellBackgroundProducer( metaData .isFeatureSupported( AbstractTableOutputProcessor.TREAT_ELLIPSE_AS_RECTANGLE ), metaData .isFeatureSupported( OutputProcessorFeature.UNALIGNED_PAGEBANDS ) ); } if ( tableOpen == false ) { tableOpen = true; final AttributeList pageAttributes = new AttributeList(); pageAttributes.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "col-count", pointIntConverter .format( columnCount ) ); pageAttributes.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "row-count", pointIntConverter .format( rowCount ) ); pageAttributes.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "sheet-name", contentProducer .getSheetName() ); xmlWriter.writeTag( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "table", pageAttributes, XmlWriter.OPEN ); xmlWriter.writeTag( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "cols", XmlWriter.OPEN ); for ( int i = 0; i < columnCount; i++ ) { final double cellWidth = StrictGeomUtility.toExternalValue( sheetLayout.getCellWidth( i, i + 1 ) ); xmlWriter.writeTag( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "column", "width", pointShortConverter .format( cellWidth ), XmlWriter.CLOSE ); } xmlWriter.writeCloseTag(); } for ( int row = startRow; row < finishRow; row++ ) { final AttributeList rowAttributes = new AttributeList(); final double rowHeight = StrictGeomUtility.toExternalValue( sheetLayout.getRowHeight( row ) ); rowAttributes.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "height", pointShortConverter .format( rowHeight ) ); xmlWriter.writeTag( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "row", rowAttributes, XmlWriter.OPEN ); for ( short col = 0; col < columnCount; col++ ) { final RenderBox content = contentProducer.getContent( row, col ); if ( content == null ) { final CellMarker.SectionType sectionType = contentProducer.getSectionType( row, col ); final RenderBox backgroundBox = contentProducer.getBackground( row, col ); final CellBackground background; if ( backgroundBox != null ) { background = cellBackgroundProducer.getBackgroundForBox( logicalPageBox, sheetLayout, col, row, 1, 1, true, sectionType, backgroundBox ); } else { background = cellBackgroundProducer.getBackgroundAt( logicalPageBox, sheetLayout, col, row, true, sectionType ); } if ( background == null ) { xmlWriter.writeTag( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "empty-cell", XmlWriter.CLOSE ); continue; } // A empty cell with a defined background .. xmlWriter.writeTag( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "empty-cell", createCellAttributes( background ), XmlWriter.OPEN ); writeAttributes( background.getAttributes(), background.getElementType() ); xmlWriter.writeCloseTag(); continue; } if ( content.isCommited() == false ) { throw new InvalidReportStateException( "Uncommited content encountered" ); } final long contentOffset = contentProducer.getContentOffset( row, col ); final TableRectangle rectangle = sheetLayout.getTableBounds( content.getX(), content.getY() + contentOffset, content.getWidth(), content .getHeight(), null ); if ( rectangle.isOrigin( col, row ) == false ) { // A spanned cell .. xmlWriter.writeTag( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "spanned-cell", XmlWriter.CLOSE ); continue; } final CellMarker.SectionType sectionType = contentProducer.getSectionType( row, col ); final CellBackground realBackground = cellBackgroundProducer.getBackgroundForBox( logicalPageBox, sheetLayout, rectangle.getX1(), rectangle .getY1(), rectangle.getColumnSpan(), rectangle.getRowSpan(), false, sectionType, content ); final AttributeList attributeList; if ( realBackground != null ) { attributeList = createCellAttributes( realBackground ); } else { attributeList = new AttributeList(); } attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "row-span", pointIntConverter .format( rectangle.getRowSpan() ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "col-span", pointIntConverter .format( rectangle.getColumnSpan() ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "href", (String) content.getStyleSheet() .getStyleProperty( ElementStyleKeys.HREF_TARGET ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "href-window", (String) content .getStyleSheet().getStyleProperty( ElementStyleKeys.HREF_WINDOW ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "href-title", (String) content .getStyleSheet().getStyleProperty( ElementStyleKeys.HREF_TITLE ) ); // export the cell and all content .. xmlWriter.writeTag( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "cell", attributeList, XmlWriter.OPEN ); writeAttributes( content.getAttributes(), content.getElementType() ); processBoxChilds( content ); xmlWriter.writeCloseTag(); content.setFinishedTable( true ); } xmlWriter.writeCloseTag(); } if ( incremental == false ) { xmlWriter.writeCloseTag(); // table tableOpen = false; } } private AttributeList createBoxAttributeList( final RenderBox box ) { final AttributeList attributeList = new AttributeList(); if ( StringUtils.isEmpty( box.getName() ) == false ) { attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "name", box.getName() ); } attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "type", box.getClass().getSimpleName() ); final BoxDefinition definition = box.getBoxDefinition(); final Border border = definition.getBorder(); final StaticBoxLayoutProperties sblp = box.getStaticBoxLayoutProperties(); final BorderEdge top = border.getTop(); if ( BorderEdge.EMPTY.equals( top ) == false || ignoreEmptyBorders == false ) { attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-top-color", ColorValueConverter.colorToString( top.getColor() ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-top-width", pointConverter.format( StrictGeomUtility.toExternalValue( sblp.getBorderTop() ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-top-style", String.valueOf( top.getBorderStyle() ) ); } final BorderEdge left = border.getLeft(); if ( BorderEdge.EMPTY.equals( left ) == false || ignoreEmptyBorders == false ) { attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-left-color", ColorValueConverter.colorToString( left.getColor() ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-left-width", pointConverter.format( StrictGeomUtility.toExternalValue( sblp.getBorderLeft() ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-left-style", String.valueOf( left.getBorderStyle() ) ); } final BorderEdge bottom = border.getBottom(); if ( BorderEdge.EMPTY.equals( bottom ) == false || ignoreEmptyBorders == false ) { attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-bottom-color", ColorValueConverter.colorToString( bottom.getColor() ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-bottom-width", pointConverter.format( StrictGeomUtility.toExternalValue( sblp.getBorderBottom() ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-bottom-style", String.valueOf( bottom.getBorderStyle() ) ); } final BorderEdge right = border.getRight(); if ( BorderEdge.EMPTY.equals( right ) == false || ignoreEmptyBorders == false ) { attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-right-color", ColorValueConverter.colorToString( right.getColor() ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-right-width", pointConverter.format( StrictGeomUtility.toExternalValue( sblp.getBorderRight() ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-right-style", String.valueOf( right.getBorderStyle() ) ); } final BorderCorner topLeft = border.getTopLeft(); if ( isEmptyCorner( topLeft ) == false ) { attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-top-left-x", pointConverter.format( StrictGeomUtility.toExternalValue( topLeft.getWidth() ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-top-left-y", pointConverter.format( StrictGeomUtility.toExternalValue( topLeft.getHeight() ) ) ); } final BorderCorner topRight = border.getTopRight(); if ( isEmptyCorner( topRight ) == false ) { attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-top-right-x", pointConverter.format( StrictGeomUtility.toExternalValue( topRight.getWidth() ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-top-right-y", pointConverter.format( StrictGeomUtility.toExternalValue( topRight.getHeight() ) ) ); } final BorderCorner bottomLeft = border.getBottomLeft(); if ( isEmptyCorner( bottomLeft ) == false ) { attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-bottom-left-x", pointConverter.format( StrictGeomUtility.toExternalValue( bottomLeft.getWidth() ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-bottom-left-y", pointConverter.format( StrictGeomUtility.toExternalValue( bottomLeft.getHeight() ) ) ); } final BorderCorner bottomRight = border.getBottomRight(); if ( isEmptyCorner( bottomRight ) == false ) { attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-bottom-right-x", pointConverter.format( StrictGeomUtility.toExternalValue( bottomRight.getWidth() ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-bottom-right-y", pointConverter.format( StrictGeomUtility.toExternalValue( bottomRight.getHeight() ) ) ); } if ( sblp.getMarginTop() > 0 ) { attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "margin-top", pointConverter.format( StrictGeomUtility.toExternalValue( sblp.getMarginTop() ) ) ); } if ( sblp.getMarginLeft() > 0 ) { attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "margin-left", pointConverter.format( StrictGeomUtility.toExternalValue( sblp.getMarginLeft() ) ) ); } if ( sblp.getMarginBottom() > 0 ) { attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "margin-bottom", pointConverter.format( StrictGeomUtility.toExternalValue( sblp.getMarginBottom() ) ) ); } if ( sblp.getMarginRight() > 0 ) { attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "margin-right", pointConverter.format( StrictGeomUtility.toExternalValue( sblp.getMarginRight() ) ) ); } if ( definition.getPaddingTop() > 0 ) { attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "padding-top", pointConverter.format( StrictGeomUtility.toExternalValue( definition.getPaddingTop() ) ) ); } if ( definition.getPaddingLeft() > 0 ) { attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "padding-left", pointConverter.format( StrictGeomUtility.toExternalValue( definition.getPaddingLeft() ) ) ); } if ( definition.getPaddingBottom() > 0 ) { attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "padding-bottom", pointConverter.format( StrictGeomUtility.toExternalValue( definition.getPaddingBottom() ) ) ); } if ( definition.getPaddingRight() > 0 ) { attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "padding-right", pointConverter.format( StrictGeomUtility.toExternalValue( definition.getPaddingRight() ) ) ); } attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "x", pointConverter.format( StrictGeomUtility.toExternalValue( box.getX() ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "y", pointConverter.format( StrictGeomUtility.toExternalValue( box.getY() ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "width", pointConverter.format( StrictGeomUtility.toExternalValue( box.getWidth() ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "height", pointConverter.format( StrictGeomUtility.toExternalValue( box.getHeight() ) ) ); final Color backgroundColor = (Color) box.getStyleSheet().getStyleProperty( ElementStyleKeys.BACKGROUND_COLOR ); if ( backgroundColor != null ) { attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "background-color", ColorValueConverter.colorToString( backgroundColor ) ); } final Color color = (Color) box.getStyleSheet().getStyleProperty( ElementStyleKeys.PAINT ); if ( color != null ) { attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "color", ColorValueConverter.colorToString( color ) ); } attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "font-face", (String) box.getStyleSheet().getStyleProperty( TextStyleKeys.FONT ) ); final Object o = box.getStyleSheet().getStyleProperty( TextStyleKeys.FONTSIZE ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "font-size", pointIntConverter.format( o ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "font-style-bold", String.valueOf( box.getStyleSheet().getStyleProperty( TextStyleKeys.BOLD ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "font-style-italics", String.valueOf( box.getStyleSheet().getStyleProperty( TextStyleKeys.ITALIC ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "font-style-underline", String.valueOf( box.getStyleSheet().getStyleProperty( TextStyleKeys.UNDERLINED ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "font-style-strikethrough", String.valueOf( box.getStyleSheet().getStyleProperty( TextStyleKeys.STRIKETHROUGH ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "anchor", (String) box.getStyleSheet().getStyleProperty( ElementStyleKeys.ANCHOR_NAME ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "href", (String) box.getStyleSheet().getStyleProperty( ElementStyleKeys.HREF_TARGET ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "href-window", (String) box.getStyleSheet().getStyleProperty( ElementStyleKeys.HREF_WINDOW ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "href-title", (String) box.getStyleSheet().getStyleProperty( ElementStyleKeys.HREF_TITLE ) ); return attributeList; } private void writeElementAttributes( final RenderNode element ) throws IOException { final ReportAttributeMap attributes = element.getAttributes(); final ElementType type = element.getElementType(); writeAttributes( attributes, type ); } private void writeAttributes( final ReportAttributeMap attributes, ElementType type ) throws IOException { if ( type == null ) { type = AutoLayoutBoxType.INSTANCE; } final Set<AttributeMap.DualKey> collection = attributes.keySet(); final AttributeMap.DualKey[] attributeNames = collection.toArray( new AttributeMap.DualKey[ collection.size() ] ); Arrays.sort( attributeNames, new DualKeySorter() ); for ( int j = 0; j < attributeNames.length; j++ ) { final String namespace = attributeNames[ j ].namespace; if ( AttributeNames.Designtime.NAMESPACE.equals( namespace ) ) { continue; } final String name = attributeNames[ j ].name; final Object value = attributes.getAttribute( namespace, name ); if ( value == null ) { continue; } if ( metaData.isFeatureSupported( XmlTableOutputProcessorMetaData.WRITE_RESOURCEKEYS ) == false && value instanceof ResourceKey ) { continue; } final AttributeMetaData attrMeta = type.getMetaData().getAttributeDescription( namespace, name ); if ( attrMeta == null ) { // if you want to use attributes in this output target, declare the attribute's metadata first. continue; } final AttributeList attList = new AttributeList(); if ( value instanceof String ) { final String s = (String) value; if ( StringUtils.isEmpty( s ) ) { continue; } if ( xmlWriter.isNamespaceDefined( namespace ) == false && attList.isNamespaceUriDefined( namespace ) == false ) { attList.addNamespaceDeclaration( "autoGenNs", namespace ); } // preserve strings, but discard anything else. Until a attribute has a definition, we cannot // hope to understand the attribute's value. String-attributes can be expressed in XML easily, // and string is also how all unknown attributes are stored by the parser. attList.setAttribute( namespace, name, s ); this.xmlWriter.writeTag( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "attribute", attList, XmlWriter.CLOSE ); continue; } if ( xmlWriter.isNamespaceDefined( namespace ) == false && attList.isNamespaceUriDefined( namespace ) == false ) { attList.addNamespaceDeclaration( "autoGenNs", namespace ); } try { final PropertyEditor propertyEditor = attrMeta.getEditor(); final String textValue; if ( propertyEditor != null ) { propertyEditor.setValue( value ); textValue = propertyEditor.getAsText(); } else { textValue = ConverterRegistry.toAttributeValue( value ); } if ( textValue != null ) { if ( "".equals( textValue ) == false ) { attList.setAttribute( namespace, name, textValue ); this.xmlWriter .writeTag( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "attribute", attList, XmlWriter.CLOSE ); } } else { if ( value instanceof ResourceKey ) { final ResourceKey reskey = (ResourceKey) value; final String identifierAsString = reskey.getIdentifierAsString(); attList.setAttribute( namespace, name, "resource-key:" + reskey.getSchema() + ":" + identifierAsString ); this.xmlWriter .writeTag( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "attribute", attList, XmlWriter.CLOSE ); } else { XmlDocumentWriter.logger.debug( "Attribute '" + namespace + '|' + name + "' is not convertible to a text - returned null: " + value ); } } } catch ( BeanException e ) { if ( attrMeta.isTransient() == false ) { XmlDocumentWriter.logger.warn( "Attribute '" + namespace + '|' + name + "' is not convertible with the bean-methods" ); } else { XmlDocumentWriter.logger.debug( "Attribute '" + namespace + '|' + name + "' is not convertible with the bean-methods" ); } } } } private boolean isEmptyCorner( final BorderCorner corner ) { if ( ignoreEmptyBorders == false ) { return false; } return corner.getWidth() == 0 && corner.getHeight() == 0; } private AttributeList createCellAttributes( final CellBackground border ) { final AttributeList attributeList = new AttributeList(); final BorderEdge top = border.getTop(); if ( BorderEdge.EMPTY.equals( top ) == false || ignoreEmptyBorders == false ) { attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-top-color", ColorValueConverter.colorToString( top.getColor() ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-top-width", pointConverter.format( StrictGeomUtility.toExternalValue( top.getWidth() ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-top-style", String.valueOf( top.getBorderStyle() ) ); } final BorderEdge left = border.getLeft(); if ( BorderEdge.EMPTY.equals( left ) == false || ignoreEmptyBorders == false ) { attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-left-color", ColorValueConverter.colorToString( left.getColor() ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-left-width", pointConverter.format( StrictGeomUtility.toExternalValue( left.getWidth() ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-left-style", String.valueOf( left.getBorderStyle() ) ); } final BorderEdge bottom = border.getBottom(); if ( BorderEdge.EMPTY.equals( bottom ) == false || ignoreEmptyBorders == false ) { attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-bottom-color", ColorValueConverter.colorToString( bottom.getColor() ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-bottom-width", pointConverter.format( StrictGeomUtility.toExternalValue( bottom.getWidth() ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-bottom-style", String.valueOf( bottom.getBorderStyle() ) ); } final BorderEdge right = border.getRight(); if ( BorderEdge.EMPTY.equals( right ) == false || ignoreEmptyBorders == false ) { attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-right-color", ColorValueConverter.colorToString( right.getColor() ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-right-width", pointConverter.format( StrictGeomUtility.toExternalValue( right.getWidth() ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-right-style", String.valueOf( right.getBorderStyle() ) ); } final BorderCorner topLeft = border.getTopLeft(); if ( isEmptyCorner( topLeft ) == false ) { attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-top-left-x", pointConverter.format( StrictGeomUtility.toExternalValue( topLeft.getWidth() ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-top-left-y", pointConverter.format( StrictGeomUtility.toExternalValue( topLeft.getHeight() ) ) ); } final BorderCorner topRight = border.getTopRight(); if ( isEmptyCorner( topRight ) == false ) { attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-top-right-x", pointConverter.format( StrictGeomUtility.toExternalValue( topRight.getWidth() ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-top-right-y", pointConverter.format( StrictGeomUtility.toExternalValue( topRight.getHeight() ) ) ); } final BorderCorner bottomLeft = border.getBottomLeft(); if ( isEmptyCorner( bottomLeft ) == false ) { attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-bottom-left-x", pointConverter.format( StrictGeomUtility.toExternalValue( bottomLeft.getWidth() ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-bottom-left-y", pointConverter.format( StrictGeomUtility.toExternalValue( bottomLeft.getHeight() ) ) ); } final BorderCorner bottomRight = border.getBottomRight(); if ( isEmptyCorner( bottomRight ) == false ) { attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-bottom-right-x", pointConverter.format( StrictGeomUtility.toExternalValue( bottomRight.getWidth() ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "border-bottom-right-y", pointConverter.format( StrictGeomUtility.toExternalValue( bottomRight.getHeight() ) ) ); } final Color backgroundColor = border.getBackgroundColor(); if ( backgroundColor != null ) { attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "background-color", ColorValueConverter.colorToString( backgroundColor ) ); } final String[] anchors = border.getAnchors(); if ( anchors.length > 0 ) { final StringBuilder anchorText = new StringBuilder( 100 ); for ( int i = 0; i < anchors.length; i++ ) { final String anchor = anchors[ i ]; if ( i == 0 ) { anchorText.append( ' ' ); } anchorText.append( anchor ); } attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "anchor", anchorText.toString() ); } return attributeList; } protected boolean startBlockBox( final BlockRenderBox box ) { try { final int nodeType = box.getNodeType(); if ( ( nodeType & LayoutNodeTypes.MASK_BOX_PAGEAREA ) == LayoutNodeTypes.MASK_BOX_PAGEAREA ) { final AttributeList list = createBoxAttributeList( box ); list.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "type", box.getName() ); xmlWriter.writeTag( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "page-area", list, XmlWriter.OPEN ); writeElementAttributes( box ); return true; } else if ( nodeType == LayoutNodeTypes.TYPE_BOX_PARAGRAPH ) { return startBox( box, "p" ); } else { return startBox( box, "block" ); } } catch ( IOException e ) { throw new InvalidReportStateException( e.getMessage(), e ); } } protected void finishBlockBox( final BlockRenderBox box ) { finishBox(); } protected boolean startInlineBox( final InlineRenderBox box ) { if ( box.getNodeType() == LayoutNodeTypes.TYPE_BOX_LINEBOX ) { return startBox( box, "line" ); } else { return startBox( box, "inline" ); } } protected void finishInlineBox( final InlineRenderBox box ) { finishBox(); } protected boolean startOtherBox( final RenderBox box ) { try { xmlWriter.writeTag( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "other-box", createBoxAttributeList( box ), XmlWriter.OPEN ); writeElementAttributes( box ); return true; } catch ( IOException e ) { throw new InvalidReportStateException( e.getMessage(), e ); } } protected void finishOtherBox( final RenderBox box ) { finishBox(); } protected boolean startCanvasBox( final CanvasRenderBox box ) { return startBox( box, "canvas" ); } private boolean startBox( final RenderBox box, final String tagName ) { try { xmlWriter.writeTag( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, tagName, createBoxAttributeList( box ), XmlWriter.OPEN ); writeElementAttributes( box ); return true; } catch ( IOException e ) { throw new InvalidReportStateException( e.getMessage(), e ); } } protected void finishCanvasBox( final CanvasRenderBox box ) { finishBox(); } protected boolean startRowBox( final RenderBox box ) { try { xmlWriter .writeTag( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "row", createBoxAttributeList( box ), XmlWriter.OPEN ); writeElementAttributes( box ); return true; } catch ( IOException e ) { throw new InvalidReportStateException( e.getMessage(), e ); } } protected void finishRowBox( final RenderBox box ) { finishBox(); } private void finishBox() { try { xmlWriter.writeCloseTag(); } catch ( IOException e ) { throw new InvalidReportStateException( e.getMessage(), e ); } } protected void processOtherNode( final RenderNode node ) { try { final int nodeType = node.getNodeType(); if ( nodeType == LayoutNodeTypes.TYPE_NODE_TEXT ) { final RenderableText text = (RenderableText) node; final AttributeList attributeList = new AttributeList(); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "x", pointConverter.format( StrictGeomUtility.toExternalValue( node.getX() ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "y", pointConverter.format( StrictGeomUtility.toExternalValue( node.getY() ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "width", pointConverter.format( StrictGeomUtility.toExternalValue( node.getWidth() ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "height", pointConverter.format( StrictGeomUtility.toExternalValue( node.getHeight() ) ) ); xmlWriter.writeTag( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "text", attributeList, XmlWriter.OPEN ); xmlWriter.writeTextNormalized( text.getRawText(), true ); xmlWriter.writeCloseTag(); } else if ( nodeType == LayoutNodeTypes.TYPE_NODE_COMPLEX_TEXT ) { final RenderableComplexText renderableComplexText = (RenderableComplexText) node; final AttributeList attributeList = new AttributeList(); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "x", pointConverter.format( StrictGeomUtility.toExternalValue( node.getX() ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "y", pointConverter.format( StrictGeomUtility.toExternalValue( node.getY() ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "width", pointConverter.format( StrictGeomUtility.toExternalValue( node.getWidth() ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "height", pointConverter.format( StrictGeomUtility.toExternalValue( node.getHeight() ) ) ); final String text = renderableComplexText.getRawText(); xmlWriter.writeTag( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "text", attributeList, XmlWriter.OPEN ); xmlWriter.writeTextNormalized( text, true ); xmlWriter.writeCloseTag(); } else if ( nodeType == LayoutNodeTypes.TYPE_NODE_SPACER ) { final SpacerRenderNode spacer = (SpacerRenderNode) node; final AttributeList attributeList = new AttributeList(); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "width", pointConverter.format( StrictGeomUtility.toExternalValue( node.getWidth() ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "height", pointConverter.format( StrictGeomUtility.toExternalValue( node.getHeight() ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "preserve", String.valueOf( spacer.isDiscardable() == false ) ); xmlWriter.writeTag( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "spacer", attributeList, XmlWriter.CLOSE ); } } catch ( IOException e ) { throw new InvalidReportStateException( e.getMessage(), e ); } } protected void processRenderableContent( final RenderableReplacedContentBox node ) { try { final RenderableReplacedContent prc = node.getContent(); final AttributeList attributeList = new AttributeList(); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "x", pointConverter.format( StrictGeomUtility.toExternalValue( node.getX() ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "y", pointConverter.format( StrictGeomUtility.toExternalValue( node.getY() ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "width", pointConverter.format( StrictGeomUtility.toExternalValue( node.getWidth() ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "height", pointConverter.format( StrictGeomUtility.toExternalValue( node.getHeight() ) ) ); attributeList .setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "source", String.valueOf( prc.getSource() ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "content-width", pointConverter.format( StrictGeomUtility.toExternalValue( prc.getContentWidth() ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "content-height", pointConverter.format( StrictGeomUtility.toExternalValue( prc.getContentHeight() ) ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "requested-width", convertRenderLength( prc.getRequestedWidth() ) ); attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "requested-height", convertRenderLength( prc.getRequestedHeight() ) ); final Object o = prc.getRawObject(); if ( o != null ) { attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "raw-object-type", o.getClass().getName() ); } else { attributeList.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "raw-object-type", "null" ); } xmlWriter .writeTag( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "replaced-content", attributeList, XmlWriter.OPEN ); writeElementAttributes( node ); xmlWriter.writeCloseTag(); } catch ( IOException e ) { throw new InvalidReportStateException( e.getMessage(), e ); } } private String convertRenderLength( final RenderLength length ) { if ( length == null ) { return null; } if ( RenderLength.AUTO.equals( length ) ) { return "auto"; } if ( length.isPercentage() ) { return pointConverter.format( StrictGeomUtility.toExternalValue( -length.getValue() ) ) + '%'; } return pointConverter.format( StrictGeomUtility.toExternalValue( length.getValue() ) ); } protected void processParagraphChilds( final ParagraphRenderBox box ) { processBoxChilds( box ); } protected boolean startTableCellBox( final TableCellRenderBox box ) { try { AttributeList attrs = createBoxAttributeList( box ); attrs.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "col-span", String.valueOf( box.getColSpan() ) ); attrs.setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "row-span", String.valueOf( box.getRowSpan() ) ); attrs .setAttribute( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "col-index", String.valueOf( box.getColumnIndex() ) ); xmlWriter.writeTag( XmlDocumentWriter.LAYOUT_OUTPUT_NAMESPACE, "table-cell", attrs, XmlWriter.OPEN ); writeElementAttributes( box ); return true; } catch ( IOException e ) { throw new InvalidReportStateException( e.getMessage(), e ); } } protected void finishTableCellBox( final TableCellRenderBox box ) { finishBox(); } protected boolean startTableRowBox( final TableRowRenderBox box ) { return startBox( box, "table-row" ); } protected void finishTableRowBox( final TableRowRenderBox box ) { finishBox(); } protected boolean startTableSectionBox( final TableSectionRenderBox box ) { return startBox( box, "table-section" ); } protected void finishTableSectionBox( final TableSectionRenderBox box ) { finishBox(); } protected boolean startTableColumnGroupBox( final TableColumnGroupNode box ) { return startBox( box, "colgroup" ); } protected void finishTableColumnGroupBox( final TableColumnGroupNode box ) { finishBox(); } protected boolean startTableBox( final TableRenderBox box ) { return startBox( box, "table" ); } protected void finishTableBox( final TableRenderBox box ) { finishBox(); } protected boolean startAutoBox( final RenderBox box ) { return startBox( box, "auto" ); } protected void finishAutoBox( final RenderBox box ) { finishBox(); } private static class DualKeySorter implements Comparator<AttributeMap.DualKey> { public int compare( final AttributeMap.DualKey o1, final AttributeMap.DualKey o2 ) { if ( o1 == null && o2 == null ) { return 0; } if ( o1 == null ) { return -1; } if ( o2 == null ) { return 1; } int ns = o1.namespace.compareTo( o2.namespace ); if ( ns != 0 ) { return ns; } return o1.name.compareTo( o2.name ); } } }