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