When accessibility is enabled, this allows to set the appropriate Scope attribute on the corresponding TH cells. git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1390412 13f79535-47bb-0310-9956-ffa450edef68tags/fop-2_0
package org.apache.fop.accessibility.fo; | package org.apache.fop.accessibility.fo; | ||||
import java.util.Locale; | import java.util.Locale; | ||||
import java.util.Stack; | |||||
import org.xml.sax.SAXException; | import org.xml.sax.SAXException; | ||||
import org.xml.sax.helpers.AttributesImpl; | import org.xml.sax.helpers.AttributesImpl; | ||||
import org.apache.fop.fo.FONode; | import org.apache.fop.fo.FONode; | ||||
import org.apache.fop.fo.FOText; | import org.apache.fop.fo.FOText; | ||||
import org.apache.fop.fo.extensions.ExtensionElementMapping; | import org.apache.fop.fo.extensions.ExtensionElementMapping; | ||||
import org.apache.fop.fo.extensions.InternalElementMapping; | |||||
import org.apache.fop.fo.flow.AbstractGraphics; | import org.apache.fop.fo.flow.AbstractGraphics; | ||||
import org.apache.fop.fo.flow.BasicLink; | import org.apache.fop.fo.flow.BasicLink; | ||||
import org.apache.fop.fo.flow.Block; | import org.apache.fop.fo.flow.Block; | ||||
private LayoutMasterSet layoutMasterSet; | private LayoutMasterSet layoutMasterSet; | ||||
private final Stack<Table> tables = new Stack<Table>(); | |||||
private final Stack<Boolean> inTableHeader = new Stack<Boolean>(); | |||||
public StructureTreeEventTrigger(StructureTreeEventHandler structureTreeEventHandler) { | public StructureTreeEventTrigger(StructureTreeEventHandler structureTreeEventHandler) { | ||||
this.structureTreeEventHandler = structureTreeEventHandler; | this.structureTreeEventHandler = structureTreeEventHandler; | ||||
} | } | ||||
@Override | @Override | ||||
public void startTable(Table tbl) { | public void startTable(Table tbl) { | ||||
tables.push(tbl); | |||||
startElement(tbl); | startElement(tbl); | ||||
} | } | ||||
@Override | @Override | ||||
public void endTable(Table tbl) { | public void endTable(Table tbl) { | ||||
endElement(tbl); | endElement(tbl); | ||||
tables.pop(); | |||||
} | } | ||||
@Override | @Override | ||||
public void startHeader(TableHeader header) { | public void startHeader(TableHeader header) { | ||||
inTableHeader.push(Boolean.TRUE); | |||||
startElement(header); | startElement(header); | ||||
} | } | ||||
@Override | @Override | ||||
public void endHeader(TableHeader header) { | public void endHeader(TableHeader header) { | ||||
endElement(header); | endElement(header); | ||||
inTableHeader.pop(); | |||||
} | } | ||||
@Override | @Override | ||||
public void startFooter(TableFooter footer) { | public void startFooter(TableFooter footer) { | ||||
// TODO Shouldn't it be true? | |||||
inTableHeader.push(Boolean.FALSE); | |||||
startElement(footer); | startElement(footer); | ||||
} | } | ||||
@Override | @Override | ||||
public void endFooter(TableFooter footer) { | public void endFooter(TableFooter footer) { | ||||
endElement(footer); | endElement(footer); | ||||
inTableHeader.pop(); | |||||
} | } | ||||
@Override | @Override | ||||
public void startBody(TableBody body) { | public void startBody(TableBody body) { | ||||
inTableHeader.push(Boolean.FALSE); | |||||
startElement(body); | startElement(body); | ||||
} | } | ||||
@Override | @Override | ||||
public void endBody(TableBody body) { | public void endBody(TableBody body) { | ||||
endElement(body); | endElement(body); | ||||
inTableHeader.pop(); | |||||
} | } | ||||
@Override | @Override | ||||
AttributesImpl attributes = new AttributesImpl(); | AttributesImpl attributes = new AttributesImpl(); | ||||
addSpanAttribute(attributes, "number-columns-spanned", tc.getNumberColumnsSpanned()); | addSpanAttribute(attributes, "number-columns-spanned", tc.getNumberColumnsSpanned()); | ||||
addSpanAttribute(attributes, "number-rows-spanned", tc.getNumberRowsSpanned()); | addSpanAttribute(attributes, "number-rows-spanned", tc.getNumberRowsSpanned()); | ||||
boolean rowHeader = inTableHeader.peek(); | |||||
boolean columnHeader = tables.peek().getColumn(tc.getColumnNumber() - 1).isHeader(); | |||||
if (rowHeader || columnHeader) { | |||||
final String th = "TH"; | |||||
String role = tc.getCommonAccessibility().getRole(); | |||||
/* Do not override a custom role */ | |||||
if (role == null) { | |||||
role = th; | |||||
addNoNamespaceAttribute(attributes, "role", th); | |||||
} | |||||
if (role.equals(th)) { | |||||
if (columnHeader) { | |||||
String scope = rowHeader ? "Both" : "Row"; | |||||
addAttribute(attributes, InternalElementMapping.URI, InternalElementMapping.SCOPE, | |||||
InternalElementMapping.STANDARD_PREFIX, scope); | |||||
} | |||||
} | |||||
} | |||||
startElement(tc, attributes); | startElement(tc, attributes); | ||||
} | } | ||||
*/ | */ | ||||
int PR_X_NUMBER_CONVERSION_FEATURES = 276; | int PR_X_NUMBER_CONVERSION_FEATURES = 276; | ||||
/** Scope for table header */ | |||||
int PR_X_HEADER_COLUMN = 277; | |||||
/** Number of property constants defined */ | /** Number of property constants defined */ | ||||
int PROPERTY_COUNT = 276; | |||||
int PROPERTY_COUNT = 277; | |||||
// compound property constants | // compound property constants | ||||
m.setInherited(false); | m.setInherited(false); | ||||
m.setDefault("false"); | m.setDefault("false"); | ||||
addPropertyMaker("table-omit-header-at-break", m); | addPropertyMaker("table-omit-header-at-break", m); | ||||
// fox:scope | |||||
m = new EnumProperty.Maker(PR_X_HEADER_COLUMN); | |||||
m.useGeneric(genericBoolean); | |||||
m.setDefault("false"); | |||||
addPropertyMaker("fox:header", m); | |||||
} | } | ||||
private void createWritingModeProperties() { | private void createWritingModeProperties() { |
PROPERTY_ATTRIBUTES.add("disable-column-balancing"); | PROPERTY_ATTRIBUTES.add("disable-column-balancing"); | ||||
//These are FOP's extension properties for accessibility | //These are FOP's extension properties for accessibility | ||||
PROPERTY_ATTRIBUTES.add("alt-text"); | PROPERTY_ATTRIBUTES.add("alt-text"); | ||||
PROPERTY_ATTRIBUTES.add("header"); | |||||
} | } | ||||
/** | /** |
/** The "struct-ref" attribute, to refer to a structure tree element. */ | /** The "struct-ref" attribute, to refer to a structure tree element. */ | ||||
public static final String STRUCT_REF = "struct-ref"; | public static final String STRUCT_REF = "struct-ref"; | ||||
public static final String SCOPE = "scope"; | |||||
private static final Set<String> PROPERTY_ATTRIBUTES = new java.util.HashSet<String>(); | private static final Set<String> PROPERTY_ATTRIBUTES = new java.util.HashSet<String>(); | ||||
static { | static { | ||||
//These are FOP's extension properties for accessibility | //These are FOP's extension properties for accessibility | ||||
PROPERTY_ATTRIBUTES.add(STRUCT_ID); | PROPERTY_ATTRIBUTES.add(STRUCT_ID); | ||||
PROPERTY_ATTRIBUTES.add(STRUCT_REF); | PROPERTY_ATTRIBUTES.add(STRUCT_REF); | ||||
PROPERTY_ATTRIBUTES.add(SCOPE); | |||||
} | } | ||||
/** | /** |
import org.apache.fop.apps.FOPException; | import org.apache.fop.apps.FOPException; | ||||
import org.apache.fop.datatypes.Length; | import org.apache.fop.datatypes.Length; | ||||
import org.apache.fop.fo.Constants; | |||||
import org.apache.fop.fo.FONode; | import org.apache.fop.fo.FONode; | ||||
import org.apache.fop.fo.PropertyList; | import org.apache.fop.fo.PropertyList; | ||||
import org.apache.fop.fo.ValidationException; | import org.apache.fop.fo.ValidationException; | ||||
private Length columnWidth; | private Length columnWidth; | ||||
private int numberColumnsRepeated; | private int numberColumnsRepeated; | ||||
private int numberColumnsSpanned; | private int numberColumnsSpanned; | ||||
private boolean isHeader; | |||||
// Unused but valid items, commented out for performance: | // Unused but valid items, commented out for performance: | ||||
// private int visibility; | // private int visibility; | ||||
// End of property values | // End of property values | ||||
if (!this.implicitColumn) { | if (!this.implicitColumn) { | ||||
this.pList = pList; | this.pList = pList; | ||||
} | } | ||||
isHeader = (pList.get(Constants.PR_X_HEADER_COLUMN).getEnum() == Constants.EN_TRUE); | |||||
} | } | ||||
/** {@inheritDoc} */ | /** {@inheritDoc} */ | ||||
this.pList = null; | this.pList = null; | ||||
} | } | ||||
/** | |||||
* Returns {@code true} if this column is made of header cells. | |||||
* | |||||
* @return {@code true} if cells in this column are like TH cells in HTML | |||||
*/ | |||||
public boolean isHeader() { | |||||
return isHeader; | |||||
} | |||||
} | } |
import org.apache.fop.accessibility.StructureTreeEventHandler; | import org.apache.fop.accessibility.StructureTreeEventHandler; | ||||
import org.apache.fop.events.EventBroadcaster; | import org.apache.fop.events.EventBroadcaster; | ||||
import org.apache.fop.fo.extensions.ExtensionElementMapping; | import org.apache.fop.fo.extensions.ExtensionElementMapping; | ||||
import org.apache.fop.fo.extensions.InternalElementMapping; | |||||
import org.apache.fop.fo.pagination.Flow; | import org.apache.fop.fo.pagination.Flow; | ||||
import org.apache.fop.pdf.PDFFactory; | import org.apache.fop.pdf.PDFFactory; | ||||
import org.apache.fop.pdf.PDFParentTree; | import org.apache.fop.pdf.PDFParentTree; | ||||
PDFStructElem structElem = createStructureElement(parent, structureType); | PDFStructElem structElem = createStructureElement(parent, structureType); | ||||
setAttributes(structElem, attributes); | setAttributes(structElem, attributes); | ||||
addKidToParent(structElem, parent, attributes); | addKidToParent(structElem, parent, attributes); | ||||
registerStructureElement(structElem, pdfFactory); | |||||
registerStructureElement(structElem, pdfFactory, attributes); | |||||
return structElem; | return structElem; | ||||
} | } | ||||
parent.addKid(kid); | parent.addKid(kid); | ||||
} | } | ||||
protected void registerStructureElement(PDFStructElem structureElement, PDFFactory pdfFactory) { | |||||
protected void registerStructureElement(PDFStructElem structureElement, PDFFactory pdfFactory, | |||||
Attributes attributes) { | |||||
pdfFactory.getDocument().registerStructureElement(structureElement); | pdfFactory.getDocument().registerStructureElement(structureElement); | ||||
} | } | ||||
} | } | ||||
@Override | @Override | ||||
protected PDFStructElem createStructureElement(StructureHierarchyMember parent, | |||||
StructureType structureType) { | |||||
PDFStructElem grandParent = ((PDFStructElem) parent).getParentStructElem(); | |||||
//TODO What to do with cells from table-footer? Currently they are mapped on TD. | |||||
if (grandParent.getStructureType() == StandardStructureTypes.Table.THEAD) { | |||||
structureType = StandardStructureTypes.Table.TH; | |||||
} else { | |||||
structureType = StandardStructureTypes.Table.TD; | |||||
} | |||||
return super.createStructureElement(parent, structureType); | |||||
} | |||||
@Override | |||||
protected void registerStructureElement(PDFStructElem structureElement, PDFFactory pdfFactory) { | |||||
protected void registerStructureElement(PDFStructElem structureElement, PDFFactory pdfFactory, | |||||
Attributes attributes) { | |||||
if (structureElement.getStructureType() == Table.TH) { | if (structureElement.getStructureType() == Table.TH) { | ||||
pdfFactory.getDocument().registerStructureElement(structureElement, Scope.COLUMN); | |||||
String scopeAttribute = attributes.getValue(InternalElementMapping.URI, | |||||
InternalElementMapping.SCOPE); | |||||
Scope scope = (scopeAttribute == null) | |||||
? Scope.COLUMN | |||||
: Scope.valueOf(scopeAttribute.toUpperCase(Locale.ENGLISH)); | |||||
pdfFactory.getDocument().registerStructureElement(structureElement, scope); | |||||
} else { | } else { | ||||
pdfFactory.getDocument().registerStructureElement(structureElement); | pdfFactory.getDocument().registerStructureElement(structureElement); | ||||
} | } |
documents. Example: the fix of marks layering will be such a case when it's done. | documents. Example: the fix of marks layering will be such a case when it's done. | ||||
--> | --> | ||||
<release version="FOP Trunk" date="TBD"> | <release version="FOP Trunk" date="TBD"> | ||||
<action context="Renderers" dev="VH" type="add" fixes-bug="53902"> | |||||
Added possibility to define ‘header’ table columns (the same way as fo:table-header allows | |||||
to define header rows). When accessibility is enabled, this allows to set the appropriate | |||||
Scope attribute on the corresponding TH cells. | |||||
</action> | |||||
<action context="Fonts" dev="MH" type="add" fixes-bug="53868" importance="low" due-to="Luis Bernardo"> | <action context="Fonts" dev="MH" type="add" fixes-bug="53868" importance="low" due-to="Luis Bernardo"> | ||||
Full font embedding in PDF | Full font embedding in PDF | ||||
</action> | </action> |
testConverter("/org/apache/fop/fo/pagination/side-regions.fo"); | testConverter("/org/apache/fop/fo/pagination/side-regions.fo"); | ||||
} | } | ||||
@Test | |||||
public void headerTableCellMustPropagateScope() throws Exception { | |||||
testConverter("table-header_scope.fo"); | |||||
} | |||||
private static InputStream getResource(String name) { | private static InputStream getResource(String name) { | ||||
return FO2StructureTreeConverterTestCase.class.getResourceAsStream(name); | return FO2StructureTreeConverterTestCase.class.getResourceAsStream(name); | ||||
} | } |
xmlns:fox="http://xmlgraphics.apache.org/fop/extensions" | xmlns:fox="http://xmlgraphics.apache.org/fop/extensions" | ||||
xmlns:foi="http://xmlgraphics.apache.org/fop/internal"> | xmlns:foi="http://xmlgraphics.apache.org/fop/internal"> | ||||
<xsl:output method="xml" indent="no"/> | |||||
<xsl:output method="xml" indent="yes"/> | |||||
<xsl:template name="copy"> | <xsl:template name="copy"> | ||||
<xsl:copy> | <xsl:copy> | ||||
<xsl:call-template name="copy"/> | <xsl:call-template name="copy"/> | ||||
</xsl:template> | </xsl:template> | ||||
<xsl:template match="fo:table|fo:table-header|fo:table-footer|fo:table-body|fo:table-row|fo:table-cell"> | |||||
<xsl:template match="fo:table|fo:table-header|fo:table-footer|fo:table-body|fo:table-row"> | |||||
<xsl:call-template name="copy"/> | <xsl:call-template name="copy"/> | ||||
</xsl:template> | </xsl:template> | ||||
<xsl:template name="get.column.header"> | |||||
<xsl:value-of select="ancestor::fo:table/fo:table-column[ | |||||
count(preceding-sibling::fo:table-column) = count(current()/preceding-sibling::fo:table-cell)]/@fox:header"/> | |||||
</xsl:template> | |||||
<xsl:template match="fo:table-cell"> | |||||
<xsl:variable name="header"><xsl:call-template name="get.column.header"/></xsl:variable> | |||||
<xsl:copy> | |||||
<xsl:if test="$header = 'true'"> | |||||
<xsl:attribute name="role">TH</xsl:attribute> | |||||
<xsl:attribute name="scope" namespace="http://xmlgraphics.apache.org/fop/internal">Row</xsl:attribute> | |||||
</xsl:if> | |||||
<xsl:apply-templates select="@*|node()"/> | |||||
</xsl:copy> | |||||
</xsl:template> | |||||
<xsl:template match="fo:table-header/fo:table-cell|fo:table-header/fo:table-row/fo:table-cell"> | |||||
<xsl:variable name="header"><xsl:call-template name="get.column.header"/></xsl:variable> | |||||
<xsl:copy> | |||||
<xsl:attribute name="role">TH</xsl:attribute> | |||||
<xsl:if test="$header = 'true'"> | |||||
<xsl:attribute name="scope" namespace="http://xmlgraphics.apache.org/fop/internal">Both</xsl:attribute> | |||||
</xsl:if> | |||||
<xsl:apply-templates select="@*|node()"/> | |||||
</xsl:copy> | |||||
</xsl:template> | |||||
<!-- Formatting Objects for Lists --> | <!-- Formatting Objects for Lists --> | ||||
<xsl:template match="fo:list-block|fo:list-item|fo:list-item-label|fo:list-item-body"> | <xsl:template match="fo:list-block|fo:list-item|fo:list-item-label|fo:list-item-body"> | ||||
<xsl:call-template name="copy"/> | <xsl:call-template name="copy"/> |
<?xml version="1.0" standalone="no"?> | |||||
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format" | |||||
xmlns:fox="http://xmlgraphics.apache.org/fop/extensions" | |||||
font-family="sans-serif"> | |||||
<fo:layout-master-set> | |||||
<fo:simple-page-master master-name="page" | |||||
page-height="120pt" page-width="220pt" margin="10pt"> | |||||
<fo:region-body display-align="center"/> | |||||
</fo:simple-page-master> | |||||
</fo:layout-master-set> | |||||
<fo:page-sequence master-reference="page"> | |||||
<fo:flow flow-name="xsl-region-body" line-height="10pt" font-size="8pt"> | |||||
<fo:table width="100%" table-layout="fixed"> | |||||
<fo:table-column fox:header="true" column-width="proportional-column-width(1)"/> | |||||
<fo:table-column column-width="proportional-column-width(1)"/> | |||||
<fo:table-column column-width="proportional-column-width(1)"/> | |||||
<fo:table-header font-weight="bold"> | |||||
<fo:table-row> | |||||
<fo:table-cell border="1pt solid black" padding-left="1pt"> | |||||
<fo:block>Table Header</fo:block> | |||||
</fo:table-cell> | |||||
<fo:table-cell border="1pt solid black" padding-left="1pt"> | |||||
<fo:block>Column 1</fo:block> | |||||
</fo:table-cell> | |||||
<fo:table-cell border="1pt solid black" padding-left="1pt"> | |||||
<fo:block>Column 2</fo:block> | |||||
</fo:table-cell> | |||||
</fo:table-row> | |||||
</fo:table-header> | |||||
<fo:table-body> | |||||
<fo:table-row> | |||||
<fo:table-cell id="Row1" border="1pt solid black" padding-left="1pt" font-weight="bold"> | |||||
<fo:block>Row 1</fo:block> | |||||
</fo:table-cell> | |||||
<fo:table-cell border="1pt solid black" padding-left="1pt"> | |||||
<fo:block>Cell 1.1</fo:block> | |||||
</fo:table-cell> | |||||
<fo:table-cell border="1pt solid black" padding-left="1pt"> | |||||
<fo:block>Cell 1.2</fo:block> | |||||
</fo:table-cell> | |||||
</fo:table-row> | |||||
<fo:table-row> | |||||
<fo:table-cell border="1pt solid black" padding-left="1pt" font-weight="bold" role="TH"> | |||||
<fo:block>Row 2</fo:block> | |||||
</fo:table-cell> | |||||
<fo:table-cell border="1pt solid black" padding-left="1pt"> | |||||
<fo:block>Cell 2.1</fo:block> | |||||
</fo:table-cell> | |||||
<fo:table-cell border="1pt solid black" padding-left="1pt"> | |||||
<fo:block>Cell 2.2</fo:block> | |||||
</fo:table-cell> | |||||
</fo:table-row> | |||||
</fo:table-body> | |||||
</fo:table> | |||||
</fo:flow> | |||||
</fo:page-sequence> | |||||
</fo:root> |
package org.apache.fop.fo; | package org.apache.fop.fo; | ||||
import java.io.IOException; | |||||
import static org.mockito.Matchers.any; | import static org.mockito.Matchers.any; | ||||
import static org.mockito.Matchers.anyString; | import static org.mockito.Matchers.anyString; | ||||
import static org.mockito.Mockito.mock; | import static org.mockito.Mockito.mock; | ||||
import static org.mockito.Mockito.when; | import static org.mockito.Mockito.when; | ||||
import java.io.IOException; | |||||
import org.apache.xmlgraphics.image.loader.ImageException; | import org.apache.xmlgraphics.image.loader.ImageException; | ||||
import org.apache.xmlgraphics.image.loader.ImageManager; | import org.apache.xmlgraphics.image.loader.ImageManager; | ||||
import org.apache.xmlgraphics.image.loader.ImageSessionContext; | import org.apache.xmlgraphics.image.loader.ImageSessionContext; | ||||
import org.apache.fop.apps.FOUserAgent; | import org.apache.fop.apps.FOUserAgent; | ||||
import org.apache.fop.apps.FopFactory; | import org.apache.fop.apps.FopFactory; | ||||
import org.apache.fop.events.EventBroadcaster; | |||||
import org.apache.fop.fo.flow.table.ColumnNumberManager; | |||||
import org.apache.fop.fo.flow.table.ColumnNumberManagerHolder; | |||||
/** | /** | ||||
* A helper class for creating mocks of {@link FONode} and its descendants. | * A helper class for creating mocks of {@link FONode} and its descendants. | ||||
public static FONode mockFONode() { | public static FONode mockFONode() { | ||||
FONode mockFONode = mock(FONode.class); | FONode mockFONode = mock(FONode.class); | ||||
mockGetFOEventHandler(mockFONode); | mockGetFOEventHandler(mockFONode); | ||||
mockGetImageManager(mockFONode.getFOEventHandler().getUserAgent()); | |||||
return mockFONode; | return mockFONode; | ||||
} | } | ||||
private static void mockGetFOEventHandler(FONode mockFONode) { | |||||
public static FOEventHandler mockGetFOEventHandler(FONode mockFONode) { | |||||
FOEventHandler mockFOEventHandler = mock(FOEventHandler.class); | FOEventHandler mockFOEventHandler = mock(FOEventHandler.class); | ||||
mockGetUserAgent(mockFOEventHandler); | mockGetUserAgent(mockFOEventHandler); | ||||
when(mockFONode.getFOEventHandler()).thenReturn(mockFOEventHandler); | when(mockFONode.getFOEventHandler()).thenReturn(mockFOEventHandler); | ||||
return mockFOEventHandler; | |||||
} | } | ||||
private static void mockGetUserAgent(FOEventHandler mockFOEventHandler) { | |||||
public static FOUserAgent mockGetUserAgent(FOEventHandler mockFOEventHandler) { | |||||
FOUserAgent mockFOUserAgent = mock(FOUserAgent.class); | FOUserAgent mockFOUserAgent = mock(FOUserAgent.class); | ||||
mockGetImageManager(mockFOUserAgent); | |||||
when(mockFOEventHandler.getUserAgent()).thenReturn(mockFOUserAgent); | when(mockFOEventHandler.getUserAgent()).thenReturn(mockFOUserAgent); | ||||
return mockFOUserAgent; | |||||
} | } | ||||
private static void mockGetImageManager(FOUserAgent mockFOUserAgent) { | |||||
public static EventBroadcaster mockGetEventBroadcaster(FOUserAgent mockFOUserAgent) { | |||||
EventBroadcaster mockBroadcaster = mock(EventBroadcaster.class); | |||||
when(mockFOUserAgent.getEventBroadcaster()).thenReturn(mockBroadcaster); | |||||
return mockBroadcaster; | |||||
} | |||||
public static ImageManager mockGetImageManager(FOUserAgent mockFOUserAgent) { | |||||
try { | try { | ||||
ImageManager mockImageManager = mock(ImageManager.class); | ImageManager mockImageManager = mock(ImageManager.class); | ||||
when(mockImageManager.getImageInfo(anyString(), any(ImageSessionContext.class))) | when(mockImageManager.getImageInfo(anyString(), any(ImageSessionContext.class))) | ||||
.thenReturn(null); | .thenReturn(null); | ||||
when(mockFOUserAgent.getImageManager()).thenReturn(mockImageManager); | when(mockFOUserAgent.getImageManager()).thenReturn(mockImageManager); | ||||
return mockImageManager; | |||||
} catch (ImageException e) { | } catch (ImageException e) { | ||||
throw new RuntimeException(e); | throw new RuntimeException(e); | ||||
} catch (IOException e) { | } catch (IOException e) { | ||||
} | } | ||||
} | } | ||||
public static ColumnNumberManager mockGetColumnNumberManager(ColumnNumberManagerHolder mock) { | |||||
ColumnNumberManager mockColumnNumberManager = mock(ColumnNumberManager.class); | |||||
when(mock.getColumnNumberManager()).thenReturn(mockColumnNumberManager); | |||||
return mockColumnNumberManager; | |||||
} | |||||
} | } |
/* | |||||
* Licensed to the Apache Software Foundation (ASF) under one or more | |||||
* contributor license agreements. See the NOTICE file distributed with | |||||
* this work for additional information regarding copyright ownership. | |||||
* The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
* (the "License"); you may not use this file except in compliance with | |||||
* the License. You may obtain a copy of the License at | |||||
* | |||||
* http://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* Unless required by applicable law or agreed to in writing, software | |||||
* distributed under the License is distributed on an "AS IS" BASIS, | |||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
* See the License for the specific language governing permissions and | |||||
* limitations under the License. | |||||
*/ | |||||
/* $Id$ */ | |||||
package org.apache.fop.fo.flow.table; | |||||
import org.junit.Test; | |||||
import org.xml.sax.Attributes; | |||||
import org.xml.sax.Locator; | |||||
import org.xml.sax.helpers.AttributesImpl; | |||||
import static org.junit.Assert.assertEquals; | |||||
import static org.mockito.Matchers.any; | |||||
import static org.mockito.Matchers.anyString; | |||||
import static org.mockito.Matchers.eq; | |||||
import static org.mockito.Mockito.mock; | |||||
import static org.mockito.Mockito.verify; | |||||
import static org.mockito.Mockito.when; | |||||
import org.apache.fop.apps.FOUserAgent; | |||||
import org.apache.fop.events.EventBroadcaster; | |||||
import org.apache.fop.fo.ElementMappingRegistry; | |||||
import org.apache.fop.fo.FOEventHandler; | |||||
import org.apache.fop.fo.FONodeMocks; | |||||
import org.apache.fop.fo.FOValidationEventProducer; | |||||
import org.apache.fop.fo.PropertyList; | |||||
import org.apache.fop.fo.StaticPropertyList; | |||||
import org.apache.fop.fo.ValidationException; | |||||
import org.apache.fop.fo.expr.PropertyException; | |||||
import org.apache.fop.fo.extensions.ExtensionElementMapping; | |||||
import org.apache.fop.util.XMLUtil; | |||||
/** | |||||
* Tests that the fox:header property is correctly parsed and set up at the FO tree level. | |||||
*/ | |||||
public class HeaderColumnTestCase { | |||||
@Test | |||||
public void testWrongValue() throws ValidationException { | |||||
Table parent = createTableParent(); | |||||
EventBroadcaster mockEventBroadcaster = FONodeMocks.mockGetEventBroadcaster( | |||||
parent.getFOEventHandler().getUserAgent()); | |||||
FOValidationEventProducer eventProducer = mockGetEventProducerFor(mockEventBroadcaster); | |||||
TableColumn column = new TableColumn(parent); | |||||
PropertyList propertyList = new StaticPropertyList(column, null); | |||||
Attributes atts = createScopeAttribute("blah"); | |||||
propertyList.addAttributesToList(atts); | |||||
verify(eventProducer).invalidPropertyValue(any(), eq("fo:table-column"), | |||||
eq("fox:header"), eq("blah"), any(PropertyException.class), any(Locator.class)); | |||||
} | |||||
@Test | |||||
public void testCorrectValue() throws Exception { | |||||
testCorrectValue(true); | |||||
testCorrectValue(false); | |||||
} | |||||
private void testCorrectValue(boolean expectedValue) throws Exception { | |||||
Table parent = createTableParent(); | |||||
FONodeMocks.mockGetColumnNumberManager(parent); | |||||
TableColumn column = new TableColumn(parent, true); | |||||
PropertyList propertyList = new StaticPropertyList(column, null); | |||||
Attributes atts = createScopeAttribute(String.valueOf(expectedValue)); | |||||
propertyList.addAttributesToList(atts); | |||||
column.bind(propertyList); | |||||
assertEquals(expectedValue, column.isHeader()); | |||||
} | |||||
private Table createTableParent() { | |||||
Table parent = mock(Table.class); | |||||
FOEventHandler mockFOEventHandler = FONodeMocks.mockGetFOEventHandler(parent); | |||||
FOUserAgent mockUserAgent = mockFOEventHandler.getUserAgent(); | |||||
mockGetElementMappingRegistry(mockUserAgent); | |||||
return parent; | |||||
} | |||||
private Attributes createScopeAttribute(String value) { | |||||
AttributesImpl atts = new AttributesImpl(); | |||||
atts.addAttribute(ExtensionElementMapping.URI, "header", "fox:header", XMLUtil.CDATA, value); | |||||
return atts; | |||||
} | |||||
private ElementMappingRegistry mockGetElementMappingRegistry(FOUserAgent mockUserAgent) { | |||||
ElementMappingRegistry mockRegistry = mock(ElementMappingRegistry.class); | |||||
when(mockRegistry.getElementMapping(anyString())).thenReturn(new ExtensionElementMapping()); | |||||
when(mockUserAgent.getElementMappingRegistry()).thenReturn(mockRegistry); | |||||
return mockRegistry; | |||||
} | |||||
private FOValidationEventProducer mockGetEventProducerFor(EventBroadcaster mockEventBroadcaster) { | |||||
FOValidationEventProducer mockEventProducer = mock(FOValidationEventProducer.class); | |||||
when(mockEventBroadcaster.getEventProducerFor(eq(FOValidationEventProducer.class))) | |||||
.thenReturn(mockEventProducer); | |||||
return mockEventProducer; | |||||
} | |||||
} |
<?xml version="1.0" standalone="no"?> | |||||
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format" | |||||
xmlns:fox="http://xmlgraphics.apache.org/fop/extensions" | |||||
font-family="sans-serif"> | |||||
<fo:layout-master-set> | |||||
<fo:simple-page-master master-name="page" | |||||
page-height="120pt" page-width="220pt" margin="10pt"> | |||||
<fo:region-body display-align="center"/> | |||||
</fo:simple-page-master> | |||||
</fo:layout-master-set> | |||||
<fo:page-sequence master-reference="page"> | |||||
<fo:flow flow-name="xsl-region-body" line-height="10pt" font-size="8pt"> | |||||
<fo:table width="100%" table-layout="fixed"> | |||||
<fo:table-column fox:header="true" column-width="proportional-column-width(1)"/> | |||||
<fo:table-column column-width="proportional-column-width(1)"/> | |||||
<fo:table-column column-width="proportional-column-width(1)"/> | |||||
<fo:table-header font-weight="bold"> | |||||
<fo:table-row> | |||||
<fo:table-cell border="1pt solid black" padding-left="1pt"> | |||||
<fo:block>Table Header</fo:block> | |||||
</fo:table-cell> | |||||
<fo:table-cell border="1pt solid black" padding-left="1pt"> | |||||
<fo:block>Column 1</fo:block> | |||||
</fo:table-cell> | |||||
<fo:table-cell border="1pt solid black" padding-left="1pt"> | |||||
<fo:block>Column 2</fo:block> | |||||
</fo:table-cell> | |||||
</fo:table-row> | |||||
</fo:table-header> | |||||
<fo:table-body> | |||||
<fo:table-row> | |||||
<fo:table-cell id="Row1" border="1pt solid black" padding-left="1pt" font-weight="bold"> | |||||
<fo:block>Row 1</fo:block> | |||||
</fo:table-cell> | |||||
<fo:table-cell border="1pt solid black" padding-left="1pt"> | |||||
<fo:block>Cell 1.1</fo:block> | |||||
</fo:table-cell> | |||||
<fo:table-cell border="1pt solid black" padding-left="1pt"> | |||||
<fo:block>Cell 1.2</fo:block> | |||||
</fo:table-cell> | |||||
</fo:table-row> | |||||
<fo:table-row> | |||||
<fo:table-cell border="1pt solid black" padding-left="1pt" font-weight="bold" role="TH"> | |||||
<fo:block>Row 2</fo:block> | |||||
</fo:table-cell> | |||||
<fo:table-cell border="1pt solid black" padding-left="1pt"> | |||||
<fo:block>Cell 2.1</fo:block> | |||||
</fo:table-cell> | |||||
<fo:table-cell border="1pt solid black" padding-left="1pt"> | |||||
<fo:block>Cell 2.2</fo:block> | |||||
</fo:table-cell> | |||||
</fo:table-row> | |||||
<fo:table-row> | |||||
<fo:table-cell border="1pt solid black" padding-left="1pt" role="TD"> | |||||
<fo:block>Non-header</fo:block> | |||||
</fo:table-cell> | |||||
<fo:table-cell border="1pt solid black" padding-left="1pt"> | |||||
<fo:block>Cell 3.1</fo:block> | |||||
</fo:table-cell> | |||||
<fo:table-cell border="1pt solid black" padding-left="1pt"> | |||||
<fo:block>Cell 3.2</fo:block> | |||||
</fo:table-cell> | |||||
</fo:table-row> | |||||
</fo:table-body> | |||||
</fo:table> | |||||
</fo:flow> | |||||
</fo:page-sequence> | |||||
</fo:root> |