| /* |
| * Copyright 1999-2005 The Apache Software Foundation. |
| * |
| * Licensed 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; |
| |
| import java.io.OutputStream; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| import org.apache.fop.apps.FOPException; |
| import org.apache.fop.apps.FOUserAgent; |
| import org.apache.fop.apps.FormattingResults; |
| import org.apache.fop.area.AreaTreeHandler; |
| import org.apache.fop.render.RendererFactory; |
| import org.apache.fop.util.Service; |
| import org.apache.fop.fo.ElementMapping.Maker; |
| import org.apache.fop.fo.pagination.Root; |
| import org.xml.sax.Attributes; |
| import org.xml.sax.Locator; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.SAXParseException; |
| import org.xml.sax.helpers.DefaultHandler; |
| |
| /** |
| * SAX Handler that passes parsed data to the various |
| * FO objects, where they can be used either to build |
| * an FO Tree, or used by Structure Renderers to build |
| * other data structures. |
| */ |
| public class FOTreeBuilder extends DefaultHandler { |
| |
| /** |
| * Table mapping element names to the makers of objects |
| * representing formatting objects. |
| */ |
| protected Map fobjTable = new java.util.HashMap(); |
| |
| /** |
| * logging instance |
| */ |
| protected Log log = LogFactory.getLog(FOTreeBuilder.class); |
| |
| /** |
| * Set of mapped namespaces. |
| */ |
| protected Set namespaces = new java.util.HashSet(); |
| |
| /** |
| * The root of the formatting object tree |
| */ |
| protected Root rootFObj = null; |
| |
| /** |
| * Current formatting object being handled |
| */ |
| protected FONode currentFObj = null; |
| |
| /** |
| * Current propertyList for the node being handled. |
| */ |
| protected PropertyList currentPropertyList; |
| |
| /** |
| * The class that handles formatting and rendering to a stream |
| * (mark-fop@inomial.com) |
| */ |
| private FOEventHandler foEventHandler; |
| |
| /** The SAX locator object managing the line and column counters */ |
| private Locator locator; |
| |
| /** |
| * FOTreeBuilder constructor |
| * @param renderType output type as defined in Constants class |
| * @param foUserAgent in effect for this process |
| * @param stream OutputStream to direct results |
| * @throws FOPException if the FOTreeBuilder cannot be properly created |
| */ |
| public FOTreeBuilder(int renderType, FOUserAgent foUserAgent, |
| OutputStream stream) throws FOPException { |
| |
| //This creates either an AreaTreeHandler and ultimately a Renderer, or |
| //one of the RTF-, MIF- etc. Handlers. |
| foEventHandler = RendererFactory.createFOEventHandler(foUserAgent, renderType, stream); |
| foEventHandler.setPropertyListMaker(new PropertyListMaker() { |
| public PropertyList make(FObj fobj, PropertyList parentPropertyList) { |
| return new StaticPropertyList(fobj, parentPropertyList); |
| } |
| }); |
| |
| // Add standard element mappings |
| setupDefaultMappings(); |
| |
| // add additional ElementMappings defined within FOUserAgent |
| List addlEMs = foUserAgent.getAdditionalElementMappings(); |
| |
| if (addlEMs != null) { |
| for (int i = 0; i < addlEMs.size(); i++) { |
| addElementMapping((ElementMapping) addlEMs.get(i)); |
| } |
| } |
| } |
| |
| /** |
| * Sets all the element and property list mappings to their default values. |
| * |
| */ |
| private void setupDefaultMappings() { |
| addElementMapping("org.apache.fop.fo.FOElementMapping"); |
| addElementMapping("org.apache.fop.fo.extensions.svg.SVGElementMapping"); |
| addElementMapping("org.apache.fop.fo.extensions.svg.BatikExtensionElementMapping"); |
| addElementMapping("org.apache.fop.fo.extensions.ExtensionElementMapping"); |
| |
| // add mappings from available services |
| Iterator providers = Service.providers(ElementMapping.class); |
| if (providers != null) { |
| while (providers.hasNext()) { |
| String str = (String)providers.next(); |
| try { |
| addElementMapping(str); |
| } catch (IllegalArgumentException e) { |
| log.warn("Error while adding element mapping", e); |
| } |
| |
| } |
| } |
| } |
| |
| /** |
| * Add the element mapping with the given class name. |
| * @param mappingClassName the class name representing the element mapping. |
| * @throws IllegalArgumentException if there was not such element mapping. |
| */ |
| public void addElementMapping(String mappingClassName) |
| throws IllegalArgumentException { |
| |
| try { |
| ElementMapping mapping = |
| (ElementMapping)Class.forName(mappingClassName).newInstance(); |
| addElementMapping(mapping); |
| } catch (ClassNotFoundException e) { |
| throw new IllegalArgumentException("Could not find " |
| + mappingClassName); |
| } catch (InstantiationException e) { |
| throw new IllegalArgumentException("Could not instantiate " |
| + mappingClassName); |
| } catch (IllegalAccessException e) { |
| throw new IllegalArgumentException("Could not access " |
| + mappingClassName); |
| } catch (ClassCastException e) { |
| throw new IllegalArgumentException(mappingClassName |
| + " is not an ElementMapping"); |
| } |
| } |
| |
| private void addElementMapping(ElementMapping mapping) { |
| this.fobjTable.put(mapping.getNamespaceURI(), mapping.getTable()); |
| this.namespaces.add(mapping.getNamespaceURI().intern()); |
| } |
| |
| /** |
| * SAX Handler for locator |
| * @see org.xml.sax.ContentHandler#setDocumentLocator(Locator) |
| */ |
| public void setDocumentLocator(Locator locator) { |
| this.locator = locator; |
| } |
| |
| /** |
| * SAX Handler for characters |
| * @see org.xml.sax.ContentHandler#characters(char[], int, int) |
| */ |
| public void characters(char[] data, int start, int length) |
| throws FOPException { |
| if (currentFObj != null) { |
| currentFObj.addCharacters(data, start, start + length, |
| currentPropertyList, locator); |
| } |
| } |
| |
| /** |
| * SAX Handler for the start of the document |
| * @see org.xml.sax.ContentHandler#startDocument() |
| */ |
| public void startDocument() throws SAXException { |
| rootFObj = null; // allows FOTreeBuilder to be reused |
| if (log.isDebugEnabled()) { |
| log.debug("Building formatting object tree"); |
| } |
| foEventHandler.startDocument(); |
| } |
| |
| /** |
| * SAX Handler for the end of the document |
| * @see org.xml.sax.ContentHandler#endDocument() |
| */ |
| public void endDocument() throws SAXException { |
| rootFObj = null; |
| currentFObj = null; |
| if (log.isDebugEnabled()) { |
| log.debug("Parsing of document complete"); |
| } |
| foEventHandler.endDocument(); |
| } |
| |
| /** |
| * SAX Handler for the start of an element |
| * @see org.xml.sax.ContentHandler#startElement(String, String, String, Attributes) |
| */ |
| public void startElement(String namespaceURI, String localName, String rawName, |
| Attributes attlist) throws SAXException { |
| |
| /* the node found in the FO document */ |
| FONode foNode; |
| PropertyList propertyList; |
| |
| // Check to ensure first node encountered is an fo:root |
| if (rootFObj == null) { |
| if (!namespaceURI.equals(FOElementMapping.URI) |
| || !localName.equals("root")) { |
| throw new SAXException(new IllegalArgumentException( |
| "Error: First element must be fo:root formatting object")); |
| } |
| } else { // check that incoming node is valid for currentFObj |
| if (namespaceURI.equals(FOElementMapping.URI)) { |
| // currently no fox: elements to validate |
| // || namespaceURI.equals(ExtensionElementMapping.URI) */) { |
| try { |
| currentFObj.validateChildNode(locator, namespaceURI, localName); |
| } catch (ValidationException e) { |
| throw e; |
| } |
| } |
| } |
| |
| ElementMapping.Maker fobjMaker = findFOMaker(namespaceURI, localName); |
| // System.out.println("found a " + fobjMaker.toString()); |
| |
| try { |
| foNode = fobjMaker.make(currentFObj); |
| propertyList = foNode.createPropertyList(currentPropertyList, foEventHandler); |
| foNode.processNode(localName, locator, attlist, propertyList); |
| foNode.startOfNode(); |
| } catch (IllegalArgumentException e) { |
| throw new SAXException(e); |
| } |
| |
| if (rootFObj == null) { |
| rootFObj = (Root) foNode; |
| rootFObj.setFOEventHandler(foEventHandler); |
| } else { |
| currentFObj.addChildNode(foNode); |
| } |
| |
| currentFObj = foNode; |
| if (propertyList != null) { |
| currentPropertyList = propertyList; |
| } |
| } |
| |
| /** |
| * SAX Handler for the end of an element |
| * @see org.xml.sax.ContentHandler#endElement(String, String, String) |
| */ |
| public void endElement(String uri, String localName, String rawName) |
| throws FOPException { |
| currentFObj.endOfNode(); |
| |
| if (currentPropertyList.getFObj() == currentFObj) { |
| currentPropertyList = currentPropertyList.getParentPropertyList(); |
| } |
| currentFObj = currentFObj.getParent(); |
| } |
| |
| /** |
| * Finds the Maker used to create node objects of a particular type |
| * @param namespaceURI URI for the namespace of the element |
| * @param localName name of the Element |
| * @return the ElementMapping.Maker that can create an FO object for this element |
| * @throws FOPException if a Maker could not be found for a bound namespace. |
| */ |
| private Maker findFOMaker(String namespaceURI, String localName) throws FOPException { |
| Map table = (Map)fobjTable.get(namespaceURI); |
| Maker fobjMaker = null; |
| if (table != null) { |
| fobjMaker = (ElementMapping.Maker)table.get(localName); |
| // try default |
| if (fobjMaker == null) { |
| fobjMaker = (ElementMapping.Maker)table.get(ElementMapping.DEFAULT); |
| } |
| } |
| |
| if (fobjMaker == null) { |
| if (namespaces.contains(namespaceURI.intern())) { |
| throw new FOPException(FONode.errorText(locator) + |
| "No element mapping definition found for " |
| + FONode.getNodeString(namespaceURI, localName), locator); |
| } else { |
| log.warn("Unknown formatting object " + namespaceURI + "^" + localName); |
| fobjMaker = new UnknownXMLObj.Maker(namespaceURI); |
| } |
| } |
| return fobjMaker; |
| } |
| |
| /** |
| * org.xml.sax.ErrorHandler#warning |
| **/ |
| public void warning(SAXParseException e) { |
| log.warn(e.toString()); |
| } |
| |
| /** |
| * org.xml.sax.ErrorHandler#error |
| **/ |
| public void error(SAXParseException e) { |
| log.error(e.toString()); |
| } |
| |
| /** |
| * org.xml.sax.ErrorHandler#fatalError |
| **/ |
| public void fatalError(SAXParseException e) throws SAXException { |
| log.error(e.toString()); |
| throw e; |
| } |
| |
| /** |
| * Provides access to the underlying FOEventHandler object. |
| * @return the FOEventHandler object |
| */ |
| public FOEventHandler getEventHandler() { |
| return foEventHandler; |
| } |
| |
| /** |
| * Returns the results of the rendering process. Information includes |
| * the total number of pages generated and the number of pages per |
| * page-sequence. |
| * @return the results of the rendering process. |
| */ |
| public FormattingResults getResults() { |
| if (getEventHandler() instanceof AreaTreeHandler) { |
| return ((AreaTreeHandler)getEventHandler()).getResults(); |
| } else { |
| //No formatting results available for output formats no |
| //involving the layout engine. |
| return null; |
| } |
| } |
| |
| /** |
| * Resets this object for another run. |
| */ |
| public void reset() { |
| currentFObj = null; |
| rootFObj = null; |
| foEventHandler = null; |
| } |
| |
| } |
| |