/* * 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; import java.io.OutputStream; import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.xml.sax.helpers.DefaultHandler; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.xmlgraphics.util.QName; 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.fo.ElementMapping.Maker; import org.apache.fop.fo.extensions.ExtensionElementMapping; import org.apache.fop.fo.pagination.Root; import org.apache.fop.util.ContentHandlerFactory; import org.apache.fop.util.ContentHandlerFactory.ObjectBuiltListener; import org.apache.fop.util.ContentHandlerFactory.ObjectSource; /** * 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 { /** logging instance */ protected Log log = LogFactory.getLog(FOTreeBuilder.class); /** The registry for ElementMapping instances */ protected ElementMappingRegistry elementMappingRegistry; /** The root of the formatting object tree */ protected Root rootFObj = null; /** Main DefaultHandler that handles the FO namespace. */ protected MainFOHandler mainFOHandler; /** Current delegate ContentHandler to receive the SAX events */ protected ContentHandler delegate; /** Provides information used during tree building stage. */ private FOTreeBuilderContext builderContext; /** The object that handles formatting and rendering to a stream */ private FOEventHandler foEventHandler; /** The SAX locator object managing the line and column counters */ private Locator locator; /** The user agent for this processing run. */ private FOUserAgent userAgent; private boolean used = false; private boolean empty = true; private int depth; /** * FOTreeBuilder constructor * * @param outputFormat the MIME type of the output format to use (ex. "application/pdf"). * @param foUserAgent the {@link FOUserAgent} in effect for this process * @param stream the OutputStream to direct the results to * @throws FOPException if the FOTreeBuilder cannot be properly created */ public FOTreeBuilder( String outputFormat, FOUserAgent foUserAgent, OutputStream stream) throws FOPException { this.userAgent = foUserAgent; this.elementMappingRegistry = userAgent.getFactory().getElementMappingRegistry(); //This creates either an AreaTreeHandler and ultimately a Renderer, or //one of the RTF-, MIF- etc. Handlers. foEventHandler = foUserAgent.getRendererFactory().createFOEventHandler( foUserAgent, outputFormat, stream); builderContext = new FOTreeBuilderContext(); builderContext.setPropertyListMaker(new PropertyListMaker() { public PropertyList make(FObj fobj, PropertyList parentPropertyList) { return new StaticPropertyList(fobj, parentPropertyList); } }); } /** {@inheritDoc} */ public void setDocumentLocator(Locator locator) { this.locator = locator; } /** * @return a {@link Locator} instance if it is available and not disabled */ protected Locator getEffectiveLocator() { return (userAgent.isLocatorEnabled() ? this.locator : null); } /** {@inheritDoc} */ public void characters(char[] data, int start, int length) throws SAXException { delegate.characters(data, start, length); } /** {@inheritDoc} */ public void startDocument() throws SAXException { if (used) { throw new IllegalStateException("FOTreeBuilder (and the Fop class) cannot be reused." + " Please instantiate a new instance."); } used = true; empty = true; rootFObj = null; // allows FOTreeBuilder to be reused if (log.isDebugEnabled()) { log.debug("Building formatting object tree"); } foEventHandler.startDocument(); this.mainFOHandler = new MainFOHandler(); this.mainFOHandler.startDocument(); this.delegate = this.mainFOHandler; } /** {@inheritDoc} */ public void endDocument() throws SAXException { this.delegate.endDocument(); if (this.rootFObj == null && empty) { FOValidationEventProducer eventProducer = FOValidationEventProducer.Provider.get(userAgent.getEventBroadcaster()); eventProducer.emptyDocument(this); } rootFObj = null; if (log.isDebugEnabled()) { log.debug("Parsing of document complete"); } foEventHandler.endDocument(); } /** {@inheritDoc} */ public void startElement(String namespaceURI, String localName, String rawName, Attributes attlist) throws SAXException { this.depth++; delegate.startElement(namespaceURI, localName, rawName, attlist); } /** {@inheritDoc} */ public void endElement(String uri, String localName, String rawName) throws SAXException { this.delegate.endElement(uri, localName, rawName); this.depth--; if (depth == 0) { if (delegate != mainFOHandler) { //Return from sub-handler back to main handler delegate.endDocument(); delegate = mainFOHandler; delegate.endElement(uri, localName, rawName); } } } /** {@inheritDoc} */ public void warning(SAXParseException e) { log.warn(e.getLocalizedMessage()); } /** {@inheritDoc} */ public void error(SAXParseException e) { log.error(e.toString()); } /** {@inheritDoc} */ public void fatalError(SAXParseException e) throws SAXException { log.error(e.toString()); throw e; } /** * Provides access to the underlying {@link 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; } } /** * Main DefaultHandler implementation which builds the FO tree. */ private class MainFOHandler extends DefaultHandler { /** Current formatting object being handled */ protected FONode currentFObj = null; /** Current propertyList for the node being handled */ protected PropertyList currentPropertyList; /** Current marker nesting-depth */ private int nestedMarkerDepth = 0; /** {@inheritDoc} */ public void startElement(String namespaceURI, String localName, String rawName, Attributes attlist) throws SAXException { /* the node found in the FO document */ FONode foNode; PropertyList propertyList = null; // Check to ensure first node encountered is an fo:root if (rootFObj == null) { empty = false; if (!namespaceURI.equals(FOElementMapping.URI) || !localName.equals("root")) { FOValidationEventProducer eventProducer = FOValidationEventProducer.Provider.get( userAgent.getEventBroadcaster()); eventProducer.invalidFORoot(this, FONode.getNodeString(namespaceURI, localName), getEffectiveLocator()); } } else { // check that incoming node is valid for currentFObj if (currentFObj.getNamespaceURI().equals(FOElementMapping.URI) || currentFObj.getNamespaceURI().equals(ExtensionElementMapping.URI)) { currentFObj.validateChildNode(locator, namespaceURI, localName); } } ElementMapping.Maker fobjMaker = findFOMaker(namespaceURI, localName); try { foNode = fobjMaker.make(currentFObj); if (rootFObj == null) { rootFObj = (Root) foNode; rootFObj.setBuilderContext(builderContext); rootFObj.setFOEventHandler(foEventHandler); } propertyList = foNode.createPropertyList( currentPropertyList, foEventHandler); foNode.processNode(localName, getEffectiveLocator(), attlist, propertyList); if (foNode.getNameId() == Constants.FO_MARKER) { if (builderContext.inMarker()) { nestedMarkerDepth++; } else { builderContext.switchMarkerContext(true); } } if (foNode.getNameId() == Constants.FO_PAGE_SEQUENCE) { builderContext.getXMLWhiteSpaceHandler().reset(); } } catch (IllegalArgumentException e) { throw new SAXException(e); } ContentHandlerFactory chFactory = foNode.getContentHandlerFactory(); if (chFactory != null) { ContentHandler subHandler = chFactory.createContentHandler(); if (subHandler instanceof ObjectSource && foNode instanceof ObjectBuiltListener) { ((ObjectSource) subHandler).setObjectBuiltListener( (ObjectBuiltListener) foNode); } subHandler.startDocument(); subHandler.startElement(namespaceURI, localName, rawName, attlist); depth = 1; delegate = subHandler; } if (currentFObj != null) { currentFObj.addChildNode(foNode); } currentFObj = foNode; if (propertyList != null && !builderContext.inMarker()) { currentPropertyList = propertyList; } // fo:characters can potentially be removed during // white-space handling. // Do not notify the FOEventHandler. if (currentFObj.getNameId() != Constants.FO_CHARACTER) { currentFObj.startOfNode(); } } /** {@inheritDoc} */ public void endElement(String uri, String localName, String rawName) throws SAXException { if (currentFObj == null) { throw new SAXException( "endElement() called for " + rawName + " where there is no current element."); } else if (!currentFObj.getLocalName().equals(localName) || !currentFObj.getNamespaceURI().equals(uri)) { throw new SAXException("Mismatch: " + currentFObj.getLocalName() + " (" + currentFObj.getNamespaceURI() + ") vs. " + localName + " (" + uri + ")"); } // fo:characters can potentially be removed during // white-space handling. // Do not notify the FOEventHandler. if (currentFObj.getNameId() != Constants.FO_CHARACTER) { currentFObj.endOfNode(); } if (currentPropertyList != null && currentPropertyList.getFObj() == currentFObj && !builderContext.inMarker()) { currentPropertyList = currentPropertyList.getParentPropertyList(); } if (currentFObj.getNameId() == Constants.FO_MARKER) { if (nestedMarkerDepth == 0) { builderContext.switchMarkerContext(false); } else { nestedMarkerDepth--; } } if (currentFObj.getParent() == null) { log.debug("endElement for top-level " + currentFObj.getName()); } currentFObj = currentFObj.getParent(); } /** {@inheritDoc} */ public void characters(char[] data, int start, int length) throws FOPException { if (currentFObj != null) { currentFObj.characters(data, start, length, currentPropertyList, getEffectiveLocator()); } } /** {@inheritDoc} */ public void endDocument() throws SAXException { currentFObj = null; } /** * Finds the {@link Maker} used to create {@link FONode} 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 { Maker maker = elementMappingRegistry.findFOMaker(namespaceURI, localName, locator); if (maker instanceof UnknownXMLObj.Maker) { FOValidationEventProducer eventProducer = FOValidationEventProducer.Provider.get( userAgent.getEventBroadcaster()); eventProducer.unknownFormattingObject(this, currentFObj.getName(), new QName(namespaceURI, localName), getEffectiveLocator()); } return maker; } } }