1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 /* $Id: FO2StructureTreeConverterTestCase.java 1876041 2020-04-02 10:41:17Z ssteiner $ */ 19 20 package org.apache.fop.accessibility.fo; 21 22 import java.io.ByteArrayInputStream; 23 import java.io.ByteArrayOutputStream; 24 import java.io.File; 25 import java.io.IOException; 26 import java.io.InputStream; 27 import java.io.OutputStream; 28 import java.io.StringWriter; 29 30 import javax.xml.transform.Result; 31 import javax.xml.transform.Source; 32 import javax.xml.transform.Transformer; 33 import javax.xml.transform.TransformerConfigurationException; 34 import javax.xml.transform.TransformerException; 35 import javax.xml.transform.TransformerFactory; 36 import javax.xml.transform.TransformerFactoryConfigurationError; 37 import javax.xml.transform.dom.DOMResult; 38 import javax.xml.transform.dom.DOMSource; 39 import javax.xml.transform.sax.SAXTransformerFactory; 40 import javax.xml.transform.sax.TransformerHandler; 41 import javax.xml.transform.stream.StreamResult; 42 import javax.xml.transform.stream.StreamSource; 43 44 import org.custommonkey.xmlunit.Diff; 45 import org.junit.Test; 46 import org.w3c.dom.Document; 47 import org.xml.sax.SAXException; 48 import org.xml.sax.helpers.AttributesImpl; 49 50 import static org.junit.Assert.assertEquals; 51 import static org.junit.Assert.assertNull; 52 import static org.junit.Assert.assertTrue; 53 54 import org.apache.fop.accessibility.StructureTree2SAXEventAdapter; 55 import org.apache.fop.accessibility.StructureTreeEventHandler; 56 import org.apache.fop.apps.FOPException; 57 import org.apache.fop.apps.FOUserAgent; 58 import org.apache.fop.apps.FopFactory; 59 import org.apache.fop.fo.FODocumentParser; 60 import org.apache.fop.fo.FODocumentParser.FOEventHandlerFactory; 61 import org.apache.fop.fo.FOEventHandler; 62 import org.apache.fop.fo.LoadingException; 63 import org.apache.fop.fotreetest.DummyFOEventHandler; 64 import org.apache.fop.render.intermediate.IFContext; 65 import org.apache.fop.render.pdf.PDFDocumentHandler; 66 67 public class FO2StructureTreeConverterTestCase { 68 69 private static class FOLoader { 70 71 private final String resourceName; 72 FOLoader(String resourceName)73 FOLoader(String resourceName) { 74 this.resourceName = resourceName; 75 } 76 getFoInputStream()77 public InputStream getFoInputStream() { 78 return getResource(resourceName); 79 } 80 } 81 82 private static final String STRUCTURE_TREE_SEQUENCE_NAME = "structure-tree-sequence"; 83 84 private FOLoader foLoader; 85 private boolean keepEmptyTags = true; 86 87 @Test testCompleteDocument()88 public void testCompleteDocument() throws Exception { 89 testConverter("/org/apache/fop/fo/complete_document.fo"); 90 } 91 92 @Test testAbbreviationProperty()93 public void testAbbreviationProperty() throws Exception { 94 testConverter("abb.fo"); 95 } 96 97 @Test testTableFooters()98 public void testTableFooters() throws Exception { 99 testConverter("table-footers.fo"); 100 } 101 102 @Test testArtifact()103 public void testArtifact() throws Exception { 104 testConverter("artifact.fo"); 105 } 106 107 @Test testSideRegions()108 public void testSideRegions() throws Exception { 109 testConverter("/org/apache/fop/fo/pagination/side-regions.fo"); 110 } 111 112 @Test headerTableCellMustPropagateScope()113 public void headerTableCellMustPropagateScope() throws Exception { 114 testConverter("table-header_scope.fo"); 115 } 116 117 @Test testLanguage()118 public void testLanguage() throws Exception { 119 testConverter("language.fo"); 120 } 121 getResource(String name)122 private static InputStream getResource(String name) { 123 return FO2StructureTreeConverterTestCase.class.getResourceAsStream(name); 124 } 125 126 @Test testPDFA()127 public void testPDFA() throws Exception { 128 FOUserAgent userAgent = FopFactory.newInstance(new File(".").toURI()).newFOUserAgent(); 129 userAgent.getRendererOptions().put("pdf-a-mode", "PDF/A-1b"); 130 userAgent.setAccessibility(true); 131 PDFDocumentHandler d = new PDFDocumentHandler(new IFContext(userAgent)); 132 OutputStream writer = new ByteArrayOutputStream(); 133 StreamResult result = new StreamResult(writer); 134 d.setResult(result); 135 d.getStructureTreeEventHandler(); 136 d.startDocument(); 137 assertNull(d.getStructureTreeEventHandler().startNode("table-body", null, null)); 138 } 139 140 @Test testRemoveBlocks()141 public void testRemoveBlocks() throws Exception { 142 keepEmptyTags = false; 143 compare("<fo:root xmlns:fo=\"http://www.w3.org/1999/XSL/Format\">\n" 144 + " <fo:layout-master-set>\n" 145 + " <fo:simple-page-master master-name=\"simple\">\n" 146 + " <fo:region-body />\n" 147 + " </fo:simple-page-master>\n" 148 + " </fo:layout-master-set>\n" 149 + " <fo:page-sequence master-reference=\"simple\">\n" 150 + " <fo:flow flow-name=\"xsl-region-body\">\n" 151 + " <fo:block/>" 152 + " <fo:block><fo:block/></fo:block>\n" 153 + " <fo:block>a</fo:block>\n" 154 + " <fo:block><fo:leader/></fo:block>\n" 155 + " <fo:block>a<fo:leader/></fo:block>\n" 156 + " </fo:flow>\n" 157 + " </fo:page-sequence>\n" 158 + "</fo:root>\n", 159 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" 160 + "<structure-tree-sequence>\n" 161 + "<structure-tree xmlns=\"http://xmlgraphics.apache.org/fop/intermediate\" " 162 + "xmlns:foi=\"http://xmlgraphics.apache.org/fop/internal\" " 163 + "xmlns:fox=\"http://xmlgraphics.apache.org/fop/extensions\">\n" 164 + "<fo:flow xmlns:fo=\"http://www.w3.org/1999/XSL/Format\" flow-name=\"xsl-region-body\">\n" 165 + "<fo:block>\n" 166 + "<marked-content/>\n" 167 + "</fo:block>\n" 168 + "<fo:block>\n" 169 + "<marked-content/>\n" 170 + "</fo:block>\n" 171 + "</fo:flow>\n" 172 + "</structure-tree>\n" 173 + "</structure-tree-sequence>\n"); 174 } 175 compare(final String fo, String tree)176 private void compare(final String fo, String tree) throws Exception { 177 foLoader = new FOLoader("") { 178 public InputStream getFoInputStream() { 179 return new ByteArrayInputStream(fo.getBytes()); 180 } 181 }; 182 DOMResult actualStructureTree = buildActualStructureTree(); 183 Document doc = (Document) actualStructureTree.getNode(); 184 StringWriter sw = new StringWriter(); 185 TransformerFactory tf = TransformerFactory.newInstance(); 186 Transformer transformer = tf.newTransformer(); 187 transformer.transform(new DOMSource(doc), new StreamResult(sw)); 188 assertEquals(tree.replace("\n", ""), sw.toString().replace("\n", "")); 189 } 190 testConverter(String foResourceName)191 private void testConverter(String foResourceName) throws Exception { 192 foLoader = new FOLoader(foResourceName); 193 DOMResult expectedStructureTree = loadExpectedStructureTree(); 194 DOMResult actualStructureTree = buildActualStructureTree(); 195 final Diff diff = createDiff(expectedStructureTree, actualStructureTree); 196 assertTrue(diff.toString(), diff.identical()); 197 } 198 loadExpectedStructureTree()199 private DOMResult loadExpectedStructureTree() { 200 DOMResult expectedStructureTree = new DOMResult(); 201 InputStream xslt = getResource("fo2StructureTree.xsl"); 202 runXSLT(xslt, foLoader.getFoInputStream(), expectedStructureTree); 203 return expectedStructureTree; 204 } 205 runXSLT(InputStream xslt, InputStream doc, Result result)206 private static void runXSLT(InputStream xslt, InputStream doc, Result result) { 207 Source fo = new StreamSource(doc); 208 try { 209 Transformer transformer = TransformerFactory.newInstance() 210 .newTransformer(new StreamSource(xslt)); 211 transformer.transform(fo, result); 212 } catch (TransformerConfigurationException e) { 213 throw new RuntimeException(e); 214 } catch (TransformerException e) { 215 throw new RuntimeException(e); 216 } finally { 217 closeStream(xslt); 218 closeStream(doc); 219 } 220 } 221 closeStream(InputStream stream)222 private static void closeStream(InputStream stream) { 223 try { 224 stream.close(); 225 } catch (IOException e) { 226 throw new RuntimeException(e); 227 } 228 } 229 buildActualStructureTree()230 private DOMResult buildActualStructureTree() throws Exception { 231 DOMResult actualStructureTree = new DOMResult(); 232 createStructureTreeFromDocument(foLoader.getFoInputStream(), actualStructureTree); 233 return actualStructureTree; 234 } 235 createStructureTreeFromDocument(InputStream foInputStream, Result result)236 private void createStructureTreeFromDocument(InputStream foInputStream, 237 Result result) throws Exception { 238 TransformerHandler tHandler = createTransformerHandler(result); 239 startStructureTreeSequence(tHandler); 240 StructureTreeEventHandler structureTreeEventHandler 241 = StructureTree2SAXEventAdapter.newInstance(tHandler); 242 FODocumentParser documentParser = createDocumentParser(structureTreeEventHandler); 243 FOUserAgent userAgent = createFOUserAgent(documentParser); 244 parseDocument(foInputStream, documentParser, userAgent); 245 endStructureTreeSequence(tHandler); 246 } 247 createTransformerHandler(Result domResult)248 private static TransformerHandler createTransformerHandler(Result domResult) 249 throws TransformerConfigurationException, TransformerFactoryConfigurationError { 250 SAXTransformerFactory factory = (SAXTransformerFactory) SAXTransformerFactory.newInstance(); 251 TransformerHandler transformerHandler = factory.newTransformerHandler(); 252 transformerHandler.setResult(domResult); 253 return transformerHandler; 254 } 255 startStructureTreeSequence(TransformerHandler tHandler)256 private static void startStructureTreeSequence(TransformerHandler tHandler) throws SAXException { 257 tHandler.startDocument(); 258 tHandler.startElement("", STRUCTURE_TREE_SEQUENCE_NAME, STRUCTURE_TREE_SEQUENCE_NAME, 259 new AttributesImpl()); 260 } 261 createDocumentParser( final StructureTreeEventHandler structureTreeEventHandler)262 private static FODocumentParser createDocumentParser( 263 final StructureTreeEventHandler structureTreeEventHandler) { 264 return FODocumentParser.newInstance(new FOEventHandlerFactory() { 265 public FOEventHandler newFOEventHandler(FOUserAgent foUserAgent) { 266 return new FO2StructureTreeConverter(structureTreeEventHandler, 267 new DummyFOEventHandler(foUserAgent)); 268 } 269 }); 270 } 271 272 private FOUserAgent createFOUserAgent(FODocumentParser documentParser) { 273 FOUserAgent userAgent = documentParser.createFOUserAgent(); 274 userAgent.setAccessibility(true); 275 userAgent.setKeepEmptyTags(keepEmptyTags); 276 return userAgent; 277 } 278 279 private static void parseDocument(InputStream foInputStream, FODocumentParser documentParser, 280 FOUserAgent userAgent) throws FOPException, LoadingException { 281 try { 282 documentParser.parse(foInputStream, userAgent); 283 } finally { 284 closeStream(foInputStream); 285 } 286 } 287 288 private static void endStructureTreeSequence(TransformerHandler tHandler) throws SAXException { 289 tHandler.endElement("", STRUCTURE_TREE_SEQUENCE_NAME, STRUCTURE_TREE_SEQUENCE_NAME); 290 tHandler.endDocument(); 291 } 292 293 private static Diff createDiff(DOMResult expected, DOMResult actual) { 294 Diff diff = new Diff(getDocument(expected), getDocument(actual)); 295 return diff; 296 } 297 298 private static Document getDocument(DOMResult result) { 299 return (Document) result.getNode(); 300 } 301 } 302