You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

FOTreeBuilder.java 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  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. /* $Id$ */
  18. package org.apache.fop.fo;
  19. import java.io.OutputStream;
  20. import org.xml.sax.Attributes;
  21. import org.xml.sax.ContentHandler;
  22. import org.xml.sax.Locator;
  23. import org.xml.sax.SAXException;
  24. import org.xml.sax.SAXParseException;
  25. import org.xml.sax.helpers.DefaultHandler;
  26. import org.apache.commons.logging.Log;
  27. import org.apache.commons.logging.LogFactory;
  28. import org.apache.xmlgraphics.util.QName;
  29. import org.apache.fop.accessibility.fo.FO2StructureTreeConverter;
  30. import org.apache.fop.apps.FOPException;
  31. import org.apache.fop.apps.FOUserAgent;
  32. import org.apache.fop.apps.FormattingResults;
  33. import org.apache.fop.fo.ElementMapping.Maker;
  34. import org.apache.fop.fo.extensions.ExtensionElementMapping;
  35. import org.apache.fop.fo.pagination.Root;
  36. import org.apache.fop.render.pdf.extensions.PDFElementMapping;
  37. import org.apache.fop.util.ContentHandlerFactory;
  38. import org.apache.fop.util.ContentHandlerFactory.ObjectBuiltListener;
  39. import org.apache.fop.util.ContentHandlerFactory.ObjectSource;
  40. /**
  41. * SAX Handler that passes parsed data to the various
  42. * FO objects, where they can be used either to build
  43. * an FO Tree, or used by Structure Renderers to build
  44. * other data structures.
  45. */
  46. public class FOTreeBuilder extends DefaultHandler {
  47. /** logging instance */
  48. private static final Log LOG = LogFactory.getLog(FOTreeBuilder.class);
  49. /** The registry for ElementMapping instances */
  50. protected ElementMappingRegistry elementMappingRegistry;
  51. /** The root of the formatting object tree */
  52. protected Root rootFObj;
  53. /** Main DefaultHandler that handles the FO namespace. */
  54. protected MainFOHandler mainFOHandler;
  55. /** Current delegate ContentHandler to receive the SAX events */
  56. protected ContentHandler delegate;
  57. /** Provides information used during tree building stage. */
  58. private FOTreeBuilderContext builderContext;
  59. /** The object that handles formatting and rendering to a stream */
  60. private FOEventHandler foEventHandler;
  61. /** The SAX locator object managing the line and column counters */
  62. private Locator locator;
  63. /** The user agent for this processing run. */
  64. private FOUserAgent userAgent;
  65. private boolean used;
  66. private boolean empty = true;
  67. private int depth;
  68. private boolean errorinstart;
  69. /**
  70. * <code>FOTreeBuilder</code> constructor
  71. *
  72. * @param outputFormat the MIME type of the output format to use (ex. "application/pdf").
  73. * @param foUserAgent the {@link FOUserAgent} in effect for this process
  74. * @param stream the <code>OutputStream</code> to direct the results to
  75. * @throws FOPException if the <code>FOTreeBuilder</code> cannot be properly created
  76. */
  77. public FOTreeBuilder(
  78. String outputFormat,
  79. FOUserAgent foUserAgent,
  80. OutputStream stream)
  81. throws FOPException {
  82. this.userAgent = foUserAgent;
  83. this.elementMappingRegistry = userAgent.getElementMappingRegistry();
  84. //This creates either an AreaTreeHandler and ultimately a Renderer, or
  85. //one of the RTF-, MIF- etc. Handlers.
  86. foEventHandler = foUserAgent.getRendererFactory().createFOEventHandler(
  87. foUserAgent, outputFormat, stream);
  88. if (userAgent.isAccessibilityEnabled()) {
  89. foEventHandler = new FO2StructureTreeConverter(
  90. foUserAgent.getStructureTreeEventHandler(), foEventHandler);
  91. }
  92. builderContext = new FOTreeBuilderContext();
  93. builderContext.setPropertyListMaker(new PropertyListMaker() {
  94. public PropertyList make(FObj fobj, PropertyList parentPropertyList) {
  95. return new StaticPropertyList(fobj, parentPropertyList);
  96. }
  97. });
  98. }
  99. /** {@inheritDoc} */
  100. public void setDocumentLocator(Locator locator) {
  101. this.locator = locator;
  102. }
  103. /**
  104. * @return a {@link Locator} instance if it is available and not disabled
  105. */
  106. protected Locator getEffectiveLocator() {
  107. return (userAgent.isLocatorEnabled() ? this.locator : null);
  108. }
  109. /** {@inheritDoc} */
  110. public void characters(char[] data, int start, int length)
  111. throws SAXException {
  112. delegate.characters(data, start, length);
  113. }
  114. /** {@inheritDoc} */
  115. public void startDocument() throws SAXException {
  116. if (used) {
  117. throw new IllegalStateException("FOTreeBuilder (and the Fop class) cannot be reused."
  118. + " Please instantiate a new instance.");
  119. }
  120. used = true;
  121. empty = true;
  122. rootFObj = null; // allows FOTreeBuilder to be reused
  123. if (LOG.isDebugEnabled()) {
  124. LOG.debug("Building formatting object tree");
  125. }
  126. foEventHandler.startDocument();
  127. this.mainFOHandler = new MainFOHandler();
  128. this.mainFOHandler.startDocument();
  129. this.delegate = this.mainFOHandler;
  130. }
  131. /** {@inheritDoc} */
  132. public void endDocument() throws SAXException {
  133. this.delegate.endDocument();
  134. if (this.rootFObj == null && empty) {
  135. FOValidationEventProducer eventProducer
  136. = FOValidationEventProducer.Provider.get(userAgent.getEventBroadcaster());
  137. eventProducer.emptyDocument(this);
  138. }
  139. rootFObj = null;
  140. if (LOG.isDebugEnabled()) {
  141. LOG.debug("Parsing of document complete");
  142. }
  143. foEventHandler.endDocument();
  144. }
  145. /** {@inheritDoc} */
  146. public void startElement(String namespaceURI, String localName, String rawName,
  147. Attributes attlist) throws SAXException {
  148. this.depth++;
  149. errorinstart = false;
  150. try {
  151. delegate.startElement(namespaceURI, localName, rawName, attlist);
  152. } catch (SAXException e) {
  153. errorinstart = true;
  154. throw e;
  155. }
  156. }
  157. /** {@inheritDoc} */
  158. public void endElement(String uri, String localName, String rawName)
  159. throws SAXException {
  160. if (!errorinstart) {
  161. this.delegate.endElement(uri, localName, rawName);
  162. this.depth--;
  163. if (depth == 0) {
  164. if (delegate != mainFOHandler) {
  165. //Return from sub-handler back to main handler
  166. delegate.endDocument();
  167. delegate = mainFOHandler;
  168. delegate.endElement(uri, localName, rawName);
  169. }
  170. }
  171. }
  172. }
  173. /** {@inheritDoc} */
  174. public void warning(SAXParseException e) {
  175. LOG.warn(e.getLocalizedMessage());
  176. }
  177. /** {@inheritDoc} */
  178. public void error(SAXParseException e) {
  179. LOG.error(e.toString());
  180. }
  181. /** {@inheritDoc} */
  182. public void fatalError(SAXParseException e) throws SAXException {
  183. LOG.error(e.toString());
  184. throw e;
  185. }
  186. /**
  187. * Provides access to the underlying {@link FOEventHandler} object.
  188. *
  189. * @return the FOEventHandler object
  190. */
  191. public FOEventHandler getEventHandler() {
  192. return foEventHandler;
  193. }
  194. /**
  195. * Returns the results of the rendering process. Information includes
  196. * the total number of pages generated and the number of pages per
  197. * page-sequence.
  198. *
  199. * @return the results of the rendering process.
  200. */
  201. public FormattingResults getResults() {
  202. return getEventHandler().getResults();
  203. }
  204. /**
  205. * Main <code>DefaultHandler</code> implementation which builds the FO tree.
  206. */
  207. private class MainFOHandler extends DefaultHandler {
  208. /** Current formatting object being handled */
  209. protected FONode currentFObj;
  210. /** Current propertyList for the node being handled */
  211. protected PropertyList currentPropertyList;
  212. /** Current marker nesting-depth */
  213. private int nestedMarkerDepth;
  214. /** {@inheritDoc} */
  215. public void startElement(String namespaceURI, String localName, String rawName,
  216. Attributes attlist) throws SAXException {
  217. /* the node found in the FO document */
  218. FONode foNode;
  219. PropertyList propertyList = null;
  220. // Check to ensure first node encountered is an fo:root
  221. if (rootFObj == null) {
  222. empty = false;
  223. if (!namespaceURI.equals(FOElementMapping.URI)
  224. || !localName.equals("root")) {
  225. FOValidationEventProducer eventProducer
  226. = FOValidationEventProducer.Provider.get(
  227. userAgent.getEventBroadcaster());
  228. eventProducer.invalidFORoot(this, FONode.getNodeString(namespaceURI, localName),
  229. getEffectiveLocator());
  230. }
  231. } else { // check that incoming node is valid for currentFObj
  232. if (currentFObj.getNamespaceURI().equals(FOElementMapping.URI)
  233. || currentFObj.getNamespaceURI().equals(ExtensionElementMapping.URI)
  234. || currentFObj.getNamespaceURI().equals(PDFElementMapping.NAMESPACE)) {
  235. currentFObj.validateChildNode(locator, namespaceURI, localName);
  236. }
  237. }
  238. ElementMapping.Maker fobjMaker = findFOMaker(namespaceURI, localName);
  239. try {
  240. foNode = fobjMaker.make(currentFObj);
  241. if (rootFObj == null) {
  242. rootFObj = (Root) foNode;
  243. rootFObj.setBuilderContext(builderContext);
  244. rootFObj.setFOEventHandler(foEventHandler);
  245. }
  246. propertyList = foNode.createPropertyList(
  247. currentPropertyList, foEventHandler);
  248. foNode.processNode(localName, getEffectiveLocator(),
  249. attlist, propertyList);
  250. if (foNode.getNameId() == Constants.FO_MARKER) {
  251. if (builderContext.inMarker()) {
  252. nestedMarkerDepth++;
  253. } else {
  254. builderContext.switchMarkerContext(true);
  255. }
  256. }
  257. if (foNode.getNameId() == Constants.FO_PAGE_SEQUENCE) {
  258. builderContext.getXMLWhiteSpaceHandler().reset();
  259. }
  260. } catch (IllegalArgumentException e) {
  261. throw new SAXException(e);
  262. }
  263. ContentHandlerFactory chFactory = foNode.getContentHandlerFactory();
  264. if (chFactory != null) {
  265. ContentHandler subHandler = chFactory.createContentHandler();
  266. if (subHandler instanceof ObjectSource
  267. && foNode instanceof ObjectBuiltListener) {
  268. ((ObjectSource) subHandler).setObjectBuiltListener(
  269. (ObjectBuiltListener) foNode);
  270. }
  271. subHandler.startDocument();
  272. subHandler.startElement(namespaceURI, localName,
  273. rawName, attlist);
  274. depth = 1;
  275. delegate = subHandler;
  276. }
  277. if (currentFObj != null) {
  278. currentFObj.addChildNode(foNode);
  279. }
  280. currentFObj = foNode;
  281. if (propertyList != null && !builderContext.inMarker()) {
  282. currentPropertyList = propertyList;
  283. }
  284. // fo:characters can potentially be removed during
  285. // white-space handling.
  286. // Do not notify the FOEventHandler.
  287. if (currentFObj.getNameId() != Constants.FO_CHARACTER
  288. && (!builderContext.inMarker() || currentFObj.getNameId() == Constants.FO_MARKER)) {
  289. currentFObj.startOfNode();
  290. }
  291. }
  292. /** {@inheritDoc} */
  293. public void endElement(String uri, String localName, String rawName)
  294. throws SAXException {
  295. if (currentFObj == null) {
  296. throw new SAXException(
  297. "endElement() called for " + rawName
  298. + " where there is no current element.");
  299. } else if (!currentFObj.getLocalName().equals(localName)
  300. || !currentFObj.getNamespaceURI().equals(uri)) {
  301. throw new SAXException("Mismatch: " + currentFObj.getLocalName()
  302. + " (" + currentFObj.getNamespaceURI()
  303. + ") vs. " + localName + " (" + uri + ")");
  304. }
  305. // fo:characters can potentially be removed during
  306. // white-space handling.
  307. // Do not notify the FOEventHandler.
  308. if (currentFObj.getNameId() != Constants.FO_CHARACTER
  309. && (!builderContext.inMarker() || currentFObj.getNameId() == Constants.FO_MARKER)) {
  310. currentFObj.endOfNode();
  311. }
  312. if (currentPropertyList != null
  313. && currentPropertyList.getFObj() == currentFObj
  314. && !builderContext.inMarker()) {
  315. currentPropertyList = currentPropertyList.getParentPropertyList();
  316. }
  317. if (currentFObj.getNameId() == Constants.FO_MARKER) {
  318. if (nestedMarkerDepth == 0) {
  319. builderContext.switchMarkerContext(false);
  320. } else {
  321. nestedMarkerDepth--;
  322. }
  323. }
  324. if (currentFObj.getParent() == null) {
  325. LOG.debug("endElement for top-level " + currentFObj.getName());
  326. }
  327. currentFObj = currentFObj.getParent();
  328. }
  329. /** {@inheritDoc} */
  330. public void characters(char[] data, int start, int length)
  331. throws FOPException {
  332. if (currentFObj != null) {
  333. currentFObj.characters(data, start, length,
  334. currentPropertyList, getEffectiveLocator());
  335. }
  336. }
  337. /** {@inheritDoc} */
  338. public void endDocument() throws SAXException {
  339. currentFObj = null;
  340. }
  341. /**
  342. * Finds the {@link Maker} used to create {@link FONode} objects of a particular type
  343. *
  344. * @param namespaceURI URI for the namespace of the element
  345. * @param localName name of the Element
  346. * @return the ElementMapping.Maker that can create an FO object for this element
  347. * @throws FOPException if a Maker could not be found for a bound namespace.
  348. */
  349. private Maker findFOMaker(String namespaceURI, String localName) throws FOPException {
  350. Maker maker = elementMappingRegistry.findFOMaker(namespaceURI, localName, locator);
  351. if (maker instanceof UnknownXMLObj.Maker) {
  352. FOValidationEventProducer eventProducer
  353. = FOValidationEventProducer.Provider.get(
  354. userAgent.getEventBroadcaster());
  355. String name = (currentFObj != null ? currentFObj.getName()
  356. : "{" + namespaceURI + "}" + localName);
  357. eventProducer.unknownFormattingObject(this, name,
  358. new QName(namespaceURI, localName),
  359. getEffectiveLocator());
  360. }
  361. return maker;
  362. }
  363. }
  364. }