From 309f4e2998c676913e64070adda8dca5e3768fad Mon Sep 17 00:00:00 2001 From: Adrian Cumiskey Date: Mon, 14 Apr 2008 13:05:52 +0000 Subject: [PATCH] Merged revisions 647692,647742,647745 via svnmerge from https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk ................ r647692 | jeremias | 2008-04-14 09:25:22 +0100 (Mon, 14 Apr 2008) | 3 lines Initialized merge tracking via "svnmerge" with revisions "1-615152" from https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_ProcessingFeedback ................ r647742 | jeremias | 2008-04-14 12:53:29 +0100 (Mon, 14 Apr 2008) | 298 lines Merged branch https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_ProcessingFeedback into Trunk. Changes on branch: ........ r615153 | jeremias | 2008-01-25 10:07:21 +0100 (Fr, 25 Jan 2008) | 1 line Created temporary branch for processing feedback. ........ r615155 | jeremias | 2008-01-25 10:11:59 +0100 (Fr, 25 Jan 2008) | 1 line Initial commit of what I've built already for those who prefer code to minimalistic design docs. ........ r615278 | jeremias | 2008-01-25 18:25:00 +0100 (Fr, 25 Jan 2008) | 1 line EventProducer interfaces now operational. ........ r615773 | jeremias | 2008-01-28 10:06:16 +0100 (Mo, 28 Jan 2008) | 1 line No casting in client code when creating EventProducer instances. ........ r616242 | vhennebert | 2008-01-29 11:34:45 +0100 (Di, 29 Jan 2008) | 3 lines Trick to avoid hard-coding the class name of EventProducer in the source file. Feel free to revert if it's not ok. ........ r616900 | jeremias | 2008-01-30 21:59:31 +0100 (Mi, 30 Jan 2008) | 1 line Generate event model XMLs in to the build directory: build/gensrc and build/test-gensrc (the latter is new and needs to be setup as source folder in your IDE!) ........ r616907 | jeremias | 2008-01-30 22:12:59 +0100 (Mi, 30 Jan 2008) | 1 line Added an XMLResourceBundle that uses an XML file instead of a properties file to load the translations. The XML format is the same as for Cocoon's XMLResourceBundle. ........ r617097 | vhennebert | 2008-01-31 11:53:21 +0100 (Do, 31 Jan 2008) | 2 lines Minor typo + slight improvement of Javadoc ........ r617176 | jeremias | 2008-01-31 19:14:19 +0100 (Do, 31 Jan 2008) | 5 lines Renamed FopEvent to Event as suggested by Simon. EventProducerCollectorTask.java now reads the EventSeverity from a doclet tag. Added generation of EventProducer translations (including simple merging, no validation, yet) EventFormatter introduced (only basic functionality, yet). Added a simple EventListener implementation that uses EventFormatter to convert the events to human-readable, localized messages that are sent to the log via Commons Logging. ........ r617362 | jeremias | 2008-02-01 08:18:07 +0100 (Fr, 01 Feb 2008) | 1 line Some remaining rename operations based on an earlier discussion. ........ r617413 | jeremias | 2008-02-01 10:46:26 +0100 (Fr, 01 Feb 2008) | 2 lines Extracted formatting functionality into utility class AdvancedMessageFormat.java. AdvancedMessageFormat.java now supports conditional sub-groups (delimited by []). ........ r618682 | jeremias | 2008-02-05 17:07:08 +0100 (Di, 05 Feb 2008) | 1 line Add support for special object formatters (where toString() isn't good enough). ATM, it's hard-coded but could later be hooked into dynamic discovery if we have multiple such formatters. The SAX Locator is the only example for now. ........ r618686 | jeremias | 2008-02-05 17:12:56 +0100 (Di, 05 Feb 2008) | 3 lines Hooked most of FONode into the new event mechanism. The FOUserAgent provides a DefaultEventBroadcaster instance. If a producer method declares throwing an exception, the event is automatically marked FATAL and the dynamic proxy throws an exception right after notifying the listeners. The exceptions are created through the EventExceptionManager. It currently contains only one exception factory for ValidationException. If we need more such factories it's better to register them dynamically. Right now, they're hard-coded. ........ r619313 | jeremias | 2008-02-07 10:14:15 +0100 (Do, 07 Feb 2008) | 1 line Make sure no events are now just silently swallowed because after upgrading a user doesn't know about the event system. ........ r619314 | jeremias | 2008-02-07 10:14:46 +0100 (Do, 07 Feb 2008) | 1 line Log what translation file is being written. ........ r619320 | jeremias | 2008-02-07 10:31:00 +0100 (Do, 07 Feb 2008) | 2 lines FObj hooked into the event system. Code reduction using a protected method on FONode to acquire a FOValidationEventProducer. ........ r619359 | jeremias | 2008-02-07 11:59:19 +0100 (Do, 07 Feb 2008) | 2 lines Fop's QName now extends XGCommons' QName to initiate a transition. Hooked PropertyList into the event mechanism. ........ r631252 | jeremias | 2008-02-26 16:24:33 +0100 (Di, 26 Feb 2008) | 1 line Removed superfluous warning. ........ r631268 | jeremias | 2008-02-26 17:08:11 +0100 (Di, 26 Feb 2008) | 1 line Deprecated two methods which are a problem for localization. Also helps finding additional spots to switch over to the event mechanism. ........ r633852 | jeremias | 2008-03-05 15:20:24 +0100 (Mi, 05 Mrz 2008) | 1 line Add severity to formatting parameters. ........ r633855 | jeremias | 2008-03-05 15:21:57 +0100 (Mi, 05 Mrz 2008) | 4 lines Added support for additional field styles: {,if,,} {,equals,,,} ........ r633856 | jeremias | 2008-03-05 15:24:04 +0100 (Mi, 05 Mrz 2008) | 2 lines Javadocs and TODOs. EventListeners can change the event severity. ........ r633857 | jeremias | 2008-03-05 15:27:08 +0100 (Mi, 05 Mrz 2008) | 4 lines Javadocs. Moved out event listener registration into a CompositeEventListener. Event broadcaster uses the events effective severity, not the initial value (for the case where listeners override the initial value). Set up a special EventBroadCaster in the FOUserAgent that filters events through a class (FOValidationEventListenerProxy) that adjusts the event severity for relaxed validation. ........ r633858 | jeremias | 2008-03-05 15:32:07 +0100 (Mi, 05 Mrz 2008) | 2 lines Instead of always decentrally checking whether strict validation is enabled or not, this is now done in a special event listener. The event producer method caller simply indicates whether it can recover from the error condition and continue. Started switching to event production in table FOs. ........ r634027 | jeremias | 2008-03-05 21:58:35 +0100 (Mi, 05 Mrz 2008) | 7 lines Moved AdvancedMessageFormat into its own package. AdvancedMessageFormat got the following added functionality: - Alternative conditional regions [ bla {field}] -> [ bla {field1}| even more bla {field2}] - Functions: functions get access to the parameters and they can produce an object that is then formatted ({#gatherContextInfo}) - "if" and "equals" format moved to top-level classes and added by dynamic registration. EventFormatter now supports includes in the form {{includeName}} so you can include other entries from the resource bundle for better reuse. Some more events in table code. ........ r634031 | jeremias | 2008-03-05 22:05:22 +0100 (Mi, 05 Mrz 2008) | 1 line SVN Props ........ r634208 | jeremias | 2008-03-06 11:26:52 +0100 (Do, 06 Mrz 2008) | 2 lines Improved context gathering. Moved GatherContextInfoFunction to an inner class of FONode to reduce visibilities. ........ r634209 | jeremias | 2008-03-06 11:28:14 +0100 (Do, 06 Mrz 2008) | 1 line Made FOPException localizable. ........ r634280 | jeremias | 2008-03-06 15:38:30 +0100 (Do, 06 Mrz 2008) | 2 lines ExceptionFactory is now dynamically registered. More table warnings and errors switch to events. ........ r634326 | jeremias | 2008-03-06 17:08:16 +0100 (Do, 06 Mrz 2008) | 1 line Remaining table FOs switched to events. ........ r634328 | jeremias | 2008-03-06 17:09:21 +0100 (Do, 06 Mrz 2008) | 1 line Deprecated FOP's QName. Mixing with Commons' variant only produces problems. ........ r634381 | jeremias | 2008-03-06 20:12:57 +0100 (Do, 06 Mrz 2008) | 2 lines Made the "invalidChild" event fully localizable by adding a "lookup" field for the optional rule to be displayed. And a few switches to the event system. ........ r634692 | jeremias | 2008-03-07 15:31:43 +0100 (Fr, 07 Mrz 2008) | 1 line More FO tree stuff switched to events. ........ r634712 | jeremias | 2008-03-07 16:19:21 +0100 (Fr, 07 Mrz 2008) | 1 line Avoid an NPE that says nothing (ex. could happen if the message template is wrong). ........ r634738 | jeremias | 2008-03-07 17:38:21 +0100 (Fr, 07 Mrz 2008) | 2 lines Non-FO children were not properly run through validation by FOTreeBuilder. Unified the way that non-FO elements are validated. Some FOs were already fixed. I now fixed the rest, so foreign elements can occur everywhere. ........ r637833 | jeremias | 2008-03-17 12:01:41 +0100 (Mo, 17 Mrz 2008) | 1 line Exception while cloning for RetrieveMarker to be handled by user as suggested by Andreas. ........ r637835 | jeremias | 2008-03-17 12:03:31 +0100 (Mo, 17 Mrz 2008) | 1 line Throw a RuntimeException of no other Exception class is specified for an event as a fallback if someone just sets the event severity to FATAL. ........ r637838 | jeremias | 2008-03-17 12:06:10 +0100 (Mo, 17 Mrz 2008) | 1 line Throw a meaningful exception when the property name is wrong. Otherwise, there will be an ArrayIndexOutOfBoundsException. ........ r637859 | jeremias | 2008-03-17 13:35:26 +0100 (Mo, 17 Mrz 2008) | 1 line Throw a meaningful exception when the property name is wrong. Otherwise, there will be an ArrayIndexOutOfBoundsException. ........ r637938 | jeremias | 2008-03-17 16:19:51 +0100 (Mo, 17 Mrz 2008) | 1 line Switched pagination package to events. ........ r637947 | jeremias | 2008-03-17 16:45:16 +0100 (Mo, 17 Mrz 2008) | 1 line Removed unlocalizable validation helper methods. ........ r637952 | jeremias | 2008-03-17 16:59:02 +0100 (Mo, 17 Mrz 2008) | 1 line Events on FOTreeBuilder. ........ r638299 | jeremias | 2008-03-18 11:09:30 +0100 (Di, 18 Mrz 2008) | 2 lines Added support for java.util.text's ChoiceFormat to AdvancedMessageFormat. Reuse the regexes as constants. ........ r638302 | jeremias | 2008-03-18 11:17:06 +0100 (Di, 18 Mrz 2008) | 1 line Events for inline-level layout managers. ........ r638774 | jeremias | 2008-03-19 11:17:36 +0100 (Mi, 19 Mrz 2008) | 1 line Added DEBUG level. ........ r638777 | jeremias | 2008-03-19 11:23:40 +0100 (Mi, 19 Mrz 2008) | 3 lines Generalized FOValidationEventListenerProxy into FOPEventListenerProxy, the main proxy for FOP's own event manipulation proxy. Done because of support for overflow="hidden" vs. overflow="error-if-overflow". Switched block-level layout managers to events. Some cleanup along the way. ........ r639222 | jeremias | 2008-03-20 10:27:34 +0100 (Do, 20 Mrz 2008) | 2 lines Some initial work for event forwarding from Batik. Missing errors/exceptions converted to events in PageSequenceMaster. ........ r639270 | jeremias | 2008-03-20 13:50:35 +0100 (Do, 20 Mrz 2008) | 1 line Removed DEBUG event severity again. Promoted constrained geometry adjustment event to INFO level as per discussion. ........ r640395 | jeremias | 2008-03-24 13:39:13 +0100 (Mo, 24 Mrz 2008) | 3 lines Moved the creation of the fallback LoggingEventListener to FOUserAgent so event before the startDocument() SAX event arrive in the log. Dynamic discovery of event models. Renderers and extensions can register renderer-specific event models. Switched the most important parts of the renderers to events (maybe not everything is converted). ........ r640397 | jeremias | 2008-03-24 13:43:04 +0100 (Mo, 24 Mrz 2008) | 1 line Remaining fixcrlfs. Xalan likes to mix CRLF and LF on Windows. ........ r640398 | jeremias | 2008-03-24 13:43:54 +0100 (Mo, 24 Mrz 2008) | 1 line Ignore namespace declarations for property handling. ........ r640463 | jeremias | 2008-03-24 17:59:52 +0100 (Mo, 24 Mrz 2008) | 2 lines Event in area package. Exposed getUserAgent() in Renderer interface (was already public in AbstractRenderer). ........ r642972 | jeremias | 2008-03-31 14:18:39 +0200 (Mo, 31 Mrz 2008) | 1 line Code restructured a bit. ........ r642975 | jeremias | 2008-03-31 14:24:07 +0200 (Mo, 31 Mrz 2008) | 2 lines Plugged fonts package into the event subsystem. Note: I did not follow the same pattern as for the rest as the font package is to be considered FOP-external, so I just added a manual adapter for the FontEventListener. This demonstrates how an external library can be integrated with the event system. Missing warning for unknown formatting objects added. Warning is not issued by the ElementMappingRegistry anymore but by FOTreeBuilder which has access to more context information. ........ r642997 | jeremias | 2008-03-31 16:10:08 +0200 (Mo, 31 Mrz 2008) | 1 line Added an example to demonstrate how to write your own event listener and how to deal with the exceptions thrown in the process. ........ r642998 | jeremias | 2008-03-31 16:13:40 +0200 (Mo, 31 Mrz 2008) | 1 line Removed unused method. Event formatting should not be part of the Event class. Use EventFormatter.format(Event) instead. ........ r643066 | jeremias | 2008-03-31 19:18:54 +0200 (Mo, 31 Mrz 2008) | 1 line First part of the event subsystem documentation (DRAFT). ........ r643784 | jeremias | 2008-04-02 10:05:33 +0200 (Mi, 02 Apr 2008) | 1 line More documentation. ........ r643785 | jeremias | 2008-04-02 10:06:38 +0200 (Mi, 02 Apr 2008) | 1 line Some nits. ........ r643787 | jeremias | 2008-04-02 10:24:41 +0200 (Mi, 02 Apr 2008) | 1 line Completed javadocs ........ r643824 | jeremias | 2008-04-02 12:00:30 +0200 (Mi, 02 Apr 2008) | 1 line Javadocs. ........ r645847 | vhennebert | 2008-04-08 12:54:16 +0200 (Di, 08 Apr 2008) | 2 lines Minor typo fixes ........ r645848 | vhennebert | 2008-04-08 12:58:30 +0200 (Di, 08 Apr 2008) | 2 lines Another small typo fix ........ r647678 | jeremias | 2008-04-14 09:20:26 +0200 (Mo, 14 Apr 2008) | 1 line Renamed *EventProducer.Factory.create() to *EventProducer.Provider.get() to better reflect what the method does (instances may be cached and reused). ........ ................ r647745 | jeremias | 2008-04-14 13:01:06 +0100 (Mon, 14 Apr 2008) | 1 line svnmerge didn't work for me in this case. Remove svn merge info. ................ git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_AFPGOCAResources@647787 13f79535-47bb-0310-9956-ffa450edef68 --- build.xml | 168 +++++- .../java/embedding/events/ExampleEvents.java | 227 ++++++++ .../java/embedding/events/missing-image.fo | 33 ++ lib/build/qdox-1.6.3.jar | Bin 0 -> 104078 bytes lib/build/qdox.LICENSE.txt | 201 ++++++++ .../fop/tools/EventConventionException.java | 28 + .../fop/tools/EventProducerCollector.java | 183 +++++++ .../fop/tools/EventProducerCollectorTask.java | 200 +++++++ .../apache/fop/tools/merge-translation.xsl | 55 ++ .../apache/fop/tools/model2translation.xsl | 35 ++ src/documentation/content/xdocs/site.xml | 1 + .../content/xdocs/trunk/events.xml | 422 +++++++++++++++ ...nts.EventExceptionManager$ExceptionFactory | 4 + ....apache.fop.events.model.EventModelFactory | 7 + ...p.util.text.AdvancedMessageFormat$Function | 1 + ...text.AdvancedMessageFormat$ObjectFormatter | 1 + ...til.text.AdvancedMessageFormat$PartFactory | 6 + .../org/apache/fop/apps/FOPException.java | 21 + src/java/org/apache/fop/apps/FOUserAgent.java | 45 ++ .../apache/fop/area/AreaEventProducer.java | 91 ++++ .../org/apache/fop/area/AreaTreeHandler.java | 5 +- .../org/apache/fop/area/AreaTreeObject.java | 10 +- .../org/apache/fop/area/AreaTreeParser.java | 2 +- .../fop/area/CachedRenderPagesModel.java | 77 ++- .../org/apache/fop/area/PageViewport.java | 16 +- .../org/apache/fop/area/RenderPagesModel.java | 45 +- .../fop/events/CompositeEventListener.java | 69 +++ .../fop/events/DefaultEventBroadcaster.java | 160 ++++++ src/java/org/apache/fop/events/Event.java | 150 ++++++ .../apache/fop/events/EventBroadcaster.java | 61 +++ .../fop/events/EventExceptionManager.java | 84 +++ .../org/apache/fop/events/EventFormatter.java | 196 +++++++ .../org/apache/fop/events/EventFormatter.xml | 101 ++++ .../apache/fop/events/EventFormatter_de.xml | 23 + .../org/apache/fop/events/EventListener.java | 37 ++ .../org/apache/fop/events/EventProducer.java | 31 ++ .../fop/events/FOPEventListenerProxy.java | 73 +++ .../fop/events/FOPEventModelFactory.java | 37 ++ .../fop/events/LoggingEventListener.java | 92 ++++ .../fop/events/PropertyExceptionFactory.java | 47 ++ .../fop/events/ResourceEventProducer.java | 136 +++++ .../UnsupportedOperationExceptionFactory.java | 43 ++ .../events/ValidationExceptionFactory.java | 51 ++ .../model/AbstractEventModelFactory.java | 61 +++ .../fop/events/model/EventMethodModel.java | 198 +++++++ .../apache/fop/events/model/EventModel.java | 135 +++++ .../fop/events/model/EventModelFactory.java | 33 ++ .../fop/events/model/EventModelParser.java | 140 +++++ .../fop/events/model/EventProducerModel.java | 105 ++++ .../fop/events/model/EventSeverity.java | 82 +++ .../org/apache/fop/fo/ElementMapping.java | 3 +- .../apache/fop/fo/ElementMappingRegistry.java | 2 +- .../org/apache/fop/fo/FOElementMapping.java | 2 +- .../org/apache/fop/fo/FOEventHandler.java | 2 + src/java/org/apache/fop/fo/FONode.java | 172 +++++-- src/java/org/apache/fop/fo/FOText.java | 9 +- src/java/org/apache/fop/fo/FOTreeBuilder.java | 67 ++- .../fop/fo/FOValidationEventProducer.java | 348 +++++++++++++ src/java/org/apache/fop/fo/FObj.java | 77 +-- src/java/org/apache/fop/fo/PropertyList.java | 67 ++- .../fop/fo/expr/FromParentFunction.java | 8 +- .../fop/fo/expr/InheritedPropFunction.java | 5 + .../fop/fo/expr/NearestSpecPropFunction.java | 5 + .../extensions/ExtensionElementMapping.java | 9 +- .../extensions/destination/Destination.java | 14 +- .../fop/fo/flow/AbstractListItemPart.java | 29 +- .../fo/flow/AbstractPageNumberCitation.java | 4 +- .../org/apache/fop/fo/flow/BasicLink.java | 21 +- .../org/apache/fop/fo/flow/BidiOverride.java | 32 +- .../apache/fop/fo/flow/BlockContainer.java | 18 +- .../org/apache/fop/fo/flow/Character.java | 7 +- .../apache/fop/fo/flow/ExternalGraphic.java | 18 +- src/java/org/apache/fop/fo/flow/Float.java | 7 +- src/java/org/apache/fop/fo/flow/Footnote.java | 8 +- .../org/apache/fop/fo/flow/FootnoteBody.java | 4 +- .../fop/fo/flow/InitialPropertySet.java | 4 +- src/java/org/apache/fop/fo/flow/Inline.java | 33 +- .../apache/fop/fo/flow/InlineContainer.java | 18 +- .../fop/fo/flow/InstreamForeignObject.java | 43 +- .../org/apache/fop/fo/flow/ListBlock.java | 18 +- src/java/org/apache/fop/fo/flow/ListItem.java | 32 +- src/java/org/apache/fop/fo/flow/Marker.java | 13 +- .../org/apache/fop/fo/flow/MultiCase.java | 3 +- .../apache/fop/fo/flow/MultiProperties.java | 9 +- .../apache/fop/fo/flow/MultiPropertySet.java | 7 +- .../org/apache/fop/fo/flow/MultiSwitch.java | 11 +- .../org/apache/fop/fo/flow/MultiToggle.java | 11 +- .../org/apache/fop/fo/flow/PageNumber.java | 4 +- .../apache/fop/fo/flow/RetrieveMarker.java | 31 +- src/java/org/apache/fop/fo/flow/Wrapper.java | 25 +- .../flow/table/FixedColRowGroupBuilder.java | 12 +- .../org/apache/fop/fo/flow/table/Table.java | 39 +- .../fop/fo/flow/table/TableAndCaption.java | 47 +- .../apache/fop/fo/flow/table/TableBody.java | 29 +- .../fop/fo/flow/table/TableCaption.java | 21 +- .../apache/fop/fo/flow/table/TableCell.java | 28 +- .../fop/fo/flow/table/TableCellContainer.java | 7 +- .../apache/fop/fo/flow/table/TableColumn.java | 21 +- .../fop/fo/flow/table/TableEventProducer.java | 159 ++++++ .../apache/fop/fo/flow/table/TableFObj.java | 29 +- .../apache/fop/fo/flow/table/TableRow.java | 13 +- .../fo/pagination/AbstractPageSequence.java | 13 +- .../fop/fo/pagination/ColorProfile.java | 21 +- .../ConditionalPageMasterReference.java | 16 +- .../fop/fo/pagination/Declarations.java | 21 +- .../org/apache/fop/fo/pagination/Flow.java | 39 +- .../fop/fo/pagination/LayoutMasterSet.java | 63 +-- .../fo/pagination/PageNumberGenerator.java | 6 +- .../fop/fo/pagination/PageSequence.java | 28 +- .../fop/fo/pagination/PageSequenceMaster.java | 38 +- .../fo/pagination/PageSequenceWrapper.java | 19 +- .../org/apache/fop/fo/pagination/Region.java | 29 +- .../apache/fop/fo/pagination/RegionAfter.java | 14 +- .../apache/fop/fo/pagination/RegionBA.java | 6 +- .../fop/fo/pagination/RegionBefore.java | 15 +- .../apache/fop/fo/pagination/RegionBody.java | 24 +- .../apache/fop/fo/pagination/RegionEnd.java | 18 +- .../apache/fop/fo/pagination/RegionSE.java | 6 +- .../apache/fop/fo/pagination/RegionStart.java | 15 +- .../RepeatablePageMasterAlternatives.java | 43 +- .../RepeatablePageMasterReference.java | 20 +- .../org/apache/fop/fo/pagination/Root.java | 20 +- .../apache/fop/fo/pagination/SideRegion.java | 6 +- .../fop/fo/pagination/SimplePageMaster.java | 130 +++-- .../pagination/SinglePageMasterReference.java | 16 +- .../fop/fo/pagination/StaticContent.java | 18 +- .../org/apache/fop/fo/pagination/Title.java | 19 +- .../fop/fo/pagination/bookmarks/Bookmark.java | 37 +- .../pagination/bookmarks/BookmarkTitle.java | 17 +- .../fo/pagination/bookmarks/BookmarkTree.java | 24 +- .../apache/fop/fonts/FontEventAdapter.java | 82 +++ .../apache/fop/fonts/FontEventListener.java | 52 ++ src/java/org/apache/fop/fonts/FontInfo.java | 22 +- src/java/org/apache/fop/fonts/LazyFont.java | 1 + .../org/apache/fop/fonts/SingleByteFont.java | 28 +- src/java/org/apache/fop/fonts/Typeface.java | 12 + .../fop/fonts/autodetect/FontInfoFinder.java | 38 +- .../loader/batik/ImageConverterSVG2G2D.java | 31 +- .../fop/image/loader/batik/PreloaderSVG.java | 14 +- .../apache/fop/layoutmgr/AbstractBreaker.java | 8 +- .../BlockContainerLayoutManager.java | 44 +- .../layoutmgr/BlockLevelEventProducer.java | 168 ++++++ .../layoutmgr/BlockStackingLayoutManager.java | 10 +- .../ExternalDocumentLayoutManager.java | 19 +- .../apache/fop/layoutmgr/LayoutException.java | 105 ++++ .../org/apache/fop/layoutmgr/PageBreaker.java | 33 +- .../fop/layoutmgr/PageBreakingAlgorithm.java | 43 +- .../apache/fop/layoutmgr/PageProvider.java | 10 +- .../layoutmgr/StaticContentLayoutManager.java | 43 +- .../inline/ContentLayoutManager.java | 1 - .../inline/InlineLevelEventProducer.java | 66 +++ .../layoutmgr/inline/LeaderLayoutManager.java | 16 +- .../layoutmgr/inline/LineLayoutManager.java | 14 +- .../fop/layoutmgr/table/ColumnSetup.java | 15 +- .../table/RowGroupLayoutManager.java | 12 +- .../layoutmgr/table/TableLayoutManager.java | 18 +- .../fop/render/AbstractGenericSVGHandler.java | 24 +- .../render/AbstractPathOrientedRenderer.java | 2 +- .../apache/fop/render/AbstractRenderer.java | 25 +- src/java/org/apache/fop/render/Renderer.java | 6 + .../fop/render/RendererEventProducer.java | 53 ++ .../fop/render/afp/AFPEventProducer.java | 63 +++ .../fop/render/afp/AFPEventProducer.xml | 4 + .../apache/fop/render/afp/AFPRenderer.java | 30 +- .../apache/fop/render/afp/AFPSVGHandler.java | 3 +- .../bitmap/BitmapRendererEventProducer.java | 80 +++ .../bitmap/BitmapRendererEventProducer.xml | 6 + .../apache/fop/render/bitmap/PNGRenderer.java | 17 +- .../fop/render/bitmap/TIFFRenderer.java | 11 +- .../fop/render/java2d/Java2DRenderer.java | 17 +- .../fop/render/java2d/Java2DSVGHandler.java | 63 +-- .../fop/render/pcl/PCLEventProducer.java | 66 +++ .../fop/render/pcl/PCLEventProducer.xml | 4 + .../apache/fop/render/pcl/PCLGenerator.java | 12 +- .../apache/fop/render/pcl/PCLRenderer.java | 33 +- .../fop/render/pcl/PCLRendererContext.java | 3 +- .../fop/render/pdf/PDFEventProducer.java | 64 +++ .../fop/render/pdf/PDFEventProducer.xml | 4 + .../apache/fop/render/pdf/PDFRenderer.java | 100 ++-- .../apache/fop/render/pdf/PDFSVGHandler.java | 29 +- .../apache/fop/render/ps/PSEventProducer.java | 65 +++ .../apache/fop/render/ps/PSEventProducer.xml | 4 + .../org/apache/fop/render/ps/PSRenderer.java | 29 +- .../apache/fop/render/ps/PSSVGHandler.java | 68 +-- .../apache/fop/render/ps/ResourceHandler.java | 8 +- .../extensions/AbstractPSCommentElement.java | 5 +- .../AbstractPSExtensionElement.java | 6 +- .../extensions/AbstractPSExtensionObject.java | 13 +- .../ps/extensions/PSCommentAfterElement.java | 3 + .../ps/extensions/PSCommentBeforeElement.java | 3 + .../ps/extensions/PSExtensionHandler.java | 20 +- .../ps/extensions/PSPageSetupCodeElement.java | 5 +- .../ps/extensions/PSSetPageDeviceElement.java | 11 +- .../ps/extensions/PSSetupCodeElement.java | 5 +- .../fop/render/rtf/RTFEventProducer.java | 94 ++++ .../fop/render/rtf/RTFEventProducer.xml | 8 + .../org/apache/fop/render/rtf/RTFHandler.java | 153 +++--- .../apache/fop/render/xml/XMLRenderer.java | 2 +- .../org/apache/fop/svg/SVGEventProducer.java | 89 ++++ src/java/org/apache/fop/svg/SVGUserAgent.java | 131 +---- .../apache/fop/svg/SimpleSVGUserAgent.java | 126 +++++ src/java/org/apache/fop/util/QName.java | 94 +--- .../apache/fop/util/XMLResourceBundle.java | 398 ++++++++++++++ .../fop/util/text/AdvancedMessageFormat.java | 487 ++++++++++++++++++ .../apache/fop/util/text/ChoiceFieldPart.java | 91 ++++ .../apache/fop/util/text/EqualsFieldPart.java | 92 ++++ .../fop/util/text/GlyphNameFieldPart.java | 89 ++++ .../apache/fop/util/text/HexFieldPart.java | 84 +++ .../org/apache/fop/util/text/IfFieldPart.java | 116 +++++ .../fop/util/text/LocatorFormatter.java | 42 ++ ....apache.fop.events.model.EventModelFactory | 1 + .../org/apache/fop/UtilityCodeTestSuite.java | 4 + .../apache/fop/events/BasicEventTestCase.java | 95 ++++ .../fop/events/FOPTestEventModelFactory.java | 37 ++ .../apache/fop/events/TestEventProducer.java | 48 ++ .../util/AdvancedMessageFormatTestCase.java | 183 +++++++ .../fop/util/XMLResourceBundleTestCase.java | 60 +++ .../fop/util/XMLResourceBundleTestCase.xml | 5 + .../fop/util/XMLResourceBundleTestCase_de.xml | 4 + .../fop/util/invalid-translation-file.xml | 5 + 220 files changed, 9158 insertions(+), 1624 deletions(-) create mode 100644 examples/embedding/java/embedding/events/ExampleEvents.java create mode 100644 examples/embedding/java/embedding/events/missing-image.fo create mode 100644 lib/build/qdox-1.6.3.jar create mode 100644 lib/build/qdox.LICENSE.txt create mode 100644 src/codegen/java/org/apache/fop/tools/EventConventionException.java create mode 100644 src/codegen/java/org/apache/fop/tools/EventProducerCollector.java create mode 100644 src/codegen/java/org/apache/fop/tools/EventProducerCollectorTask.java create mode 100644 src/codegen/java/org/apache/fop/tools/merge-translation.xsl create mode 100644 src/codegen/java/org/apache/fop/tools/model2translation.xsl create mode 100644 src/documentation/content/xdocs/trunk/events.xml create mode 100644 src/java/META-INF/services/org.apache.fop.events.EventExceptionManager$ExceptionFactory create mode 100644 src/java/META-INF/services/org.apache.fop.events.model.EventModelFactory create mode 100644 src/java/META-INF/services/org.apache.fop.util.text.AdvancedMessageFormat$Function create mode 100644 src/java/META-INF/services/org.apache.fop.util.text.AdvancedMessageFormat$ObjectFormatter create mode 100644 src/java/META-INF/services/org.apache.fop.util.text.AdvancedMessageFormat$PartFactory create mode 100644 src/java/org/apache/fop/area/AreaEventProducer.java create mode 100644 src/java/org/apache/fop/events/CompositeEventListener.java create mode 100644 src/java/org/apache/fop/events/DefaultEventBroadcaster.java create mode 100644 src/java/org/apache/fop/events/Event.java create mode 100644 src/java/org/apache/fop/events/EventBroadcaster.java create mode 100644 src/java/org/apache/fop/events/EventExceptionManager.java create mode 100644 src/java/org/apache/fop/events/EventFormatter.java create mode 100644 src/java/org/apache/fop/events/EventFormatter.xml create mode 100644 src/java/org/apache/fop/events/EventFormatter_de.xml create mode 100644 src/java/org/apache/fop/events/EventListener.java create mode 100644 src/java/org/apache/fop/events/EventProducer.java create mode 100644 src/java/org/apache/fop/events/FOPEventListenerProxy.java create mode 100644 src/java/org/apache/fop/events/FOPEventModelFactory.java create mode 100644 src/java/org/apache/fop/events/LoggingEventListener.java create mode 100644 src/java/org/apache/fop/events/PropertyExceptionFactory.java create mode 100644 src/java/org/apache/fop/events/ResourceEventProducer.java create mode 100644 src/java/org/apache/fop/events/UnsupportedOperationExceptionFactory.java create mode 100644 src/java/org/apache/fop/events/ValidationExceptionFactory.java create mode 100644 src/java/org/apache/fop/events/model/AbstractEventModelFactory.java create mode 100644 src/java/org/apache/fop/events/model/EventMethodModel.java create mode 100644 src/java/org/apache/fop/events/model/EventModel.java create mode 100644 src/java/org/apache/fop/events/model/EventModelFactory.java create mode 100644 src/java/org/apache/fop/events/model/EventModelParser.java create mode 100644 src/java/org/apache/fop/events/model/EventProducerModel.java create mode 100644 src/java/org/apache/fop/events/model/EventSeverity.java create mode 100644 src/java/org/apache/fop/fo/FOValidationEventProducer.java create mode 100644 src/java/org/apache/fop/fo/flow/table/TableEventProducer.java create mode 100644 src/java/org/apache/fop/fonts/FontEventAdapter.java create mode 100644 src/java/org/apache/fop/fonts/FontEventListener.java create mode 100644 src/java/org/apache/fop/layoutmgr/BlockLevelEventProducer.java create mode 100644 src/java/org/apache/fop/layoutmgr/LayoutException.java create mode 100644 src/java/org/apache/fop/layoutmgr/inline/InlineLevelEventProducer.java create mode 100644 src/java/org/apache/fop/render/RendererEventProducer.java create mode 100644 src/java/org/apache/fop/render/afp/AFPEventProducer.java create mode 100644 src/java/org/apache/fop/render/afp/AFPEventProducer.xml create mode 100644 src/java/org/apache/fop/render/bitmap/BitmapRendererEventProducer.java create mode 100644 src/java/org/apache/fop/render/bitmap/BitmapRendererEventProducer.xml create mode 100644 src/java/org/apache/fop/render/pcl/PCLEventProducer.java create mode 100644 src/java/org/apache/fop/render/pcl/PCLEventProducer.xml create mode 100644 src/java/org/apache/fop/render/pdf/PDFEventProducer.java create mode 100644 src/java/org/apache/fop/render/pdf/PDFEventProducer.xml create mode 100644 src/java/org/apache/fop/render/ps/PSEventProducer.java create mode 100644 src/java/org/apache/fop/render/ps/PSEventProducer.xml create mode 100644 src/java/org/apache/fop/render/rtf/RTFEventProducer.java create mode 100644 src/java/org/apache/fop/render/rtf/RTFEventProducer.xml create mode 100644 src/java/org/apache/fop/svg/SVGEventProducer.java create mode 100644 src/java/org/apache/fop/svg/SimpleSVGUserAgent.java create mode 100644 src/java/org/apache/fop/util/XMLResourceBundle.java create mode 100644 src/java/org/apache/fop/util/text/AdvancedMessageFormat.java create mode 100644 src/java/org/apache/fop/util/text/ChoiceFieldPart.java create mode 100644 src/java/org/apache/fop/util/text/EqualsFieldPart.java create mode 100644 src/java/org/apache/fop/util/text/GlyphNameFieldPart.java create mode 100644 src/java/org/apache/fop/util/text/HexFieldPart.java create mode 100644 src/java/org/apache/fop/util/text/IfFieldPart.java create mode 100644 src/java/org/apache/fop/util/text/LocatorFormatter.java create mode 100644 test/java/META-INF/services/org.apache.fop.events.model.EventModelFactory create mode 100644 test/java/org/apache/fop/events/BasicEventTestCase.java create mode 100644 test/java/org/apache/fop/events/FOPTestEventModelFactory.java create mode 100644 test/java/org/apache/fop/events/TestEventProducer.java create mode 100644 test/java/org/apache/fop/util/AdvancedMessageFormatTestCase.java create mode 100644 test/java/org/apache/fop/util/XMLResourceBundleTestCase.java create mode 100644 test/java/org/apache/fop/util/XMLResourceBundleTestCase.xml create mode 100644 test/java/org/apache/fop/util/XMLResourceBundleTestCase_de.xml create mode 100644 test/java/org/apache/fop/util/invalid-translation-file.xml diff --git a/build.xml b/build.xml index 2169003f8..5a72154f7 100644 --- a/build.xml +++ b/build.xml @@ -118,13 +118,15 @@ list of possible build targets. - - - - - + + + + + + + @@ -362,7 +364,7 @@ list of possible build targets. - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -390,31 +498,22 @@ list of possible build targets. - - - - - - - - - - - + + + - + + - + @@ -452,13 +551,13 @@ list of possible build targets. - + - + @@ -487,13 +586,13 @@ list of possible build targets. - + - + @@ -608,14 +707,14 @@ list of possible build targets. - + - + @@ -693,6 +792,7 @@ list of possible build targets. + + + + + + + + + + + + + + + diff --git a/examples/embedding/java/embedding/events/ExampleEvents.java b/examples/embedding/java/embedding/events/ExampleEvents.java new file mode 100644 index 000000000..9c52e4ba1 --- /dev/null +++ b/examples/embedding/java/embedding/events/ExampleEvents.java @@ -0,0 +1,227 @@ +/* + * 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 embedding.events; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URL; + +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.sax.SAXResult; +import javax.xml.transform.stream.StreamSource; + +import org.xml.sax.SAXException; + +import org.apache.commons.io.IOUtils; + +import org.apache.fop.apps.FOPException; +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.apps.Fop; +import org.apache.fop.apps.FopFactory; +import org.apache.fop.apps.MimeConstants; +import org.apache.fop.events.Event; +import org.apache.fop.events.EventFormatter; +import org.apache.fop.events.EventListener; +import org.apache.fop.events.model.EventSeverity; + +/** + * This class demonstrates how to register an event listener with FOP so you can customize + * FOP's error behaviour. + */ +public class ExampleEvents { + + // configure fopFactory as desired + private FopFactory fopFactory = FopFactory.newInstance(); + + /** + * Converts an FO file to a PDF file using FOP + * @param fo the FO file + * @param pdf the target PDF file + * @throws IOException In case of an I/O problem + * @throws FOPException In case of a FOP problem + * @throws TransformerException In case of a problem with XSLT + */ + public void convertFO2PDF(URL fo, File pdf) + throws IOException, FOPException, TransformerException { + + OutputStream out = null; + + try { + //Create the user agent for this processing run + FOUserAgent foUserAgent = fopFactory.newFOUserAgent(); + + //Adding a simple logging listener that writes to stdout and stderr + foUserAgent.getEventBroadcaster().addEventListener(new SysOutEventListener()); + + // Add your own event listener + foUserAgent.getEventBroadcaster().addEventListener(new MyEventListener()); + + // configure foUserAgent further as desired + + // Setup output stream. Note: Using BufferedOutputStream + // for performance reasons (helpful with FileOutputStreams). + out = new FileOutputStream(pdf); + out = new BufferedOutputStream(out); + + // Construct fop with desired output format + Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, foUserAgent, out); + + // Setup JAXP using identity transformer + TransformerFactory factory = TransformerFactory.newInstance(); + Transformer transformer = factory.newTransformer(); // identity transformer + + // Setup input stream + Source src = new StreamSource(fo.toExternalForm()); + + // Resulting SAX events (the generated FO) must be piped through to FOP + Result res = new SAXResult(fop.getDefaultHandler()); + + // Start XSLT transformation and FOP processing + transformer.transform(src, res); + + } finally { + IOUtils.closeQuietly(out); + } + } + + private static class MyEventListener implements EventListener { + + public void processEvent(Event event) { + if ("org.apache.fop.events.ResourceEventProducer.imageNotFound" + .equals(event.getEventID())) { + + //Get the FileNotFoundException that's part of the event's parameters + FileNotFoundException fnfe = (FileNotFoundException)event.getParam("fnfe"); + + System.out.println("---=== imageNotFound Event for " + event.getParam("uri") + + "!!! ===---"); + //Stop processing when an image could not be found. Otherwise, FOP would just + //continue without the image! + + System.out.println("Throwing a RuntimeException..."); + throw new RuntimeException(EventFormatter.format(event), fnfe); + } else { + //ignore all other events + } + } + + } + + /** A simple event listener that writes the events to stdout and sterr. */ + private static class SysOutEventListener implements EventListener { + + /** {@inheritDoc} */ + public void processEvent(Event event) { + String msg = EventFormatter.format(event); + EventSeverity severity = event.getSeverity(); + if (severity == EventSeverity.INFO) { + System.out.println("[INFO ] " + msg); + } else if (severity == EventSeverity.WARN) { + System.out.println("[WARN ] " + msg); + } else if (severity == EventSeverity.ERROR) { + System.err.println("[ERROR] " + msg); + } else if (severity == EventSeverity.FATAL) { + System.err.println("[FATAL] " + msg); + } else { + assert false; + } + } + } + + + /** + * This method extracts the original exception from some exception. The exception + * might be nested multiple levels deep. + * @param t the Throwable to inspect + * @return the original Throwable or the method parameter t if there are no nested Throwables. + */ + private static Throwable getOriginalThrowable(Throwable t) { + if (t instanceof SAXException) { + SAXException saxe = (SAXException)t; + if (saxe.getException() != null) { + return getOriginalThrowable(saxe.getException()); + } else { + return saxe; + } + } else { + if (t.getCause() != null) { + return getOriginalThrowable(t.getCause()); + } else { + return t; + } + } + } + + /** + * Main method. + * @param args command-line arguments + */ + public static void main(String[] args) { + try { + System.out.println("FOP ExampleEvents\n"); + System.out.println("Preparing..."); + + //Setup directories + File baseDir = new File("."); + File outDir = new File(baseDir, "out"); + outDir.mkdirs(); + + //Setup input and output files + URL fo = ExampleEvents.class.getResource("missing-image.fo"); + File pdffile = new File(outDir, "out.pdf"); + + System.out.println("Input: XSL-FO (" + fo.toExternalForm() + ")"); + System.out.println("Output: PDF (" + pdffile + ")"); + System.out.println(); + System.out.println("Transforming..."); + + ExampleEvents app = new ExampleEvents(); + + try { + app.convertFO2PDF(fo, pdffile); + } catch (TransformerException te) { + //Note: We don't get the original exception here! + //FOP needs to embed the exception in a SAXException and the TraX transformer + //again wraps the SAXException in a TransformerException. Even our own + //RuntimeException just wraps the original FileNotFoundException. + //So we need to unpack to get the original exception (about three layers deep). + Throwable originalThrowable = getOriginalThrowable(te); + originalThrowable.printStackTrace(System.err); + System.out.println("Aborted!"); + System.exit(-1); + } + + System.out.println("Success!"); + } catch (Exception e) { + //Some other error (shouldn't happen in this example) + e.printStackTrace(System.err); + System.exit(-1); + } + } + +} diff --git a/examples/embedding/java/embedding/events/missing-image.fo b/examples/embedding/java/embedding/events/missing-image.fo new file mode 100644 index 000000000..2c55f66ce --- /dev/null +++ b/examples/embedding/java/embedding/events/missing-image.fo @@ -0,0 +1,33 @@ + + + + + + + + + + + + + The following image is not available: + + + + + diff --git a/lib/build/qdox-1.6.3.jar b/lib/build/qdox-1.6.3.jar new file mode 100644 index 0000000000000000000000000000000000000000..3e99cb06491b1aa501e46bbca4fafdad78a997d5 GIT binary patch literal 104078 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®h|E7_1% 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 Bgnj@3 literal 0 HcmV?d00001 diff --git a/lib/build/qdox.LICENSE.txt b/lib/build/qdox.LICENSE.txt new file mode 100644 index 000000000..3e4e3d004 --- /dev/null +++ b/lib/build/qdox.LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. \ No newline at end of file diff --git a/src/codegen/java/org/apache/fop/tools/EventConventionException.java b/src/codegen/java/org/apache/fop/tools/EventConventionException.java new file mode 100644 index 000000000..675f4a0ca --- /dev/null +++ b/src/codegen/java/org/apache/fop/tools/EventConventionException.java @@ -0,0 +1,28 @@ +/* + * 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.tools; + +public class EventConventionException extends Exception { + + public EventConventionException(String message) { + super(message); + } + +} diff --git a/src/codegen/java/org/apache/fop/tools/EventProducerCollector.java b/src/codegen/java/org/apache/fop/tools/EventProducerCollector.java new file mode 100644 index 000000000..e42395ae7 --- /dev/null +++ b/src/codegen/java/org/apache/fop/tools/EventProducerCollector.java @@ -0,0 +1,183 @@ +/* + * 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.tools; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.Map; + +import org.apache.fop.events.EventProducer; +import org.apache.fop.events.model.EventMethodModel; +import org.apache.fop.events.model.EventModel; +import org.apache.fop.events.model.EventProducerModel; +import org.apache.fop.events.model.EventSeverity; + +import com.thoughtworks.qdox.JavaDocBuilder; +import com.thoughtworks.qdox.model.DefaultDocletTagFactory; +import com.thoughtworks.qdox.model.DocletTag; +import com.thoughtworks.qdox.model.DocletTagFactory; +import com.thoughtworks.qdox.model.JavaClass; +import com.thoughtworks.qdox.model.JavaMethod; +import com.thoughtworks.qdox.model.JavaParameter; +import com.thoughtworks.qdox.model.Type; + +/** + * Finds EventProducer interfaces and builds the event model for them. + */ +public class EventProducerCollector { + + private static final String CLASSNAME_EVENT_PRODUCER = EventProducer.class.getName(); + private static final Map PRIMITIVE_MAP; + + static { + Map m = new java.util.HashMap(); + m.put("boolean", Boolean.class); + m.put("byte", Byte.class); + m.put("char", Character.class); + m.put("short", Short.class); + m.put("int", Integer.class); + m.put("long", Long.class); + m.put("float", Float.class); + m.put("double", Double.class); + PRIMITIVE_MAP = Collections.unmodifiableMap(m); + } + + private DocletTagFactory tagFactory; + private EventModel model = new EventModel(); + + public EventProducerCollector() { + this.tagFactory = createDocletTagFactory(); + } + + protected DocletTagFactory createDocletTagFactory() { + return new DefaultDocletTagFactory(); + } + + public void scanFile(File src, String filename) + throws IOException, EventConventionException, ClassNotFoundException { + JavaDocBuilder builder = new JavaDocBuilder(this.tagFactory); + builder.addSource(src); + JavaClass[] classes = builder.getClasses(); + for (int i = 0, c = classes.length; i < c; i++) { + JavaClass clazz = classes[i]; + if (clazz.isInterface() && implementsInterface(clazz, CLASSNAME_EVENT_PRODUCER)) { + processEventProducerInterface(clazz, filename); + } + } + } + + private boolean implementsInterface(JavaClass clazz, String intf) { + JavaClass[] classes = clazz.getImplementedInterfaces(); + for (int i = 0, c = classes.length; i < c; i++) { + JavaClass cl = classes[i]; + if (cl.getFullyQualifiedName().equals(intf)) { + return true; + } + } + return false; + } + + /** + * Processes an EventProducer interface and creates an EventProducerModel from it. + * @param clazz the EventProducer interface + * @param javaFilename the filename of the Java source of the interface + * @throws EventConventionException if the event producer conventions are violated + * @throws ClassNotFoundException if a required class cannot be found + */ + protected void processEventProducerInterface(JavaClass clazz, String javaFilename) + throws EventConventionException, ClassNotFoundException { + EventProducerModel prodMeta = new EventProducerModel(clazz.getFullyQualifiedName()); + JavaMethod[] methods = clazz.getMethods(true); + for (int i = 0, c = methods.length; i < c; i++) { + JavaMethod method = methods[i]; + EventMethodModel methodMeta = createMethodModel(method); + prodMeta.addMethod(methodMeta); + } + this.model.addProducer(prodMeta); + } + + private EventMethodModel createMethodModel(JavaMethod method) + throws EventConventionException, ClassNotFoundException { + JavaClass clazz = method.getParentClass(); + //Check EventProducer conventions + if (!method.getReturns().isVoid()) { + throw new EventConventionException("All methods of interface " + + clazz.getFullyQualifiedName() + " must have return type 'void'!"); + } + String methodSig = clazz.getFullyQualifiedName() + "." + method.getCallSignature(); + JavaParameter[] params = method.getParameters(); + if (params.length < 1) { + throw new EventConventionException("The method " + methodSig + + " must have at least one parameter: 'Object source'!"); + } + Type firstType = params[0].getType(); + if (firstType.isPrimitive() || !"source".equals(params[0].getName())) { + throw new EventConventionException("The first parameter of the method " + methodSig + + " must be: 'Object source'!"); + } + + //build method model + DocletTag tag = method.getTagByName("event.severity"); + EventSeverity severity; + if (tag != null) { + severity = EventSeverity.valueOf(tag.getValue()); + } else { + severity = EventSeverity.INFO; + } + EventMethodModel methodMeta = new EventMethodModel( + method.getName(), severity); + if (params.length > 1) { + for (int j = 1, cj = params.length; j < cj; j++) { + JavaParameter p = params[j]; + Class type; + JavaClass pClass = p.getType().getJavaClass(); + if (p.getType().isPrimitive()) { + type = (Class)PRIMITIVE_MAP.get(pClass.getName()); + if (type == null) { + throw new UnsupportedOperationException( + "Primitive datatype not supported: " + pClass.getName()); + } + } else { + String className = pClass.getFullyQualifiedName(); + type = Class.forName(className); + } + methodMeta.addParameter(type, p.getName()); + } + } + Type[] exceptions = method.getExceptions(); + if (exceptions != null && exceptions.length > 0) { + //We only use the first declared exception because that is always thrown + JavaClass cl = exceptions[0].getJavaClass(); + methodMeta.setExceptionClass(cl.getFullyQualifiedName()); + methodMeta.setSeverity(EventSeverity.FATAL); //In case it's not set in the comments + } + return methodMeta; + } + + public EventModel getModel() { + return this.model; + } + + public void saveModelToXML(File modelFile) throws IOException { + getModel().saveToXML(modelFile); + } + +} diff --git a/src/codegen/java/org/apache/fop/tools/EventProducerCollectorTask.java b/src/codegen/java/org/apache/fop/tools/EventProducerCollectorTask.java new file mode 100644 index 000000000..755e3da0b --- /dev/null +++ b/src/codegen/java/org/apache/fop/tools/EventProducerCollectorTask.java @@ -0,0 +1,200 @@ +/* + * 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.tools; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Iterator; +import java.util.List; + +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.URIResolver; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.sax.SAXTransformerFactory; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +import org.w3c.dom.Node; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.selectors.FilenameSelector; + +public class EventProducerCollectorTask extends Task { + + private List filesets = new java.util.ArrayList(); + private File modelFile; + private File translationFile; + + /** {@inheritDoc} */ + public void execute() throws BuildException { + try { + EventProducerCollector collector = new EventProducerCollector(); + processFileSets(collector); + getModelFile().getParentFile().mkdirs(); + collector.saveModelToXML(getModelFile()); + log("Event model written to " + getModelFile()); + if (getTranslationFile() != null) { + updateTranslationFile(); + } + } catch (ClassNotFoundException e) { + throw new BuildException(e); + } catch (EventConventionException ece) { + throw new BuildException(ece); + } catch (IOException ioe) { + throw new BuildException(ioe); + } + } + + private static final String MODEL2TRANSLATION = "model2translation.xsl"; + private static final String MERGETRANSLATION = "merge-translation.xsl"; + + protected void updateTranslationFile() throws IOException { + try { + boolean resultExists = getTranslationFile().exists(); + SAXTransformerFactory tFactory + = (SAXTransformerFactory)SAXTransformerFactory.newInstance(); + + //Generate fresh generated translation file as template + Source src = new StreamSource(getModelFile()); + StreamSource xslt1 = new StreamSource( + getClass().getResourceAsStream(MODEL2TRANSLATION)); + if (xslt1.getInputStream() == null) { + throw new FileNotFoundException(MODEL2TRANSLATION + " not found"); + } + DOMResult domres = new DOMResult(); + Transformer transformer = tFactory.newTransformer(xslt1); + transformer.transform(src, domres); + final Node generated = domres.getNode(); + + Node sourceDocument; + if (resultExists) { + //Load existing translation file into memory (because we overwrite it later) + src = new StreamSource(getTranslationFile()); + domres = new DOMResult(); + transformer = tFactory.newTransformer(); + transformer.transform(src, domres); + sourceDocument = domres.getNode(); + } else { + //Simply use generated as source document + sourceDocument = generated; + } + + //Generate translation file (with potentially new translations) + src = new DOMSource(sourceDocument); + Result res = new StreamResult(getTranslationFile()); + StreamSource xslt2 = new StreamSource( + getClass().getResourceAsStream(MERGETRANSLATION)); + if (xslt2.getInputStream() == null) { + throw new FileNotFoundException(MERGETRANSLATION + " not found"); + } + transformer = tFactory.newTransformer(xslt2); + transformer.setURIResolver(new URIResolver() { + public Source resolve(String href, String base) throws TransformerException { + if ("my:dom".equals(href)) { + return new DOMSource(generated); + } + return null; + } + }); + if (resultExists) { + transformer.setParameter("generated-url", "my:dom"); + } + transformer.transform(src, res); + if (resultExists) { + log("Translation file updated: " + getTranslationFile()); + } else { + log("Translation file generated: " + getTranslationFile()); + } + } catch (TransformerException te) { + throw new IOException(te.getMessage()); + } + } + + protected void processFileSets(EventProducerCollector collector) + throws IOException, EventConventionException, ClassNotFoundException { + Iterator iter = filesets.iterator(); + while (iter.hasNext()) { + FileSet fs = (FileSet)iter.next(); + DirectoryScanner ds = fs.getDirectoryScanner(getProject()); + String[] srcFiles = ds.getIncludedFiles(); + File directory = fs.getDir(getProject()); + for (int i = 0, c = srcFiles.length; i < c; i++) { + String filename = srcFiles[i]; + File src = new File(directory, filename); + collector.scanFile(src, filename); + } + } + } + + public void addFileset(FileSet set) { + filesets.add(set); + } + + public void setModelFile(File f) { + this.modelFile = f; + } + + public File getModelFile() { + return this.modelFile; + } + + public void setTranslationFile(File f) { + this.translationFile = f; + } + + public File getTranslationFile() { + return this.translationFile; + } + + public static void main(String[] args) { + try { + Project project = new Project(); + + EventProducerCollectorTask generator = new EventProducerCollectorTask(); + generator.setProject(project); + project.setName("Test"); + FileSet fileset = new FileSet(); + fileset.setDir(new File("test/java")); + + FilenameSelector selector = new FilenameSelector(); + selector.setName("**/*.java"); + fileset.add(selector); + generator.addFileset(fileset); + + File targetDir = new File("build/codegen1"); + targetDir.mkdirs(); + + generator.setModelFile(new File("D:/out.xml")); + generator.setTranslationFile(new File("D:/out1.xml")); + generator.execute(); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/src/codegen/java/org/apache/fop/tools/merge-translation.xsl b/src/codegen/java/org/apache/fop/tools/merge-translation.xsl new file mode 100644 index 000000000..d15d22fd3 --- /dev/null +++ b/src/codegen/java/org/apache/fop/tools/merge-translation.xsl @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/codegen/java/org/apache/fop/tools/model2translation.xsl b/src/codegen/java/org/apache/fop/tools/model2translation.xsl new file mode 100644 index 000000000..a1cf404ee --- /dev/null +++ b/src/codegen/java/org/apache/fop/tools/model2translation.xsl @@ -0,0 +1,35 @@ + + + + + + + + + en + + + + + + + . + + + + diff --git a/src/documentation/content/xdocs/site.xml b/src/documentation/content/xdocs/site.xml index 18716903c..b0a3214e7 100644 --- a/src/documentation/content/xdocs/site.xml +++ b/src/documentation/content/xdocs/site.xml @@ -155,6 +155,7 @@ + diff --git a/src/documentation/content/xdocs/trunk/events.xml b/src/documentation/content/xdocs/trunk/events.xml new file mode 100644 index 000000000..d2fe5318c --- /dev/null +++ b/src/documentation/content/xdocs/trunk/events.xml @@ -0,0 +1,422 @@ + + + + + +

+ Events/Processing Feedback + $Revision: 634267 $ +
+ +
+ Introduction +

+ In versions until 0.20.5, FOP used + Avalon-style Logging where + it was possible to supply a logger per processing run. During the redesign + the logging infrastructure was switched over to + Commons Logging which is (like Log4J or + java.util.logging) a "static" logging framework (the logger is accessed through static + variables). This made it very difficult in a multi-threaded system to retrieve information + for a single processing run. +

+

+ With FOP's event subsystem, we'd like to close this gap again and even go further. The + first point is to realize that we have two kinds of "logging". Firstly, we have the logging + infrastructure for the (FOP) developer who needs to be able to enable finer log messages + for certain parts of FOP to track down a certain problem. Secondly, we have the user who + would like to be informed about missing images, overflowing lines or substituted fonts. + These messages (or events) are targeted at less technical people and may ideally be + localized (translated). Furthermore, tool and solution builders would like to integrate + FOP into their own solutions. For example, an FO editor should be able to point the user + to the right place where a particular problem occurred while developing a document template. + Finally, some integrators would like to abort processing if a resource (an image or a font) + has not been found, while others would simply continue. The event system allows to + react on these events. +

+

+ On this page, we won't discuss logging as such. We will show how the event subsystem can + be used for various tasks. We'll first look at the event subsystem from the consumer side. + Finally, the production of events inside FOP will be discussed (this is mostly interesting + for FOP developers only). +

+
+
+ The consumer side +

+ The event subsystem is located in the org.apache.fop.events package and its + base is the Event class. An instance is created for each event and is sent + to a set of EventListener instances by the EventBroadcaster. + An Event contains: +

+
    +
  • an event ID,
  • +
  • a source object (which generated the event),
  • +
  • a severity level (Info, Warning, Error and Fatal Error) and
  • +
  • a map of named parameters.
  • +
+

+ The EventFormatter class can be used to translate the events into + human-readable, localized messages. +

+

+ A full example of what is shown here can be found in the + examples/embedding/java/embedding/events directory in the FOP distribution. + The example can also be accessed + via the web. +

+
+ Writing an EventListener +

+ The following code sample shows a very simple EventListener. It basically just sends + all events to System.out (stdout) or System.err (stderr) depending on the event severity. +

+ +

+ You can see that for every event the method processEvent of the + EventListener will be called. Inside this method you can do whatever + processing you would like including throwing a RuntimeException, if you want + to abort the current processing run. +

+

+ The code above also shows how you can turn an event into a human-readable, localized + message that can be presented to a user. The EventFormatter class does + this for you. It provides additional methods if you'd like to explicitly specify + the locale. +

+

+ It is possible to gather all events for a whole processing run so they can be + evaluated afterwards. However, care should be taken about memory consumption since + the events provide references to objects inside FOP which may themselves have + references to other objects. So holding on to these objects may mean that whole + object trees cannot be released! +

+
+
+ Adding an EventListener +

+ To register the event listener with FOP, get the EventBroadcaster which + is associated with the user agent (FOUserAgent) and add it there: +

+ +

+ Please note that this is done separately for each processing run, i.e. for each + new user agent. +

+
+
+ An additional listener example +

+ Here's an additional example of an event listener: +

+

+ By default, FOP continues processing even if an image wasn't found. If you have + more strict requirements and want FOP to stop if an image is not available, you can + do something like the following: +

+ +

+ This throws a RuntimeException with the FileNotFoundException + as the cause. Further processing effectively stops in FOP. You can catch the exception + in your code and react as you see necessary. +

+
+
+
+ The producer side (for FOP developers) +

+ This section is primarily for FOP and FOP plug-in developers. It describes how to use + the event subsystem for producing events. +

+ + The event package has been designed in order to be theoretically useful for use cases + outside FOP. If you think this is interesting independently from FOP, please talk to + us. + +
+ Producing and sending an event +

+ The basics are very simple. Just instantiate an Event object and fill + it with the necessary parameters. Then pass it to the EventBroadcaster + which distributes the events to the interested listeneners. Here's a code example: +

+ +

+ The Event.paramsBuilder() is a + fluent interface + to help with the build-up of the parameters. You could just as well instantiate a + Map (Map<String, Object>) and fill it with values. +

+
+
+ The EventProducer interface +

+ To simplify event production, the event subsystem provides the EventProducer + interface. You can create interfaces which extend EventProducer. These + interfaces will contain one method per event to be generated. By contract, each event + method must have as its first parameter a parameter named "source" (Type Object) which + indicates the object that generated the event. After that come an arbitrary number of + parameters of any type as needed by the event. +

+

+ The event producer interface does not need to have any implementation. The implementation + is produced at runtime by a dynamic proxy created by DefaultEventBroadcaster. + The dynamic proxy creates Event instances for each method call against + the event producer interface. Each parameter (except "source") is added to the event's + parameter map. +

+

+ To simplify the code needed to get an instance of the event producer interface it is + suggested to create a public inner provider class inside the interface. +

+

+ Here's an example of such an event producer interface: +

+ +

+ To produce the same event as in the first example above, you'd use the following code: +

+ +
+
+ The event model +

+ Inside an invocation handler for a dynamic proxy, there's no information about + the names of each parameter. The JVM doesn't provide it. The only thing you know is + the interface and method name. In order to properly fill the Event's + parameter map we need to know the parameter names. These are retrieved from an + event object model. This is found in the org.apache.fop.events.model + package. The data for the object model is retrieved from an XML representation of the + event model that is loaded as a resource. The XML representation is generated using an + Ant task at build time (ant resourcegen). The Ant task (found in + src/codegen/java/org/apache/fop/tools/EventProducerCollectorTask.java) + scans FOP's sources for descendants of the EventProducer interface and + uses QDox to parse these interfaces. +

+

+ Primarily, the QDox-based collector task records the parameters' names and types. + Furthermore, it extracts additional attributes embedded as Javadoc comments from + the methods. At the moment, the only such attribute is "@event.severity" which indicates + the default event severity (which can be changed by event listeners). The example event + producer above shows the Javadocs for an event method. +

+

+ There's one more information that is extracted from the event producer information for + the event model: an optional primary exception. The first exception in the "throws" + declaration of an event method is noted. It is used to throw an exception from + the invocation handler if the event has an event severity of "FATAL" when all + listeners have been called (listeners can update the event severity). Please note + that an implementation of + org.apache.fop.events.EventExceptionManager$ExceptionFactory has to be + registered for the EventExceptionManager to be able to construct the + exception from an event. +

+

+ For a given application, there can be multiple event models active at the same time. + In FOP, each renderer is considered to be a plug-in and provides its own specific + event model. The individual event models are provided through an + EventModelFactory. This interface is implemented for each event model + and registered through the service provider mechanism + (see the plug-ins section for details). +

+
+
+ Event severity +

+ Four different levels of severity for events has been defined: +

+
    +
  1. INFO: informational only
  2. +
  3. WARN: a Warning
  4. +
  5. ERROR: an error condition from which FOP can recover. FOP will continue processing.
  6. +
  7. FATAL: a fatal error which causes an exception in the end and FOP will stop processing.
  8. +
+

+ Event listeners can choose to ignore certain events based on their event severity. + Please note that you may recieve an event "twice" in a specific case: if there is + a fatal error an event is generated and sent to the listeners. After that an exception + is thrown with the same information and processing stops. If the fatal event is + shown to the user and the following exception is equally presented to the user it + may appear that the event is duplicated. Of course, the same information is just + published through two different channels. +

+
+
+ Plug-ins to the event subsystem +

+ The event subsystem is extensible. There are a number of extension points: +

+
    +
  • + org.apache.fop.events.model.EventModelFactory: Provides + an event model to the event subsystem. +
  • +
  • + org.apache.fop.events.EventExceptionManager$ExceptionFactory: + Creates exceptions for events, i.e. turns an event into a specific exception. +
  • +
+

+ The names in bold above are used as filenames for the service provider files that + are placed in the META-INF/services directory. That way, they are + automatically detected. This is a mechanism defined by the + JAR file specification. +

+
+
+ Localization (L10n) +

+ One goal of the event subsystem was to have localized (translated) event messages. + The EventFormatter class can be used to convert an event to a + human-readable message. Each EventProducer can provide its own XML-based + translation file. If there is none, a central translation file is used, called + "EventFormatter.xml" (found in the same directory as the EventFormatter + class). +

+

+ The XML format used by the EventFormatter is the same as + Apache Cocoon's catalog format. Here's an example: +

+ + + + [ (See position {loc})| (See {#gatherContextInfo})| (No context info available)] + + + RTF output requires that all table-columns for a table are defined. Output will be incorrect.{{locator}} + + + Ignored deferred event for {node} ({start,if,start,end}).{{locator}} + + +]]> +

+ The example (extracted from the RTF handler's event producer) has message templates for + two event methods. The class used to do variable replacement in the templates is + org.apache.fop.util.text.AdvancedMessageFormat which is more powerful + than the MessageFormat classes provided by the Java class library + (java.util.text package). +

+

+ "locator" is a template that is reused by the other message templates + by referencing it through "{{locator}}". This is some kind of include command. +

+

+ Normal event parameters are accessed by name inside single curly braces, for example: + "{node}". For objects, this format just uses the toString() method to turn + the object into a string, unless there is an ObjectFormatter registered + for that type (there's an example for org.xml.sax.Locator). +

+

+ The single curly braces pattern supports additional features. For example, it is possible + to do this: "{start,if,start,end}". "if" here is a special field modifier that evaluates + "start" as a boolean and if that is true returns the text right after the second comma + ("start"). Otherwise it returns the text after the third comma ("end"). The "equals" + modifier is similar to "if" but it takes as an additional (comma-separated) parameter + right after the "equals" modifier, a string that is compared to the value of the variable. + An example: {severity,equals,EventSeverity:FATAL,,some text} (this adds "some text" if + the severity is not FATAL). +

+

+ Additional such modifiers can be added by implementing the + AdvancedMessageFormat$Part and AdvancedMessageFormat$PartFactory + interfaces. +

+

+ Square braces can be used to specify optional template sections. The whole section will + be omitted if any of the variables used within are unavailable. Pipe (|) characters can + be used to specify alternative sub-templates (see "locator" above for an example). +

+

+ Developers can also register a function (in the above example: + {#gatherContextInfo}) + to do more complex information rendering. These functions are implementations of the + AdvancedMessageFormat$Function interface. Please take care that this is + done in a locale-independent way as there is no locale information available, yet. +

+
+
+ + diff --git a/src/java/META-INF/services/org.apache.fop.events.EventExceptionManager$ExceptionFactory b/src/java/META-INF/services/org.apache.fop.events.EventExceptionManager$ExceptionFactory new file mode 100644 index 000000000..9fa7b8d04 --- /dev/null +++ b/src/java/META-INF/services/org.apache.fop.events.EventExceptionManager$ExceptionFactory @@ -0,0 +1,4 @@ +org.apache.fop.events.ValidationExceptionFactory +org.apache.fop.events.PropertyExceptionFactory +org.apache.fop.events.UnsupportedOperationExceptionFactory +org.apache.fop.layoutmgr.LayoutException$LayoutExceptionFactory diff --git a/src/java/META-INF/services/org.apache.fop.events.model.EventModelFactory b/src/java/META-INF/services/org.apache.fop.events.model.EventModelFactory new file mode 100644 index 000000000..8dc13dbfd --- /dev/null +++ b/src/java/META-INF/services/org.apache.fop.events.model.EventModelFactory @@ -0,0 +1,7 @@ +org.apache.fop.events.FOPEventModelFactory +org.apache.fop.render.afp.AFPEventProducer$EventModelFactory +org.apache.fop.render.bitmap.BitmapRendererEventProducer$EventModelFactory +org.apache.fop.render.pcl.PCLEventProducer$EventModelFactory +org.apache.fop.render.pdf.PDFEventProducer$EventModelFactory +org.apache.fop.render.ps.PSEventProducer$EventModelFactory +org.apache.fop.render.rtf.RTFEventProducer$EventModelFactory diff --git a/src/java/META-INF/services/org.apache.fop.util.text.AdvancedMessageFormat$Function b/src/java/META-INF/services/org.apache.fop.util.text.AdvancedMessageFormat$Function new file mode 100644 index 000000000..375130f2a --- /dev/null +++ b/src/java/META-INF/services/org.apache.fop.util.text.AdvancedMessageFormat$Function @@ -0,0 +1 @@ +org.apache.fop.fo.FONode$GatherContextInfoFunction diff --git a/src/java/META-INF/services/org.apache.fop.util.text.AdvancedMessageFormat$ObjectFormatter b/src/java/META-INF/services/org.apache.fop.util.text.AdvancedMessageFormat$ObjectFormatter new file mode 100644 index 000000000..9e3860b31 --- /dev/null +++ b/src/java/META-INF/services/org.apache.fop.util.text.AdvancedMessageFormat$ObjectFormatter @@ -0,0 +1 @@ +org.apache.fop.util.text.LocatorFormatter diff --git a/src/java/META-INF/services/org.apache.fop.util.text.AdvancedMessageFormat$PartFactory b/src/java/META-INF/services/org.apache.fop.util.text.AdvancedMessageFormat$PartFactory new file mode 100644 index 000000000..1647fb8d5 --- /dev/null +++ b/src/java/META-INF/services/org.apache.fop.util.text.AdvancedMessageFormat$PartFactory @@ -0,0 +1,6 @@ +org.apache.fop.util.text.IfFieldPart$Factory +org.apache.fop.util.text.EqualsFieldPart$Factory +org.apache.fop.util.text.ChoiceFieldPart$Factory +org.apache.fop.util.text.HexFieldPart$Factory +org.apache.fop.util.text.GlyphNameFieldPart$Factory +org.apache.fop.events.EventFormatter$LookupFieldPartFactory diff --git a/src/java/org/apache/fop/apps/FOPException.java b/src/java/org/apache/fop/apps/FOPException.java index 3e1c180e9..851712b09 100644 --- a/src/java/org/apache/fop/apps/FOPException.java +++ b/src/java/org/apache/fop/apps/FOPException.java @@ -32,6 +32,8 @@ public class FOPException extends SAXException { private String systemId; private int line; private int column; + + private String localizedMessage; /** * Constructs a new FOP exception with the specified detail message. @@ -210,5 +212,24 @@ public class FOPException extends SAXException { } } } + + /** + * Sets the localized message for this exception. + * @param msg the localized message + */ + public void setLocalizedMessage(String msg) { + this.localizedMessage = msg; + } + + /** {@inheritDoc} */ + public String getLocalizedMessage() { + if (this.localizedMessage != null) { + return this.localizedMessage; + } else { + return super.getLocalizedMessage(); + } + } + + } diff --git a/src/java/org/apache/fop/apps/FOUserAgent.java b/src/java/org/apache/fop/apps/FOUserAgent.java index a7405b466..307087f74 100644 --- a/src/java/org/apache/fop/apps/FOUserAgent.java +++ b/src/java/org/apache/fop/apps/FOUserAgent.java @@ -36,6 +36,12 @@ import org.apache.xmlgraphics.image.loader.ImageSessionContext; import org.apache.xmlgraphics.image.loader.impl.AbstractImageSessionContext; import org.apache.fop.Version; +import org.apache.fop.events.DefaultEventBroadcaster; +import org.apache.fop.events.Event; +import org.apache.fop.events.EventBroadcaster; +import org.apache.fop.events.EventProducer; +import org.apache.fop.events.FOPEventListenerProxy; +import org.apache.fop.events.LoggingEventListener; import org.apache.fop.fo.FOEventHandler; import org.apache.fop.render.Renderer; import org.apache.fop.render.RendererFactory; @@ -89,6 +95,7 @@ public class FOUserAgent { private Renderer rendererOverride = null; private FOEventHandler foEventHandlerOverride = null; private boolean locatorEnabled = true; // true by default (for error messages). + private EventBroadcaster eventBroadcaster = new FOPEventBroadcaster(); /** Producer: Metadata element for the system/software that produces * the document. (Some renderers can store this in the document.) @@ -530,5 +537,43 @@ public class FOUserAgent { return locatorEnabled; } + /** + * Returns the event broadcaster that control events sent inside a processing run. Clients + * can register event listeners with the event broadcaster to listen for events that occur + * while a document is being processed. + * @return the event broadcaster. + */ + public EventBroadcaster getEventBroadcaster() { + return this.eventBroadcaster; + } + + private class FOPEventBroadcaster extends DefaultEventBroadcaster { + + private FOPEventListenerProxy rootListener; + + public FOPEventBroadcaster() { + this.rootListener = new FOPEventListenerProxy( + this.listeners, FOUserAgent.this); + } + + /** {@inheritDoc} */ + public void broadcastEvent(Event event) { + rootListener.processEvent(event); + } + + /** {@inheritDoc} */ + protected EventProducer createProxyFor(Class clazz) { + if (!this.listeners.hasEventListeners()) { + //Backwards-compatibility: Make sure at least the LoggingEventListener is plugged + //in so no events are just silently swallowed. + addEventListener( + new LoggingEventListener(LogFactory.getLog(FOUserAgent.class))); + + } + return super.createProxyFor(clazz); + } + + } + } diff --git a/src/java/org/apache/fop/area/AreaEventProducer.java b/src/java/org/apache/fop/area/AreaEventProducer.java new file mode 100644 index 000000000..7747d2d79 --- /dev/null +++ b/src/java/org/apache/fop/area/AreaEventProducer.java @@ -0,0 +1,91 @@ +/* + * 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.area; + +import org.apache.fop.events.EventBroadcaster; +import org.apache.fop.events.EventProducer; + +/** + * Event producer interface for events related to the area tree. + */ +public interface AreaEventProducer extends EventProducer { + + /** + * Provider class for the event producer. + */ + class Provider { + + /** + * Returns an event producer. + * @param broadcaster the event broadcaster to use + * @return the event producer + */ + public static AreaEventProducer get(EventBroadcaster broadcaster) { + return (AreaEventProducer)broadcaster.getEventProducerFor( + AreaEventProducer.class); + } + } + + /** + * An unresolved ID reference was encountered. + * @param source the event source + * @param type the type of reference + * @param id the unresolved ID + * @event.severity WARN + */ + void unresolvedIDReference(Object source, String type, String id); + + /** + * An unresolved ID reference was encountered on a page. + * @param source the event source + * @param page the page the ID reference was found on + * @param id the unresolved ID + * @event.severity WARN + */ + void unresolvedIDReferenceOnPage(Object source, String page, String id); + + /** + * A page could not be loaded/deserialized from a file. + * @param source the event source + * @param page the page to be loaded + * @param e the original exception + * @event.severity ERROR + */ + void pageLoadError(Object source, String page, Exception e); + + /** + * A page could not be saved/serialized to a file. + * @param source the event source + * @param page the page to be serialized + * @param e the original exception + * @event.severity ERROR + */ + void pageSaveError(Object source, String page, Exception e); + + /** + * A page could not be rendered. + * @param source the event source + * @param page the page to be serialized + * @param e the original exception + * @event.severity ERROR + */ + void pageRenderingError(Object source, String page, Exception e); + +} diff --git a/src/java/org/apache/fop/area/AreaTreeHandler.java b/src/java/org/apache/fop/area/AreaTreeHandler.java index 7454f4667..d3ea41554 100644 --- a/src/java/org/apache/fop/area/AreaTreeHandler.java +++ b/src/java/org/apache/fop/area/AreaTreeHandler.java @@ -325,8 +325,9 @@ public class AreaTreeHandler extends FOEventHandler { if (pageVPList != null) { res.resolveIDRef(ids[count], pageVPList); } else { - log.warn(odi.getName() + ": Unresolved id reference \"" - + ids[count] + "\" found."); + AreaEventProducer eventProducer = AreaEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.unresolvedIDReference(this, odi.getName(), ids[count]); idTracker.addUnresolvedIDRef(ids[count], res); } } diff --git a/src/java/org/apache/fop/area/AreaTreeObject.java b/src/java/org/apache/fop/area/AreaTreeObject.java index 94250deba..1c2269a01 100644 --- a/src/java/org/apache/fop/area/AreaTreeObject.java +++ b/src/java/org/apache/fop/area/AreaTreeObject.java @@ -23,7 +23,7 @@ import java.util.Collections; import java.util.Iterator; import java.util.Map; -import org.apache.fop.util.QName; +import org.apache.xmlgraphics.util.QName; /** * Abstract base class for all area tree objects. @@ -53,12 +53,12 @@ public abstract class AreaTreeObject { if (atts.size() == 0) { return; } - Iterator iter = atts.keySet().iterator(); + Iterator iter = atts.entrySet().iterator(); while (iter.hasNext()) { - QName qName = (QName)iter.next(); - String value = (String)atts.get(qName); + Map.Entry entry = (Map.Entry)iter.next(); + String value = (String)entry.getValue(); //The casting is only to ensure type safety (too bad we can't use generics, yet) - setForeignAttribute(qName, value); + setForeignAttribute((QName)entry.getKey(), value); } } diff --git a/src/java/org/apache/fop/area/AreaTreeParser.java b/src/java/org/apache/fop/area/AreaTreeParser.java index fafb99ed6..19edd3d5e 100644 --- a/src/java/org/apache/fop/area/AreaTreeParser.java +++ b/src/java/org/apache/fop/area/AreaTreeParser.java @@ -50,6 +50,7 @@ import org.apache.commons.logging.LogFactory; import org.apache.xmlgraphics.image.loader.ImageInfo; import org.apache.xmlgraphics.image.loader.ImageManager; import org.apache.xmlgraphics.image.loader.ImageSessionContext; +import org.apache.xmlgraphics.util.QName; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.area.Trait.Background; @@ -78,7 +79,6 @@ import org.apache.fop.util.ColorUtil; import org.apache.fop.util.ContentHandlerFactory; import org.apache.fop.util.ContentHandlerFactoryRegistry; import org.apache.fop.util.DefaultErrorListener; -import org.apache.fop.util.QName; /** * This is a parser for the area tree XML (intermediate format) which is used to reread an area diff --git a/src/java/org/apache/fop/area/CachedRenderPagesModel.java b/src/java/org/apache/fop/area/CachedRenderPagesModel.java index 363fa02d1..b34a7e8d1 100644 --- a/src/java/org/apache/fop/area/CachedRenderPagesModel.java +++ b/src/java/org/apache/fop/area/CachedRenderPagesModel.java @@ -19,24 +19,27 @@ package org.apache.fop.area; -import org.apache.commons.io.IOUtils; -import org.apache.fop.apps.FOPException; -import org.apache.fop.apps.FOUserAgent; -import org.apache.fop.fonts.FontInfo; -import org.xml.sax.SAXException; - -import java.util.Map; -import java.util.HashMap; -import java.util.Iterator; - +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; import java.io.File; -import java.io.FileOutputStream; import java.io.FileInputStream; -import java.io.ObjectOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.io.OutputStream; -import java.io.BufferedOutputStream; -import java.io.BufferedInputStream; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.xml.sax.SAXException; + +import org.apache.commons.io.IOUtils; + +import org.apache.fop.apps.FOPException; +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.events.ResourceEventProducer; +import org.apache.fop.fonts.FontInfo; /** * A simple cached render pages model. @@ -69,46 +72,39 @@ public class CachedRenderPagesModel extends RenderPagesModel { */ protected boolean checkPreparedPages(PageViewport newpage, boolean renderUnresolved) { for (Iterator iter = prepared.iterator(); iter.hasNext();) { - PageViewport p = (PageViewport)iter.next(); - if (p.isResolved() || renderUnresolved) { - if (p != newpage) { + PageViewport pageViewport = (PageViewport)iter.next(); + if (pageViewport.isResolved() || renderUnresolved) { + if (pageViewport != newpage) { try { // load page from cache - String name = (String)pageMap.get(p); + String name = (String)pageMap.get(pageViewport); File tempFile = new File(baseDir, name); log.debug("Loading page from: " + tempFile); ObjectInputStream in = new ObjectInputStream( new BufferedInputStream( new FileInputStream(tempFile))); try { - p.loadPage(in); + pageViewport.loadPage(in); } finally { IOUtils.closeQuietly(in); } if (!tempFile.delete()) { - log.warn("Temporary file could not be deleted: " + tempFile); + ResourceEventProducer eventProducer + = ResourceEventProducer.Provider.get( + renderer.getUserAgent().getEventBroadcaster()); + eventProducer.cannotDeleteTempFile(this, tempFile); } - pageMap.remove(p); + pageMap.remove(pageViewport); } catch (Exception e) { - log.error(e); + AreaEventProducer eventProducer + = AreaEventProducer.Provider.get( + renderer.getUserAgent().getEventBroadcaster()); + eventProducer.pageLoadError(this, pageViewport.getPageNumberString(), e); } } - try { - renderer.renderPage(p); - if (!p.isResolved()) { - String[] idrefs = p.getIDRefs(); - for (int count = 0; count < idrefs.length; count++) { - log.warn("Page " + p.getPageNumberString() - + ": Unresolved id reference \"" + idrefs[count] - + "\" found."); - } - } - } catch (Exception e) { - // use error handler to handle this FOP or IO Exception - log.error(e); - } - p.clear(); + renderPage(pageViewport); + pageViewport.clear(); iter.remove(); } else { if (!renderer.supportsOutOfOrder()) { @@ -147,8 +143,11 @@ public class CachedRenderPagesModel extends RenderPagesModel { if (log.isDebugEnabled()) { log.debug("Page saved to temporary file: " + tempFile); } - } catch (Exception e) { - log.error(e); + } catch (IOException ioe) { + AreaEventProducer eventProducer + = AreaEventProducer.Provider.get( + renderer.getUserAgent().getEventBroadcaster()); + eventProducer.pageSaveError(this, page.getPageNumberString(), ioe); } } diff --git a/src/java/org/apache/fop/area/PageViewport.java b/src/java/org/apache/fop/area/PageViewport.java index af557ade9..da7ef1def 100644 --- a/src/java/org/apache/fop/area/PageViewport.java +++ b/src/java/org/apache/fop/area/PageViewport.java @@ -21,14 +21,15 @@ package org.apache.fop.area; import java.awt.Rectangle; import java.awt.geom.Rectangle2D; -import java.io.ObjectOutputStream; +import java.io.IOException; import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.Collections; -import java.util.List; -import java.util.Map; import java.util.HashMap; import java.util.Iterator; +import java.util.List; +import java.util.Map; import java.util.Set; import org.apache.commons.logging.Log; @@ -516,9 +517,9 @@ public class PageViewport extends AreaTreeObject implements Resolvable, Cloneabl * The map of unresolved references are set on the page so that * the resolvers can be properly serialized and reloaded. * @param out the object output stream to write the contents - * @throws Exception if there is a problem saving the page + * @throws IOException in case of an I/O error while serializing the page */ - public void savePage(ObjectOutputStream out) throws Exception { + public void savePage(ObjectOutputStream out) throws IOException { // set the unresolved references so they are serialized page.setUnresolvedReferences(unresolvedIDRefs); out.writeObject(page); @@ -531,9 +532,10 @@ public class PageViewport extends AreaTreeObject implements Resolvable, Cloneabl * if there are any unresolved references that were resolved * while saved they will be resolved on the page contents. * @param in the object input stream to read the page from - * @throws Exception if there is an error loading the page + * @throws ClassNotFoundException if a class was not found while loading the page + * @throws IOException if an I/O error occurred while loading the page */ - public void loadPage(ObjectInputStream in) throws Exception { + public void loadPage(ObjectInputStream in) throws IOException, ClassNotFoundException { page = (Page) in.readObject(); unresolvedIDRefs = page.getUnresolvedReferences(); if (unresolvedIDRefs != null && pendingResolved != null) { diff --git a/src/java/org/apache/fop/area/RenderPagesModel.java b/src/java/org/apache/fop/area/RenderPagesModel.java index e080e9cbe..b21566902 100644 --- a/src/java/org/apache/fop/area/RenderPagesModel.java +++ b/src/java/org/apache/fop/area/RenderPagesModel.java @@ -156,23 +156,7 @@ public class RenderPagesModel extends AreaTreeModel { && pageViewport.getPageSequence().isFirstPage(pageViewport)) { renderer.startPageSequence(getCurrentPageSequence()); } - try { - renderer.renderPage(pageViewport); - if (!pageViewport.isResolved()) { - String[] idrefs = pageViewport.getIDRefs(); - for (int count = 0; count < idrefs.length; count++) { - log.warn("Page " + pageViewport.getPageNumberString() - + ": Unresolved id reference \"" + idrefs[count] - + "\" found."); - } - } - } catch (Exception e) { - // use error handler to handle this FOP or IO Exception - log.error("Error while rendering page " + pageViewport.getPageIndex(), e); - if (e instanceof RuntimeException) { - throw (RuntimeException)e; - } - } + renderPage(pageViewport); pageViewport.clear(); iter.remove(); } else { @@ -185,6 +169,33 @@ public class RenderPagesModel extends AreaTreeModel { return renderer.supportsOutOfOrder() || prepared.isEmpty(); } + /** + * Renders the given page and notified about unresolved IDs if any. + * @param pageViewport the page to be rendered. + */ + protected void renderPage(PageViewport pageViewport) { + try { + renderer.renderPage(pageViewport); + if (!pageViewport.isResolved()) { + String[] idrefs = pageViewport.getIDRefs(); + for (int count = 0; count < idrefs.length; count++) { + AreaEventProducer eventProducer = AreaEventProducer.Provider.get( + renderer.getUserAgent().getEventBroadcaster()); + eventProducer.unresolvedIDReferenceOnPage(this, + pageViewport.getPageNumberString(), idrefs[count]); + } + } + } catch (Exception e) { + AreaEventProducer eventProducer = AreaEventProducer.Provider.get( + renderer.getUserAgent().getEventBroadcaster()); + eventProducer.pageRenderingError(this, + pageViewport.getPageNumberString(), e); + if (e instanceof RuntimeException) { + throw (RuntimeException)e; + } + } + } + /** * Prepare a page. * An unresolved page can be prepared if the renderer supports diff --git a/src/java/org/apache/fop/events/CompositeEventListener.java b/src/java/org/apache/fop/events/CompositeEventListener.java new file mode 100644 index 000000000..a65728b71 --- /dev/null +++ b/src/java/org/apache/fop/events/CompositeEventListener.java @@ -0,0 +1,69 @@ +/* + * 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.events; + +import java.util.List; + +/** + * EventListener implementation forwards events to possibly multiple other EventListeners. + */ +public class CompositeEventListener implements EventListener { + + private List listeners = new java.util.ArrayList(); + + /** + * Adds an event listener to the broadcaster. It is appended to the list of previously + * registered listeners (the order of registration defines the calling order). + * @param listener the listener to be added + */ + public synchronized void addEventListener(EventListener listener) { + this.listeners.add(listener); + } + + /** + * Removes an event listener from the broadcaster. If the event listener is not registered, + * nothing happens. + * @param listener the listener to be removed + */ + public synchronized void removeEventListener(EventListener listener) { + this.listeners.remove(listener); + } + + private synchronized int getListenerCount() { + return this.listeners.size(); + } + + /** + * Indicates whether any listeners have been registered with the broadcaster. + * @return true if listeners are present, false otherwise + */ + public boolean hasEventListeners() { + return (getListenerCount() > 0); + } + + /** {@inheritDoc} */ + public synchronized void processEvent(Event event) { + for (int i = 0, c = getListenerCount(); i < c; i++) { + EventListener listener = (EventListener)this.listeners.get(i); + listener.processEvent(event); + } + } + +} diff --git a/src/java/org/apache/fop/events/DefaultEventBroadcaster.java b/src/java/org/apache/fop/events/DefaultEventBroadcaster.java new file mode 100644 index 000000000..bb1752a72 --- /dev/null +++ b/src/java/org/apache/fop/events/DefaultEventBroadcaster.java @@ -0,0 +1,160 @@ +/* + * 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.events; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.xmlgraphics.util.Service; + +import org.apache.fop.events.model.EventMethodModel; +import org.apache.fop.events.model.EventModel; +import org.apache.fop.events.model.EventModelFactory; +import org.apache.fop.events.model.EventProducerModel; +import org.apache.fop.events.model.EventSeverity; + +/** + * Default implementation of the EventBroadcaster interface. It holds a list of event listeners + * and can provide {@link EventProducer} instances for type-safe event production. + */ +public class DefaultEventBroadcaster implements EventBroadcaster { + + /** Holds all registered event listeners */ + protected CompositeEventListener listeners = new CompositeEventListener(); + + /** {@inheritDoc} */ + public void addEventListener(EventListener listener) { + this.listeners.addEventListener(listener); + } + + /** {@inheritDoc} */ + public void removeEventListener(EventListener listener) { + this.listeners.removeEventListener(listener); + } + + /** {@inheritDoc} */ + public boolean hasEventListeners() { + return this.listeners.hasEventListeners(); + } + + /** {@inheritDoc} */ + public void broadcastEvent(Event event) { + this.listeners.processEvent(event); + } + + private static List/**/ eventModels = new java.util.ArrayList(); + private Map proxies = new java.util.HashMap(); + + static { + Iterator iter = Service.providers(EventModelFactory.class, true); + while (iter.hasNext()) { + EventModelFactory factory = (EventModelFactory)iter.next(); + addEventModel(factory.createEventModel()); + } + } + + /** + * Adds a new {@link EventModel} to the list of registered event models. + * @param eventModel the event model instance + */ + public static void addEventModel(EventModel eventModel) { + eventModels.add(eventModel); + } + + /** {@inheritDoc} */ + public EventProducer getEventProducerFor(Class clazz) { + if (!EventProducer.class.isAssignableFrom(clazz)) { + throw new IllegalArgumentException( + "Class must be an implementation of the EventProducer interface: " + + clazz.getName()); + } + EventProducer producer; + producer = (EventProducer)this.proxies.get(clazz); + if (producer == null) { + producer = createProxyFor(clazz); + this.proxies.put(clazz, producer); + } + return producer; + } + + private EventProducerModel getEventProducerModel(Class clazz) { + for (int i = 0, c = eventModels.size(); i < c; i++) { + EventModel eventModel = (EventModel)eventModels.get(i); + EventProducerModel producerModel = eventModel.getProducer(clazz); + if (producerModel != null) { + return producerModel; + } + } + return null; + } + + /** + * Creates a dynamic proxy for the given EventProducer interface that will handle the + * conversion of the method call into the broadcasting of an event instance. + * @param clazz a descendant interface of EventProducer + * @return the EventProducer instance + */ + protected EventProducer createProxyFor(Class clazz) { + final EventProducerModel producerModel = getEventProducerModel(clazz); + if (producerModel == null) { + throw new IllegalStateException("Event model doesn't contain the definition for " + + clazz.getName()); + } + return (EventProducer)Proxy.newProxyInstance(clazz.getClassLoader(), + new Class[] {clazz}, + new InvocationHandler() { + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable { + String methodName = method.getName(); + EventMethodModel methodModel = producerModel.getMethod(methodName); + String eventID = producerModel.getInterfaceName() + "." + methodName; + if (methodModel == null) { + throw new IllegalStateException( + "Event model isn't consistent" + + " with the EventProducer interface. Please rebuild FOP!" + + " Affected method: " + + eventID); + } + Map params = new java.util.HashMap(); + int i = 1; + Iterator iter = methodModel.getParameters().iterator(); + while (iter.hasNext()) { + EventMethodModel.Parameter param + = (EventMethodModel.Parameter)iter.next(); + params.put(param.getName(), args[i]); + i++; + } + Event ev = new Event(args[0], eventID, methodModel.getSeverity(), params); + broadcastEvent(ev); + + if (ev.getSeverity() == EventSeverity.FATAL) { + EventExceptionManager.throwException(ev, + methodModel.getExceptionClass()); + } + return null; + } + }); + } + +} diff --git a/src/java/org/apache/fop/events/Event.java b/src/java/org/apache/fop/events/Event.java new file mode 100644 index 000000000..d3da1809e --- /dev/null +++ b/src/java/org/apache/fop/events/Event.java @@ -0,0 +1,150 @@ +/* + * 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.events; + +import java.util.Collections; +import java.util.EventObject; +import java.util.Map; + +import org.apache.fop.events.model.EventSeverity; + +/** + * This is the default event class used by this package. Each event has a unique event identifier + * (a String), a severity indicator and a map of name/value pairs. + */ +public class Event extends EventObject { + + private static final long serialVersionUID = -1310594422868258083L; + + private String eventID; + private EventSeverity severity; + private Map params; + + /** + * Creates a new Event. + * @param source the object that creates the event + * @param eventID the unique identifier of the event + * @param severity the severity level + * @param params the event parameters (a map of name/value pairs) + */ + public Event(Object source, String eventID, EventSeverity severity, Map params) { + super(source); + this.eventID = eventID; + setSeverity(severity); + this.params = params; + } + + /** + * Returns the event identifier. + * @return the event identifier + */ + public String getEventID() { + return this.eventID; + } + + /** + * Returns the event group identifier. + * @return the event group identifier (or null if there is no group identifier) + */ + public String getEventGroupID() { + int pos = this.eventID.lastIndexOf('.'); + if (pos > 0) { + return this.eventID.substring(0, pos); + } else { + return null; + } + } + + /** + * Returns the severity level. + * @return the severity level + */ + public EventSeverity getSeverity() { + return this.severity; + } + + /** + * Sets the event's severity level. This method can be used to increase or decrease the + * severity level in a listener. + * @param severity the new event severity + */ + public void setSeverity(EventSeverity severity) { + this.severity = severity; + } + + /** + * Returns a parameter. + * @param key the key to the parameter + * @return the parameter value or null if no value with this key is found + */ + public Object getParam(String key) { + if (this.params != null) { + return this.params.get(key); + } else { + return null; + } + } + + /** + * Returns an unmodifiable {@link java.util.Map} with all event parameters. + * @return the parameter map + */ + public Map getParams() { + return Collections.unmodifiableMap(this.params); + } + + /** + * Creates and returns a fluent builder object for building up the parameter map. + * @return the parameter builder + */ + public static ParamsBuilder paramsBuilder() { + return new ParamsBuilder(); + } + + /** + * This class is a fluent builder class for building up the parameter map. + */ + public static class ParamsBuilder { + private Map params; + + /** + * Adds a new parameter (a name/value pair). + * @param name the name of the parameter + * @param value the value of the parameter + * @return this instance + */ + public ParamsBuilder param(String name, Object value) { + if (this.params == null) { + this.params = new java.util.HashMap(); + } + this.params.put(name, value); + return this; + } + + /** + * Returns the accumulated parameter map. + * @return the accumulated parameter map + */ + public Map build() { + return this.params; + } + } + +} diff --git a/src/java/org/apache/fop/events/EventBroadcaster.java b/src/java/org/apache/fop/events/EventBroadcaster.java new file mode 100644 index 000000000..6c8df7375 --- /dev/null +++ b/src/java/org/apache/fop/events/EventBroadcaster.java @@ -0,0 +1,61 @@ +/* + * 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.events; + +/** + * The EventBroadcaster is the central relay point for events. It receives events from various + * parts of the application and forwards them to any registered EventListener. + */ +public interface EventBroadcaster { + + /** + * Adds an event listener to the broadcaster. It is appended to the list of previously + * registered listeners (the order of registration defines the calling order). + * @param listener the listener to be added + */ + void addEventListener(EventListener listener); + + /** + * Removes an event listener from the broadcaster. If the event listener is not registered, + * nothing happens. + * @param listener the listener to be removed + */ + void removeEventListener(EventListener listener); + + /** + * Indicates whether any listeners have been registered with the broadcaster. + * @return true if listeners are present, false otherwise + */ + boolean hasEventListeners(); + + /** + * Broadcasts an event. This method is usually called from within the observed component. + * @param event the event to be broadcast + */ + void broadcastEvent(Event event); + + /** + * Returns an event producer instance for the given interface class. + * @param clazz the Class object identifying an {@link EventProducer} interface + * @return the event producer instance + */ + EventProducer getEventProducerFor(Class clazz); + +} diff --git a/src/java/org/apache/fop/events/EventExceptionManager.java b/src/java/org/apache/fop/events/EventExceptionManager.java new file mode 100644 index 000000000..093ae7010 --- /dev/null +++ b/src/java/org/apache/fop/events/EventExceptionManager.java @@ -0,0 +1,84 @@ +/* + * 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.events; + +import java.util.Iterator; +import java.util.Map; + +import org.apache.xmlgraphics.util.Service; + +/** + * This class is reponsible for converting events into exceptions. + */ +public class EventExceptionManager { + + private static final Map EXCEPTION_FACTORIES = new java.util.HashMap(); + + static { + Iterator iter; + iter = Service.providers(ExceptionFactory.class, true); + while (iter.hasNext()) { + ExceptionFactory factory = (ExceptionFactory)iter.next(); + EXCEPTION_FACTORIES.put(factory.getExceptionClass().getName(), factory); + } + } + + /** + * Converts an event into an exception and throws that. If the exception class is null, + * a {@link RuntimeException} will be thrown. + * @param event the event to be converted + * @param exceptionClass the exception class to be thrown + * @throws Throwable this happens always + */ + public static void throwException(Event event, String exceptionClass) throws Throwable { + if (exceptionClass != null) { + ExceptionFactory factory = (ExceptionFactory)EXCEPTION_FACTORIES.get(exceptionClass); + if (factory != null) { + throw factory.createException(event); + } else { + throw new IllegalArgumentException( + "No such ExceptionFactory available: " + exceptionClass); + } + } else { + String msg = EventFormatter.format(event); + throw new RuntimeException(msg); + } + } + + /** + * This interface is implementation by exception factories that can create exceptions from + * events. + */ + public interface ExceptionFactory { + + /** + * Creates an exception from an event. + * @param event the event + * @return the newly created exception + */ + Throwable createException(Event event); + + /** + * Returns the {@link Exception} class created by this factory. + * @return the exception class + */ + Class getExceptionClass(); + } +} diff --git a/src/java/org/apache/fop/events/EventFormatter.java b/src/java/org/apache/fop/events/EventFormatter.java new file mode 100644 index 000000000..56964039b --- /dev/null +++ b/src/java/org/apache/fop/events/EventFormatter.java @@ -0,0 +1,196 @@ +/* + * 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.events; + +import java.util.Locale; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.fop.util.XMLResourceBundle; +import org.apache.fop.util.text.AdvancedMessageFormat; +import org.apache.fop.util.text.AdvancedMessageFormat.Part; +import org.apache.fop.util.text.AdvancedMessageFormat.PartFactory; + +/** + * Converts events into human-readable, localized messages. + */ +public final class EventFormatter { + + private static final Pattern INCLUDES_PATTERN = Pattern.compile("\\{\\{.+\\}\\}"); + + private static ResourceBundle defaultBundle = XMLResourceBundle.getXMLBundle( + EventFormatter.class.getName(), EventFormatter.class.getClassLoader()); + + private static Log log = LogFactory.getLog(EventFormatter.class); + + private EventFormatter() { + //utility class + } + + /** + * Formats an event using the default locale. + * @param event the event + * @return the formatted message + */ + public static String format(Event event) { + ResourceBundle bundle = null; + String groupID = event.getEventGroupID(); + if (groupID != null) { + try { + bundle = XMLResourceBundle.getXMLBundle( + groupID, + EventFormatter.class.getClassLoader()); + } catch (MissingResourceException mre) { + if (log.isTraceEnabled()) { + log.trace("No XMLResourceBundle for " + groupID + " available."); + } + } + } + if (bundle == null) { + bundle = defaultBundle; + } + return format(event, bundle); + } + + /** + * Formats an event using a given locale. + * @param event the event + * @param locale the locale + * @return the formatted message + */ + public static String format(Event event, Locale locale) { + ResourceBundle bundle = null; + String groupID = event.getEventGroupID(); + if (groupID != null) { + try { + bundle = XMLResourceBundle.getXMLBundle( + groupID, locale, + EventFormatter.class.getClassLoader()); + } catch (MissingResourceException mre) { + if (log.isTraceEnabled()) { + log.trace("No XMLResourceBundle for " + groupID + " available."); + } + } + } + if (bundle == null) { + bundle = XMLResourceBundle.getXMLBundle( + EventFormatter.class.getName(), + locale, + EventFormatter.class.getClassLoader()); + } + return format(event, bundle); + } + + private static String format(Event event, ResourceBundle bundle) { + String template = bundle.getString(event.getEventID()); + return format(event, processIncludes(template, bundle)); + } + + private static String processIncludes(String template, ResourceBundle bundle) { + CharSequence input = template; + int replacements; + StringBuffer sb; + do { + sb = new StringBuffer(Math.max(16, input.length())); + replacements = processIncludesInner(input, sb, bundle); + input = sb; + } while (replacements > 0); + String s = sb.toString(); + return s; + } + + private static int processIncludesInner(CharSequence template, StringBuffer sb, + ResourceBundle bundle) { + int replacements = 0; + Matcher m = INCLUDES_PATTERN.matcher(template); + while (m.find()) { + String include = m.group(); + include = include.substring(2, include.length() - 2); + m.appendReplacement(sb, bundle.getString(include)); + replacements++; + } + m.appendTail(sb); + return replacements; + } + + /** + * Formats the event using a given pattern. The pattern needs to be compatible with + * {@link AdvancedMessageFormat}. + * @param event the event + * @param pattern the pattern (compatible with {@link AdvancedMessageFormat}) + * @return the formatted message + */ + public static String format(Event event, String pattern) { + AdvancedMessageFormat format = new AdvancedMessageFormat(pattern); + Map params = new java.util.HashMap(event.getParams()); + params.put("source", event.getSource()); + params.put("severity", event.getSeverity()); + return format.format(params); + } + + private static class LookupFieldPart implements Part { + + private String fieldName; + + public LookupFieldPart(String fieldName) { + this.fieldName = fieldName; + } + + public boolean isGenerated(Map params) { + return getKey(params) != null; + } + + public void write(StringBuffer sb, Map params) { + sb.append(defaultBundle.getString(getKey(params))); + } + + private String getKey(Map params) { + return (String)params.get(fieldName); + } + + /** {@inheritDoc} */ + public String toString() { + return "{" + this.fieldName + ", lookup}"; + } + + } + + /** PartFactory for lookups. */ + public static class LookupFieldPartFactory implements PartFactory { + + /** {@inheritDoc} */ + public Part newPart(String fieldName, String values) { + return new LookupFieldPart(fieldName); + } + + /** {@inheritDoc} */ + public String getFormat() { + return "lookup"; + } + + } + +} diff --git a/src/java/org/apache/fop/events/EventFormatter.xml b/src/java/org/apache/fop/events/EventFormatter.xml new file mode 100644 index 000000000..f17da1161 --- /dev/null +++ b/src/java/org/apache/fop/events/EventFormatter.xml @@ -0,0 +1,101 @@ + + + + + [ (See position {loc})| (See {#gatherContextInfo})| (No context info available)] + An fo:marker is permitted only as the descendant of an fo:flow. + An fo:retrieve-marker is permitted only as the descendant of an fo:static-content. + An fo:bidi-override that is a descendant of an fo:leader or of the fo:inline child of an fo:footnote may not have block-level children, unless it has a nearer ancestor that is an fo:inline-container. + An fo:inline that is a descendant of an fo:leader or fo:footnote may not have block-level children, unless it has a nearer ancestor that is an fo:inline-container. + The element must be a child of fo:simple-page-master. + The element must be a child of fo:declarations. + The element must be a child of fo:declarations or fo:simple-page-master. + For "{elementName}", only one "{offendingNode}" may be declared.{{locator}} + For "{elementName}", "{tooLateNode}" must be declared before "{tooEarlyNode}"!{{locator}} + "{offendingNode}" is not a valid child element of "{elementName}"![ {ruleViolated,lookup}]{{locator}} + "{elementName}" is missing child elements.[ +Required content model: {contentModel}]{{locator}} + Element "{elementName}" is missing required property "{propertyName}"!{{locator}} + Property ID "{id}" (found on "{elementName}") previously used; ID values must be unique within a document!{severity,equals,EventSeverity:FATAL,, +Any reference to it will be considered a reference to the first occurrence in the document.}{{locator}} + fo:marker must be an initial child: {mcname}{{locator}} + fo:marker "marker-class-name" must be unique for same parent: {mcname}{{locator}} + Invalid property encountered on "{elementName}": {attr}{{locator}} + Invalid property value encountered in {propName}="{propValue}"[: {e}]{{locator}} + The following feature isn't implemented by Apache FOP, yet: {feature} (on {elementName}){{locator}} + Missing attribute on {elementName}: Either external-destination or internal-destination must be specified.{{locator}} + Unable to clone subtree of fo:marker (marker-class-name="{markerClassName}") for fo:retrieve-marker.{{locator}} + Duplicate color profile profile name: {name}{{locator}} + Region-name ("{regionName}") is being mapped to multiple region-classes ({defaultRegionClass1} and {defaultRegionClass2}).{{locator}} + The page master name ("{name}") must be unique across page-masters and page-sequence-masters.{{locator}} + Duplicate flow-name "{flowName}" found within {elementName}.{{locator}} + The flow-name "{flowName}" on {elementName} could not be mapped to a region-name in the layout-master-set.{{locator}} + The master-reference "{masterReference}" on {elementName} matches no simple-page-master or page-sequence-master.{{locator}} + The region-name "{regionName}" for {elementName} is not permitted.{{locator}} + Border and padding for {elementName} "{regionName}" must be '0' (See 6.4.13 in XSL 1.0).{{locator}} + If overflow property is set to "scroll" on {elementName}, a column-count other than "1" may not be specified.{{locator}} + First element must be the fo:root formatting object. Found {elementName} instead. Please make sure you're producing a valid XSL-FO document. + Document is empty (something might be wrong with your XSLT stylesheet). + Unknown formatting object "{offendingNode}" encountered (a child of {elementName}}.{{locator}} + Only a value of "auto" for block-progression-dimension has a well-specified behavior on fo:table. Falling back to "auto".{{locator}} + In collapsing border model a table does not have padding (see http://www.w3.org/TR/REC-CSS2/tables.html#collapsing-borders), but a non-zero value for padding was found. The padding will be ignored.{{locator}} + Either fo:table-rows or fo:table-cells may be children of an {elementName} but not both.{{locator}} + This table uses the collapsing border model. In order to resolve borders in an efficient way the table-footer must be known before any table-body is parsed. Either put the footer at the correct place or switch to the separate border model.{{locator}} + starts-row/ends-row for fo:table-cells non-applicable for children of an fo:table-row.{{locator}} + The column-number or number of cells in the row overflows the number of fo:table-columns specified for the table.{{locator}} + {propName} must be 1 or bigger, but got {actualValue}{{locator}} + table-layout=\"fixed\" and column-width unspecified => falling back to proportional-column-width(1){{locator}} + padding-* properties are not applicable to {elementName}, but a non-zero value for padding was found.{{locator}} + {elementName} overlaps in column {column}. + {breakBefore,if,break-before,break-after} ignored on {elementName} because of row spanning in progress (See XSL 1.1, {breakBefore,if,7.20.2,7.20.1}){{locator}} + Image not found.[ URI: {uri}.]{{locator}} + Image not available.[ URI: {uri}.] Reason:[ {reason}][ {e}]{{locator}} + I/O error while loading image.[ URI: {uri}.][ Reason: {ioe}]{{locator}} + The intrinsic dimensions of an instream-foreign-object could not be determined.{{locator}} + Error while handling URI: {uri}. Reason: {e}{{locator}} + Some XML content will be ignored. Could not render XML in namespace "{namespaceURI}".[ Reason: {e}] + Some XML content will be ignored. No handler defined for XML with namespace "{namespaceURI}". + Error while writing an image to the target file.[ Reason: {e}] + Temporary file could not be deleted: {tempFile} + fo:leader is set to "use-content" but has no content.{{locator}} + Line {line} of a paragraph overflows the available area by {overflowLength,choice,50000#{overflowLength} millipoints|50000<more than 50 points}.{{locator}} + The contents of table-row {row} are taller than they should be (there is a block-progression-dimension or height constraint on the indicated row). Due to its contents the row grows to {effCellBPD} millipoints, but the row shouldn't get any taller than {maxCellBPD} millipoints.{{locator}} + table-layout="fixed" and width="auto", but auto-layout not supported => assuming width="100%".{{locator}} + The extent in inline-progression-direction (width) of a {elementName} is bigger than the available space ({effIPD}mpt > {maxIPD}mpt).{{locator}} + Adjusting end-indent based on overconstrained geometry rules for {elementName}.{{locator}} + Content overflows the viewport of an {elementName} in block-progression direction by {amount} millipoints.{clip,if, Content will be clipped.}{{locator}} + Content overflows the viewport of the {elementName} on page {page} in block-progression direction by {amount} millipoints.{clip,if, Content will be clipped.}{{locator}} + Flow "{flowName}" does not map to the region-body in page-master "{masterName}". FOP presently does not support this.{{locator}} + Subsequences exhausted in page-sequence-master "{pageSequenceMasterName}", {canRecover,if,using previous subsequence,cannot recover}.{{locator}} + No subsequences in page-sequence-master "{pageSequenceMasterName}".{{locator}} + No simple-page-master matching "{pageMasterName}" in page-sequence-master "{pageSequenceMasterName}".{{locator}} + SVG error: {message} + SVG alert: {message} + SVG info: {message} + SVG graphic could not be built. Reason: {e} + SVG graphic could not be rendered. Reason: {e} + I/O error while writing to target file.[ Reason: {ioe}] + {type}: Unresolved ID reference "{id}" found. + Page {page}: Unresolved ID reference "{id}" found. + Error while deserializing page {page}.[ Reason: {e}] + Error while serializing page {page}.[ Reason: {e}] + Error while rendering page {page}.[ Reason: {e}] + Font "{requested}" not found. Substituting with "{effective}". + Unable to load font file: {fontURL}.[ Reason: {e}] + Glyph "{ch}" (0x{ch,hex}[, {ch,glyph-name}]) not available in font "{fontName}". + diff --git a/src/java/org/apache/fop/events/EventFormatter_de.xml b/src/java/org/apache/fop/events/EventFormatter_de.xml new file mode 100644 index 000000000..c65d24f73 --- /dev/null +++ b/src/java/org/apache/fop/events/EventFormatter_de.xml @@ -0,0 +1,23 @@ + + + + + [ (Siehe Position {loc})| (Siehe {#gatherContextInfo})| (Keine Kontextinformationen verfügbar)] + In "{elementName}" darf nur ein einziges "{offendingNode}" vorkommen!{{locator}} + Dem Element "{elementName}" fehlt ein verlangtes Property "{propertyName}"!{{locator}} + diff --git a/src/java/org/apache/fop/events/EventListener.java b/src/java/org/apache/fop/events/EventListener.java new file mode 100644 index 000000000..f8293aed9 --- /dev/null +++ b/src/java/org/apache/fop/events/EventListener.java @@ -0,0 +1,37 @@ +/* + * 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.events; + +/** + * This interface is implemented by clients who want to listen for events. + */ +public interface EventListener extends java.util.EventListener { + + /** + * This method is called for each event that is generated. With the event's ID it is possible + * to react to certain events. Events can also simply be recorded and presented to a user. + * It is possible to throw an (unchecked) exception if the processing needs to be aborted + * because some special event occured. This way the client can configure the behaviour of + * the observed application. + * @param event the event + */ + void processEvent(Event event); + +} diff --git a/src/java/org/apache/fop/events/EventProducer.java b/src/java/org/apache/fop/events/EventProducer.java new file mode 100644 index 000000000..88da771a4 --- /dev/null +++ b/src/java/org/apache/fop/events/EventProducer.java @@ -0,0 +1,31 @@ +/* + * 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.events; + +/** + * This is a marker interface which all event producer interfaces need to extend. These interfaces + * must agree to the following convention: + *
    + *
  • The first parameter of each method must be: Object source + *
+ */ +public interface EventProducer { + +} diff --git a/src/java/org/apache/fop/events/FOPEventListenerProxy.java b/src/java/org/apache/fop/events/FOPEventListenerProxy.java new file mode 100644 index 000000000..d4c237844 --- /dev/null +++ b/src/java/org/apache/fop/events/FOPEventListenerProxy.java @@ -0,0 +1,73 @@ +/* + * 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.events; + +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.events.model.EventSeverity; +import org.apache.fop.fo.FOValidationEventProducer; +import org.apache.fop.layoutmgr.BlockLevelEventProducer; + +/** + * EventListener proxy that inspects all events and adjusts severity levels where necessary. + * For validation events, it reacts on each event based on the strict validation setting in + * the user agent. + * For layout events, it reduces the default severity level if FOP signals that it can recover + * from the event. + */ +public class FOPEventListenerProxy implements EventListener { + + private static final String FOVALIDATION_EVENT_ID_PREFIX + = FOValidationEventProducer.class.getName(); + + private static final String BLOCK_LEVEL_EVENT_ID_PREFIX + = BlockLevelEventProducer.class.getName(); + + private EventListener delegate; + private FOUserAgent userAgent; + + /** + * Main constructor. + * @param delegate the event listener to delegate events to + * @param userAgent the FO user agent + */ + public FOPEventListenerProxy(EventListener delegate, FOUserAgent userAgent) { + this.delegate = delegate; + this.userAgent = userAgent; + } + + /** {@inheritDoc} */ + public synchronized void processEvent(Event event) { + if (event.getEventID().startsWith(FOVALIDATION_EVENT_ID_PREFIX)) { + Boolean canRecover = (Boolean)event.getParam("canRecover"); + if (Boolean.TRUE.equals(canRecover) && !userAgent.validateStrictly()) { + //Reduce severity if FOP can recover + event.setSeverity(EventSeverity.WARN); + } + } else if (event.getEventID().startsWith(BLOCK_LEVEL_EVENT_ID_PREFIX)) { + Boolean canRecover = (Boolean)event.getParam("canRecover"); + if (Boolean.TRUE.equals(canRecover)) { + //Reduce severity if FOP can recover + event.setSeverity(EventSeverity.WARN); + } + } + this.delegate.processEvent(event); + } + +} diff --git a/src/java/org/apache/fop/events/FOPEventModelFactory.java b/src/java/org/apache/fop/events/FOPEventModelFactory.java new file mode 100644 index 000000000..5a75042fa --- /dev/null +++ b/src/java/org/apache/fop/events/FOPEventModelFactory.java @@ -0,0 +1,37 @@ +/* + * 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.events; + +import org.apache.fop.events.model.AbstractEventModelFactory; +import org.apache.fop.events.model.EventModel; + +/** + * Factory for FOP's main event model. + */ +public class FOPEventModelFactory extends AbstractEventModelFactory { + + private static final String EVENT_MODEL_FILENAME = "event-model.xml"; + + /** {@inheritDoc} */ + public EventModel createEventModel() { + return loadModel(getClass(), EVENT_MODEL_FILENAME); + } + +} diff --git a/src/java/org/apache/fop/events/LoggingEventListener.java b/src/java/org/apache/fop/events/LoggingEventListener.java new file mode 100644 index 000000000..9ba8ed2df --- /dev/null +++ b/src/java/org/apache/fop/events/LoggingEventListener.java @@ -0,0 +1,92 @@ +/* + * 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.events; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.fop.events.model.EventSeverity; + +/** + * EventListener implementation that redirects events to Commons Logging. The events are + * converted to localized messages. + */ +public class LoggingEventListener implements EventListener { + + /** Default logger instance */ + private static Log defaultLog = LogFactory.getLog(LoggingEventListener.class); + + private Log log; + private boolean skipFatal; + + /** + * Creates an instance logging to the default log category of this class. + */ + public LoggingEventListener() { + this(defaultLog); + } + + /** + * Creates an instance logging to a given logger. Events with fatal severity level will be + * skipped. + * @param log the target logger + */ + public LoggingEventListener(Log log) { + this(log, true); + } + + /** + * Creates an instance logging to a given logger. + * @param log the target logger + * @param skipFatal true if events with fatal severity level should be skipped (i.e. not logged) + */ + public LoggingEventListener(Log log, boolean skipFatal) { + this.log = log; + this.skipFatal = skipFatal; + } + + /** + * Returns the target logger for this instance. + * @return the target logger + */ + public Log getLog() { + return this.log; + } + + /** {@inheritDoc} */ + public void processEvent(Event event) { + String msg = EventFormatter.format(event); + EventSeverity severity = event.getSeverity(); + if (severity == EventSeverity.INFO) { + log.info(msg); + } else if (severity == EventSeverity.WARN) { + log.warn(msg); + } else if (severity == EventSeverity.ERROR) { + log.error(msg); + } else if (severity == EventSeverity.FATAL) { + if (!skipFatal) { + log.fatal(msg); + } + } else { + assert false; + } + } + +} diff --git a/src/java/org/apache/fop/events/PropertyExceptionFactory.java b/src/java/org/apache/fop/events/PropertyExceptionFactory.java new file mode 100644 index 000000000..667c4a16e --- /dev/null +++ b/src/java/org/apache/fop/events/PropertyExceptionFactory.java @@ -0,0 +1,47 @@ +/* + * 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.events; + +import java.util.Locale; + +import org.apache.fop.events.EventExceptionManager.ExceptionFactory; +import org.apache.fop.fo.expr.PropertyException; + +/** + * Exception factory for {@link PropertyException}. + */ +public class PropertyExceptionFactory implements ExceptionFactory { + + /** {@inheritDoc} */ + public Throwable createException(Event event) { + String msg = EventFormatter.format(event, Locale.ENGLISH); + PropertyException ex = new PropertyException(msg); + if (!Locale.ENGLISH.equals(Locale.getDefault())) { + ex.setLocalizedMessage(EventFormatter.format(event)); + } + return ex; + } + + /** {@inheritDoc} */ + public Class getExceptionClass() { + return PropertyException.class; + } + +} \ No newline at end of file diff --git a/src/java/org/apache/fop/events/ResourceEventProducer.java b/src/java/org/apache/fop/events/ResourceEventProducer.java new file mode 100644 index 000000000..21da4f1d7 --- /dev/null +++ b/src/java/org/apache/fop/events/ResourceEventProducer.java @@ -0,0 +1,136 @@ +/* + * 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.events; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; + +import org.w3c.dom.Document; + +import org.xml.sax.Locator; + +import org.apache.xmlgraphics.image.loader.ImageException; + +/** + * Event producer interface for resource events (missing images, fonts etc.). + */ +public interface ResourceEventProducer extends EventProducer { + + /** + * Provider class for the event producer. + */ + class Provider { + + /** + * Returns an event producer. + * @param broadcaster the event broadcaster to use + * @return the requested event producer + */ + public static ResourceEventProducer get(EventBroadcaster broadcaster) { + return (ResourceEventProducer)broadcaster.getEventProducerFor( + ResourceEventProducer.class); + } + } + + /** + * Image not found. + * @param source the event source + * @param uri the original URI of the image + * @param fnfe the "file not found" exception + * @param loc the location of the error or null + * @event.severity ERROR + */ + void imageNotFound(Object source, String uri, FileNotFoundException fnfe, Locator loc); + + /** + * Error while processing image. + * @param source the event source + * @param uri the original URI of the image + * @param e the image exception + * @param loc the location of the error or null + * @event.severity ERROR + */ + void imageError(Object source, String uri, ImageException e, Locator loc); + + /** + * I/O error while loading an image. + * @param source the event source + * @param uri the original URI of the image + * @param ioe the I/O exception + * @param loc the location of the error or null + * @event.severity ERROR + */ + void imageIOError(Object source, String uri, IOException ioe, Locator loc); + + /** + * Error while writing/serializing an image to an output format. + * @param source the event source + * @param e the original exception + * @event.severity ERROR + */ + void imageWritingError(Object source, Exception e); + + /** + * Error while handling a URI. + * @param source the event source + * @param uri the original URI of the image + * @param e the original exception + * @param loc the location of the error or null + * @event.severity ERROR + */ + void uriError(Object source, String uri, Exception e, Locator loc); + + /** + * Intrinsic size of fo:instream-foreign-object could not be determined. + * @param source the event source + * @param loc the location of the error or null + * @event.severity ERROR + */ + void ifoNoIntrinsicSize(Object source, Locator loc); + + /** + * Error processing foreign XML content. + * @param source the event source + * @param doc the foreign XML + * @param namespaceURI the namespace URI of the foreign XML + * @param e the original exception + * @event.severity ERROR + */ + void foreignXMLProcessingError(Object source, Document doc, String namespaceURI, Exception e); + + /** + * No handler for foreign XML content. + * @param source the event source + * @param doc the foreign XML + * @param namespaceURI the namespace URI of the foreign XML + * @event.severity ERROR + */ + void foreignXMLNoHandler(Object source, Document doc, String namespaceURI); + + /** + * Cannot delete a temporary file. + * @param source the event source + * @param tempFile the temporary file + * @event.severity ERROR + */ + void cannotDeleteTempFile(Object source, File tempFile); + +} diff --git a/src/java/org/apache/fop/events/UnsupportedOperationExceptionFactory.java b/src/java/org/apache/fop/events/UnsupportedOperationExceptionFactory.java new file mode 100644 index 000000000..06ce8dd25 --- /dev/null +++ b/src/java/org/apache/fop/events/UnsupportedOperationExceptionFactory.java @@ -0,0 +1,43 @@ +/* + * 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.events; + +import java.util.Locale; + +import org.apache.fop.events.EventExceptionManager.ExceptionFactory; + +/** + * Exception factory for {@link UnsupportedOperationException}. + */ +public class UnsupportedOperationExceptionFactory implements ExceptionFactory { + + /** {@inheritDoc} */ + public Throwable createException(Event event) { + String msg = EventFormatter.format(event, Locale.ENGLISH); + UnsupportedOperationException ex = new UnsupportedOperationException(msg); + return ex; + } + + /** {@inheritDoc} */ + public Class getExceptionClass() { + return UnsupportedOperationException.class; + } + +} \ No newline at end of file diff --git a/src/java/org/apache/fop/events/ValidationExceptionFactory.java b/src/java/org/apache/fop/events/ValidationExceptionFactory.java new file mode 100644 index 000000000..9dba84007 --- /dev/null +++ b/src/java/org/apache/fop/events/ValidationExceptionFactory.java @@ -0,0 +1,51 @@ +/* + * 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.events; + +import java.util.Locale; + +import org.xml.sax.Locator; + +import org.apache.fop.events.EventExceptionManager.ExceptionFactory; +import org.apache.fop.fo.ValidationException; + +/** + * Exception factory for {@link ValidationException}. + */ +public class ValidationExceptionFactory implements ExceptionFactory { + + /** {@inheritDoc} */ + public Throwable createException(Event event) { + Locator loc = (Locator)event.getParam("loc"); + String msg = EventFormatter.format(event, Locale.ENGLISH); + ValidationException ex = new ValidationException(msg, loc); + if (!Locale.ENGLISH.equals(Locale.getDefault())) { + ex.setLocalizedMessage(EventFormatter.format(event)); + } + return ex; + } + + /** {@inheritDoc} */ + public Class getExceptionClass() { + return ValidationException.class; + } + + +} \ No newline at end of file diff --git a/src/java/org/apache/fop/events/model/AbstractEventModelFactory.java b/src/java/org/apache/fop/events/model/AbstractEventModelFactory.java new file mode 100644 index 000000000..ee980f34e --- /dev/null +++ b/src/java/org/apache/fop/events/model/AbstractEventModelFactory.java @@ -0,0 +1,61 @@ +/* + * 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.events.model; + +import java.io.InputStream; +import java.util.MissingResourceException; + +import javax.xml.transform.TransformerException; +import javax.xml.transform.stream.StreamSource; + +import org.apache.commons.io.IOUtils; + +import org.apache.fop.events.DefaultEventBroadcaster; + +/** + * This interface is used to instantiate (load, parse) event models. + */ +public abstract class AbstractEventModelFactory implements EventModelFactory { + + /** + * Loads an event model and returns its instance. + * @param resourceBaseClass base class to use for loading resources + * @param resourceName the resource name pointing to the event model to be loaded + * @return the newly loaded event model. + */ + public EventModel loadModel(Class resourceBaseClass, String resourceName) { + InputStream in = resourceBaseClass.getResourceAsStream(resourceName); + if (in == null) { + throw new MissingResourceException( + "File " + resourceName + " not found", + DefaultEventBroadcaster.class.getName(), ""); + } + try { + return EventModelParser.parse(new StreamSource(in)); + } catch (TransformerException e) { + throw new MissingResourceException( + "Error reading " + resourceName + ": " + e.getMessage(), + DefaultEventBroadcaster.class.getName(), ""); + } finally { + IOUtils.closeQuietly(in); + } + } + +} diff --git a/src/java/org/apache/fop/events/model/EventMethodModel.java b/src/java/org/apache/fop/events/model/EventMethodModel.java new file mode 100644 index 000000000..930cda53d --- /dev/null +++ b/src/java/org/apache/fop/events/model/EventMethodModel.java @@ -0,0 +1,198 @@ +/* + * 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.events.model; + +import java.io.Serializable; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +import org.apache.xmlgraphics.util.XMLizable; + +/** + * Represents an event method. Each method in an event producer interface will result in one + * instance of EventMethodModel. + */ +public class EventMethodModel implements Serializable, XMLizable { + + private static final long serialVersionUID = -7548882973341444354L; + + private String methodName; + private EventSeverity severity; + private List params = new java.util.ArrayList(); + private String exceptionClass; + + /** + * Creates an new instance. + * @param methodName the event method's name + * @param severity the event severity + */ + public EventMethodModel(String methodName, EventSeverity severity) { + this.methodName = methodName; + this.severity = severity; + } + + /** + * Adds a method parameter. + * @param param the method parameter + */ + public void addParameter(Parameter param) { + this.params.add(param); + } + + /** + * Adds a method parameter. + * @param type the type of the parameter + * @param name the name of the parameter + * @return the resulting Parameter instance + */ + public Parameter addParameter(Class type, String name) { + Parameter param = new Parameter(type, name); + addParameter(param); + return param; + } + + /** + * Sets the event method name. + * @param name the event name + */ + public void setMethodName(String name) { + this.methodName = name; + } + + /** + * Returns the event method name + * @return the event name + */ + public String getMethodName() { + return this.methodName; + } + + /** + * Sets the event's severity level. + * @param severity the severity + */ + public void setSeverity(EventSeverity severity) { + this.severity = severity; + } + + /** + * Returns the event's severity level. + * @return the severity + */ + public EventSeverity getSeverity() { + return this.severity; + } + + /** + * Returns an unmodifiable list of parameters for this event method. + * @return the list of parameters + */ + public List getParameters() { + return Collections.unmodifiableList(this.params); + } + + /** + * Sets the primary exception class for this event method. Note: Not all event methods throw + * exceptions! + * @param exceptionClass the exception class + */ + public void setExceptionClass(String exceptionClass) { + this.exceptionClass = exceptionClass; + } + + /** + * Returns the primary exception class for this event method. This method returns null if + * the event is only informational or just a warning. + * @return the primary exception class or null + */ + public String getExceptionClass() { + return this.exceptionClass; + } + + /** {@inheritDoc} */ + public void toSAX(ContentHandler handler) throws SAXException { + AttributesImpl atts = new AttributesImpl(); + atts.addAttribute(null, "name", "name", "CDATA", getMethodName()); + atts.addAttribute(null, "severity", "severity", "CDATA", getSeverity().getName()); + if (getExceptionClass() != null) { + atts.addAttribute(null, "exception", "exception", "CDATA", getExceptionClass()); + } + String elName = "method"; + handler.startElement(null, elName, elName, atts); + Iterator iter = this.params.iterator(); + while (iter.hasNext()) { + ((XMLizable)iter.next()).toSAX(handler); + } + handler.endElement(null, elName, elName); + } + + /** + * Represents an event parameter. + */ + public static class Parameter implements Serializable, XMLizable { + + private static final long serialVersionUID = 6062500277953887099L; + + private Class type; + private String name; + + /** + * Creates a new event parameter. + * @param type the parameter type + * @param name the parameter name + */ + public Parameter(Class type, String name) { + this.type = type; + this.name = name; + } + + /** + * Returns the parameter type. + * @return the parameter type + */ + public Class getType() { + return this.type; + } + + /** + * Returns the parameter name. + * @return the parameter name + */ + public String getName() { + return this.name; + } + + /** {@inheritDoc} */ + public void toSAX(ContentHandler handler) throws SAXException { + AttributesImpl atts = new AttributesImpl(); + atts.addAttribute(null, "type", "type", "CDATA", getType().getName()); + atts.addAttribute(null, "name", "name", "CDATA", getName()); + String elName = "parameter"; + handler.startElement(null, elName, elName, atts); + handler.endElement(null, elName, elName); + } + + } +} diff --git a/src/java/org/apache/fop/events/model/EventModel.java b/src/java/org/apache/fop/events/model/EventModel.java new file mode 100644 index 000000000..61e221b3b --- /dev/null +++ b/src/java/org/apache/fop/events/model/EventModel.java @@ -0,0 +1,135 @@ +/* + * 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.events.model; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.util.Iterator; +import java.util.Map; + +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Result; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerFactoryConfigurationError; +import javax.xml.transform.sax.SAXTransformerFactory; +import javax.xml.transform.sax.TransformerHandler; +import javax.xml.transform.stream.StreamResult; + +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +import org.apache.xmlgraphics.util.XMLizable; + +/** + * Represents a whole event model that supports multiple event producers. + */ +public class EventModel implements Serializable, XMLizable { + + private static final long serialVersionUID = 7468592614934605082L; + + private Map producers = new java.util.LinkedHashMap(); + + /** + * Creates a new, empty event model + */ + public EventModel() { + } + + /** + * Adds the model of an event producer to the event model. + * @param producer the event producer model + */ + public void addProducer(EventProducerModel producer) { + this.producers.put(producer.getInterfaceName(), producer); + } + + /** + * Returns an iterator over the contained event producer models. + * @return an iterator (Iterator<EventProducerModel>) + */ + public Iterator getProducers() { + return this.producers.values().iterator(); + } + + /** + * Returns the model of an event producer with the given interface name. + * @param interfaceName the fully qualified name of the event producer + * @return the model instance for the event producer (or null if it wasn't found) + */ + public EventProducerModel getProducer(String interfaceName) { + return (EventProducerModel)this.producers.get(interfaceName); + } + + /** + * Returns the model of an event producer with the given interface. + * @param clazz the interface of the event producer + * @return the model instance for the event producer (or null if it wasn't found) + */ + public EventProducerModel getProducer(Class clazz) { + return getProducer(clazz.getName()); + } + + /** {@inheritDoc} */ + public void toSAX(ContentHandler handler) throws SAXException { + AttributesImpl atts = new AttributesImpl(); + String elName = "event-model"; + handler.startElement(null, elName, elName, atts); + Iterator iter = getProducers(); + while (iter.hasNext()) { + ((XMLizable)iter.next()).toSAX(handler); + } + handler.endElement(null, elName, elName); + } + + private void writeXMLizable(XMLizable object, File outputFile) throws IOException { + Result res = new StreamResult(outputFile); + + try { + SAXTransformerFactory tFactory + = (SAXTransformerFactory)SAXTransformerFactory.newInstance(); + TransformerHandler handler = tFactory.newTransformerHandler(); + Transformer transformer = handler.getTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + handler.setResult(res); + handler.startDocument(); + object.toSAX(handler); + handler.endDocument(); + } catch (TransformerConfigurationException e) { + throw new IOException(e.getMessage()); + } catch (TransformerFactoryConfigurationError e) { + throw new IOException(e.getMessage()); + } catch (SAXException e) { + throw new IOException(e.getMessage()); + } + } + + /** + * Saves this event model to an XML file. + * @param modelFile the target file + * @throws IOException if an I/O error occurs + */ + public void saveToXML(File modelFile) throws IOException { + writeXMLizable(this, modelFile); + } + +} diff --git a/src/java/org/apache/fop/events/model/EventModelFactory.java b/src/java/org/apache/fop/events/model/EventModelFactory.java new file mode 100644 index 000000000..cd760501c --- /dev/null +++ b/src/java/org/apache/fop/events/model/EventModelFactory.java @@ -0,0 +1,33 @@ +/* + * 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.events.model; + +/** + * This interface is used to instantiate (load, parse) event models. + */ +public interface EventModelFactory { + + /** + * Creates a new EventModel instance. + * @return the new EventModel instance + */ + EventModel createEventModel(); + +} diff --git a/src/java/org/apache/fop/events/model/EventModelParser.java b/src/java/org/apache/fop/events/model/EventModelParser.java new file mode 100644 index 000000000..600e495c5 --- /dev/null +++ b/src/java/org/apache/fop/events/model/EventModelParser.java @@ -0,0 +1,140 @@ +/* + * 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.events.model; + +import java.util.Stack; + +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.sax.SAXResult; +import javax.xml.transform.sax.SAXTransformerFactory; + +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.fop.util.DefaultErrorListener; + +/** + * This is a parser for the event model XML. + */ +public class EventModelParser { + + /** Logger instance */ + protected static Log log = LogFactory.getLog(EventModelParser.class); + + private static SAXTransformerFactory tFactory + = (SAXTransformerFactory)SAXTransformerFactory.newInstance(); + + /** + * Parses an event model file into an EventModel instance. + * @param src the Source instance pointing to the XML file + * @return the created event model structure + * @throws TransformerException if an error occurs while parsing the XML file + */ + public static EventModel parse(Source src) + throws TransformerException { + Transformer transformer = tFactory.newTransformer(); + transformer.setErrorListener(new DefaultErrorListener(log)); + + EventModel model = new EventModel(); + SAXResult res = new SAXResult(getContentHandler(model)); + + transformer.transform(src, res); + return model; + } + + /** + * Creates a new ContentHandler instance that you can send the event model XML to. The parsed + * content is accumulated in the model structure. + * @param model the EventModel + * @return the ContentHandler instance to receive the SAX stream from the XML file + */ + public static ContentHandler getContentHandler(EventModel model) { + return new Handler(model); + } + + private static class Handler extends DefaultHandler { + + private EventModel model; + private Stack objectStack = new Stack(); + + public Handler(EventModel model) { + this.model = model; + } + + /** {@inheritDoc} */ + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { + try { + if ("event-model".equals(localName)) { + if (objectStack.size() > 0) { + throw new SAXException("event-model must be the root element"); + } + objectStack.push(model); + } else if ("producer".equals(localName)) { + EventProducerModel producer = new EventProducerModel( + attributes.getValue("name")); + EventModel parent = (EventModel)objectStack.peek(); + parent.addProducer(producer); + objectStack.push(producer); + } else if ("method".equals(localName)) { + EventSeverity severity = EventSeverity.valueOf(attributes.getValue("severity")); + String ex = attributes.getValue("exception"); + EventMethodModel method = new EventMethodModel( + attributes.getValue("name"), severity); + if (ex != null && ex.length() > 0) { + method.setExceptionClass(ex); + } + EventProducerModel parent = (EventProducerModel)objectStack.peek(); + parent.addMethod(method); + objectStack.push(method); + } else if ("parameter".equals(localName)) { + String className = attributes.getValue("type"); + Class type; + try { + type = Class.forName(className); + } catch (ClassNotFoundException e) { + throw new SAXException("Could not find Class for: " + className, e); + } + String name = attributes.getValue("name"); + EventMethodModel parent = (EventMethodModel)objectStack.peek(); + objectStack.push(parent.addParameter(type, name)); + } else { + throw new SAXException("Invalid element: " + qName); + } + } catch (ClassCastException cce) { + throw new SAXException("XML format error: " + qName, cce); + } + } + + /** {@inheritDoc} */ + public void endElement(String uri, String localName, String qName) throws SAXException { + objectStack.pop(); + } + + } + +} diff --git a/src/java/org/apache/fop/events/model/EventProducerModel.java b/src/java/org/apache/fop/events/model/EventProducerModel.java new file mode 100644 index 000000000..938609cd9 --- /dev/null +++ b/src/java/org/apache/fop/events/model/EventProducerModel.java @@ -0,0 +1,105 @@ +/* + * 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.events.model; + +import java.io.Serializable; +import java.util.Iterator; +import java.util.Map; + +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +import org.apache.xmlgraphics.util.XMLizable; + +/** + * Represents the model of an event producer with multiple event methods. + */ +public class EventProducerModel implements Serializable, XMLizable { + + private static final long serialVersionUID = 122267104123721902L; + + private String interfaceName; + private Map methods = new java.util.LinkedHashMap(); + + /** + * Creates a new instance. + * @param interfaceName the fully qualified interface name of the event producer + */ + public EventProducerModel(String interfaceName) { + this.interfaceName = interfaceName; + } + + /** + * Returns the fully qualified interface name of the event producer. + * @return the fully qualified interface name + */ + public String getInterfaceName() { + return this.interfaceName; + } + + /** + * Sets the fully qualified interface name of the event producer. + * @param name the fully qualified interface name + */ + public void setInterfaceName(String name) { + this.interfaceName = name; + } + + /** + * Adds a model instance of an event method. + * @param method the event method model + */ + public void addMethod(EventMethodModel method) { + this.methods.put(method.getMethodName(), method); + } + + /** + * Returns the model instance of an event method for the given method name. + * @param methodName the method name + * @return the model instance (or null if no method with the given name exists) + */ + public EventMethodModel getMethod(String methodName) { + return (EventMethodModel)this.methods.get(methodName); + } + + /** + * Returns an iterator over the contained event producer methods. + * @return an iterator (Iterator<EventMethodModel>) + */ + public Iterator getMethods() { + return this.methods.values().iterator(); + } + + /** {@inheritDoc} */ + public void toSAX(ContentHandler handler) throws SAXException { + AttributesImpl atts = new AttributesImpl(); + atts.addAttribute(null, "name", "name", "CDATA", getInterfaceName()); + String elName = "producer"; + handler.startElement(null, elName, elName, atts); + Iterator iter = getMethods(); + while (iter.hasNext()) { + ((XMLizable)iter.next()).toSAX(handler); + } + handler.endElement(null, elName, elName); + } + + +} diff --git a/src/java/org/apache/fop/events/model/EventSeverity.java b/src/java/org/apache/fop/events/model/EventSeverity.java new file mode 100644 index 000000000..d37c53c1e --- /dev/null +++ b/src/java/org/apache/fop/events/model/EventSeverity.java @@ -0,0 +1,82 @@ +/* + * 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.events.model; + +import java.io.ObjectStreamException; +import java.io.Serializable; + +/** Enumeration class for event severities. */ +public final class EventSeverity implements Serializable { + + private static final long serialVersionUID = 4108175215810759243L; + + /** info level */ + public static final EventSeverity INFO = new EventSeverity("INFO"); + /** warning level */ + public static final EventSeverity WARN = new EventSeverity("WARN"); + /** error level */ + public static final EventSeverity ERROR = new EventSeverity("ERROR"); + /** fatal error */ + public static final EventSeverity FATAL = new EventSeverity("FATAL"); + + private String name; + + /** + * Constructor to add a new named item. + * @param name Name of the item. + */ + private EventSeverity(String name) { + this.name = name; + } + + /** @return the name of the enumeration */ + public String getName() { + return this.name; + } + + /** + * Returns the enumeration/singleton object based on its name. + * @param name the name of the enumeration value + * @return the enumeration object + */ + public static EventSeverity valueOf(String name) { + if (INFO.getName().equalsIgnoreCase(name)) { + return INFO; + } else if (WARN.getName().equalsIgnoreCase(name)) { + return WARN; + } else if (ERROR.getName().equalsIgnoreCase(name)) { + return ERROR; + } else if (FATAL.getName().equalsIgnoreCase(name)) { + return FATAL; + } else { + throw new IllegalArgumentException("Illegal value for enumeration: " + name); + } + } + + private Object readResolve() throws ObjectStreamException { + return valueOf(getName()); + } + + /** {@inheritDoc} */ + public String toString() { + return "EventSeverity:" + name; + } + +} diff --git a/src/java/org/apache/fop/fo/ElementMapping.java b/src/java/org/apache/fop/fo/ElementMapping.java index 495983750..502fb07b8 100644 --- a/src/java/org/apache/fop/fo/ElementMapping.java +++ b/src/java/org/apache/fop/fo/ElementMapping.java @@ -24,9 +24,10 @@ import java.util.Map; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; -import org.apache.fop.util.QName; import org.w3c.dom.DOMImplementation; +import org.apache.xmlgraphics.util.QName; + /** * Abstract base class for Element Mappings (including FO Element Mappings) * which provide the framework of valid elements and attibutes for a given diff --git a/src/java/org/apache/fop/fo/ElementMappingRegistry.java b/src/java/org/apache/fop/fo/ElementMappingRegistry.java index 2ba142203..abc4ec834 100644 --- a/src/java/org/apache/fop/fo/ElementMappingRegistry.java +++ b/src/java/org/apache/fop/fo/ElementMappingRegistry.java @@ -23,6 +23,7 @@ import java.util.Iterator; import java.util.Map; import org.w3c.dom.DOMImplementation; + import org.xml.sax.Locator; import org.apache.commons.logging.Log; @@ -144,7 +145,6 @@ public class ElementMappingRegistry { + "No element mapping definition found for " + FONode.getNodeString(namespaceURI, localName), locator); } else { - log.warn("Unknown formatting object " + namespaceURI + "^" + localName); fobjMaker = new UnknownXMLObj.Maker(namespaceURI); } } diff --git a/src/java/org/apache/fop/fo/FOElementMapping.java b/src/java/org/apache/fop/fo/FOElementMapping.java index 51ec85d21..62721afeb 100644 --- a/src/java/org/apache/fop/fo/FOElementMapping.java +++ b/src/java/org/apache/fop/fo/FOElementMapping.java @@ -22,7 +22,7 @@ package org.apache.fop.fo; // Java import java.util.HashMap; -import org.apache.fop.util.QName; +import org.apache.xmlgraphics.util.QName; /** * Element mapping class for all XSL-FO elements. diff --git a/src/java/org/apache/fop/fo/FOEventHandler.java b/src/java/org/apache/fop/fo/FOEventHandler.java index d226cfb6c..5a9a5bb9d 100644 --- a/src/java/org/apache/fop/fo/FOEventHandler.java +++ b/src/java/org/apache/fop/fo/FOEventHandler.java @@ -48,6 +48,7 @@ import org.apache.fop.fo.flow.table.TableColumn; import org.apache.fop.fo.flow.table.TableRow; import org.apache.fop.fo.pagination.Flow; import org.apache.fop.fo.pagination.PageSequence; +import org.apache.fop.fonts.FontEventAdapter; import org.apache.fop.fonts.FontInfo; /** @@ -101,6 +102,7 @@ public abstract class FOEventHandler { public FOEventHandler(FOUserAgent foUserAgent) { this.foUserAgent = foUserAgent; this.fontInfo = new FontInfo(); + this.fontInfo.setEventListener(new FontEventAdapter(foUserAgent.getEventBroadcaster())); } /** diff --git a/src/java/org/apache/fop/fo/FONode.java b/src/java/org/apache/fop/fo/FONode.java index f0422e414..197a2482d 100644 --- a/src/java/org/apache/fop/fo/FONode.java +++ b/src/java/org/apache/fop/fo/FONode.java @@ -21,6 +21,8 @@ package org.apache.fop.fo; // Java import java.util.ListIterator; +import java.util.Map; +import java.util.NoSuchElementException; import org.xml.sax.Attributes; import org.xml.sax.Locator; @@ -29,6 +31,8 @@ import org.xml.sax.helpers.LocatorImpl; 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.fo.extensions.ExtensionAttachment; @@ -37,6 +41,7 @@ import org.apache.fop.fo.extensions.svg.SVGElementMapping; import org.apache.fop.fo.pagination.Root; import org.apache.fop.util.CharUtilities; import org.apache.fop.util.ContentHandlerFactory; +import org.apache.fop.util.text.AdvancedMessageFormat.Function; /** * Base class for nodes in the XML tree @@ -143,6 +148,10 @@ public abstract class FONode implements Cloneable { return parent.getFOEventHandler(); } + /** + * Indicates whether this node is a child of an fo:marker. + * @return true if this node is a child of an fo:marker + */ protected boolean inMarker() { return getFOEventHandler().inMarker(); } @@ -239,7 +248,7 @@ public abstract class FONode implements Cloneable { * @param start starting array element to add * @param end ending array element to add * @param pList currently applicable PropertyList - * @param locator location in fo source file. + * @param locator location in the XSL-FO source file. * @throws FOPException if there's a problem during processing */ protected void addCharacters(char[] data, int start, int end, @@ -343,54 +352,50 @@ public abstract class FONode implements Cloneable { } /** - * Helper function to standardize property error exceptions - * (e.g., not specifying either an internal- or an external-destination - * property for an FO:link) - * @param problem text to display that indicates the problem - * @throws ValidationException the validation error provoked by the method call + * Returns an instance of the FOValidationEventProducer. + * @return an event producer for FO validation */ - protected void attributeError(String problem) - throws ValidationException { - throw new ValidationException(errorText(locator) + getName() - + ", " + problem, locator); + protected FOValidationEventProducer getFOValidationEventProducer() { + return FOValidationEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); } - + /** - * Helper function to standardize attribute warnings - * (e.g., currently unsupported properties) - * @param problem text to display that indicates the problem + * Helper function to standardize "too many" error exceptions + * (e.g., two fo:declarations within fo:root) + * @param loc org.xml.sax.Locator object of the error (*not* parent node) + * @param nsURI namespace URI of incoming invalid node + * @param lName local name (i.e., no prefix) of incoming node + * @throws ValidationException the validation error provoked by the method call */ - public void attributeWarning(String problem) { - log.warn(warningText(locator) + getName() + ", " + problem); + protected void tooManyNodesError(Locator loc, String nsURI, String lName) + throws ValidationException { + tooManyNodesError(loc, new QName(nsURI, lName)); } /** * Helper function to standardize "too many" error exceptions * (e.g., two fo:declarations within fo:root) * @param loc org.xml.sax.Locator object of the error (*not* parent node) - * @param nsURI namespace URI of incoming invalid node - * @param lName local name (i.e., no prefix) of incoming node + * @param offendingNode the qualified name of the offending node * @throws ValidationException the validation error provoked by the method call */ - protected void tooManyNodesError(Locator loc, String nsURI, String lName) + protected void tooManyNodesError(Locator loc, QName offendingNode) throws ValidationException { - throw new ValidationException(errorText(loc) + "For " + getName() - + ", only one " + getNodeString(nsURI, lName) + " may be declared.", - loc); + getFOValidationEventProducer().tooManyNodes(this, getName(), offendingNode, loc); } /** * Helper function to standardize "too many" error exceptions * (e.g., two fo:declarations within fo:root) - * This overrloaded method helps make the caller code better self-documenting + * This overloaded method helps make the caller code better self-documenting * @param loc org.xml.sax.Locator object of the error (*not* parent node) * @param offendingNode incoming node that would cause a duplication. * @throws ValidationException the validation error provoked by the method call */ protected void tooManyNodesError(Locator loc, String offendingNode) throws ValidationException { - throw new ValidationException(errorText(loc) + "For " + getName() - + ", only one " + offendingNode + " may be declared.", loc); + tooManyNodesError(loc, new QName(FO_URI, offendingNode)); } /** @@ -402,9 +407,23 @@ public abstract class FONode implements Cloneable { * @throws ValidationException the validation error provoked by the method call */ protected void nodesOutOfOrderError(Locator loc, String tooLateNode, - String tooEarlyNode) throws ValidationException { - throw new ValidationException(errorText(loc) + "For " + getName() + ", " + tooLateNode - + " must be declared before " + tooEarlyNode + ".", loc); + String tooEarlyNode) throws ValidationException { + nodesOutOfOrderError(loc, tooLateNode, tooEarlyNode, false); + } + + /** + * Helper function to standardize "out of order" exceptions + * (e.g., fo:layout-master-set appearing after fo:page-sequence) + * @param loc org.xml.sax.Locator object of the error (*not* parent node) + * @param tooLateNode string name of node that should be earlier in document + * @param tooEarlyNode string name of node that should be later in document + * @param canRecover indicates whether FOP can recover from this problem and continue working + * @throws ValidationException the validation error provoked by the method call + */ + protected void nodesOutOfOrderError(Locator loc, String tooLateNode, + String tooEarlyNode, boolean canRecover) throws ValidationException { + getFOValidationEventProducer().nodeOutOfOrder(this, getName(), + tooLateNode, tooEarlyNode, canRecover, loc); } /** @@ -417,24 +436,24 @@ public abstract class FONode implements Cloneable { */ protected void invalidChildError(Locator loc, String nsURI, String lName) throws ValidationException { - invalidChildError(loc, nsURI, lName, null); + invalidChildError(loc, getName(), nsURI, lName, null); } /** * Helper function to return "invalid child" exceptions with more * complex validation rules (i.e., needing more explanation of the problem) * @param loc org.xml.sax.Locator object of the error (*not* parent node) + * @param parentName the name of the parent element * @param nsURI namespace URI of incoming invalid node * @param lName local name (i.e., no prefix) of incoming node - * @param ruleViolated text explanation of problem + * @param ruleViolated name of the rule violated (used to lookup a resource in a bundle) * @throws ValidationException the validation error provoked by the method call */ - protected void invalidChildError(Locator loc, String nsURI, String lName, + protected void invalidChildError(Locator loc, String parentName, String nsURI, String lName, String ruleViolated) throws ValidationException { - throw new ValidationException(errorText(loc) + getNodeString(nsURI, lName) - + " is not a valid child element of " + getName() - + ((ruleViolated != null) ? ": " + ruleViolated : "."), loc); + getFOValidationEventProducer().invalidChild(this, parentName, + new QName(nsURI, lName), ruleViolated, loc); } /** @@ -446,9 +465,22 @@ public abstract class FONode implements Cloneable { */ protected void missingChildElementError(String contentModel) throws ValidationException { - throw new ValidationException(errorText(locator) + getName() - + " is missing child elements. \nRequired Content Model: " - + contentModel, locator); + getFOValidationEventProducer().missingChildElement(this, getName(), + contentModel, false, locator); + } + + /** + * Helper function to throw an error caused by missing mandatory child elements. + * E.g., fo:layout-master-set not having any page-master child element. + * @param contentModel The XSL Content Model for the fo: object or a similar description + * indicating the necessary child elements. + * @param canRecover indicates whether FOP can recover from this problem and continue working + * @throws ValidationException the validation error provoked by the method call + */ + protected void missingChildElementError(String contentModel, boolean canRecover) + throws ValidationException { + getFOValidationEventProducer().missingChildElement(this, getName(), + contentModel, canRecover, locator); } /** @@ -458,8 +490,7 @@ public abstract class FONode implements Cloneable { */ protected void missingPropertyError(String propertyName) throws ValidationException { - throw new ValidationException(errorText(locator) + getName() - + " is missing required \"" + propertyName + "\" property.", locator); + getFOValidationEventProducer().missingProperty(this, getName(), propertyName, locator); } /** @@ -513,9 +544,10 @@ public abstract class FONode implements Cloneable { /** * Returns a String containing as much context information as possible about a node. Call - * this methods only in exceptional conditions because this method may perform quite extensive + * this method only in exceptional conditions because this method may perform quite extensive * information gathering inside the FO tree. - * @return a String containing + * @return a String containing context information + * @deprecated Not localized! Should rename getContextInfoAlt() to getContextInfo() when done! */ public String getContextInfo() { StringBuffer sb = new StringBuffer(); @@ -542,6 +574,54 @@ public abstract class FONode implements Cloneable { return sb.toString(); } + /** + * Returns a String containing as some context information about a node. It does not take the + * locator into consideration and returns null if no useful context information can be found. + * Call this method only in exceptional conditions because this method may perform quite + * extensive information gathering inside the FO tree. All text returned by this method that + * is not extracted from document content needs to be locale-independent. + * @return a String containing context information + */ + protected String getContextInfoAlt() { + String s = gatherContextInfo(); + if (s != null) { + StringBuffer sb = new StringBuffer(); + if (getLocalName() != null) { + sb.append(getName()); + sb.append(", "); + } + sb.append("\""); + sb.append(s); + sb.append("\""); + return sb.toString(); + } else { + return null; + } + } + + /** Function for AdvancedMessageFormat to retrieve context info from an FONode. */ + public static class GatherContextInfoFunction implements Function { + + /** {@inheritDoc} */ + public Object evaluate(Map params) { + Object obj = params.get("source"); + if (obj instanceof PropertyList) { + PropertyList propList = (PropertyList)obj; + obj = propList.getFObj(); + } + if (obj instanceof FONode) { + FONode node = (FONode)obj; + return node.getContextInfoAlt(); + } + return null; + } + + /** {@inheritDoc} */ + public Object getName() { + return "gatherContextInfo"; + } + } + /** * Gathers context information for the getContextInfo() method. * @return the collected context information or null, if none is available @@ -687,7 +767,7 @@ public abstract class FONode implements Cloneable { * of child nodes * @return the parent node */ - public FObj parentNode(); + FObj parentNode(); /** * Convenience method with return type of FONode @@ -695,7 +775,7 @@ public abstract class FONode implements Cloneable { * (FONode) next();) * @return the next node (if any), as a type FONode */ - public FONode nextNode(); + FONode nextNode(); /** * Convenience method with return type of FONode @@ -703,7 +783,7 @@ public abstract class FONode implements Cloneable { * (FONode) previous();) * @return the previous node (if any), as a type FONode */ - public FONode previousNode(); + FONode previousNode(); /** * Returns the first node in the list, and decreases the index, @@ -711,7 +791,7 @@ public abstract class FONode implements Cloneable { * @return the first node in the list * @throws NoSuchElementException if the list is empty */ - public FONode firstNode(); + FONode firstNode(); /** * Returns the last node in the list, and advances the @@ -720,7 +800,7 @@ public abstract class FONode implements Cloneable { * @return the last node in the list * @throws NoSuchElementException if the list is empty */ - public FONode lastNode(); + FONode lastNode(); } } diff --git a/src/java/org/apache/fop/fo/FOText.java b/src/java/org/apache/fop/fo/FOText.java index 6d1ac417f..99d37dba9 100644 --- a/src/java/org/apache/fop/fo/FOText.java +++ b/src/java/org/apache/fop/fo/FOText.java @@ -19,11 +19,11 @@ package org.apache.fop.fo; -// Java import java.awt.Color; import java.util.NoSuchElementException; -// FOP +import org.xml.sax.Locator; + import org.apache.fop.apps.FOPException; import org.apache.fop.datatypes.Length; import org.apache.fop.fo.flow.Block; @@ -34,9 +34,6 @@ import org.apache.fop.fo.properties.KeepProperty; import org.apache.fop.fo.properties.Property; import org.apache.fop.fo.properties.SpaceProperty; -// SAX -import org.xml.sax.Locator; - /** * A text node (PCDATA) in the formatting object tree. * @@ -396,7 +393,7 @@ public class FOText extends FONode { return ca[i]; } default: - log.warn("Invalid text-tranform value: " + textTransform); + assert false; //should never happen as the property subsystem catches that case return ca[i]; } } diff --git a/src/java/org/apache/fop/fo/FOTreeBuilder.java b/src/java/org/apache/fop/fo/FOTreeBuilder.java index d02a058fe..84abc4b8b 100644 --- a/src/java/org/apache/fop/fo/FOTreeBuilder.java +++ b/src/java/org/apache/fop/fo/FOTreeBuilder.java @@ -31,6 +31,8 @@ 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; @@ -130,6 +132,7 @@ public class FOTreeBuilder extends DefaultHandler { 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 @@ -146,8 +149,10 @@ public class FOTreeBuilder extends DefaultHandler { public void endDocument() throws SAXException { this.delegate.endDocument(); if (this.rootFObj == null && empty) { - throw new ValidationException( - "Document is empty (something might be wrong with your XSLT stylesheet)."); + FOValidationEventProducer eventProducer + = FOValidationEventProducer.Provider.get( + foEventHandler.getUserAgent().getEventBroadcaster()); + eventProducer.emptyDocument(this); } rootFObj = null; if (log.isDebugEnabled()) { @@ -178,18 +183,6 @@ public class FOTreeBuilder extends DefaultHandler { } } - /** - * 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 { - return elementMappingRegistry.findFOMaker(namespaceURI, localName, locator); - } - /** {@inheritDoc} */ public void warning(SAXParseException e) { log.warn(e.getLocalizedMessage()); @@ -258,22 +251,21 @@ public class FOTreeBuilder extends DefaultHandler { if (rootFObj == null) { empty = false; if (!namespaceURI.equals(FOElementMapping.URI) - || !localName.equals("root")) { - throw new ValidationException( - "Error: First element must be the fo:root formatting object. " - + "Found " + FONode.getNodeString(namespaceURI, localName) - + " instead." - + " Please make sure you're producing a valid XSL-FO document."); + || !localName.equals("root")) { + FOValidationEventProducer eventProducer + = FOValidationEventProducer.Provider.get( + foEventHandler.getUserAgent().getEventBroadcaster()); + eventProducer.invalidFORoot(this, FONode.getNodeString(namespaceURI, localName), + getEffectiveLocator()); } } else { // check that incoming node is valid for currentFObj - if (namespaceURI.equals(FOElementMapping.URI) - || namespaceURI.equals(ExtensionElementMapping.URI)) { + if (currentFObj.getNamespaceURI().equals(FOElementMapping.URI) + || currentFObj.getNamespaceURI().equals(ExtensionElementMapping.URI)) { currentFObj.validateChildNode(locator, namespaceURI, localName); } } - ElementMapping.Maker fobjMaker = - findFOMaker(namespaceURI, localName); + ElementMapping.Maker fobjMaker = findFOMaker(namespaceURI, localName); try { foNode = fobjMaker.make(currentFObj); @@ -342,8 +334,7 @@ public class FOTreeBuilder extends DefaultHandler { if (currentPropertyList != null && currentPropertyList.getFObj() == currentFObj && !foEventHandler.inMarker()) { - currentPropertyList = - currentPropertyList.getParentPropertyList(); + currentPropertyList = currentPropertyList.getParentPropertyList(); } if (currentFObj.getNameId() == Constants.FO_MARKER) { @@ -373,7 +364,29 @@ public class FOTreeBuilder extends DefaultHandler { /** {@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( + foEventHandler.getUserAgent().getEventBroadcaster()); + eventProducer.unknownFormattingObject(this, currentFObj.getName(), + new QName(namespaceURI, localName), + getEffectiveLocator()); + } + return maker; + } + } } diff --git a/src/java/org/apache/fop/fo/FOValidationEventProducer.java b/src/java/org/apache/fop/fo/FOValidationEventProducer.java new file mode 100644 index 000000000..aa7b14941 --- /dev/null +++ b/src/java/org/apache/fop/fo/FOValidationEventProducer.java @@ -0,0 +1,348 @@ +/* + * 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 org.xml.sax.Locator; + +import org.apache.xmlgraphics.util.QName; + +import org.apache.fop.apps.FOPException; +import org.apache.fop.events.EventBroadcaster; +import org.apache.fop.events.EventProducer; +import org.apache.fop.fo.expr.PropertyException; + +/** + * Event producer interface for XSL-FO validation messages. + */ +public interface FOValidationEventProducer extends EventProducer { + + /** + * Provider class for the event producer. + */ + class Provider { + + /** + * Returns an event producer. + * @param broadcaster the event broadcaster to use + * @return the event producer + */ + public static FOValidationEventProducer get(EventBroadcaster broadcaster) { + return (FOValidationEventProducer)broadcaster.getEventProducerFor( + FOValidationEventProducer.class); + } + } + + /** + * Too many child nodes. + * @param source the event source + * @param elementName the name of the context node + * @param offendingNode the offending node + * @param loc the location of the error or null + * @throws ValidationException the validation error provoked by the method call + * @event.severity FATAL + */ + void tooManyNodes(Object source, String elementName, QName offendingNode, + Locator loc) throws ValidationException; + + /** + * The node order is wrong. + * @param source the event source + * @param elementName the name of the context node + * @param tooLateNode string name of node that should be earlier in document + * @param tooEarlyNode string name of node that should be later in document + * @param canRecover indicates whether FOP can recover from this problem and continue working + * @param loc the location of the error or null + * @throws ValidationException the validation error provoked by the method call + */ + void nodeOutOfOrder(Object source, String elementName, + String tooLateNode, String tooEarlyNode, boolean canRecover, + Locator loc) throws ValidationException; + + /** + * An invalid child was encountered. + * @param source the event source + * @param elementName the name of the context node + * @param offendingNode the offending node + * @param ruleViolated the rule that was violated or null + * @param loc the location of the error or null + * @throws ValidationException the validation error provoked by the method call + */ + void invalidChild(Object source, String elementName, QName offendingNode, String ruleViolated, + Locator loc) throws ValidationException; + + /** + * A required child element is missing. + * @param source the event source + * @param elementName the name of the context node + * @param contentModel the expected content model + * @param canRecover indicates whether FOP can recover from this problem and continue working + * @param loc the location of the error or null + * @throws ValidationException the validation error provoked by the method call + * @event.severity FATAL + */ + void missingChildElement(Object source, String elementName, + String contentModel, boolean canRecover, + Locator loc) throws ValidationException; + + /** + * An element is missing a required property. + * @param source the event source + * @param elementName the name of the context node + * @param propertyName the name of the missing property + * @param loc the location of the error or null + * @throws ValidationException the validation error provoked by the method call + * @event.severity FATAL + */ + void missingProperty(Object source, String elementName, String propertyName, + Locator loc) throws ValidationException; + + /** + * An id was used twice in a document. + * @param source the event source + * @param elementName the name of the context node + * @param id the id that was reused + * @param canRecover indicates whether FOP can recover from this problem and continue working + * @param loc the location of the error or null + * @throws ValidationException the validation error provoked by the method call + * @event.severity FATAL + */ + void idNotUnique(Object source, String elementName, String id, boolean canRecover, + Locator loc) throws ValidationException; + + /** + * There are multiple color profiles defined with the same name. + * @param source the event source + * @param elementName the name of the context node + * @param name the duplicate color profile name + * @param loc the location of the error or null + * @event.severity WARN + */ + void colorProfileNameNotUnique(Object source, String elementName, String name, + Locator loc); + + /** + * There are multiple page masters defined with the same name. + * @param source the event source + * @param elementName the name of the context node + * @param name the duplicate page master name + * @param loc the location of the error or null + * @throws ValidationException the validation error provoked by the method call + * @event.severity FATAL + */ + void masterNameNotUnique(Object source, String elementName, String name, + Locator loc) throws ValidationException; + + /** + * A marker is not an initial child on a node. + * @param source the event source + * @param elementName the name of the context node + * @param mcname the marker class name + * @param loc the location of the error or null + * @event.severity ERROR + */ + void markerNotInitialChild(Object source, String elementName, String mcname, Locator loc); + + /** + * A marker class name is not unique within the same parent. + * @param source the event source + * @param elementName the name of the context node + * @param mcname the marker class name + * @param loc the location of the error or null + * @event.severity ERROR + */ + void markerNotUniqueForSameParent(Object source, String elementName, + String mcname, Locator loc); + + /** + * An invalid property was found. + * @param source the event source + * @param elementName the name of the context node + * @param attr the invalid attribute + * @param canRecover indicates whether FOP can recover from this problem and continue working + * @param loc the location of the error or null + * @throws ValidationException the validation error provoked by the method call + * @event.severity FATAL + */ + void invalidProperty(Object source, String elementName, QName attr, boolean canRecover, + Locator loc) throws ValidationException; + + /** + * An invalid property value was encountered. + * @param source the event source + * @param elementName the name of the context node + * @param propName the property name + * @param propValue the property value + * @param e the property exception caused by the invalid value + * @param loc the location of the error or null + * @event.severity ERROR + */ + void invalidPropertyValue(Object source, String elementName, + String propName, String propValue, PropertyException e, + Locator loc); + + /** + * A feature is not supported, yet. + * @param source the event source + * @param elementName the name of the context node + * @param feature the unsupported feature + * @param loc the location of the error or null + * @event.severity WARN + */ + void unimplementedFeature(Object source, String elementName, String feature, + Locator loc); + + /** + * Missing internal-/external-destination on basic-link or bookmark. + * @param source the event source + * @param elementName the name of the context node + * @param loc the location of the error or null + * @throws ValidationException the validation error provoked by the method call + * @event.severity FATAL + */ + void missingLinkDestination(Object source, String elementName, Locator loc) + throws ValidationException; + + /** + * Indicates a problem while cloning a marker (ex. due to invalid property values). + * @param source the event source + * @param markerClassName the "marker-class-name" of the marker + * @param fe the FOP exception that cause this problem + * @param loc the location of the error or null + * @event.severity ERROR + */ + void markerCloningFailed(Object source, String markerClassName, FOPException fe, Locator loc); + + /** + * A region name is mapped to multiple region classes. + * @param source the event source + * @param regionName the region name + * @param defaultRegionClass1 the first default region class + * @param defaultRegionClass2 the second default region class + * @param loc the location of the error or null + * @throws ValidationException the validation error provoked by the method call + * @event.severity FATAL + */ + void regionNameMappedToMultipleRegionClasses(Object source, String regionName, + String defaultRegionClass1, String defaultRegionClass2, Locator loc) + throws ValidationException; + + /** + * There are multiple flows with the same name. + * @param source the event source + * @param elementName the name of the context node + * @param flowName the flow name + * @param loc the location of the error or null + * @throws ValidationException the validation error provoked by the method call + * @event.severity FATAL + */ + void duplicateFlowNameInPageSequence(Object source, String elementName, String flowName, + Locator loc) throws ValidationException; + + /** + * A flow name could not be mapped to a region. + * @param source the event source + * @param elementName the name of the context node + * @param flowName the flow name + * @param loc the location of the error or null + * @throws ValidationException the validation error provoked by the method call + * @event.severity FATAL + */ + void flowNameNotMapped(Object source, String elementName, String flowName, + Locator loc) throws ValidationException; + + /** + * A page master could not be found. + * @param source the event source + * @param elementName the name of the context node + * @param masterReference the page master reference + * @param loc the location of the error or null + * @throws ValidationException the validation error provoked by the method call + * @event.severity FATAL + */ + void masterNotFound(Object source, String elementName, String masterReference, + Locator loc) throws ValidationException; + + /** + * An illegal region name was used. + * @param source the event source + * @param elementName the name of the context node + * @param regionName the region name + * @param loc the location of the error or null + * @throws ValidationException the validation error provoked by the method call + * @event.severity FATAL + */ + void illegalRegionName(Object source, String elementName, String regionName, + Locator loc) throws ValidationException; + + /** + * A non-zero border and/or padding has been encountered on a region. + * @param source the event source + * @param elementName the name of the context node + * @param regionName the region name + * @param canRecover indicates whether FOP can recover from this problem and continue working + * @param loc the location of the error or null + * @throws ValidationException the validation error provoked by the method call + * @event.severity FATAL + */ + void nonZeroBorderPaddingOnRegion(Object source, String elementName, String regionName, + boolean canRecover, Locator loc) throws ValidationException; + + /** + * If overflow property is set to "scroll", a column-count other than "1" may not be specified. + * @param source the event source + * @param elementName the name of the context node + * @param loc the location of the error or null + * @throws ValidationException the validation error provoked by the method call + * @event.severity FATAL + */ + void columnCountErrorOnRegionBodyOverflowScroll(Object source, String elementName, + Locator loc) throws ValidationException; + + /** + * fo:root must be root. + * @param source the event source + * @param elementName the name of the context node + * @param loc the location of the error or null + * @throws ValidationException the validation error provoked by the method call + * @event.severity FATAL + */ + void invalidFORoot(Object source, String elementName, + Locator loc) throws ValidationException; + + /** + * No FO document was found. + * @param source the event source + * @throws ValidationException the validation error provoked by the method call + * @event.severity FATAL + */ + void emptyDocument(Object source) throws ValidationException; + + /** + * An unknown/unsupported formatting object has been encountered. + * @param source the event source + * @param elementName the name of the context node + * @param offendingNode the offending node + * @param loc the location of the error or null + * @event.severity WARN + */ + void unknownFormattingObject(Object source, String elementName, + QName offendingNode, Locator loc); + +} diff --git a/src/java/org/apache/fop/fo/FObj.java b/src/java/org/apache/fop/fo/FObj.java index b2587df2d..a03a351e0 100644 --- a/src/java/org/apache/fop/fo/FObj.java +++ b/src/java/org/apache/fop/fo/FObj.java @@ -27,13 +27,15 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; +import org.xml.sax.Attributes; +import org.xml.sax.Locator; + +import org.apache.xmlgraphics.util.QName; + import org.apache.fop.apps.FOPException; import org.apache.fop.fo.extensions.ExtensionAttachment; import org.apache.fop.fo.flow.Marker; import org.apache.fop.fo.properties.PropertyMaker; -import org.apache.fop.util.QName; -import org.xml.sax.Attributes; -import org.xml.sax.Locator; /** * Base class for representation of formatting objects and their processing. @@ -171,25 +173,7 @@ public abstract class FObj extends FONode implements Constants { if (!idrefs.contains(id)) { idrefs.add(id); } else { - if (getUserAgent().validateStrictly()) { - throw new ValidationException("Property id \"" + id - + "\" previously used; id values must be unique" - + " in document.", locator); - } else { - if (log.isWarnEnabled()) { - StringBuffer msg = new StringBuffer(); - msg.append("Found non-unique id on ").append(getName()); - if (locator.getLineNumber() != -1) { - msg.append(" (at ").append(locator.getLineNumber()) - .append("/").append(locator.getColumnNumber()) - .append(")"); - } - msg.append("\nAny reference to it will be considered " - + "a reference to the first occurrence " - + "in the document."); - log.warn(msg); - } - } + getFOValidationEventProducer().idNotUnique(this, getName(), id, true, locator); } } } @@ -283,16 +267,22 @@ public abstract class FObj extends FONode implements Constants { return false; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public FONodeIterator getChildNodes() { - if (firstChild != null) { + if (hasChildren()) { return new FObjIterator(this); } return null; } + /** + * Indicates whether this formatting object has children. + * @return true if there are children + */ + public boolean hasChildren() { + return this.firstChild != null; + } + /** * Return an iterator over the object's childNodes starting * at the passed-in node (= first call to iterator.next() will @@ -348,8 +338,8 @@ public abstract class FObj extends FONode implements Constants { if (node instanceof FObj || (node instanceof FOText && ((FOText) node).willCreateArea())) { - log.error( - "fo:marker must be an initial child: " + mcname); + getFOValidationEventProducer().markerNotInitialChild(this, getName(), + mcname, locator); return; } else if (node instanceof FOText) { iter.remove(); @@ -363,8 +353,8 @@ public abstract class FObj extends FONode implements Constants { if (!markers.containsKey(mcname)) { markers.put(mcname, marker); } else { - log.error("fo:marker 'marker-class-name' " - + "must be unique for same parent: " + mcname); + getFOValidationEventProducer().markerNotUniqueForSameParent(this, getName(), + mcname, locator); } } @@ -382,6 +372,33 @@ public abstract class FObj extends FONode implements Constants { return markers; } + /** {@inheritDoc} */ + protected String getContextInfoAlt() { + StringBuffer sb = new StringBuffer(); + if (getLocalName() != null) { + sb.append(getName()); + sb.append(", "); + } + if (hasId()) { + sb.append("id=").append(getId()); + return sb.toString(); + } + String s = gatherContextInfo(); + if (s != null) { + sb.append("\""); + if (s.length() < 32) { + sb.append(s); + } else { + sb.append(s.substring(0, 32)); + sb.append("..."); + } + sb.append("\""); + return sb.toString(); + } else { + return null; + } + } + /** {@inheritDoc} */ protected String gatherContextInfo() { if (getLocator() != null) { diff --git a/src/java/org/apache/fop/fo/PropertyList.java b/src/java/org/apache/fop/fo/PropertyList.java index 3d050efed..b6766bfe9 100644 --- a/src/java/org/apache/fop/fo/PropertyList.java +++ b/src/java/org/apache/fop/fo/PropertyList.java @@ -20,13 +20,13 @@ package org.apache.fop.fo; // Java -import java.text.MessageFormat; - import org.xml.sax.Attributes; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.xmlgraphics.util.QName; + import org.apache.fop.apps.FopFactory; import org.apache.fop.fo.expr.PropertyException; import org.apache.fop.fo.properties.CommonAbsolutePosition; @@ -41,7 +41,6 @@ import org.apache.fop.fo.properties.CommonRelativePosition; import org.apache.fop.fo.properties.CommonTextDecoration; import org.apache.fop.fo.properties.Property; import org.apache.fop.fo.properties.PropertyMaker; -import org.apache.fop.util.QName; /** * Class containing the collection of properties for a given FObj. @@ -150,7 +149,7 @@ public abstract class PropertyList { * the default value. * @param propId The Constants ID of the property whose value is desired. * @return the Property corresponding to that name - * @throws PropertyException ... + * @throws PropertyException if there is a problem evaluating the property */ public Property get(int propId) throws PropertyException { return get(propId, true, true); @@ -166,7 +165,7 @@ public abstract class PropertyList { * value is needed * @param bTryDefault true when the default value may be used as a last resort * @return the property - * @throws PropertyException ... + * @throws PropertyException if there is a problem evaluating the property */ public Property get(int propId, boolean bTryInherit, boolean bTryDefault) throws PropertyException { @@ -321,20 +320,18 @@ public abstract class PropertyList { } else if (!factory.isNamespaceIgnored(attributeNS)) { ElementMapping mapping = factory.getElementMappingRegistry().getElementMapping( attributeNS); + QName attr = new QName(attributeNS, attributeName); if (mapping != null) { - QName attName = new QName(attributeNS, attributeName); - if (mapping.isAttributeProperty(attName) + if (mapping.isAttributeProperty(attr) && mapping.getStandardPrefix() != null) { convertAttributeToProperty(attributes, - mapping.getStandardPrefix() + ":" + attName.getLocalName(), + mapping.getStandardPrefix() + ":" + attr.getLocalName(), attributeValue); } else { - getFObj().addForeignAttribute(attName, attributeValue); + getFObj().addForeignAttribute(attr, attributeValue); } } else { - handleInvalidProperty( - "Error processing foreign attribute: " - + attributeNS + "/@" + attributeName, attributeName); + handleInvalidProperty(attr); } } } @@ -345,11 +342,8 @@ public abstract class PropertyList { * @param propertyName the property name to check * @return true if the base property name and the subproperty name (if any) * can be correctly mapped to an id - * @throws ValidationException in case the property name - * is invalid for the FO namespace */ - protected boolean isValidPropertyName(String propertyName) - throws ValidationException { + protected boolean isValidPropertyName(String propertyName) { int propId = FOPropertyMapping.getPropertyId( findBasePropertyName(propertyName)); @@ -359,9 +353,6 @@ public abstract class PropertyList { if (propId == -1 || (subpropId == -1 && findSubPropertyName(propertyName) != null)) { - String errorMessage = MessageFormat.format( - "Invalid property name ''{0}''.", new Object[] {propertyName}); - handleInvalidProperty(errorMessage, propertyName); return false; } return true; @@ -382,19 +373,23 @@ public abstract class PropertyList { if (attributeValue != null) { - if (!isValidPropertyName(attributeName)) { - //will log an error or throw an exception + if (attributeName.startsWith("xmlns:")) { + //Ignore namespace declarations return; } - FObj parentFO = fobj.findNearestAncestorFObj(); - /* Handle "compound" properties, ex. space-before.minimum */ String basePropertyName = findBasePropertyName(attributeName); String subPropertyName = findSubPropertyName(attributeName); int propId = FOPropertyMapping.getPropertyId(basePropertyName); int subpropId = FOPropertyMapping.getSubPropertyId(subPropertyName); + + if (propId == -1 + || (subpropId == -1 && subPropertyName != null)) { + handleInvalidProperty(new QName(null, attributeName)); + } + FObj parentFO = fobj.findNearestAncestorFObj(); PropertyMaker propertyMaker = findMaker(propId); if (propertyMaker == null) { @@ -417,8 +412,8 @@ public abstract class PropertyList { } prop = propertyMaker.make(this, attributeValue, parentFO); } else { // e.g. "leader-length.maximum" - Property baseProperty = - findBaseProperty(attributes, parentFO, propId, + Property baseProperty + = findBaseProperty(attributes, parentFO, propId, basePropertyName, propertyMaker); prop = propertyMaker.make(baseProperty, subpropId, this, attributeValue, parentFO); @@ -427,8 +422,8 @@ public abstract class PropertyList { putExplicit(propId, prop); } } catch (PropertyException e) { - log.error("Ignoring property: " - + attributeName + "=\"" + attributeValue + "\" (" + e.getMessage() + ")"); + fobj.getFOValidationEventProducer().invalidPropertyValue(this, fobj.getName(), + attributeName, attributeValue, e, fobj.locator); } } } @@ -465,18 +460,16 @@ public abstract class PropertyList { } /** - * @param message ... - * @param propName ... - * @throws ValidationException ... + * Handles an invalid property. + * @param attr the invalid attribute + * @throws ValidationException if an exception needs to be thrown depending on the + * validation settings */ - protected void handleInvalidProperty(String message, String propName) + protected void handleInvalidProperty(QName attr) throws ValidationException { - if (!propName.startsWith("xmlns")) { - if (fobj.getUserAgent().validateStrictly()) { - fobj.attributeError(message); - } else { - log.error(message + " Property ignored."); - } + if (!attr.getQName().startsWith("xmlns")) { + fobj.getFOValidationEventProducer().invalidProperty(this, fobj.getName(), + attr, true, fobj.locator); } } diff --git a/src/java/org/apache/fop/fo/expr/FromParentFunction.java b/src/java/org/apache/fop/fo/expr/FromParentFunction.java index b5a82de56..b09d3c95f 100644 --- a/src/java/org/apache/fop/fo/expr/FromParentFunction.java +++ b/src/java/org/apache/fop/fo/expr/FromParentFunction.java @@ -64,7 +64,13 @@ public class FromParentFunction extends FunctionBase { * non-inherited properties too. Perhaps the result is different for * a property line line-height which "inherits specified"??? */ - return pInfo.getPropertyList().getFromParent(FOPropertyMapping.getPropertyId(propName)); + int propId = FOPropertyMapping.getPropertyId(propName); + if (propId < 0) { + throw new PropertyException( + "Unknown property name used with inherited-property-value function: " + + propName); + } + return pInfo.getPropertyList().getFromParent(propId); } } diff --git a/src/java/org/apache/fop/fo/expr/InheritedPropFunction.java b/src/java/org/apache/fop/fo/expr/InheritedPropFunction.java index 3e5cadf04..e24c78caa 100644 --- a/src/java/org/apache/fop/fo/expr/InheritedPropFunction.java +++ b/src/java/org/apache/fop/fo/expr/InheritedPropFunction.java @@ -58,6 +58,11 @@ public class InheritedPropFunction extends FunctionBase { } int propId = FOPropertyMapping.getPropertyId(propName); + if (propId < 0) { + throw new PropertyException( + "Unknown property name used with inherited-property-value function: " + + propName); + } return pInfo.getPropertyList().getInherited(propId); } diff --git a/src/java/org/apache/fop/fo/expr/NearestSpecPropFunction.java b/src/java/org/apache/fop/fo/expr/NearestSpecPropFunction.java index 2aab5eee9..cdde96092 100644 --- a/src/java/org/apache/fop/fo/expr/NearestSpecPropFunction.java +++ b/src/java/org/apache/fop/fo/expr/NearestSpecPropFunction.java @@ -60,6 +60,11 @@ public class NearestSpecPropFunction extends FunctionBase { // NOTE: special cases for shorthand property // Should return COMPUTED VALUE int propId = FOPropertyMapping.getPropertyId(propName); + if (propId < 0) { + throw new PropertyException( + "Unknown property name used with inherited-property-value function: " + + propName); + } return pInfo.getPropertyList().getNearestSpecified(propId); } diff --git a/src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java b/src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java index de1d019f4..fc61167b2 100644 --- a/src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java +++ b/src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java @@ -19,14 +19,15 @@ package org.apache.fop.fo.extensions; +import java.util.HashMap; +import java.util.Set; + +import org.apache.xmlgraphics.util.QName; + import org.apache.fop.fo.ElementMapping; import org.apache.fop.fo.FONode; import org.apache.fop.fo.UnknownXMLObj; import org.apache.fop.fo.extensions.destination.Destination; -import org.apache.fop.util.QName; - -import java.util.HashMap; -import java.util.Set; /** * Element mapping for FOP's proprietary extension to XSL-FO. diff --git a/src/java/org/apache/fop/fo/extensions/destination/Destination.java b/src/java/org/apache/fop/fo/extensions/destination/Destination.java index d1e631e42..e3a2bbac4 100644 --- a/src/java/org/apache/fop/fo/extensions/destination/Destination.java +++ b/src/java/org/apache/fop/fo/extensions/destination/Destination.java @@ -19,15 +19,15 @@ package org.apache.fop.fo.extensions.destination; -import org.apache.fop.fo.ValidationException; +import org.xml.sax.Attributes; +import org.xml.sax.Locator; + import org.apache.fop.apps.FOPException; -import org.apache.fop.fo.PropertyList; import org.apache.fop.fo.FONode; -import org.apache.fop.fo.pagination.Root; +import org.apache.fop.fo.PropertyList; +import org.apache.fop.fo.ValidationException; import org.apache.fop.fo.extensions.ExtensionElementMapping; - -import org.xml.sax.Attributes; -import org.xml.sax.Locator; +import org.apache.fop.fo.pagination.Root; /** * Class for named destinations in PDF. @@ -54,7 +54,7 @@ public class Destination extends FONode { Attributes attlist, PropertyList pList) throws FOPException { internalDestination = attlist.getValue("internal-destination"); if (internalDestination == null || internalDestination.length() == 0) { - attributeError("Missing attribute: internal-destination must be specified."); + missingPropertyError("internal-destination"); } } diff --git a/src/java/org/apache/fop/fo/flow/AbstractListItemPart.java b/src/java/org/apache/fop/fo/flow/AbstractListItemPart.java index 0fe6ed718..f99f9d947 100644 --- a/src/java/org/apache/fop/fo/flow/AbstractListItemPart.java +++ b/src/java/org/apache/fop/fo/flow/AbstractListItemPart.java @@ -62,14 +62,16 @@ public abstract class AbstractListItemPart extends FObj { */ protected void validateChildNode(Locator loc, String nsURI, String localName) throws ValidationException { - if (FO_URI.equals(nsURI) && localName.equals("marker")) { - if (blockItemFound) { - nodesOutOfOrderError(loc, "fo:marker", "(%block;)"); + if (FO_URI.equals(nsURI)) { + if (localName.equals("marker")) { + if (blockItemFound) { + nodesOutOfOrderError(loc, "fo:marker", "(%block;)"); + } + } else if (!isBlockItem(nsURI, localName)) { + invalidChildError(loc, nsURI, localName); + } else { + blockItemFound = true; } - } else if (!isBlockItem(nsURI, localName)) { - invalidChildError(loc, nsURI, localName); - } else { - blockItemFound = true; } } @@ -79,17 +81,8 @@ public abstract class AbstractListItemPart extends FObj { protected void endOfNode() throws FOPException { if (!this.blockItemFound) { String contentModel = "marker* (%block;)+"; - if (getUserAgent().validateStrictly()) { - missingChildElementError(contentModel); - } else { - StringBuffer message = new StringBuffer( - errorText(getLocator())); - message.append(getName()) - .append(" is missing child elements. ") - .append("Required Content Model: ") - .append(contentModel); - log.warn(message.toString()); - } + getFOValidationEventProducer().missingChildElement(this, getName(), + contentModel, true, getLocator()); } } diff --git a/src/java/org/apache/fop/fo/flow/AbstractPageNumberCitation.java b/src/java/org/apache/fop/fo/flow/AbstractPageNumberCitation.java index b6b827248..e9a1176d6 100644 --- a/src/java/org/apache/fop/fo/flow/AbstractPageNumberCitation.java +++ b/src/java/org/apache/fop/fo/flow/AbstractPageNumberCitation.java @@ -113,8 +113,10 @@ public abstract class AbstractPageNumberCitation extends FObj { * XSL Content Model: empty */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { + throws ValidationException { + if (FO_URI.equals(nsURI)) { invalidChildError(loc, nsURI, localName); + } } /** @return the Common Font Properties. */ diff --git a/src/java/org/apache/fop/fo/flow/BasicLink.java b/src/java/org/apache/fop/fo/flow/BasicLink.java index 61a4f8d19..b3ef48012 100644 --- a/src/java/org/apache/fop/fo/flow/BasicLink.java +++ b/src/java/org/apache/fop/fo/flow/BasicLink.java @@ -76,8 +76,7 @@ public class BasicLink extends Inline { externalDestination = null; } else if (externalDestination.length() == 0) { // slightly stronger than spec "should be specified" - attributeError("Missing attribute: Either external-destination or " + - "internal-destination must be specified."); + getFOValidationEventProducer().missingLinkDestination(this, getName(), locator); } } @@ -102,15 +101,17 @@ public class BasicLink extends Inline { * XSL Content Model: marker* (#PCDATA|%inline;|%block;)* */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { - if (FO_URI.equals(nsURI) && localName.equals("marker")) { - if (blockOrInlineItemFound) { - nodesOutOfOrderError(loc, "fo:marker", "(#PCDATA|%inline;|%block;)"); + throws ValidationException { + if (FO_URI.equals(nsURI)) { + if (localName.equals("marker")) { + if (blockOrInlineItemFound) { + nodesOutOfOrderError(loc, "fo:marker", "(#PCDATA|%inline;|%block;)"); + } + } else if (!isBlockOrInlineItem(nsURI, localName)) { + invalidChildError(loc, nsURI, localName); + } else { + blockOrInlineItemFound = true; } - } else if (!isBlockOrInlineItem(nsURI, localName)) { - invalidChildError(loc, nsURI, localName); - } else { - blockOrInlineItemFound = true; } } diff --git a/src/java/org/apache/fop/fo/flow/BidiOverride.java b/src/java/org/apache/fop/fo/flow/BidiOverride.java index 2157085ad..892f4a3c5 100644 --- a/src/java/org/apache/fop/fo/flow/BidiOverride.java +++ b/src/java/org/apache/fop/fo/flow/BidiOverride.java @@ -19,13 +19,14 @@ package org.apache.fop.fo.flow; +import org.xml.sax.Locator; + import org.apache.fop.apps.FOPException; import org.apache.fop.fo.FONode; import org.apache.fop.fo.FObjMixed; import org.apache.fop.fo.PropertyList; import org.apache.fop.fo.ValidationException; import org.apache.fop.fo.properties.SpaceProperty; -import org.xml.sax.Locator; /** * Class modelling the fo:bidi-override object. @@ -96,22 +97,21 @@ public class BidiOverride extends FObjMixed { * fo:inline-container." */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { - if (FO_URI.equals(nsURI) && localName.equals("marker")) { - if (blockOrInlineItemFound) { - nodesOutOfOrderError(loc, "fo:marker", - "(#PCDATA|%inline;|%block;)"); + throws ValidationException { + if (FO_URI.equals(nsURI)) { + if (localName.equals("marker")) { + if (blockOrInlineItemFound) { + nodesOutOfOrderError(loc, "fo:marker", + "(#PCDATA|%inline;|%block;)"); + } + } else if (!isBlockOrInlineItem(nsURI, localName)) { + invalidChildError(loc, nsURI, localName); + } else if (!canHaveBlockLevelChildren && isBlockItem(nsURI, localName)) { + invalidChildError(loc, getParent().getName(), nsURI, getName(), + "rule.bidiOverrideContent"); + } else { + blockOrInlineItemFound = true; } - } else if (!isBlockOrInlineItem(nsURI, localName)) { - invalidChildError(loc, nsURI, localName); - } else if (!canHaveBlockLevelChildren && isBlockItem(nsURI, localName)) { - String ruleViolated = "An fo:bidi-override" - + " that is a descendant of an fo:leader or of the fo:inline child" - + " of an fo:footnote may not have block-level children, unless it" - + " has a nearer ancestor that is an fo:inline-container."; - invalidChildError(loc, nsURI, localName, ruleViolated); - } else { - blockOrInlineItemFound = true; } } diff --git a/src/java/org/apache/fop/fo/flow/BlockContainer.java b/src/java/org/apache/fop/fo/flow/BlockContainer.java index a71999938..f1180ac16 100644 --- a/src/java/org/apache/fop/fo/flow/BlockContainer.java +++ b/src/java/org/apache/fop/fo/flow/BlockContainer.java @@ -114,15 +114,17 @@ public class BlockContainer extends FObj { * @todo - implement above restriction if possible */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { - if (FO_URI.equals(nsURI) && localName.equals("marker")) { - if (blockItemFound) { - nodesOutOfOrderError(loc, "fo:marker", "(%block;)"); + throws ValidationException { + if (FO_URI.equals(nsURI)) { + if (localName.equals("marker")) { + if (blockItemFound) { + nodesOutOfOrderError(loc, "fo:marker", "(%block;)"); + } + } else if (!isBlockItem(nsURI, localName)) { + invalidChildError(loc, nsURI, localName); + } else { + blockItemFound = true; } - } else if (!isBlockItem(nsURI, localName)) { - invalidChildError(loc, nsURI, localName); - } else { - blockItemFound = true; } } diff --git a/src/java/org/apache/fop/fo/flow/Character.java b/src/java/org/apache/fop/fo/flow/Character.java index 022d54af7..aad4209f9 100644 --- a/src/java/org/apache/fop/fo/flow/Character.java +++ b/src/java/org/apache/fop/fo/flow/Character.java @@ -22,6 +22,8 @@ package org.apache.fop.fo.flow; import java.awt.Color; import java.util.NoSuchElementException; +import org.xml.sax.Locator; + import org.apache.fop.apps.FOPException; import org.apache.fop.datatypes.Length; import org.apache.fop.fo.CharIterator; @@ -35,7 +37,6 @@ import org.apache.fop.fo.properties.CommonHyphenation; import org.apache.fop.fo.properties.CommonTextDecoration; import org.apache.fop.fo.properties.Property; import org.apache.fop.fo.properties.SpaceProperty; -import org.xml.sax.Locator; /** * Class modelling the fo:character object. @@ -134,8 +135,10 @@ public class Character extends FObj { * XSL Content Model: empty */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { + throws ValidationException { + if (FO_URI.equals(nsURI)) { invalidChildError(loc, nsURI, localName); + } } /** diff --git a/src/java/org/apache/fop/fo/flow/ExternalGraphic.java b/src/java/org/apache/fop/fo/flow/ExternalGraphic.java index fdddf3918..07f765e52 100644 --- a/src/java/org/apache/fop/fo/flow/ExternalGraphic.java +++ b/src/java/org/apache/fop/fo/flow/ExternalGraphic.java @@ -32,6 +32,7 @@ import org.apache.fop.apps.FOPException; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.datatypes.Length; import org.apache.fop.datatypes.URISpecification; +import org.apache.fop.events.ResourceEventProducer; import org.apache.fop.fo.FONode; import org.apache.fop.fo.PropertyList; import org.apache.fop.fo.ValidationException; @@ -78,11 +79,17 @@ public class ExternalGraphic extends AbstractGraphics { try { info = manager.getImageInfo(url, userAgent.getImageSessionContext()); } catch (ImageException e) { - log.error("Image not available: " + e.getMessage()); + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageError(this, url, e, getLocator()); } catch (FileNotFoundException fnfe) { - log.error(fnfe.getMessage()); + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageNotFound(this, url, fnfe, getLocator()); } catch (IOException ioe) { - log.error("I/O error while loading image: " + ioe.getMessage()); + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageIOError(this, url, ioe, getLocator()); } if (info != null) { this.intrinsicWidth = info.getSize().getWidthMpt(); @@ -93,7 +100,6 @@ public class ExternalGraphic extends AbstractGraphics { = FixedLength.getInstance(-baseline); } } - //TODO Report to caller so he can decide to throw an exception } /** {@inheritDoc} */ @@ -107,8 +113,10 @@ public class ExternalGraphic extends AbstractGraphics { *
XSL Content Model: empty */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { + throws ValidationException { + if (FO_URI.equals(nsURI)) { invalidChildError(loc, nsURI, localName); + } } /** @return the "src" property */ diff --git a/src/java/org/apache/fop/fo/flow/Float.java b/src/java/org/apache/fop/fo/flow/Float.java index 997f96c89..57d3b4ee1 100644 --- a/src/java/org/apache/fop/fo/flow/Float.java +++ b/src/java/org/apache/fop/fo/flow/Float.java @@ -46,7 +46,8 @@ public class Float extends FObj { super(parent); if (!notImplementedWarningGiven) { - log.warn("fo:float is not yet implemented."); + getFOValidationEventProducer().unimplementedFeature(this, getName(), + getName(), getLocator()); notImplementedWarningGiven = true; } } @@ -63,10 +64,12 @@ public class Float extends FObj { * XSL Content Model: (%block;)+ */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { + throws ValidationException { + if (FO_URI.equals(nsURI)) { if (!isBlockItem(nsURI, localName)) { invalidChildError(loc, nsURI, localName); } + } } /** diff --git a/src/java/org/apache/fop/fo/flow/Footnote.java b/src/java/org/apache/fop/fo/flow/Footnote.java index 95c9f25bc..c15a7e0ee 100644 --- a/src/java/org/apache/fop/fo/flow/Footnote.java +++ b/src/java/org/apache/fop/fo/flow/Footnote.java @@ -82,12 +82,13 @@ public class Footnote extends FObj { * generates an absolutely positioned area. */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { - if (FO_URI.equals(nsURI) && localName.equals("inline")) { + throws ValidationException { + if (FO_URI.equals(nsURI)) { + if (localName.equals("inline")) { if (footnoteCitation != null) { tooManyNodesError(loc, "fo:inline"); } - } else if (FO_URI.equals(nsURI) && localName.equals("footnote-body")) { + } else if (localName.equals("footnote-body")) { if (footnoteCitation == null) { nodesOutOfOrderError(loc, "fo:inline", "fo:footnote-body"); } else if (footnoteBody != null) { @@ -96,6 +97,7 @@ public class Footnote extends FObj { } else { invalidChildError(loc, nsURI, localName); } + } } /** diff --git a/src/java/org/apache/fop/fo/flow/FootnoteBody.java b/src/java/org/apache/fop/fo/flow/FootnoteBody.java index bb4c9b482..967d15215 100644 --- a/src/java/org/apache/fop/fo/flow/FootnoteBody.java +++ b/src/java/org/apache/fop/fo/flow/FootnoteBody.java @@ -73,10 +73,12 @@ public class FootnoteBody extends FObj { * XSL Content Model: (%block;)+ */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { + throws ValidationException { + if (FO_URI.equals(nsURI)) { if (!isBlockItem(nsURI, localName)) { invalidChildError(loc, nsURI, localName); } + } } /** {@inheritDoc} */ diff --git a/src/java/org/apache/fop/fo/flow/InitialPropertySet.java b/src/java/org/apache/fop/fo/flow/InitialPropertySet.java index 63299978d..6d0e495b7 100644 --- a/src/java/org/apache/fop/fo/flow/InitialPropertySet.java +++ b/src/java/org/apache/fop/fo/flow/InitialPropertySet.java @@ -72,8 +72,10 @@ public class InitialPropertySet extends FObj { * XSL Content Model: empty */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { + throws ValidationException { + if (FO_URI.equals(nsURI)) { invalidChildError(loc, nsURI, localName); + } } /** diff --git a/src/java/org/apache/fop/fo/flow/Inline.java b/src/java/org/apache/fop/fo/flow/Inline.java index 6bbd90ad1..50662d9f1 100644 --- a/src/java/org/apache/fop/fo/flow/Inline.java +++ b/src/java/org/apache/fop/fo/flow/Inline.java @@ -81,8 +81,8 @@ public class Inline extends InlineLevel { int lvlInCntr = findAncestor(FO_INLINE_CONTAINER); if (lvlLeader > 0) { - if (lvlInCntr < 0 || - (lvlInCntr > 0 && lvlInCntr > lvlLeader)) { + if (lvlInCntr < 0 + || (lvlInCntr > 0 && lvlInCntr > lvlLeader)) { canHaveBlockLevelChildren = false; } } else if (lvlFootnote > 0) { @@ -110,23 +110,20 @@ public class Inline extends InlineLevel { * nearer ancestor that is an fo:inline-container." (paraphrased) */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { - if (FO_URI.equals(nsURI) && localName.equals("marker")) { - if (blockOrInlineItemFound) { - nodesOutOfOrderError(loc, "fo:marker", - "(#PCDATA|%inline;|%block;)"); + throws ValidationException { + if (FO_URI.equals(nsURI)) { + if (localName.equals("marker")) { + if (blockOrInlineItemFound) { + nodesOutOfOrderError(loc, "fo:marker", + "(#PCDATA|%inline;|%block;)"); + } + } else if (!isBlockOrInlineItem(nsURI, localName)) { + invalidChildError(loc, nsURI, localName); + } else if (!canHaveBlockLevelChildren && isBlockItem(nsURI, localName)) { + invalidChildError(loc, getParent().getName(), nsURI, getName(), "rule.inlineContent"); + } else { + blockOrInlineItemFound = true; } - } else if (!isBlockOrInlineItem(nsURI, localName)) { - invalidChildError(loc, nsURI, localName); - } else if (!canHaveBlockLevelChildren && isBlockItem(nsURI, localName)) { - String ruleViolated = - " An fo:inline that is a descendant of an fo:leader" + - " or fo:footnote may not have block-level children," + - " unless it has a nearer ancestor that is an" + - " fo:inline-container."; - invalidChildError(loc, nsURI, localName, ruleViolated); - } else { - blockOrInlineItemFound = true; } } diff --git a/src/java/org/apache/fop/fo/flow/InlineContainer.java b/src/java/org/apache/fop/fo/flow/InlineContainer.java index a8fb7858d..3c142afe9 100644 --- a/src/java/org/apache/fop/fo/flow/InlineContainer.java +++ b/src/java/org/apache/fop/fo/flow/InlineContainer.java @@ -86,15 +86,17 @@ public class InlineContainer extends FObj { * XSL Content Model: marker* (%block;)+ */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { - if (FO_URI.equals(nsURI) && localName.equals("marker")) { - if (blockItemFound) { - nodesOutOfOrderError(loc, "fo:marker", "(%block;)"); + throws ValidationException { + if (FO_URI.equals(nsURI)) { + if (localName.equals("marker")) { + if (blockItemFound) { + nodesOutOfOrderError(loc, "fo:marker", "(%block;)"); + } + } else if (!isBlockItem(nsURI, localName)) { + invalidChildError(loc, nsURI, localName); + } else { + blockItemFound = true; } - } else if (!isBlockItem(nsURI, localName)) { - invalidChildError(loc, nsURI, localName); - } else { - blockItemFound = true; } } diff --git a/src/java/org/apache/fop/fo/flow/InstreamForeignObject.java b/src/java/org/apache/fop/fo/flow/InstreamForeignObject.java index 531bd657a..802f59c30 100644 --- a/src/java/org/apache/fop/fo/flow/InstreamForeignObject.java +++ b/src/java/org/apache/fop/fo/flow/InstreamForeignObject.java @@ -20,12 +20,17 @@ package org.apache.fop.fo.flow; import java.awt.geom.Point2D; + +import org.xml.sax.Locator; + +import org.apache.xmlgraphics.util.QName; + import org.apache.fop.apps.FOPException; import org.apache.fop.datatypes.Length; +import org.apache.fop.events.ResourceEventProducer; import org.apache.fop.fo.FONode; import org.apache.fop.fo.ValidationException; import org.apache.fop.fo.XMLObj; -import org.xml.sax.Locator; /** * Class modelling the fo:instream-foreign-object object. @@ -39,6 +44,7 @@ public class InstreamForeignObject extends AbstractGraphics { //Additional value private Point2D intrinsicDimensions; + private boolean instrisicSizeDetermined; private Length intrinsicAlignmentAdjust; @@ -68,11 +74,11 @@ public class InstreamForeignObject extends AbstractGraphics { * XSL Content Model: one (1) non-XSL namespace child */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { + throws ValidationException { if (FO_URI.equals(nsURI)) { invalidChildError(loc, nsURI, localName); } else if (firstChild != null) { - tooManyNodesError(loc, "child element"); + tooManyNodesError(loc, new QName(nsURI, null, localName)); } } @@ -81,32 +87,28 @@ public class InstreamForeignObject extends AbstractGraphics { return "instream-foreign-object"; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public int getNameId() { return FO_INSTREAM_FOREIGN_OBJECT; } - /** - * Preloads the image so the intrinsic size is available. - */ + /** Preloads the image so the intrinsic size is available. */ private void prepareIntrinsicSize() { - if (intrinsicDimensions == null) { + if (!this.instrisicSizeDetermined) { XMLObj child = (XMLObj) firstChild; Point2D csize = new Point2D.Float(-1, -1); intrinsicDimensions = child.getDimension(csize); if (intrinsicDimensions == null) { - log.error("Intrinsic dimensions of " - + " instream-foreign-object could not be determined"); + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.ifoNoIntrinsicSize(this, getLocator()); } intrinsicAlignmentAdjust = child.getIntrinsicAlignmentAdjust(); + this.instrisicSizeDetermined = true; } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public int getIntrinsicWidth() { prepareIntrinsicSize(); if (intrinsicDimensions != null) { @@ -116,9 +118,7 @@ public class InstreamForeignObject extends AbstractGraphics { } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public int getIntrinsicHeight() { prepareIntrinsicSize(); if (intrinsicDimensions != null) { @@ -128,11 +128,8 @@ public class InstreamForeignObject extends AbstractGraphics { } } - /** - * {@inheritDoc} - */ - public Length getIntrinsicAlignmentAdjust() - { + /** {@inheritDoc} */ + public Length getIntrinsicAlignmentAdjust() { prepareIntrinsicSize(); return intrinsicAlignmentAdjust; } diff --git a/src/java/org/apache/fop/fo/flow/ListBlock.java b/src/java/org/apache/fop/fo/flow/ListBlock.java index 86c581cc3..a196e92de 100644 --- a/src/java/org/apache/fop/fo/flow/ListBlock.java +++ b/src/java/org/apache/fop/fo/flow/ListBlock.java @@ -108,15 +108,17 @@ public class ListBlock extends FObj { * XSL Content Model: marker* (list-item)+ */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { - if (FO_URI.equals(nsURI) && localName.equals("marker")) { - if (hasListItem) { - nodesOutOfOrderError(loc, "fo:marker", "fo:list-item"); + throws ValidationException { + if (FO_URI.equals(nsURI)) { + if (localName.equals("marker")) { + if (hasListItem) { + nodesOutOfOrderError(loc, "fo:marker", "fo:list-item"); + } + } else if (localName.equals("list-item")) { + hasListItem = true; + } else { + invalidChildError(loc, nsURI, localName); } - } else if (FO_URI.equals(nsURI) && localName.equals("list-item")) { - hasListItem = true; - } else { - invalidChildError(loc, nsURI, localName); } } diff --git a/src/java/org/apache/fop/fo/flow/ListItem.java b/src/java/org/apache/fop/fo/flow/ListItem.java index c09313ef6..cf0e05c56 100644 --- a/src/java/org/apache/fop/fo/flow/ListItem.java +++ b/src/java/org/apache/fop/fo/flow/ListItem.java @@ -98,22 +98,24 @@ public class ListItem extends FObj { */ protected void validateChildNode(Locator loc, String nsURI, String localName) throws ValidationException { - if (FO_URI.equals(nsURI) && localName.equals("marker")) { - if (label != null) { - nodesOutOfOrderError(loc, "fo:marker", "fo:list-item-label"); + if (FO_URI.equals(nsURI)) { + if (localName.equals("marker")) { + if (label != null) { + nodesOutOfOrderError(loc, "fo:marker", "fo:list-item-label"); + } + } else if (localName.equals("list-item-label")) { + if (label != null) { + tooManyNodesError(loc, "fo:list-item-label"); + } + } else if (localName.equals("list-item-body")) { + if (label == null) { + nodesOutOfOrderError(loc, "fo:list-item-label", "fo:list-item-body"); + } else if (body != null) { + tooManyNodesError(loc, "fo:list-item-body"); + } + } else { + invalidChildError(loc, nsURI, localName); } - } else if (FO_URI.equals(nsURI) && localName.equals("list-item-label")) { - if (label != null) { - tooManyNodesError(loc, "fo:list-item-label"); - } - } else if (FO_URI.equals(nsURI) && localName.equals("list-item-body")) { - if (label == null) { - nodesOutOfOrderError(loc, "fo:list-item-label", "fo:list-item-body"); - } else if (body != null) { - tooManyNodesError(loc, "fo:list-item-body"); - } - } else { - invalidChildError(loc, nsURI, localName); } } diff --git a/src/java/org/apache/fop/fo/flow/Marker.java b/src/java/org/apache/fop/fo/flow/Marker.java index 05c9862b2..168b18180 100644 --- a/src/java/org/apache/fop/fo/flow/Marker.java +++ b/src/java/org/apache/fop/fo/flow/Marker.java @@ -59,9 +59,8 @@ public class Marker extends FObjMixed { */ public void bind(PropertyList pList) throws FOPException { if (findAncestor(FO_FLOW) < 0) { - invalidChildError(locator, FO_URI, "marker", - "An fo:marker is permitted only as the descendant " - + "of an fo:flow"); + invalidChildError(locator, getParent().getName(), FO_URI, getName(), + "rule.markerDescendantOfFlow"); } markerClassName = pList.get(PR_MARKER_CLASS_NAME).getString(); @@ -112,9 +111,11 @@ public class Marker extends FObjMixed { * @todo implement "additional" constraint, possibly within fo:retrieve-marker */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { - if (!isBlockOrInlineItem(nsURI, localName)) { - invalidChildError(loc, nsURI, localName); + throws ValidationException { + if (FO_URI.equals(nsURI)) { + if (!isBlockOrInlineItem(nsURI, localName)) { + invalidChildError(loc, nsURI, localName); + } } } diff --git a/src/java/org/apache/fop/fo/flow/MultiCase.java b/src/java/org/apache/fop/fo/flow/MultiCase.java index 42ec9d9d0..e568fba46 100644 --- a/src/java/org/apache/fop/fo/flow/MultiCase.java +++ b/src/java/org/apache/fop/fo/flow/MultiCase.java @@ -46,7 +46,8 @@ public class MultiCase extends FObj { super(parent); if (!notImplementedWarningGiven) { - log.warn("fo:multi-case is not yet implemented."); + getFOValidationEventProducer().unimplementedFeature(this, getName(), + getName(), getLocator()); notImplementedWarningGiven = true; } } diff --git a/src/java/org/apache/fop/fo/flow/MultiProperties.java b/src/java/org/apache/fop/fo/flow/MultiProperties.java index 00cb85dc7..bd3bd893e 100644 --- a/src/java/org/apache/fop/fo/flow/MultiProperties.java +++ b/src/java/org/apache/fop/fo/flow/MultiProperties.java @@ -49,7 +49,8 @@ public class MultiProperties extends FObj { super(parent); if (!notImplementedWarningGiven) { - log.warn("fo:multi-properties is not yet implemented."); + getFOValidationEventProducer().unimplementedFeature(this, getName(), + getName(), getLocator()); notImplementedWarningGiven = true; } } @@ -69,13 +70,14 @@ public class MultiProperties extends FObj { */ protected void validateChildNode(Locator loc, String nsURI, String localName) throws ValidationException { - if (FO_URI.equals(nsURI) && localName.equals("multi-property-set")) { + if (FO_URI.equals(nsURI)) { + if (localName.equals("multi-property-set")) { if (hasWrapper) { nodesOutOfOrderError(loc, "fo:multi-property-set", "fo:wrapper"); } else { hasMultiPropertySet = true; } - } else if (FO_URI.equals(nsURI) && localName.equals("wrapper")) { + } else if (localName.equals("wrapper")) { if (hasWrapper) { tooManyNodesError(loc, "fo:wrapper"); } else { @@ -84,6 +86,7 @@ public class MultiProperties extends FObj { } else { invalidChildError(loc, nsURI, localName); } + } } /** {@inheritDoc} */ diff --git a/src/java/org/apache/fop/fo/flow/MultiPropertySet.java b/src/java/org/apache/fop/fo/flow/MultiPropertySet.java index 3c9c55b4f..caa31f7b9 100644 --- a/src/java/org/apache/fop/fo/flow/MultiPropertySet.java +++ b/src/java/org/apache/fop/fo/flow/MultiPropertySet.java @@ -45,7 +45,8 @@ public class MultiPropertySet extends FObj { super(parent); if (!notImplementedWarningGiven) { - log.warn("fo:multi-property-set is not yet implemented."); + getFOValidationEventProducer().unimplementedFeature(this, getName(), + getName(), getLocator()); notImplementedWarningGiven = true; } } @@ -63,8 +64,10 @@ public class MultiPropertySet extends FObj { * XSL Content Model: empty */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { + throws ValidationException { + if (FO_URI.equals(nsURI)) { invalidChildError(loc, nsURI, localName); + } } /** {@inheritDoc} */ diff --git a/src/java/org/apache/fop/fo/flow/MultiSwitch.java b/src/java/org/apache/fop/fo/flow/MultiSwitch.java index 7c70a7346..03f404aa3 100644 --- a/src/java/org/apache/fop/fo/flow/MultiSwitch.java +++ b/src/java/org/apache/fop/fo/flow/MultiSwitch.java @@ -47,7 +47,8 @@ public class MultiSwitch extends FObj { super(parent); if (!notImplementedWarningGiven) { - log.warn("fo:multi-switch is not yet implemented."); + getFOValidationEventProducer().unimplementedFeature(this, getName(), + getName(), getLocator()); notImplementedWarningGiven = true; } } @@ -75,9 +76,11 @@ public class MultiSwitch extends FObj { * XSL Content Model: (multi-case+) */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { - if (!(FO_URI.equals(nsURI) && localName.equals("multi-case"))) { - invalidChildError(loc, nsURI, localName); + throws ValidationException { + if (FO_URI.equals(nsURI)) { + if (!localName.equals("multi-case")) { + invalidChildError(loc, nsURI, localName); + } } } diff --git a/src/java/org/apache/fop/fo/flow/MultiToggle.java b/src/java/org/apache/fop/fo/flow/MultiToggle.java index 80b36f9f4..66442c2a7 100644 --- a/src/java/org/apache/fop/fo/flow/MultiToggle.java +++ b/src/java/org/apache/fop/fo/flow/MultiToggle.java @@ -47,7 +47,8 @@ public class MultiToggle extends FObj { super(parent); if (!notImplementedWarningGiven) { - log.warn("fo:multi-toggle is not yet implemented."); + getFOValidationEventProducer().unimplementedFeature(this, getName(), + getName(), getLocator()); notImplementedWarningGiven = true; } } @@ -65,9 +66,11 @@ public class MultiToggle extends FObj { * XSL Content Model: (#PCDATA|%inline;|%block;)* */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { - if (!isBlockOrInlineItem(nsURI, localName)) { - invalidChildError(loc, nsURI, localName); + throws ValidationException { + if (FO_URI.equals(nsURI)) { + if (!isBlockOrInlineItem(nsURI, localName)) { + invalidChildError(loc, nsURI, localName); + } } } diff --git a/src/java/org/apache/fop/fo/flow/PageNumber.java b/src/java/org/apache/fop/fo/flow/PageNumber.java index 3eca1e16e..cc51dd28a 100644 --- a/src/java/org/apache/fop/fo/flow/PageNumber.java +++ b/src/java/org/apache/fop/fo/flow/PageNumber.java @@ -117,8 +117,10 @@ public class PageNumber extends FObj { * XSL Content Model: empty */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { + throws ValidationException { + if (FO_URI.equals(nsURI)) { invalidChildError(loc, nsURI, localName); + } } /** @return the Common Font Properties. */ diff --git a/src/java/org/apache/fop/fo/flow/RetrieveMarker.java b/src/java/org/apache/fop/fo/flow/RetrieveMarker.java index 7356bc72e..ea6b6f1c5 100644 --- a/src/java/org/apache/fop/fo/flow/RetrieveMarker.java +++ b/src/java/org/apache/fop/fo/flow/RetrieveMarker.java @@ -21,6 +21,8 @@ package org.apache.fop.fo.flow; import java.util.Iterator; +import org.xml.sax.Locator; + import org.apache.fop.apps.FOPException; import org.apache.fop.fo.FONode; import org.apache.fop.fo.FOText; @@ -30,7 +32,6 @@ import org.apache.fop.fo.PropertyList; import org.apache.fop.fo.ValidationException; import org.apache.fop.fo.flow.table.Table; import org.apache.fop.fo.flow.table.TableFObj; -import org.xml.sax.Locator; /** * Class modelling the fo:retrieve-marker object. @@ -48,21 +49,18 @@ public class RetrieveMarker extends FObjMixed { /** * Create a retrieve marker object. - * + * @param parent FONode that is the parent of this object * @see org.apache.fop.fo.FONode#FONode(FONode) */ public RetrieveMarker(FONode parent) { super(parent); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void bind(PropertyList pList) throws FOPException { if (findAncestor(FO_STATIC_CONTENT) < 0) { - invalidChildError(locator, FO_URI, "retrieve-marker", - "An fo:retrieve-marker is permitted only as the " + - " descendant of an fo:static-content."); + invalidChildError(locator, getParent().getName(), FO_URI, getName(), + "rule.retrieveMarkerDescendatOfStaticContent"); } retrieveClassName = pList.get(PR_RETRIEVE_CLASS_NAME).getString(); @@ -81,8 +79,10 @@ public class RetrieveMarker extends FObjMixed { * XSL Content Model: empty */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { + throws ValidationException { + if (FO_URI.equals(nsURI)) { invalidChildError(loc, nsURI, localName); + } } /** @@ -206,13 +206,12 @@ public class RetrieveMarker extends FObjMixed { try { cloneFromMarker(marker); } catch (FOPException exc) { - log.error("fo:retrieve-marker unable to clone " - + "subtree of fo:marker (marker-class-name=" - + marker.getMarkerClassName() + ")", exc); + getFOValidationEventProducer().markerCloningFailed(this, + marker.getMarkerClassName(), exc, getLocator()); return; } - } else if (log.isInfoEnabled()) { - log.info("Empty marker retrieved..."); + } else if (log.isDebugEnabled()) { + log.debug("Empty marker retrieved..."); } return; } @@ -222,9 +221,7 @@ public class RetrieveMarker extends FObjMixed { return "retrieve-marker"; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public int getNameId() { return FO_RETRIEVE_MARKER; } diff --git a/src/java/org/apache/fop/fo/flow/Wrapper.java b/src/java/org/apache/fop/fo/flow/Wrapper.java index adf9b2101..87582fb47 100644 --- a/src/java/org/apache/fop/fo/flow/Wrapper.java +++ b/src/java/org/apache/fop/fo/flow/Wrapper.java @@ -19,12 +19,13 @@ package org.apache.fop.fo.flow; +import org.xml.sax.Locator; + import org.apache.fop.apps.FOPException; import org.apache.fop.fo.FONode; import org.apache.fop.fo.FObjMixed; import org.apache.fop.fo.PropertyList; import org.apache.fop.fo.ValidationException; -import org.xml.sax.Locator; /** * Class modelling the fo:wrapper object. @@ -66,17 +67,19 @@ public class Wrapper extends FObjMixed { */ protected void validateChildNode(Locator loc, String nsURI, String localName) throws ValidationException { - if (FO_URI.equals(nsURI) && "marker".equals(localName)) { - if (blockOrInlineItemFound) { - nodesOutOfOrderError(loc, "fo:marker", - "(#PCDATA|%inline;|%block;)"); + if (FO_URI.equals(nsURI)) { + if ("marker".equals(localName)) { + if (blockOrInlineItemFound) { + nodesOutOfOrderError(loc, "fo:marker", + "(#PCDATA|%inline;|%block;)"); + } + } else if (isBlockOrInlineItem(nsURI, localName)) { + //delegate validation to parent + FONode.validateChildNode(this.parent, loc, nsURI, localName); + blockOrInlineItemFound = true; + } else { + invalidChildError(loc, nsURI, localName); } - } else if (isBlockOrInlineItem(nsURI, localName)) { - //delegate validation to parent - FONode.validateChildNode(this.parent, loc, nsURI, localName); - blockOrInlineItemFound = true; - } else { - invalidChildError(loc, nsURI, localName); } } diff --git a/src/java/org/apache/fop/fo/flow/table/FixedColRowGroupBuilder.java b/src/java/org/apache/fop/fo/flow/table/FixedColRowGroupBuilder.java index 28a30c6f7..0d24491d9 100644 --- a/src/java/org/apache/fop/fo/flow/table/FixedColRowGroupBuilder.java +++ b/src/java/org/apache/fop/fo/flow/table/FixedColRowGroupBuilder.java @@ -120,13 +120,17 @@ class FixedColRowGroupBuilder extends RowGroupBuilder { void endTableRow() { assert currentTableRow != null; if (currentRowIndex > 0 && currentTableRow.getBreakBefore() != Constants.EN_AUTO) { - currentTableRow.attributeWarning("break-before ignored because of row spanning " - + "in progress (See XSL 1.1, 7.20.2)"); + TableEventProducer eventProducer = TableEventProducer.Provider.get( + currentTableRow.getUserAgent().getEventBroadcaster()); + eventProducer.breakIgnoredDueToRowSpanning(this, currentTableRow.getName(), true, + currentTableRow.getLocator()); } if (currentRowIndex < rows.size() - 1 && currentTableRow.getBreakAfter() != Constants.EN_AUTO) { - currentTableRow.attributeWarning("break-after ignored because of row spanning " - + "in progress (See XSL 1.1, 7.20.1)"); + TableEventProducer eventProducer = TableEventProducer.Provider.get( + currentTableRow.getUserAgent().getEventBroadcaster()); + eventProducer.breakIgnoredDueToRowSpanning(this, currentTableRow.getName(), false, + currentTableRow.getLocator()); } for (Iterator iter = ((List) rows.get(currentRowIndex)).iterator(); iter.hasNext();) { GridUnit gu = (GridUnit) iter.next(); diff --git a/src/java/org/apache/fop/fo/flow/table/Table.java b/src/java/org/apache/fop/fo/flow/table/Table.java index 7d6611435..c1ef3857c 100644 --- a/src/java/org/apache/fop/fo/flow/table/Table.java +++ b/src/java/org/apache/fop/fo/flow/table/Table.java @@ -22,6 +22,8 @@ package org.apache.fop.fo.flow.table; import java.util.ArrayList; import java.util.List; +import org.xml.sax.Locator; + import org.apache.fop.apps.FOPException; import org.apache.fop.datatypes.Length; import org.apache.fop.datatypes.ValidationPercentBaseContext; @@ -35,7 +37,6 @@ import org.apache.fop.fo.properties.KeepProperty; import org.apache.fop.fo.properties.LengthPairProperty; import org.apache.fop.fo.properties.LengthRangeProperty; import org.apache.fop.fo.properties.TableColLength; -import org.xml.sax.Locator; /** * Class modelling the fo:table object. @@ -126,20 +127,22 @@ public class Table extends TableFObj implements ColumnNumberManagerHolder { orphanContentLimit = pList.get(PR_X_ORPHAN_CONTENT_LIMIT).getLength(); if (!blockProgressionDimension.getOptimum(null).isAuto()) { - attributeWarning("only a value of \"auto\" for block-progression-dimension has a" - + " well-specified behavior on fo:table. Falling back to \"auto\""); + TableEventProducer eventProducer = TableEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.nonAutoBPDOnTable(this, getLocator()); // Anyway, the bpd of a table is not used by the layout code } if (tableLayout == EN_AUTO) { - attributeWarning("table-layout=\"auto\" is currently not supported by FOP"); + getFOValidationEventProducer().unimplementedFeature(this, getName(), + "table-layout=\"auto\"", getLocator()); } if (!isSeparateBorderModel() && getCommonBorderPaddingBackground().hasPadding( ValidationPercentBaseContext.getPseudoContext())) { //See "17.6.2 The collapsing border model" in CSS2 - attributeWarning("In collapsing border model a table does not have padding" - + " (see http://www.w3.org/TR/REC-CSS2/tables.html#collapsing-borders)" - + ", but a non-zero value for padding was found. The padding will be ignored."); + TableEventProducer eventProducer = TableEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.noTablePaddingWithCollapsingBorderModel(this, getLocator()); } /* Store reference to the property list, so @@ -163,7 +166,7 @@ public class Table extends TableFObj implements ColumnNumberManagerHolder { * XSL Content Model: (marker*,table-column*,table-header?,table-footer?,table-body+) */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { + throws ValidationException { if (FO_URI.equals(nsURI)) { if ("marker".equals(localName)) { if (tableColumnFound || tableHeaderFound || tableFooterFound @@ -193,15 +196,11 @@ public class Table extends TableFObj implements ColumnNumberManagerHolder { } else { tableFooterFound = true; if (tableBodyFound) { - if (getUserAgent().validateStrictly()) { - nodesOutOfOrderError(loc, "fo:table-footer", "(table-body+)"); - } else if (!isSeparateBorderModel()) { - nodesOutOfOrderError(loc, "fo:table-footer", "(table-body+)." - + " This table uses the collapsing border" - + " model. In order to resolve borders in an efficient way" - + " the table-footer must be known before any table-body" - + " is parsed. Either put the footer at the correct place" - + " or switch to the separate border model"); + nodesOutOfOrderError(loc, "fo:table-footer", "(table-body+)", true); + if (!isSeparateBorderModel()) { + TableEventProducer eventProducer = TableEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.footerOrderCannotRecover(this, getName(), getLocator()); } } } @@ -210,8 +209,6 @@ public class Table extends TableFObj implements ColumnNumberManagerHolder { } else { invalidChildError(loc, nsURI, localName); } - } else { - invalidChildError(loc, nsURI, localName); } } @@ -225,6 +222,10 @@ public class Table extends TableFObj implements ColumnNumberManagerHolder { "(marker*,table-column*,table-header?,table-footer?" + ",table-body+)"); } + if (!hasChildren()) { + getParent().removeChild(this); + return; + } if (!inMarker()) { rowGroupBuilder.endTable(); /* clean up */ diff --git a/src/java/org/apache/fop/fo/flow/table/TableAndCaption.java b/src/java/org/apache/fop/fo/flow/table/TableAndCaption.java index f16931cfc..6dabf37db 100644 --- a/src/java/org/apache/fop/fo/flow/table/TableAndCaption.java +++ b/src/java/org/apache/fop/fo/flow/table/TableAndCaption.java @@ -62,7 +62,8 @@ public class TableAndCaption extends FObj { super(parent); if (!notImplementedWarningGiven) { - log.warn("fo:table-and-caption is not yet implemented."); + getFOValidationEventProducer().unimplementedFeature(this, getName(), + "fo:table-and-caption", getLocator()); notImplementedWarningGiven = true; } } @@ -83,30 +84,32 @@ public class TableAndCaption extends FObj { * XSL Content Model: marker* table-caption? table */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { + throws ValidationException { - if (FO_URI.equals(nsURI) && localName.equals("marker")) { - if (tableCaptionFound) { - nodesOutOfOrderError(loc, "fo:marker", "fo:table-caption"); - } else if (tableFound) { - nodesOutOfOrderError(loc, "fo:marker", "fo:table"); - } - } else if (FO_URI.equals(nsURI) && localName.equals("table-caption")) { - if (tableCaptionFound) { - tooManyNodesError(loc, "fo:table-caption"); - } else if (tableFound) { - nodesOutOfOrderError(loc, "fo:table-caption", "fo:table"); - } else { - tableCaptionFound = true; - } - } else if (FO_URI.equals(nsURI) && localName.equals("table")) { - if (tableFound) { - tooManyNodesError(loc, "fo:table"); + if (FO_URI.equals(nsURI)) { + if (localName.equals("marker")) { + if (tableCaptionFound) { + nodesOutOfOrderError(loc, "fo:marker", "fo:table-caption"); + } else if (tableFound) { + nodesOutOfOrderError(loc, "fo:marker", "fo:table"); + } + } else if (localName.equals("table-caption")) { + if (tableCaptionFound) { + tooManyNodesError(loc, "fo:table-caption"); + } else if (tableFound) { + nodesOutOfOrderError(loc, "fo:table-caption", "fo:table"); + } else { + tableCaptionFound = true; + } + } else if (localName.equals("table")) { + if (tableFound) { + tooManyNodesError(loc, "fo:table"); + } else { + tableFound = true; + } } else { - tableFound = true; + invalidChildError(loc, nsURI, localName); } - } else { - invalidChildError(loc, nsURI, localName); } } diff --git a/src/java/org/apache/fop/fo/flow/table/TableBody.java b/src/java/org/apache/fop/fo/flow/table/TableBody.java index de7bfda84..4e1673568 100644 --- a/src/java/org/apache/fop/fo/flow/table/TableBody.java +++ b/src/java/org/apache/fop/fo/flow/table/TableBody.java @@ -23,13 +23,14 @@ import java.util.ArrayList; import java.util.LinkedList; import java.util.List; +import org.xml.sax.Attributes; +import org.xml.sax.Locator; + import org.apache.fop.apps.FOPException; import org.apache.fop.fo.FONode; import org.apache.fop.fo.PropertyList; import org.apache.fop.fo.ValidationException; import org.apache.fop.fo.properties.CommonBorderPaddingBackground; -import org.xml.sax.Attributes; -import org.xml.sax.Locator; /** * Class modelling the fo:table-body object. @@ -119,13 +120,8 @@ public class TableBody extends TableCellContainer { getFOEventHandler().endBody(this); if (!(tableRowsFound || tableCellsFound)) { - if (getUserAgent().validateStrictly()) { - missingChildElementError("marker* (table-row+|table-cell+)"); - } else { - log.error("fo:table-body must not be empty. " - + "Expected: marker* (table-row+|table-cell+)"); - getParent().removeChild(this); - } + missingChildElementError("marker* (table-row+|table-cell+)", true); + getParent().removeChild(this); } else { finishLastRowGroup(); } @@ -167,23 +163,20 @@ public class TableBody extends TableCellContainer { } else if (localName.equals("table-row")) { tableRowsFound = true; if (tableCellsFound) { - invalidChildError(loc, nsURI, localName, "Either fo:table-rows" - + " or fo:table-cells may be children of an " + getName() - + " but not both"); + TableEventProducer eventProducer = TableEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.noMixRowsAndCells(this, getName(), getLocator()); } } else if (localName.equals("table-cell")) { tableCellsFound = true; if (tableRowsFound) { - invalidChildError(loc, nsURI, localName, - "Either fo:table-rows or fo:table-cells " - + "may be children of an " - + getName() + " but not both"); + TableEventProducer eventProducer = TableEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.noMixRowsAndCells(this, getName(), getLocator()); } } else { invalidChildError(loc, nsURI, localName); } - } else { - invalidChildError(loc, nsURI, localName); } } diff --git a/src/java/org/apache/fop/fo/flow/table/TableCaption.java b/src/java/org/apache/fop/fo/flow/table/TableCaption.java index 28174067c..416ef16ed 100644 --- a/src/java/org/apache/fop/fo/flow/table/TableCaption.java +++ b/src/java/org/apache/fop/fo/flow/table/TableCaption.java @@ -57,7 +57,8 @@ public class TableCaption extends FObj { super(parent); if (!notImplementedWarningGiven) { - log.warn("fo:table-caption is not yet implemented."); + getFOValidationEventProducer().unimplementedFeature(this, getName(), + "fo:table-caption", getLocator()); notImplementedWarningGiven = true; } } @@ -83,15 +84,17 @@ public class TableCaption extends FObj { * XSL Content Model: marker* (%block;) */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { - if (FO_URI.equals(nsURI) && localName.equals("marker")) { - if (blockItemFound) { - nodesOutOfOrderError(loc, "fo:marker", "(%block;)"); + throws ValidationException { + if (FO_URI.equals(nsURI)) { + if (localName.equals("marker")) { + if (blockItemFound) { + nodesOutOfOrderError(loc, "fo:marker", "(%block;)"); + } + } else if (!isBlockItem(nsURI, localName)) { + invalidChildError(loc, nsURI, localName); + } else { + blockItemFound = true; } - } else if (!isBlockItem(nsURI, localName)) { - invalidChildError(loc, nsURI, localName); - } else { - blockItemFound = true; } } diff --git a/src/java/org/apache/fop/fo/flow/table/TableCell.java b/src/java/org/apache/fop/fo/flow/table/TableCell.java index 80dbe5e2a..78e35eb52 100644 --- a/src/java/org/apache/fop/fo/flow/table/TableCell.java +++ b/src/java/org/apache/fop/fo/flow/table/TableCell.java @@ -102,17 +102,13 @@ public class TableCell extends TableFObj { */ public void endOfNode() throws FOPException { if (!blockItemFound) { - if (getUserAgent().validateStrictly()) { - missingChildElementError("marker* (%block;)+"); - } else if (firstChild != null) { - log.warn("fo:table-cell content that is not " - + "enclosed by a fo:block will be dropped/ignored."); - } + missingChildElementError("marker* (%block;)+", true); } if ((startsRow() || endsRow()) && getParent().getNameId() == FO_TABLE_ROW ) { - log.warn("starts-row/ends-row for fo:table-cells " - + "non-applicable for children of an fo:table-row."); + TableEventProducer eventProducer = TableEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.startEndRowUnderTableRowWarning(this, getLocator()); } getFOEventHandler().endCell(this); } @@ -123,14 +119,16 @@ public class TableCell extends TableFObj { */ protected void validateChildNode(Locator loc, String nsURI, String localName) throws ValidationException { - if (FO_URI.equals(nsURI) && localName.equals("marker")) { - if (blockItemFound) { - nodesOutOfOrderError(loc, "fo:marker", "(%block;)"); + if (FO_URI.equals(nsURI)) { + if (localName.equals("marker")) { + if (blockItemFound) { + nodesOutOfOrderError(loc, "fo:marker", "(%block;)"); + } + } else if (!isBlockItem(nsURI, localName)) { + invalidChildError(loc, nsURI, localName); + } else { + blockItemFound = true; } - } else if (!isBlockItem(nsURI, localName)) { - invalidChildError(loc, nsURI, localName); - } else { - blockItemFound = true; } } diff --git a/src/java/org/apache/fop/fo/flow/table/TableCellContainer.java b/src/java/org/apache/fop/fo/flow/table/TableCellContainer.java index 7c91be351..de9f271b5 100644 --- a/src/java/org/apache/fop/fo/flow/table/TableCellContainer.java +++ b/src/java/org/apache/fop/fo/flow/table/TableCellContainer.java @@ -24,7 +24,6 @@ import java.util.List; import org.apache.fop.apps.FOPException; import org.apache.fop.datatypes.Length; import org.apache.fop.fo.FONode; -import org.apache.fop.fo.ValidationException; /** * A common class for fo:table-body and fo:table-row which both can contain fo:table-cell. @@ -47,9 +46,9 @@ public abstract class TableCellContainer extends TableFObj implements ColumnNumb Table t = getTable(); if (t.hasExplicitColumns()) { if (colNumber + colSpan - 1 > t.getNumberOfColumns()) { - throw new ValidationException(FONode.errorText(locator) + "column-number or " - + "number of cells in the row overflows the number of fo:table-column " - + "specified for the table."); + TableEventProducer eventProducer = TableEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.tooManyCells(this, getLocator()); } } else { t.ensureColumnNumber(colNumber + colSpan - 1); diff --git a/src/java/org/apache/fop/fo/flow/table/TableColumn.java b/src/java/org/apache/fop/fo/flow/table/TableColumn.java index aeb401893..e6f6b420e 100644 --- a/src/java/org/apache/fop/fo/flow/table/TableColumn.java +++ b/src/java/org/apache/fop/fo/flow/table/TableColumn.java @@ -82,12 +82,16 @@ public class TableColumn extends TableFObj { super.bind(pList); if (numberColumnsRepeated <= 0) { - throw new PropertyException("number-columns-repeated must be 1 or bigger, " - + "but got " + numberColumnsRepeated); + TableEventProducer eventProducer = TableEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.valueMustBeBiggerGtEqOne(this, + "number-columns-repeated", numberColumnsRepeated, getLocator()); } if (numberColumnsSpanned <= 0) { - throw new PropertyException("number-columns-spanned must be 1 or bigger, " - + "but got " + numberColumnsSpanned); + TableEventProducer eventProducer = TableEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.valueMustBeBiggerGtEqOne(this, + "number-columns-spanned", numberColumnsSpanned, getLocator()); } /* check for unspecified width and replace with default of @@ -96,8 +100,9 @@ public class TableColumn extends TableFObj { */ if (columnWidth.getEnum() == EN_AUTO) { if (!this.implicitColumn && !getTable().isAutoLayout()) { - log.warn("table-layout=\"fixed\" and column-width unspecified " - + "=> falling back to proportional-column-width(1)"); + TableEventProducer eventProducer = TableEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.warnImplicitColumns(this, getLocator()); } columnWidth = new TableColLength(1.0, this); } @@ -146,7 +151,9 @@ public class TableColumn extends TableFObj { protected void validateChildNode(Locator loc, String nsURI, String localName) throws ValidationException { + if (FO_URI.equals(nsURI)) { invalidChildError(loc, nsURI, localName); + } } /** @@ -240,7 +247,7 @@ public class TableColumn extends TableFObj { * * @param propId the id for the property to retrieve * @return the requested Property - * @throws PropertyException + * @throws PropertyException if there is a problem evaluating the property */ public Property getProperty(int propId) throws PropertyException { return this.pList.get(propId); diff --git a/src/java/org/apache/fop/fo/flow/table/TableEventProducer.java b/src/java/org/apache/fop/fo/flow/table/TableEventProducer.java new file mode 100644 index 000000000..44ddcc038 --- /dev/null +++ b/src/java/org/apache/fop/fo/flow/table/TableEventProducer.java @@ -0,0 +1,159 @@ +/* + * 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.flow.table; + +import org.xml.sax.Locator; + +import org.apache.fop.events.EventBroadcaster; +import org.apache.fop.events.EventProducer; +import org.apache.fop.fo.ValidationException; +import org.apache.fop.fo.expr.PropertyException; + +/** + * Event producer interface for table-specific XSL-FO validation messages. + */ +public interface TableEventProducer extends EventProducer { + + /** Provider class for the event producer. */ + class Provider { + + /** + * Returns an event producer. + * @param broadcaster the event broadcaster to use + * @return the event producer + */ + public static TableEventProducer get(EventBroadcaster broadcaster) { + return (TableEventProducer)broadcaster.getEventProducerFor( + TableEventProducer.class); + } + } + + /** + * A value other than "auto" has been specified on fo:table. + * @param source the event source + * @param loc the location of the error or null + * @event.severity WARN + */ + void nonAutoBPDOnTable(Object source, Locator loc); + + /** + * Padding on fo:table is ignored if the collapsing border model is active. + * @param source the event source + * @param loc the location of the error or null + * @event.severity WARN + */ + void noTablePaddingWithCollapsingBorderModel(Object source, Locator loc); + + /** + * No mixing of table-rows and table-cells is allowed for direct children of table-body. + * @param source the event source + * @param elementName the name of the context node + * @param loc the location of the error or null + * @throws ValidationException the validation error provoked by the method call + * @event.severity FATAL + */ + void noMixRowsAndCells(Object source, String elementName, Locator loc) + throws ValidationException; + + /** + * The table-footer was found after the table-body. FOP cannot recover with collapsed border + * model. + * @param source the event source + * @param elementName the name of the context node + * @param loc the location of the error or null + * @throws ValidationException the validation error provoked by the method call + * @event.severity FATAL + */ + void footerOrderCannotRecover(Object source, String elementName, Locator loc) + throws ValidationException; + + /** + * starts-row/ends-row for fo:table-cells non-applicable for children of an fo:table-row + * @param source the event source + * @param loc the location of the error or null + * @event.severity WARN + */ + void startEndRowUnderTableRowWarning(Object source, Locator loc); + + /** + * Column-number or number of cells in the row overflows the number of fo:table-column + * specified for the table. + * @param source the event source + * @param loc the location of the error or null + * @throws ValidationException the validation error provoked by the method call + * @event.severity FATAL + */ + void tooManyCells(Object source, Locator loc) throws ValidationException; + + /** + * Property value must be 1 or bigger. + * @param source the event source + * @param propName the property name + * @param actualValue the actual value + * @param loc the location of the error or null + * @throws PropertyException the property error provoked by the method call + * @event.severity FATAL + */ + void valueMustBeBiggerGtEqOne(Object source, String propName, + int actualValue, Locator loc) throws PropertyException; + + /** + * table-layout=\"fixed\" and column-width unspecified + * => falling back to proportional-column-width(1) + * @param source the event source + * @param loc the location of the error or null + * @event.severity WARN + */ + void warnImplicitColumns(Object source, Locator loc); + + /** + * padding-* properties are not applicable. + * @param source the event source + * @param elementName the name of the context node + * @param loc the location of the error or null + * @event.severity WARN + */ + void paddingNotApplicable(Object source, String elementName, Locator loc); + + /** + * Cell overlap. + * @param source the event source + * @param elementName the name of the context node + * @param column the column index of the overlapping cell + * @param loc the location of the error or null + * @throws PropertyException the property error provoked by the method call + * @event.severity FATAL + */ + void cellOverlap(Object source, String elementName, int column, + Locator loc) throws PropertyException; + + /** + * Break ignored due to row spanning. + * @param source the event source + * @param elementName the name of the context node + * @param breakBefore true for "break-before", false for "break-after" + * @param loc the location of the error or null + * @event.severity WARN + */ + void breakIgnoredDueToRowSpanning(Object source, String elementName, boolean breakBefore, + Locator loc); + + +} diff --git a/src/java/org/apache/fop/fo/flow/table/TableFObj.java b/src/java/org/apache/fop/fo/flow/table/TableFObj.java index 9618d7ff4..24528f622 100644 --- a/src/java/org/apache/fop/fo/flow/table/TableFObj.java +++ b/src/java/org/apache/fop/fo/flow/table/TableFObj.java @@ -58,9 +58,7 @@ public abstract class TableFObj extends FObj { super(parent); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void bind(PropertyList pList) throws FOPException { super.bind(pList); borderAfterPrecedence = pList.get(PR_BORDER_AFTER_PRECEDENCE).getNumeric(); @@ -71,9 +69,9 @@ public abstract class TableFObj extends FObj { && getNameId() != FO_TABLE_CELL && getCommonBorderPaddingBackground().hasPadding( ValidationPercentBaseContext.getPseudoContext())) { - attributeWarning( - "padding-* properties are not applicable to " + getName() - + ", but a non-zero value for padding was found."); + TableEventProducer eventProducer = TableEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.paddingNotApplicable(this, getName(), getLocator()); } } @@ -156,17 +154,19 @@ public abstract class TableFObj extends FObj { ColumnNumberManager columnIndexManager = parent.getColumnNumberManager(); int columnIndex = p.getNumeric().getValue(); if (columnIndex <= 0) { + /* No warning necessary as the spec clearly defines how to handle these cases. log.warn("Specified negative or zero value for " + "column-number on " + fo.getName() + ": " + columnIndex + " forced to " - + columnIndexManager.getCurrentColumnNumber()); + + columnIndexManager.getCurrentColumnNumber());*/ return NumberProperty.getInstance(columnIndexManager.getCurrentColumnNumber()); } else { double tmpIndex = p.getNumeric().getNumericValue(); if (tmpIndex - columnIndex > 0.0) { columnIndex = (int) Math.round(tmpIndex); + /* No warning necessary as the spec clearly defines how to handle these cases. log.warn("Rounding specified column-number of " - + tmpIndex + " to " + columnIndex); + + tmpIndex + " to " + columnIndex);*/ p = NumberProperty.getInstance(columnIndex); } } @@ -179,16 +179,9 @@ public abstract class TableFObj extends FObj { /* if column-number is already in use by another * cell/column => error! */ - StringBuffer errorMessage = new StringBuffer(); - errorMessage.append(fo.getName() + " overlaps in column ") - .append(columnIndex + i); - org.xml.sax.Locator loc = fo.getLocator(); - if (loc != null && loc.getLineNumber() != -1) { - errorMessage.append(" (line #") - .append(loc.getLineNumber()).append(", column #") - .append(loc.getColumnNumber()).append(")"); - } - throw new PropertyException(errorMessage.toString()); + TableEventProducer eventProducer = TableEventProducer.Provider.get( + fo.getUserAgent().getEventBroadcaster()); + eventProducer.cellOverlap(this, fo.getName(), columnIndex + 1, fo.getLocator()); } } diff --git a/src/java/org/apache/fop/fo/flow/table/TableRow.java b/src/java/org/apache/fop/fo/flow/table/TableRow.java index a025f92fd..e5261614b 100644 --- a/src/java/org/apache/fop/fo/flow/table/TableRow.java +++ b/src/java/org/apache/fop/fo/flow/table/TableRow.java @@ -19,6 +19,9 @@ package org.apache.fop.fo.flow.table; +import org.xml.sax.Attributes; +import org.xml.sax.Locator; + import org.apache.fop.apps.FOPException; import org.apache.fop.datatypes.Length; import org.apache.fop.fo.FONode; @@ -27,8 +30,6 @@ import org.apache.fop.fo.ValidationException; import org.apache.fop.fo.properties.CommonBorderPaddingBackground; import org.apache.fop.fo.properties.KeepProperty; import org.apache.fop.fo.properties.LengthRangeProperty; -import org.xml.sax.Attributes; -import org.xml.sax.Locator; /** * Class modelling the fo:table-row object. @@ -122,9 +123,11 @@ public class TableRow extends TableCellContainer { */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { - if (!(FO_URI.equals(nsURI) && localName.equals("table-cell"))) { - invalidChildError(loc, nsURI, localName); + throws ValidationException { + if (FO_URI.equals(nsURI)) { + if (!localName.equals("table-cell")) { + invalidChildError(loc, nsURI, localName); + } } } diff --git a/src/java/org/apache/fop/fo/pagination/AbstractPageSequence.java b/src/java/org/apache/fop/fo/pagination/AbstractPageSequence.java index 70a09b720..578d74c4d 100644 --- a/src/java/org/apache/fop/fo/pagination/AbstractPageSequence.java +++ b/src/java/org/apache/fop/fo/pagination/AbstractPageSequence.java @@ -32,7 +32,9 @@ import org.apache.fop.fo.PropertyList; public abstract class AbstractPageSequence extends FObj { // The value of properties relevant for fo:page-sequence. + /** the initial-page-number value */ protected Numeric initialPageNumber; + /** the force-page-count value */ protected int forcePageCount; private String format; private int letterValue; @@ -43,6 +45,7 @@ public abstract class AbstractPageSequence extends FObj { private PageNumberGenerator pageNumberGenerator; + /** the first page number generated by the page sequence */ protected int startingPageNumber = 0; /** @@ -54,9 +57,7 @@ public abstract class AbstractPageSequence extends FObj { super(parent); } - /** - * @see org.apache.fop.fo.FObj#bind(PropertyList) - */ + /** {@inheritDoc} */ public void bind(PropertyList pList) throws FOPException { super.bind(pList); initialPageNumber = pList.get(PR_INITIAL_PAGE_NUMBER).getNumeric(); @@ -68,16 +69,14 @@ public abstract class AbstractPageSequence extends FObj { referenceOrientation = pList.get(PR_REFERENCE_ORIENTATION).getNumeric(); } - /** - * @see org.apache.fop.fo.FONode#startOfNode() - */ + /** {@inheritDoc} */ protected void startOfNode() throws FOPException { this.pageNumberGenerator = new PageNumberGenerator( format, groupingSeparator, groupingSize, letterValue); } - /** @see org.apache.fop.fo.FONode#endOfNode() */ + /** {@inheritDoc} */ protected void endOfNode() throws FOPException { } diff --git a/src/java/org/apache/fop/fo/pagination/ColorProfile.java b/src/java/org/apache/fop/fo/pagination/ColorProfile.java index 9318a8896..6067b55c5 100644 --- a/src/java/org/apache/fop/fo/pagination/ColorProfile.java +++ b/src/java/org/apache/fop/fo/pagination/ColorProfile.java @@ -19,14 +19,14 @@ package org.apache.fop.fo.pagination; +import org.xml.sax.Locator; + import org.apache.fop.apps.FOPException; import org.apache.fop.fo.FONode; import org.apache.fop.fo.FObj; import org.apache.fop.fo.PropertyList; import org.apache.fop.fo.ValidationException; -import org.xml.sax.Locator; - /** * The fo:color-profile formatting object. * This loads the color profile when needed and resolves a requested color. @@ -39,15 +39,15 @@ public class ColorProfile extends FObj { // End of property values /** + * Creates a new color-profile element. + * @param parent the parent node * @see org.apache.fop.fo.FONode#FONode(FONode) */ public ColorProfile(FONode parent) { super(parent); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void bind(PropertyList pList) throws FOPException { src = pList.get(PR_SRC).getString(); colorProfileName = pList.get(PR_COLOR_PROFILE_NAME).getString(); @@ -59,12 +59,15 @@ public class ColorProfile extends FObj { XSL 1.0/FOP: EMPTY (no child nodes permitted) */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { - invalidChildError(loc, nsURI, localName); + throws ValidationException { + if (FO_URI.equals(nsURI)) { + invalidChildError(loc, nsURI, localName); + } } /** * Return the "color-profile-name" property. + * @return the color-profile-name property */ public String getColorProfileName() { return colorProfileName; @@ -75,9 +78,7 @@ public class ColorProfile extends FObj { return "color-profile"; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public int getNameId() { return FO_COLOR_PROFILE; } diff --git a/src/java/org/apache/fop/fo/pagination/ConditionalPageMasterReference.java b/src/java/org/apache/fop/fo/pagination/ConditionalPageMasterReference.java index dfb4ba70b..a13808324 100644 --- a/src/java/org/apache/fop/fo/pagination/ConditionalPageMasterReference.java +++ b/src/java/org/apache/fop/fo/pagination/ConditionalPageMasterReference.java @@ -45,15 +45,15 @@ public class ConditionalPageMasterReference extends FObj { // End of property values /** + * Creates a new conditional-page-master-reference element. + * @param parent the parent node * @see org.apache.fop.fo.FONode#FONode(FONode) */ public ConditionalPageMasterReference(FONode parent) { super(parent); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void bind(PropertyList pList) throws FOPException { masterReference = pList.get(PR_MASTER_REFERENCE).getString(); pagePosition = pList.get(PR_PAGE_POSITION).getEnum(); @@ -65,9 +65,7 @@ public class ConditionalPageMasterReference extends FObj { } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ protected void startOfNode() throws FOPException { getConcreteParent().addConditionalPageMasterReference(this); } @@ -81,8 +79,10 @@ public class ConditionalPageMasterReference extends FObj { * XSL Content Model: empty */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { - invalidChildError(loc, nsURI, localName); + throws ValidationException { + if (FO_URI.equals(nsURI)) { + invalidChildError(loc, nsURI, localName); + } } /** diff --git a/src/java/org/apache/fop/fo/pagination/Declarations.java b/src/java/org/apache/fop/fo/pagination/Declarations.java index 9dc282d07..3eec2897a 100644 --- a/src/java/org/apache/fop/fo/pagination/Declarations.java +++ b/src/java/org/apache/fop/fo/pagination/Declarations.java @@ -42,6 +42,7 @@ public class Declarations extends FObj { private Map colorProfiles = null; /** + * Creates a new declarations element. * @param parent FONode that is the parent of this object */ public Declarations(FONode parent) { @@ -49,9 +50,7 @@ public class Declarations extends FObj { ((Root) parent).setDeclarations(this); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void bind(PropertyList pList) throws FOPException { // No properties defined for fo:declarations } @@ -73,6 +72,7 @@ public class Declarations extends FObj { /** * At the end of this element sort out the children into * a hashmap of color profiles and a list of extension attachments. + * @throws FOPException if there's a problem during processing */ protected void endOfNode() throws FOPException { if (firstChild != null) { @@ -83,7 +83,8 @@ public class Declarations extends FObj { if (!"".equals(cp.getColorProfileName())) { addColorProfile(cp); } else { - log.warn("color-profile-name required for color profile"); + getFOValidationEventProducer().missingProperty(this, + cp.getName(), "color-profile-name", locator); } } else { log.debug("Ignoring element " + node.getName() @@ -100,22 +101,18 @@ public class Declarations extends FObj { } if (colorProfiles.get(cp.getColorProfileName()) != null) { // duplicate names - log.warn("Duplicate fo:color-profile profile name: " - + cp.getColorProfileName()); + getFOValidationEventProducer().colorProfileNameNotUnique(this, + cp.getName(), cp.getColorProfileName(), locator); } colorProfiles.put(cp.getColorProfileName(), cp); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public String getLocalName() { return "declarations"; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public int getNameId() { return FO_DECLARATIONS; } diff --git a/src/java/org/apache/fop/fo/pagination/Flow.java b/src/java/org/apache/fop/fo/pagination/Flow.java index fc5d605cc..2ee77ff0a 100644 --- a/src/java/org/apache/fop/fo/pagination/Flow.java +++ b/src/java/org/apache/fop/fo/pagination/Flow.java @@ -45,16 +45,12 @@ public class Flow extends FObj { super(parent); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void bind(PropertyList pList) throws FOPException { flowName = pList.get(PR_FLOW_NAME).getString(); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ protected void startOfNode() throws FOPException { if (flowName == null || flowName.equals("")) { missingPropertyError("flow-name"); @@ -80,10 +76,7 @@ public class Flow extends FObj { getFOEventHandler().startFlow(this); } - /** - * Make sure content model satisfied, if so then tell the - * FOEventHandler that we are at the end of the flow. - */ + /** {@inheritDoc} */ protected void endOfNode() throws FOPException { if (!blockItemFound) { missingChildElementError("marker* (%block;)+"); @@ -96,21 +89,21 @@ public class Flow extends FObj { * XSL Content Model: marker* (%block;)+ */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { - if (FO_URI.equals(nsURI) && localName.equals("marker")) { - if (blockItemFound) { - nodesOutOfOrderError(loc, "fo:marker", "(%block;)"); + throws ValidationException { + if (FO_URI.equals(nsURI)) { + if (localName.equals("marker")) { + if (blockItemFound) { + nodesOutOfOrderError(loc, "fo:marker", "(%block;)"); + } + } else if (!isBlockItem(nsURI, localName)) { + invalidChildError(loc, nsURI, localName); + } else { + blockItemFound = true; } - } else if (!isBlockItem(nsURI, localName)) { - invalidChildError(loc, nsURI, localName); - } else { - blockItemFound = true; } } - /** - * @return true (Flow can generate reference areas) - */ + /** {@inheritDoc} */ public boolean generatesReferenceAreas() { return true; } @@ -125,9 +118,7 @@ public class Flow extends FObj { return "flow"; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public int getNameId() { return FO_FLOW; } diff --git a/src/java/org/apache/fop/fo/pagination/LayoutMasterSet.java b/src/java/org/apache/fop/fo/pagination/LayoutMasterSet.java index 54eb29744..1b57be57d 100644 --- a/src/java/org/apache/fop/fo/pagination/LayoutMasterSet.java +++ b/src/java/org/apache/fop/fo/pagination/LayoutMasterSet.java @@ -46,31 +46,27 @@ public class LayoutMasterSet extends FObj { private Map pageSequenceMasters; /** + * Creates a new layout-master-set element. + * @param parent the parent node * @see org.apache.fop.fo.FONode#FONode(FONode) */ public LayoutMasterSet(FONode parent) { super(parent); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void bind(PropertyList pList) throws FOPException { // No properties in layout-master-set. } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ protected void startOfNode() throws FOPException { getRoot().setLayoutMasterSet(this); simplePageMasters = new java.util.HashMap(); pageSequenceMasters = new java.util.HashMap(); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ protected void endOfNode() throws FOPException { if (firstChild == null) { missingChildElementError("(simple-page-master|page-sequence-master)+"); @@ -83,14 +79,12 @@ public class LayoutMasterSet extends FObj { XSL/FOP: (simple-page-master|page-sequence-master)+ */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { + throws ValidationException { if (FO_URI.equals(nsURI)) { if (!localName.equals("simple-page-master") && !localName.equals("page-sequence-master")) { invalidChildError(loc, nsURI, localName); } - } else { - invalidChildError(loc, nsURI, localName); } } @@ -104,23 +98,20 @@ public class LayoutMasterSet extends FObj { Map allRegions = new java.util.HashMap(); for (Iterator spm = simplePageMasters.values().iterator(); spm.hasNext();) { - SimplePageMaster simplePageMaster = - (SimplePageMaster)spm.next(); + SimplePageMaster simplePageMaster + = (SimplePageMaster)spm.next(); Map spmRegions = simplePageMaster.getRegions(); for (Iterator e = spmRegions.values().iterator(); e.hasNext();) { Region region = (Region) e.next(); if (allRegions.containsKey(region.getRegionName())) { - String defaultRegionName = - (String) allRegions.get(region.getRegionName()); + String defaultRegionName + = (String) allRegions.get(region.getRegionName()); if (!defaultRegionName.equals(region.getDefaultRegionName())) { - throw new ValidationException("Region-name (" - + region.getRegionName() - + ") is being mapped to multiple " - + "region-classes (" - + defaultRegionName + " and " - + region.getDefaultRegionName() - + ")", locator); + getFOValidationEventProducer().regionNameMappedToMultipleRegionClasses(this, + region.getRegionName(), + defaultRegionName, + region.getDefaultRegionName(), getLocator()); } } allRegions.put(region.getRegionName(), @@ -141,21 +132,16 @@ public class LayoutMasterSet extends FObj { // check for duplication of master-name String masterName = sPM.getMasterName(); if (existsName(masterName)) { - throw new ValidationException("'master-name' (" - + masterName - + ") must be unique " - + "across page-masters and page-sequence-masters", sPM.getLocator()); + getFOValidationEventProducer().masterNameNotUnique(this, + getName(), + masterName, sPM.getLocator()); } this.simplePageMasters.put(masterName, sPM); } private boolean existsName(String masterName) { - if (simplePageMasters.containsKey(masterName) - || pageSequenceMasters.containsKey(masterName)) { - return true; - } else { - return false; - } + return (simplePageMasters.containsKey(masterName) + || pageSequenceMasters.containsKey(masterName)); } /** @@ -181,10 +167,9 @@ public class LayoutMasterSet extends FObj { throws ValidationException { // check against duplication of master-name if (existsName(masterName)) { - throw new ValidationException("'master-name' (" - + masterName - + ") must be unique " - + "across page-masters and page-sequence-masters", pSM.getLocator()); + getFOValidationEventProducer().masterNameNotUnique(this, + getName(), + masterName, pSM.getLocator()); } this.pageSequenceMasters.put(masterName, pSM); } @@ -220,9 +205,7 @@ public class LayoutMasterSet extends FObj { return "layout-master-set"; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public int getNameId() { return FO_LAYOUT_MASTER_SET; } diff --git a/src/java/org/apache/fop/fo/pagination/PageNumberGenerator.java b/src/java/org/apache/fop/fo/pagination/PageNumberGenerator.java index 4289076ea..50620f678 100644 --- a/src/java/org/apache/fop/fo/pagination/PageNumberGenerator.java +++ b/src/java/org/apache/fop/fo/pagination/PageNumberGenerator.java @@ -43,7 +43,7 @@ public class PageNumberGenerator { private int minPadding = 0; // for decimal formats // preloaded strings of zeros - private String zeros[] = { + private String[] zeros = { "", "0", "00", "000", "0000", "00000" }; @@ -128,10 +128,10 @@ public class PageNumberGenerator { } private String makeRoman(int num) { - int arabic[] = { + int[] arabic = { 1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1 }; - String roman[] = { + String[] roman = { "m", "cm", "d", "cd", "c", "xc", "l", "xl", "x", "ix", "v", "iv", "i" }; diff --git a/src/java/org/apache/fop/fo/pagination/PageSequence.java b/src/java/org/apache/fop/fo/pagination/PageSequence.java index 91649fbc5..3d155a1da 100644 --- a/src/java/org/apache/fop/fo/pagination/PageSequence.java +++ b/src/java/org/apache/fop/fo/pagination/PageSequence.java @@ -78,9 +78,7 @@ public class PageSequence extends AbstractPageSequence { super(parent); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void bind(PropertyList pList) throws FOPException { super.bind(pList); country = pList.get(PR_COUNTRY).getString(); @@ -93,9 +91,7 @@ public class PageSequence extends AbstractPageSequence { } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ protected void startOfNode() throws FOPException { super.startOfNode(); flowMap = new java.util.HashMap(); @@ -105,9 +101,8 @@ public class PageSequence extends AbstractPageSequence { this.pageSequenceMaster = getRoot().getLayoutMasterSet().getPageSequenceMaster(masterReference); if (this.pageSequenceMaster == null) { - throw new ValidationException("master-reference '" + masterReference - + "' for fo:page-sequence matches no" - + " simple-page-master or page-sequence-master", locator); + getFOValidationEventProducer().masterNotFound(this, getName(), + masterReference, getLocator()); } } @@ -128,7 +123,7 @@ public class PageSequence extends AbstractPageSequence { XSL Content Model: (title?,static-content*,flow) */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { + throws ValidationException { if (FO_URI.equals(nsURI)) { if (localName.equals("title")) { if (titleFO != null) { @@ -149,8 +144,6 @@ public class PageSequence extends AbstractPageSequence { } else { invalidChildError(loc, nsURI, localName); } - } else { - invalidChildError(loc, nsURI, localName); } } @@ -184,18 +177,15 @@ public class PageSequence extends AbstractPageSequence { String flowName = flow.getFlowName(); if (hasFlowName(flowName)) { - throw new ValidationException("duplicate flow-name \"" - + flowName - + "\" found within fo:page-sequence", flow.getLocator()); + getFOValidationEventProducer().duplicateFlowNameInPageSequence(this, flow.getName(), + flowName, flow.getLocator()); } if (!getRoot().getLayoutMasterSet().regionNameExists(flowName) && !flowName.equals("xsl-before-float-separator") && !flowName.equals("xsl-footnote-separator")) { - throw new ValidationException("flow-name \"" - + flowName - + "\" could not be mapped to a region-name in the" - + " layout-master-set", flow.getLocator()); + getFOValidationEventProducer().flowNameNotMapped(this, flow.getName(), + flowName, flow.getLocator()); } } diff --git a/src/java/org/apache/fop/fo/pagination/PageSequenceMaster.java b/src/java/org/apache/fop/fo/pagination/PageSequenceMaster.java index 34ad299bd..4258a1139 100644 --- a/src/java/org/apache/fop/fo/pagination/PageSequenceMaster.java +++ b/src/java/org/apache/fop/fo/pagination/PageSequenceMaster.java @@ -29,6 +29,7 @@ import org.apache.fop.fo.FONode; import org.apache.fop.fo.FObj; import org.apache.fop.fo.PropertyList; import org.apache.fop.fo.ValidationException; +import org.apache.fop.layoutmgr.BlockLevelEventProducer; /** * The page-sequence-master formatting object. @@ -53,6 +54,8 @@ public class PageSequenceMaster extends FObj { // but the actual FO's are MasterReferences. /** + * Creates a new page-sequence-master element. + * @param parent the parent node * @see org.apache.fop.fo.FONode#FONode(FONode) */ public PageSequenceMaster(FONode parent) { @@ -95,20 +98,18 @@ public class PageSequenceMaster extends FObj { * repeatable-page-master-alternatives)+ */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { + throws ValidationException { if (FO_URI.equals(nsURI)) { if (!localName.equals("single-page-master-reference") && !localName.equals("repeatable-page-master-reference") && !localName.equals("repeatable-page-master-alternatives")) { invalidChildError(loc, nsURI, localName); } - } else { - invalidChildError(loc, nsURI, localName); } } /** - * Adds a new suqsequence specifier to the page sequence master. + * Adds a new subsequence specifier to the page sequence master. * @param pageMasterReference the subsequence to add */ protected void addSubsequenceSpecifier(SubSequenceSpecifier pageMasterReference) { @@ -199,8 +200,10 @@ public class PageSequenceMaster extends FObj { if (currentSubSequence == null) { currentSubSequence = getNextSubSequence(); if (currentSubSequence == null) { - throw new FOPException("no subsequences in page-sequence-master '" - + masterName + "'"); + BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.missingSubsequencesInPageSequenceMaster(this, + masterName, getLocator()); } } String pageMasterName = currentSubSequence @@ -209,14 +212,10 @@ public class PageSequenceMaster extends FObj { while (pageMasterName == null) { SubSequenceSpecifier nextSubSequence = getNextSubSequence(); if (nextSubSequence == null) { - if (!canRecover) { - throw new FOPException("subsequences exhausted in page-sequence-master '" - + masterName - + "', cannot recover"); - } - log.warn("subsequences exhausted in page-sequence-master '" - + masterName - + "', using previous subsequence"); + BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.pageSequenceMasterExhausted(this, + masterName, canRecover, getLocator()); currentSubSequence.reset(); canRecover = false; } else { @@ -228,9 +227,10 @@ public class PageSequenceMaster extends FObj { SimplePageMaster pageMaster = this.layoutMasterSet .getSimplePageMaster(pageMasterName); if (pageMaster == null) { - throw new FOPException("No simple-page-master matching '" - + pageMasterName + "' in page-sequence-master '" - + masterName + "'"); + BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.noMatchingPageMaster(this, + masterName, pageMasterName, getLocator()); } return pageMaster; } @@ -240,9 +240,7 @@ public class PageSequenceMaster extends FObj { return "page-sequence-master"; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public int getNameId() { return FO_PAGE_SEQUENCE_MASTER; } diff --git a/src/java/org/apache/fop/fo/pagination/PageSequenceWrapper.java b/src/java/org/apache/fop/fo/pagination/PageSequenceWrapper.java index 94c0314a3..0b3cff276 100644 --- a/src/java/org/apache/fop/fo/pagination/PageSequenceWrapper.java +++ b/src/java/org/apache/fop/fo/pagination/PageSequenceWrapper.java @@ -22,8 +22,8 @@ package org.apache.fop.fo.pagination; import org.xml.sax.Locator; import org.apache.fop.apps.FOPException; -import org.apache.fop.fo.FObj; import org.apache.fop.fo.FONode; +import org.apache.fop.fo.FObj; import org.apache.fop.fo.PropertyList; import org.apache.fop.fo.ValidationException; @@ -39,15 +39,14 @@ public class PageSequenceWrapper extends FObj { // End of property values /** + * Creates a new page-sequence-wrapper element. * @param parent FONode that is the parent of this object */ public PageSequenceWrapper(FONode parent) { super(parent); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void bind(PropertyList pList) throws FOPException { super.bind(pList); indexClass = pList.get(PR_INDEX_CLASS).getString(); @@ -59,10 +58,12 @@ public class PageSequenceWrapper extends FObj { XSL/FOP: (bookmark+) */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { - if (!(FO_URI.equals(nsURI) && (localName.equals("page-sequence") || - localName.equals("page-sequence-wrapper")))) { + throws ValidationException { + if (FO_URI.equals(nsURI)) { + if (!(localName.equals("page-sequence") + || localName.equals("page-sequence-wrapper"))) { invalidChildError(loc, nsURI, localName); + } } } @@ -81,9 +82,7 @@ public class PageSequenceWrapper extends FObj { return "page-sequence-wrapper"; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public int getNameId() { return FO_PAGE_SEQUENCE_WRAPPER; } diff --git a/src/java/org/apache/fop/fo/pagination/Region.java b/src/java/org/apache/fop/fo/pagination/Region.java index 2516f90d8..ded86514b 100644 --- a/src/java/org/apache/fop/fo/pagination/Region.java +++ b/src/java/org/apache/fop/fo/pagination/Region.java @@ -30,7 +30,6 @@ import org.apache.fop.fo.FONode; import org.apache.fop.fo.FObj; import org.apache.fop.fo.PropertyList; import org.apache.fop.fo.ValidationException; -import org.apache.fop.fo.expr.PropertyException; import org.apache.fop.fo.properties.CommonBorderPaddingBackground; /** @@ -50,6 +49,8 @@ public abstract class Region extends FObj { private SimplePageMaster layoutMaster; /** + * Creates a new Region. + * @param parent the parent node * @see org.apache.fop.fo.FONode#FONode(FONode) */ protected Region(FONode parent) { @@ -57,9 +58,7 @@ public abstract class Region extends FObj { layoutMaster = (SimplePageMaster) parent; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void bind(PropertyList pList) throws FOPException { commonBorderPaddingBackground = pList.getBorderPaddingBackgroundProps(); // clip = pList.get(PR_CLIP); @@ -76,18 +75,16 @@ public abstract class Region extends FObj { // check that name is OK. Not very pretty. if (isReserved(getRegionName()) && !getRegionName().equals(getDefaultRegionName())) { - throw new ValidationException("region-name '" + regionName - + "' for " + this.getName() - + " is not permitted.", locator); + getFOValidationEventProducer().illegalRegionName(this, getName(), + regionName, getLocator()); } } //TODO do we need context for getBPPaddingAndBorder() and getIPPaddingAndBorder()? - if (getUserAgent().validateStrictly() - && (getCommonBorderPaddingBackground().getBPPaddingAndBorder(false, null) != 0 + if ((getCommonBorderPaddingBackground().getBPPaddingAndBorder(false, null) != 0 || getCommonBorderPaddingBackground().getIPPaddingAndBorder(false, null) != 0)) { - throw new PropertyException("Border and padding for region \"" - + regionName + "\" must be '0' (See 6.4.13 in XSL 1.0)."); + getFOValidationEventProducer().nonZeroBorderPaddingOnRegion(this, getName(), + regionName, true, getLocator()); } } @@ -96,8 +93,10 @@ public abstract class Region extends FObj { * XSL Content Model: empty */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { + throws ValidationException { + if (FO_URI.equals(nsURI)) { invalidChildError(loc, nsURI, localName); + } } /** @@ -121,7 +120,7 @@ public abstract class Region extends FObj { * @param name a region name to check * @return true if the name parameter is a reserved region name */ - protected boolean isReserved(String name) /*throws FOPException*/ { + protected boolean isReserved(String name) { return (name.equals("xsl-region-before") || name.equals("xsl-region-start") || name.equals("xsl-region-end") @@ -130,9 +129,7 @@ public abstract class Region extends FObj { || name.equals("xsl-footnote-separator")); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public boolean generatesReferenceAreas() { return true; } diff --git a/src/java/org/apache/fop/fo/pagination/RegionAfter.java b/src/java/org/apache/fop/fo/pagination/RegionAfter.java index 9459a6637..2852358b5 100644 --- a/src/java/org/apache/fop/fo/pagination/RegionAfter.java +++ b/src/java/org/apache/fop/fo/pagination/RegionAfter.java @@ -34,15 +34,15 @@ import org.apache.fop.datatypes.SimplePercentBaseContext; public class RegionAfter extends RegionBA { /** + * Creates a new region-after element. + * @param parent the parent node * @see org.apache.fop.fo.FONode#FONode(FONode) */ public RegionAfter(FONode parent) { super(parent); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public Rectangle getViewportRectangle (FODimension reldims, SimplePageMaster spm) { /* Special rules apply to resolving extent as values are resolved relative * to the page size and reference orientation. @@ -82,9 +82,7 @@ public class RegionAfter extends RegionBA { return vpRect; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ protected String getDefaultRegionName() { return "xsl-region-after"; } @@ -94,9 +92,7 @@ public class RegionAfter extends RegionBA { return "region-after"; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public int getNameId() { return FO_REGION_AFTER; } diff --git a/src/java/org/apache/fop/fo/pagination/RegionBA.java b/src/java/org/apache/fop/fo/pagination/RegionBA.java index 768af2489..279164a96 100644 --- a/src/java/org/apache/fop/fo/pagination/RegionBA.java +++ b/src/java/org/apache/fop/fo/pagination/RegionBA.java @@ -36,15 +36,15 @@ public abstract class RegionBA extends SideRegion { // End of property values /** + * Creates a new region (before or after). + * @param parent the parent node * @see org.apache.fop.fo.FONode#FONode(FONode) */ protected RegionBA(FONode parent) { super(parent); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void bind(PropertyList pList) throws FOPException { super.bind(pList); precedence = pList.get(PR_PRECEDENCE).getEnum(); diff --git a/src/java/org/apache/fop/fo/pagination/RegionBefore.java b/src/java/org/apache/fop/fo/pagination/RegionBefore.java index 6115d8dd8..076737252 100644 --- a/src/java/org/apache/fop/fo/pagination/RegionBefore.java +++ b/src/java/org/apache/fop/fo/pagination/RegionBefore.java @@ -32,23 +32,22 @@ import org.apache.fop.fo.FONode; * The fo:region-before element. */ public class RegionBefore extends RegionBA { + /** + * Creates a new region-before element. + * @param parent the parent element * @see org.apache.fop.fo.FONode#FONode(FONode) */ public RegionBefore(FONode parent) { super(parent); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ protected String getDefaultRegionName() { return "xsl-region-before"; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public Rectangle getViewportRectangle (FODimension reldims, SimplePageMaster spm) { /* Special rules apply to resolving extent as values are resolved relative * to the page size and reference orientation. @@ -91,9 +90,7 @@ public class RegionBefore extends RegionBA { return "region-before"; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public int getNameId() { return FO_REGION_BEFORE; } diff --git a/src/java/org/apache/fop/fo/pagination/RegionBody.java b/src/java/org/apache/fop/fo/pagination/RegionBody.java index 4de2dd1b4..9700e72fc 100644 --- a/src/java/org/apache/fop/fo/pagination/RegionBody.java +++ b/src/java/org/apache/fop/fo/pagination/RegionBody.java @@ -43,29 +43,29 @@ public class RegionBody extends Region { // End of property values /** + * Creates a new region-body element. + * @param parent the parent node * @see org.apache.fop.fo.FONode#FONode(FONode) */ public RegionBody(FONode parent) { super(parent); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void bind(PropertyList pList) throws FOPException { super.bind(pList); commonMarginBlock = pList.getMarginBlockProps(); columnCount = pList.get(PR_COLUMN_COUNT).getNumeric(); columnGap = pList.get(PR_COLUMN_GAP).getLength(); - if ((getColumnCount() > 1) && (getOverflow() == EN_SCROLL)) { + if ((getColumnCount() != 1) && (getOverflow() == EN_SCROLL)) { /* This is an error (See XSL Rec, fo:region-body description). * The Rec allows for acting as if "1" is chosen in * these cases, but we will need to be able to change Numeric * values in order to do this. */ - attributeError("If overflow property is set to \"scroll\"," - + " a column-count other than \"1\" may not be specified."); + getFOValidationEventProducer().columnCountErrorOnRegionBodyOverflowScroll(this, + getName(), getLocator()); } } @@ -93,9 +93,7 @@ public class RegionBody extends Region { return columnGap.getValue(); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public Rectangle getViewportRectangle (FODimension reldims, SimplePageMaster spm) { /* Special rules apply to resolving margins in the page context. * Contrary to normal margins in this case top and bottom margin @@ -143,9 +141,7 @@ public class RegionBody extends Region { reldims.bpd - before - after); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ protected String getDefaultRegionName() { return "xsl-region-body"; } @@ -155,9 +151,7 @@ public class RegionBody extends Region { return "region-body"; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public int getNameId() { return FO_REGION_BODY; } diff --git a/src/java/org/apache/fop/fo/pagination/RegionEnd.java b/src/java/org/apache/fop/fo/pagination/RegionEnd.java index 912be9097..13f65d71a 100644 --- a/src/java/org/apache/fop/fo/pagination/RegionEnd.java +++ b/src/java/org/apache/fop/fo/pagination/RegionEnd.java @@ -22,26 +22,26 @@ package org.apache.fop.fo.pagination; // Java import java.awt.Rectangle; -// FOP -import org.apache.fop.fo.FONode; import org.apache.fop.datatypes.FODimension; import org.apache.fop.datatypes.LengthBase; import org.apache.fop.datatypes.SimplePercentBaseContext; +import org.apache.fop.fo.FONode; /** * The fo:region-end element. */ public class RegionEnd extends RegionSE { + /** + * Creates a new region-end element. + * @param parent the parent node * @see org.apache.fop.fo.FONode#FONode(FONode) */ public RegionEnd(FONode parent) { super(parent); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public Rectangle getViewportRectangle (FODimension reldims, SimplePageMaster spm) { /* Special rules apply to resolving extent as values are resolved relative * to the page size and reference orientation. @@ -80,9 +80,7 @@ public class RegionEnd extends RegionSE { return vpRect; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ protected String getDefaultRegionName() { return "xsl-region-end"; } @@ -92,9 +90,7 @@ public class RegionEnd extends RegionSE { return "region-end"; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public int getNameId() { return FO_REGION_END; } diff --git a/src/java/org/apache/fop/fo/pagination/RegionSE.java b/src/java/org/apache/fop/fo/pagination/RegionSE.java index 735623352..183b44342 100644 --- a/src/java/org/apache/fop/fo/pagination/RegionSE.java +++ b/src/java/org/apache/fop/fo/pagination/RegionSE.java @@ -35,15 +35,15 @@ public abstract class RegionSE extends SideRegion { // End of property values /** + * Creates a new region (start or end). + * @param parent the parent node * @see org.apache.fop.fo.FONode#FONode(FONode) */ protected RegionSE(FONode parent) { super(parent); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void bind(PropertyList pList) throws FOPException { super.bind(pList); } diff --git a/src/java/org/apache/fop/fo/pagination/RegionStart.java b/src/java/org/apache/fop/fo/pagination/RegionStart.java index d78b19c3d..7a69cfdac 100644 --- a/src/java/org/apache/fop/fo/pagination/RegionStart.java +++ b/src/java/org/apache/fop/fo/pagination/RegionStart.java @@ -32,16 +32,17 @@ import org.apache.fop.datatypes.SimplePercentBaseContext; * The fo:region-start element. */ public class RegionStart extends RegionSE { + /** + * Creates a new region-start element. + * @param parent the parent node * @see org.apache.fop.fo.FONode#FONode(FONode) */ public RegionStart(FONode parent) { super(parent); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public Rectangle getViewportRectangle (FODimension reldims, SimplePageMaster spm) { /* Special rules apply to resolving extent as values are resolved relative * to the page size and reference orientation. @@ -77,9 +78,7 @@ public class RegionStart extends RegionSE { return vpRect; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ protected String getDefaultRegionName() { return "xsl-region-start"; } @@ -89,9 +88,7 @@ public class RegionStart extends RegionSE { return "region-start"; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public int getNameId() { return FO_REGION_START; } diff --git a/src/java/org/apache/fop/fo/pagination/RepeatablePageMasterAlternatives.java b/src/java/org/apache/fop/fo/pagination/RepeatablePageMasterAlternatives.java index 9d2fe652c..509b81f21 100644 --- a/src/java/org/apache/fop/fo/pagination/RepeatablePageMasterAlternatives.java +++ b/src/java/org/apache/fop/fo/pagination/RepeatablePageMasterAlternatives.java @@ -52,38 +52,29 @@ public class RepeatablePageMasterAlternatives extends FObj private boolean hasPagePositionOnly = false; /** + * Creates a new repeatable-page-master-alternatives element. + * @param parent the parent node * @see org.apache.fop.fo.FONode#FONode(FONode) */ public RepeatablePageMasterAlternatives(FONode parent) { super(parent); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void bind(PropertyList pList) throws FOPException { maximumRepeats = pList.get(PR_MAXIMUM_REPEATS); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ protected void startOfNode() throws FOPException { conditionalPageMasterRefs = new java.util.ArrayList(); - if (parent.getName().equals("fo:page-sequence-master")) { - PageSequenceMaster pageSequenceMaster = (PageSequenceMaster)parent; - pageSequenceMaster.addSubsequenceSpecifier(this); - } else { - throw new ValidationException("fo:repeatable-page-master-alternatives " - + "must be child of fo:page-sequence-master, not " - + parent.getName(), locator); - } + assert parent.getName().equals("fo:page-sequence-master"); //Validation by the parent + PageSequenceMaster pageSequenceMaster = (PageSequenceMaster)parent; + pageSequenceMaster.addSubsequenceSpecifier(this); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ protected void endOfNode() throws FOPException { if (firstChild == null) { missingChildElementError("(conditional-page-master-reference+)"); @@ -95,10 +86,11 @@ public class RepeatablePageMasterAlternatives extends FObj XSL/FOP: (conditional-page-master-reference+) */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { - if (!(FO_URI.equals(nsURI) - && localName.equals("conditional-page-master-reference"))) { - invalidChildError(loc, nsURI, localName); + throws ValidationException { + if (FO_URI.equals(nsURI)) { + if (!localName.equals("conditional-page-master-reference")) { + invalidChildError(loc, nsURI, localName); + } } } @@ -117,11 +109,7 @@ public class RepeatablePageMasterAlternatives extends FObj } } - /** - * Get the next matching page master from the conditional - * page master references. - * @see org.apache.fop.fo.pagination.SubSequenceSpecifier - */ + /** {@inheritDoc} */ public String getNextPageMasterName(boolean isOddPage, boolean isFirstPage, boolean isLastPage, @@ -183,12 +171,11 @@ public class RepeatablePageMasterAlternatives extends FObj } /** {@inheritDoc} */ - /** @see org.apache.fop.fo.pagination.SubSequenceSpecifier#hasPagePositionOnly() */ public boolean hasPagePositionOnly() { return this.hasPagePositionOnly; } - /** @see org.apache.fop.fo.FONode#getLocalName() */ + /** {@inheritDoc} */ public String getLocalName() { return "repeatable-page-master-alternatives"; } diff --git a/src/java/org/apache/fop/fo/pagination/RepeatablePageMasterReference.java b/src/java/org/apache/fop/fo/pagination/RepeatablePageMasterReference.java index 172324232..87dc248c0 100644 --- a/src/java/org/apache/fop/fo/pagination/RepeatablePageMasterReference.java +++ b/src/java/org/apache/fop/fo/pagination/RepeatablePageMasterReference.java @@ -47,15 +47,15 @@ public class RepeatablePageMasterReference extends FObj private int numberConsumed = 0; /** + * Creates a new repeatable-page-master-reference element. + * @param parent the parent node * @see org.apache.fop.fo.FONode#FONode(FONode) */ public RepeatablePageMasterReference(FONode parent) { super(parent); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void bind(PropertyList pList) throws FOPException { masterReference = pList.get(PR_MASTER_REFERENCE).getString(); maximumRepeats = pList.get(PR_MAXIMUM_REPEATS); @@ -65,9 +65,7 @@ public class RepeatablePageMasterReference extends FObj } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ protected void startOfNode() throws FOPException { PageSequenceMaster pageSequenceMaster = (PageSequenceMaster) parent; @@ -83,13 +81,13 @@ public class RepeatablePageMasterReference extends FObj * XSL Content Model: empty */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { - invalidChildError(loc, nsURI, localName); + throws ValidationException { + if (FO_URI.equals(nsURI)) { + invalidChildError(loc, nsURI, localName); + } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public String getNextPageMasterName(boolean isOddPage, boolean isFirstPage, boolean isLastPage, diff --git a/src/java/org/apache/fop/fo/pagination/Root.java b/src/java/org/apache/fop/fo/pagination/Root.java index 6e079cf47..1cff9c3d6 100644 --- a/src/java/org/apache/fop/fo/pagination/Root.java +++ b/src/java/org/apache/fop/fo/pagination/Root.java @@ -25,7 +25,6 @@ import java.util.List; import org.xml.sax.Locator; import org.apache.fop.apps.FOPException; -import org.apache.fop.fo.ElementMapping; import org.apache.fop.fo.FOEventHandler; import org.apache.fop.fo.FONode; import org.apache.fop.fo.FObj; @@ -62,7 +61,9 @@ public class Root extends FObj { */ private FOEventHandler foEventHandler = null; - /** + /** + * Creates a new root element. + * @param parent the parent node (must be null) * @see org.apache.fop.fo.FONode#FONode(FONode) */ public Root(FONode parent) { @@ -137,13 +138,6 @@ public class Root extends FObj { } - /** @inheritDoc */ - protected void validateChildNode(Locator loc, FONode child) throws ValidationException { - if (child instanceof AbstractPageSequence) { - pageSequenceFound = true; - } - } - /** * Sets the FOEventHandler object that this Root is attached to * @param foEventHandler the FOEventHandler object @@ -291,9 +285,7 @@ public class Root extends FObj { return bookmarkTree; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public Root getRoot() { return this; } @@ -303,9 +295,7 @@ public class Root extends FObj { return "root"; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public int getNameId() { return FO_ROOT; } diff --git a/src/java/org/apache/fop/fo/pagination/SideRegion.java b/src/java/org/apache/fop/fo/pagination/SideRegion.java index 14328aa9b..552ca871b 100644 --- a/src/java/org/apache/fop/fo/pagination/SideRegion.java +++ b/src/java/org/apache/fop/fo/pagination/SideRegion.java @@ -31,7 +31,11 @@ public abstract class SideRegion extends Region { private Length extent; - /** @see org.apache.fop.fo.FONode#FONode(FONode) */ + /** + * Creates a new side region. + * @param parent the parent node + * @see org.apache.fop.fo.FONode#FONode(FONode) + */ protected SideRegion(FONode parent) { super(parent); } diff --git a/src/java/org/apache/fop/fo/pagination/SimplePageMaster.java b/src/java/org/apache/fop/fo/pagination/SimplePageMaster.java index ba1c0a6af..85a5081c8 100644 --- a/src/java/org/apache/fop/fo/pagination/SimplePageMaster.java +++ b/src/java/org/apache/fop/fo/pagination/SimplePageMaster.java @@ -63,15 +63,15 @@ public class SimplePageMaster extends FObj { private boolean hasRegionEnd = false; /** + * Creates a new simple-page-master element. + * @param parent the parent node * @see org.apache.fop.fo.FONode#FONode(FONode) */ public SimplePageMaster(FONode parent) { super(parent); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void bind(PropertyList pList) throws FOPException { commonMarginBlock = pList.getMarginBlockProps(); masterName = pList.get(PR_MASTER_NAME).getString(); @@ -85,9 +85,7 @@ public class SimplePageMaster extends FObj { } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ protected void startOfNode() throws FOPException { LayoutMasterSet layoutMasterSet = (LayoutMasterSet) parent; @@ -101,9 +99,7 @@ public class SimplePageMaster extends FObj { regions = new HashMap(5); } - /** - * Make sure content model satisfied. - */ + /** {@inheritDoc} */ protected void endOfNode() throws FOPException { if (!hasRegionBody) { missingChildElementError( @@ -116,72 +112,70 @@ public class SimplePageMaster extends FObj { * XSL Content Model: (region-body,region-before?,region-after?,region-start?,region-end?) */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { - if (FO_URI.equals(nsURI) && localName.equals("region-body")) { - if (hasRegionBody) { - tooManyNodesError(loc, "fo:region-body"); - } else { - hasRegionBody = true; - } - } else if (FO_URI.equals(nsURI) && localName.equals("region-before")) { - if (!hasRegionBody) { - nodesOutOfOrderError(loc, "fo:region-body", "fo:region-before"); - } else if (hasRegionBefore) { - tooManyNodesError(loc, "fo:region-before"); - } else if (hasRegionAfter) { - nodesOutOfOrderError(loc, "fo:region-before", "fo:region-after"); - } else if (hasRegionStart) { - nodesOutOfOrderError(loc, "fo:region-before", "fo:region-start"); - } else if (hasRegionEnd) { - nodesOutOfOrderError(loc, "fo:region-before", "fo:region-end"); - } else { - hasRegionBody = true; - } - } else if (FO_URI.equals(nsURI) && localName.equals("region-after")) { - if (!hasRegionBody) { - nodesOutOfOrderError(loc, "fo:region-body", "fo:region-after"); - } else if (hasRegionAfter) { - tooManyNodesError(loc, "fo:region-after"); - } else if (hasRegionStart) { - nodesOutOfOrderError(loc, "fo:region-after", "fo:region-start"); - } else if (hasRegionEnd) { - nodesOutOfOrderError(loc, "fo:region-after", "fo:region-end"); - } else { - hasRegionAfter = true; - } - } else if (FO_URI.equals(nsURI) && localName.equals("region-start")) { - if (!hasRegionBody) { - nodesOutOfOrderError(loc, "fo:region-body", "fo:region-start"); - } else if (hasRegionStart) { - tooManyNodesError(loc, "fo:region-start"); - } else if (hasRegionEnd) { - nodesOutOfOrderError(loc, "fo:region-start", "fo:region-end"); - } else { - hasRegionStart = true; - } - } else if (FO_URI.equals(nsURI) && localName.equals("region-end")) { - if (!hasRegionBody) { - nodesOutOfOrderError(loc, "fo:region-body", "fo:region-end"); - } else if (hasRegionEnd) { - tooManyNodesError(loc, "fo:region-end"); + throws ValidationException { + if (FO_URI.equals(nsURI)) { + if (localName.equals("region-body")) { + if (hasRegionBody) { + tooManyNodesError(loc, "fo:region-body"); + } else { + hasRegionBody = true; + } + } else if (localName.equals("region-before")) { + if (!hasRegionBody) { + nodesOutOfOrderError(loc, "fo:region-body", "fo:region-before"); + } else if (hasRegionBefore) { + tooManyNodesError(loc, "fo:region-before"); + } else if (hasRegionAfter) { + nodesOutOfOrderError(loc, "fo:region-before", "fo:region-after"); + } else if (hasRegionStart) { + nodesOutOfOrderError(loc, "fo:region-before", "fo:region-start"); + } else if (hasRegionEnd) { + nodesOutOfOrderError(loc, "fo:region-before", "fo:region-end"); + } else { + hasRegionBody = true; + } + } else if (localName.equals("region-after")) { + if (!hasRegionBody) { + nodesOutOfOrderError(loc, "fo:region-body", "fo:region-after"); + } else if (hasRegionAfter) { + tooManyNodesError(loc, "fo:region-after"); + } else if (hasRegionStart) { + nodesOutOfOrderError(loc, "fo:region-after", "fo:region-start"); + } else if (hasRegionEnd) { + nodesOutOfOrderError(loc, "fo:region-after", "fo:region-end"); + } else { + hasRegionAfter = true; + } + } else if (localName.equals("region-start")) { + if (!hasRegionBody) { + nodesOutOfOrderError(loc, "fo:region-body", "fo:region-start"); + } else if (hasRegionStart) { + tooManyNodesError(loc, "fo:region-start"); + } else if (hasRegionEnd) { + nodesOutOfOrderError(loc, "fo:region-start", "fo:region-end"); + } else { + hasRegionStart = true; + } + } else if (localName.equals("region-end")) { + if (!hasRegionBody) { + nodesOutOfOrderError(loc, "fo:region-body", "fo:region-end"); + } else if (hasRegionEnd) { + tooManyNodesError(loc, "fo:region-end"); + } else { + hasRegionEnd = true; + } } else { - hasRegionEnd = true; + invalidChildError(loc, nsURI, localName); } - } else { - invalidChildError(loc, nsURI, localName); } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public boolean generatesReferenceAreas() { return true; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ protected void addChildNode(FONode child) throws FOPException { if (child instanceof Region) { addRegion((Region)child); @@ -268,9 +262,7 @@ public class SimplePageMaster extends FObj { return "simple-page-master"; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public int getNameId() { return FO_SIMPLE_PAGE_MASTER; } diff --git a/src/java/org/apache/fop/fo/pagination/SinglePageMasterReference.java b/src/java/org/apache/fop/fo/pagination/SinglePageMasterReference.java index 43d8e40dc..119ec409e 100644 --- a/src/java/org/apache/fop/fo/pagination/SinglePageMasterReference.java +++ b/src/java/org/apache/fop/fo/pagination/SinglePageMasterReference.java @@ -46,6 +46,8 @@ public class SinglePageMasterReference extends FObj private int state; /** + * Creates a new single-page-master-reference element. + * @param parent the parent node * @see org.apache.fop.fo.FONode#FONode(FONode) */ public SinglePageMasterReference(FONode parent) { @@ -53,9 +55,7 @@ public class SinglePageMasterReference extends FObj this.state = FIRST; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void bind(PropertyList pList) throws FOPException { masterReference = pList.get(PR_MASTER_REFERENCE).getString(); @@ -64,9 +64,7 @@ public class SinglePageMasterReference extends FObj } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ protected void startOfNode() throws FOPException { PageSequenceMaster pageSequenceMaster = (PageSequenceMaster) parent; pageSequenceMaster.addSubsequenceSpecifier(this); @@ -77,8 +75,10 @@ public class SinglePageMasterReference extends FObj * XSL Content Model: empty */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { - invalidChildError(loc, nsURI, localName); + throws ValidationException { + if (FO_URI.equals(nsURI)) { + invalidChildError(loc, nsURI, localName); + } } /** {@inheritDoc} */ diff --git a/src/java/org/apache/fop/fo/pagination/StaticContent.java b/src/java/org/apache/fop/fo/pagination/StaticContent.java index 62d73e56d..184438b6f 100644 --- a/src/java/org/apache/fop/fo/pagination/StaticContent.java +++ b/src/java/org/apache/fop/fo/pagination/StaticContent.java @@ -27,24 +27,22 @@ import org.apache.fop.fo.FONode; import org.apache.fop.fo.ValidationException; /** - * Class modelling the fo:static-content object. + * Class modeling the fo:static-content object. */ public class StaticContent extends Flow { /** + * Creates a new static-content element. * @param parent FONode that is the parent of this object */ public StaticContent(FONode parent) { super(parent); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ protected void startOfNode() throws FOPException { if (getFlowName() == null || getFlowName().equals("")) { - throw new ValidationException("A 'flow-name' is required for " - + getName() + ".", locator); + missingPropertyError("flow-name"); } getFOEventHandler().startFlow(this); } @@ -66,9 +64,11 @@ public class StaticContent extends Flow { * XSL Content Model: (%block;)+ */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { - if (!isBlockItem(nsURI, localName)) { - invalidChildError(loc, nsURI, localName); + throws ValidationException { + if (FO_URI.equals(nsURI)) { + if (!isBlockItem(nsURI, localName)) { + invalidChildError(loc, nsURI, localName); + } } } diff --git a/src/java/org/apache/fop/fo/pagination/Title.java b/src/java/org/apache/fop/fo/pagination/Title.java index 398424152..f6f625ea8 100644 --- a/src/java/org/apache/fop/fo/pagination/Title.java +++ b/src/java/org/apache/fop/fo/pagination/Title.java @@ -27,7 +27,7 @@ import org.apache.fop.fo.ValidationException; import org.apache.fop.fo.flow.InlineLevel; /** - * Class modelling the fo:title object. + * Class modeling the fo:title object. */ public class Title extends InlineLevel { // The value of properties relevant for fo:title. @@ -35,6 +35,7 @@ public class Title extends InlineLevel { // End of property values /** + * Creates a new title element. * @param parent FONode that is the parent of this object */ public Title(FONode parent) { @@ -46,22 +47,20 @@ public class Title extends InlineLevel { XSL/FOP: (#PCDATA|%inline;)* */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { - if (!isInlineItem(nsURI, localName)) { - invalidChildError(loc, nsURI, localName); + throws ValidationException { + if (FO_URI.equals(nsURI)) { + if (!isInlineItem(nsURI, localName)) { + invalidChildError(loc, nsURI, localName); + } } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public String getLocalName() { return "title"; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public int getNameId() { return FO_TITLE; } diff --git a/src/java/org/apache/fop/fo/pagination/bookmarks/Bookmark.java b/src/java/org/apache/fop/fo/pagination/bookmarks/Bookmark.java index e588bb3f8..7f55ec51b 100644 --- a/src/java/org/apache/fop/fo/pagination/bookmarks/Bookmark.java +++ b/src/java/org/apache/fop/fo/pagination/bookmarks/Bookmark.java @@ -20,10 +20,13 @@ package org.apache.fop.fo.pagination.bookmarks; import java.util.ArrayList; +import java.util.List; + import org.xml.sax.Locator; + import org.apache.fop.apps.FOPException; -import org.apache.fop.fo.FObj; import org.apache.fop.fo.FONode; +import org.apache.fop.fo.FObj; import org.apache.fop.fo.PropertyList; import org.apache.fop.fo.ValidationException; @@ -68,10 +71,10 @@ public class Bookmark extends FObj { externalDestination = null; } else if (externalDestination.length() == 0) { // slightly stronger than spec "should be specified" - attributeError("Missing attribute: Either external-destination or " + - "internal-destination must be specified."); + getFOValidationEventProducer().missingLinkDestination(this, getName(), locator); } else { - attributeWarning("external-destination property not currently supported"); + getFOValidationEventProducer().unimplementedFeature(this, getName(), + "external-destination", getLocator()); } } @@ -80,18 +83,20 @@ public class Bookmark extends FObj { XSL/FOP: (bookmark-title, bookmark*) */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { - if (FO_URI.equals(nsURI) && localName.equals("bookmark-title")) { + throws ValidationException { + if (FO_URI.equals(nsURI)) { + if (localName.equals("bookmark-title")) { if (bookmarkTitle != null) { tooManyNodesError(loc, "fo:bookmark-title"); } - } else if (FO_URI.equals(nsURI) && localName.equals("bookmark")) { + } else if (localName.equals("bookmark")) { if (bookmarkTitle == null) { nodesOutOfOrderError(loc, "fo:bookmark-title", "fo:bookmark"); } } else { invalidChildError(loc, nsURI, localName); } + } } /** @@ -123,10 +128,18 @@ public class Bookmark extends FObj { return bookmarkTitle == null ? "" : bookmarkTitle.getTitle(); } + /** + * Returns the value of the internal-destination property. + * @return the internal-destination + */ public String getInternalDestination() { return internalDestination; } + /** + * Returns the value of the external-destination property. + * @return the external-destination + */ public String getExternalDestination() { return externalDestination; } @@ -141,7 +154,11 @@ public class Bookmark extends FObj { return bShow; } - public ArrayList getChildBookmarks() { + /** + * Returns a list of child bookmarks. + * @return the list of child bookmarks + */ + public List getChildBookmarks() { return childBookmarks; } @@ -150,9 +167,7 @@ public class Bookmark extends FObj { return "bookmark"; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public int getNameId() { return FO_BOOKMARK; } diff --git a/src/java/org/apache/fop/fo/pagination/bookmarks/BookmarkTitle.java b/src/java/org/apache/fop/fo/pagination/bookmarks/BookmarkTitle.java index f31aad1cb..c7024f2aa 100644 --- a/src/java/org/apache/fop/fo/pagination/bookmarks/BookmarkTitle.java +++ b/src/java/org/apache/fop/fo/pagination/bookmarks/BookmarkTitle.java @@ -21,8 +21,8 @@ package org.apache.fop.fo.pagination.bookmarks; import org.xml.sax.Locator; -import org.apache.fop.fo.FObj; import org.apache.fop.fo.FONode; +import org.apache.fop.fo.FObj; import org.apache.fop.fo.PropertyList; import org.apache.fop.fo.ValidationException; @@ -45,15 +45,16 @@ public class BookmarkTitle extends FObj { /** * Add the characters to this BookmarkTitle. - * The text data inside the BookmarkTitle xml element + * The text data inside the BookmarkTitle XML element * is used for the BookmarkTitle string. * * @param data the character data * @param start the start position in the data array * @param end the end position in the character array - * @param locator location in fo source file. + * @param pList the currently valid property list + * @param locator location in FO source file. */ - protected void addCharacters(char data[], int start, int end, + protected void addCharacters(char[] data, int start, int end, PropertyList pList, Locator locator) { title += new String(data, start, end - start); @@ -64,8 +65,10 @@ public class BookmarkTitle extends FObj { XSL/FOP: empty */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { + throws ValidationException { + if (FO_URI.equals(nsURI)) { invalidChildError(loc, nsURI, localName); + } } /** @@ -82,9 +85,7 @@ public class BookmarkTitle extends FObj { return "bookmark-title"; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public int getNameId() { return FO_BOOKMARK_TITLE; } diff --git a/src/java/org/apache/fop/fo/pagination/bookmarks/BookmarkTree.java b/src/java/org/apache/fop/fo/pagination/bookmarks/BookmarkTree.java index 6190fa22a..0f1d8a8b7 100644 --- a/src/java/org/apache/fop/fo/pagination/bookmarks/BookmarkTree.java +++ b/src/java/org/apache/fop/fo/pagination/bookmarks/BookmarkTree.java @@ -21,6 +21,7 @@ package org.apache.fop.fo.pagination.bookmarks; // Java import java.util.ArrayList; +import java.util.List; import org.xml.sax.Locator; @@ -39,15 +40,15 @@ public class BookmarkTree extends FObj { private ArrayList bookmarks = new ArrayList(); /** + * Creates a new bookmark-tree element. + * @param parent the parent node * @see org.apache.fop.fo.FONode#FONode(FONode) */ public BookmarkTree(FONode parent) { super(parent); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ protected void addChildNode(FONode obj) { if (obj instanceof Bookmark) { bookmarks.add(obj); @@ -69,14 +70,19 @@ public class BookmarkTree extends FObj { XSL/FOP: (bookmark+) */ protected void validateChildNode(Locator loc, String nsURI, String localName) - throws ValidationException { - if (!(FO_URI.equals(nsURI) && - localName.equals("bookmark"))) { + throws ValidationException { + if (FO_URI.equals(nsURI)) { + if (!localName.equals("bookmark")) { invalidChildError(loc, nsURI, localName); + } } } - public ArrayList getBookmarks() { + /** + * Returns the root bookmarks. + * @return the root bookmarks + */ + public List getBookmarks() { return bookmarks; } @@ -85,9 +91,7 @@ public class BookmarkTree extends FObj { return "bookmark-tree"; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public int getNameId() { return FO_BOOKMARK_TREE; } diff --git a/src/java/org/apache/fop/fonts/FontEventAdapter.java b/src/java/org/apache/fop/fonts/FontEventAdapter.java new file mode 100644 index 000000000..516999f70 --- /dev/null +++ b/src/java/org/apache/fop/fonts/FontEventAdapter.java @@ -0,0 +1,82 @@ +/* + * 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.fonts; + +import java.util.Map; + +import org.apache.fop.events.Event; +import org.apache.fop.events.EventBroadcaster; +import org.apache.fop.events.model.EventSeverity; + +/** + * Event listener interface for font-related events. This interface extends FontEventListener + * and EventProducer for integration into FOP's event subsystem. + */ +public class FontEventAdapter implements FontEventListener { + + private EventBroadcaster eventBroadcaster; + + /** + * Creates a new FontEventAdapter. + * @param broadcaster the event broadcaster to send the generated events to + */ + public FontEventAdapter(EventBroadcaster broadcaster) { + this.eventBroadcaster = broadcaster; + } + + /** + * Returns the event group ID. + * @return the event group ID + */ + protected String getEventGroupID() { + return getClass().getName(); + } + + /** {@inheritDoc} */ + public void fontSubstituted(Object source, FontTriplet requested, FontTriplet effective) { + Map params = new java.util.HashMap(); + params.put("requested", requested); + params.put("effective", effective); + Event ev = new Event(source, getEventGroupID() + ".fontSubstituted", + EventSeverity.WARN, params); + this.eventBroadcaster.broadcastEvent(ev); + } + + /** {@inheritDoc} */ + public void fontLoadingErrorAtAutoDetection(Object source, String fontURL, Exception e) { + Map params = new java.util.HashMap(); + params.put("fontURL", fontURL); + params.put("e", e); + Event ev = new Event(source, getEventGroupID() + ".fontLoadingErrorAtAutoDetection", + EventSeverity.WARN, params); + this.eventBroadcaster.broadcastEvent(ev); + } + + /** {@inheritDoc} */ + public void glyphNotAvailable(Object source, char ch, String fontName) { + Map params = new java.util.HashMap(); + params.put("ch", new Character(ch)); + params.put("fontName", fontName); + Event ev = new Event(source, getEventGroupID() + ".glyphNotAvailable", + EventSeverity.WARN, params); + this.eventBroadcaster.broadcastEvent(ev); + } + +} diff --git a/src/java/org/apache/fop/fonts/FontEventListener.java b/src/java/org/apache/fop/fonts/FontEventListener.java new file mode 100644 index 000000000..512df0ac0 --- /dev/null +++ b/src/java/org/apache/fop/fonts/FontEventListener.java @@ -0,0 +1,52 @@ +/* + * 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.fonts; + + +/** + * Event listener interface for font-related events. + */ +public interface FontEventListener { + + /** + * Notifies about a font being substituted as the requested one isn't available. + * @param source the event source + * @param requested the requested font triplet + * @param effective the effective font triplet + */ + void fontSubstituted(Object source, FontTriplet requested, FontTriplet effective); + + /** + * An error occurred while loading a font for auto-detection. + * @param source the event source + * @param fontURL the font URL + * @param e the original exception + */ + void fontLoadingErrorAtAutoDetection(Object source, String fontURL, Exception e); + + /** + * A glyph has been requested that is not available in the font. + * @param source the event source + * @param ch the character for which the glyph isn't available + * @param fontName the name of the font + */ + void glyphNotAvailable(Object source, char ch, String fontName); + +} diff --git a/src/java/org/apache/fop/fonts/FontInfo.java b/src/java/org/apache/fop/fonts/FontInfo.java index 218734f15..950134eb6 100644 --- a/src/java/org/apache/fop/fonts/FontInfo.java +++ b/src/java/org/apache/fop/fonts/FontInfo.java @@ -67,6 +67,8 @@ public class FontInfo { /** Cache for Font instances. */ private Map fontInstanceCache = new java.util.HashMap(); + private FontEventListener eventListener; + /** * Main constructor */ @@ -77,6 +79,15 @@ public class FontInfo { this.usedFonts = new java.util.HashMap(); } + /** + * Sets the font event listener that can be used to receive events about particular events + * in this class. + * @param listener the font event listener + */ + public void setEventListener(FontEventListener listener) { + this.eventListener = listener; + } + /** * Checks if the font setup is valid (At least the ultimate fallback font * must be registered.) @@ -158,6 +169,9 @@ public class FontInfo { public void addMetrics(String name, FontMetrics metrics) { // add the given metrics as a font with the given name + if (metrics instanceof Typeface) { + ((Typeface)metrics).setEventListener(this.eventListener); + } this.fonts.put(name, metrics); } @@ -364,8 +378,12 @@ public class FontInfo { } if (!loggedFontKeys.contains(replacedKey)) { loggedFontKeys.add(replacedKey); - log.warn("Font '" + replacedKey + "' not found. " - + "Substituting with '" + newKey + "'."); + if (this.eventListener != null) { + this.eventListener.fontSubstituted(this, replacedKey, newKey); + } else { + log.warn("Font '" + replacedKey + "' not found. " + + "Substituting with '" + newKey + "'."); + } } } diff --git a/src/java/org/apache/fop/fonts/LazyFont.java b/src/java/org/apache/fop/fonts/LazyFont.java index 07b5be305..5490e13f1 100644 --- a/src/java/org/apache/fop/fonts/LazyFont.java +++ b/src/java/org/apache/fop/fonts/LazyFont.java @@ -141,6 +141,7 @@ public class LazyFont extends Typeface implements FontDescriptor { throw new RuntimeException(ioex.getMessage()); } } + realFont.setEventListener(this.eventListener); isMetricsLoaded = true; } } diff --git a/src/java/org/apache/fop/fonts/SingleByteFont.java b/src/java/org/apache/fop/fonts/SingleByteFont.java index ac12b7615..0a47d52ab 100644 --- a/src/java/org/apache/fop/fonts/SingleByteFont.java +++ b/src/java/org/apache/fop/fonts/SingleByteFont.java @@ -117,18 +117,22 @@ public class SingleByteFont extends CustomFont { } //Give up, character is not available - Character ch = new Character(c); - if (warnedChars == null) { - warnedChars = new java.util.HashSet(); - } - if (warnedChars.size() < 8 && !warnedChars.contains(ch)) { - warnedChars.add(ch); - if (warnedChars.size() == 8) { - log.warn("Many requested glyphs are not available in font " + getFontName()); - } else { - log.warn("Glyph " + (int)c + " (0x" + Integer.toHexString(c) - + ", " + Glyphs.charToGlyphName(c) - + ") not available in font " + getFontName()); + if (this.eventListener != null) { + this.eventListener.glyphNotAvailable(this, c, getFontName()); + } else { + Character ch = new Character(c); + if (warnedChars == null) { + warnedChars = new java.util.HashSet(); + } + if (warnedChars.size() < 8 && !warnedChars.contains(ch)) { + warnedChars.add(ch); + if (warnedChars.size() == 8) { + log.warn("Many requested glyphs are not available in font " + getFontName()); + } else { + log.warn("Glyph " + (int)c + " (0x" + Integer.toHexString(c) + + ", " + Glyphs.charToGlyphName(c) + + ") not available in font " + getFontName()); + } } } return NOT_FOUND; diff --git a/src/java/org/apache/fop/fonts/Typeface.java b/src/java/org/apache/fop/fonts/Typeface.java index 173d2e8a3..b6c78a3b0 100644 --- a/src/java/org/apache/fop/fonts/Typeface.java +++ b/src/java/org/apache/fop/fonts/Typeface.java @@ -30,6 +30,9 @@ public abstract class Typeface implements FontMetrics { */ private long charMapOps = 0; + /** An optional event listener that receives events such as missing glyphs etc. */ + protected FontEventListener eventListener; + /** * Get the encoding of the font. * @return the encoding @@ -80,5 +83,14 @@ public abstract class Typeface implements FontMetrics { return getAscender(size); } + /** + * Sets the font event listener that can be used to receive events about particular events + * in this class. + * @param listener the font event listener + */ + public void setEventListener(FontEventListener listener) { + this.eventListener = listener; + } + } diff --git a/src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java b/src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java index 8207eb140..b223ea7a1 100644 --- a/src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java +++ b/src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java @@ -35,6 +35,7 @@ import org.apache.fop.fonts.CustomFont; import org.apache.fop.fonts.EmbedFontInfo; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontCache; +import org.apache.fop.fonts.FontEventListener; import org.apache.fop.fonts.FontLoader; import org.apache.fop.fonts.FontResolver; import org.apache.fop.fonts.FontTriplet; @@ -52,6 +53,17 @@ public class FontInfoFinder { /** logging instance */ private Log log = LogFactory.getLog(FontInfoFinder.class); + private FontEventListener eventListener; + + /** + * Sets the font event listener that can be used to receive events about particular events + * in this class. + * @param listener the font event listener + */ + public void setEventListener(FontEventListener listener) { + this.eventListener = listener; + } + /** * Attempts to determine FontTriplets from a given CustomFont. * It seems to be fairly accurate but will probably require some tweaking over time @@ -180,7 +192,6 @@ public class FontInfoFinder { // try to determine triplet information from font file CustomFont customFont = null; - if (fontUrl.toExternalForm().endsWith(".ttc")) { // Get a list of the TTC Font names List ttcNames = null; //List @@ -193,7 +204,9 @@ public class FontInfoFinder { FontFileReader reader = new FontFileReader(in); ttcNames = ttf.getTTCnames(reader); } catch (Exception e) { - log.error(e); + if (this.eventListener != null) { + this.eventListener.fontLoadingErrorAtAutoDetection(this, fontFileURI, e); + } } finally { IOUtils.closeQuietly(in); } @@ -212,14 +225,16 @@ public class FontInfoFinder { try { ttfLoader = new TTFFontLoader(fontFileURI, fontName, resolver); customFont = ttfLoader.getFont(); + if (this.eventListener != null) { + customFont.setEventListener(this.eventListener); + } } catch (Exception e) { - //TODO Too verbose (it's an error but we don't care if some fonts can't be loaded) - //if (log.isErrorEnabled()) { - log.error("Unable to load font file: " + embedUrl + ". Reason: " + e.getMessage()); - //} if (fontCache != null) { fontCache.registerFailedFont(embedUrl, fileLastModified); } + if (this.eventListener != null) { + this.eventListener.fontLoadingErrorAtAutoDetection(this, embedUrl, e); + } continue; } EmbedFontInfo fi = fontInfoFromCustomFont(fontUrl, customFont, fontCache); @@ -233,14 +248,16 @@ public class FontInfoFinder { // The normal case try { customFont = FontLoader.loadFont(fontUrl, null, resolver); + if (this.eventListener != null) { + customFont.setEventListener(this.eventListener); + } } catch (Exception e) { - //TODO Too verbose (it's an error but we don't care if some fonts can't be loaded) - //if (log.isErrorEnabled()) { - log.error("Unable to load font file: " + embedUrl + ". Reason: " + e.getMessage()); - //} if (fontCache != null) { fontCache.registerFailedFont(embedUrl, fileLastModified); } + if (this.eventListener != null) { + this.eventListener.fontLoadingErrorAtAutoDetection(this, embedUrl, e); + } return null; } EmbedFontInfo fi = fontInfoFromCustomFont(fontUrl, customFont, fontCache); @@ -250,7 +267,6 @@ public class FontInfoFinder { return null; } } - } diff --git a/src/java/org/apache/fop/image/loader/batik/ImageConverterSVG2G2D.java b/src/java/org/apache/fop/image/loader/batik/ImageConverterSVG2G2D.java index 81b3b4c07..be0995d8c 100644 --- a/src/java/org/apache/fop/image/loader/batik/ImageConverterSVG2G2D.java +++ b/src/java/org/apache/fop/image/loader/batik/ImageConverterSVG2G2D.java @@ -27,8 +27,11 @@ import java.util.Map; import org.apache.batik.bridge.BridgeContext; import org.apache.batik.bridge.GVTBuilder; +import org.apache.batik.bridge.UserAgent; import org.apache.batik.dom.svg.SVGDOMImplementation; import org.apache.batik.gvt.GraphicsNode; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.apache.xmlgraphics.image.loader.Image; import org.apache.xmlgraphics.image.loader.ImageException; @@ -40,7 +43,7 @@ import org.apache.xmlgraphics.image.loader.impl.ImageXMLDOM; import org.apache.xmlgraphics.java2d.Graphics2DImagePainter; import org.apache.xmlgraphics.util.UnitConv; -import org.apache.fop.svg.SVGUserAgent; +import org.apache.fop.svg.SimpleSVGUserAgent; /** * This ImageConverter converts SVG images to Java2D. @@ -52,6 +55,9 @@ import org.apache.fop.svg.SVGUserAgent; */ public class ImageConverterSVG2G2D extends AbstractImageConverter { + /** logger */ + private static Log log = LogFactory.getLog(ImageConverterSVG2G2D.class); + /** {@inheritDoc} */ public Image convert(Image src, Map hints) throws ImageException { checkSourceFlavor(src); @@ -67,9 +73,7 @@ public class ImageConverterSVG2G2D extends AbstractImageConverter { if (ptm != null) { pxToMillimeter = (float)UnitConv.mm2in(ptm.doubleValue()); } - SVGUserAgent ua = new SVGUserAgent( - pxToMillimeter, - new AffineTransform()); + UserAgent ua = createBatikUserAgent(pxToMillimeter); GVTBuilder builder = new GVTBuilder(); final BridgeContext ctx = new BridgeContext(ua); @@ -107,6 +111,25 @@ public class ImageConverterSVG2G2D extends AbstractImageConverter { return g2dImage; } + /** + * Creates a user agent for Batik. Override to provide your own user agent. + * @param pxToMillimeter the source resolution (in px per millimeter) + * @return the newly created user agent + */ + protected SimpleSVGUserAgent createBatikUserAgent(float pxToMillimeter) { + return new SimpleSVGUserAgent( + pxToMillimeter, + new AffineTransform()) { + + /** {@inheritDoc} */ + public void displayMessage(String message) { + //TODO Refine and pipe through to caller + log.debug(message); + } + + }; + } + /** {@inheritDoc} */ public ImageFlavor getSourceFlavor() { return ImageFlavor.XML_DOM; diff --git a/src/java/org/apache/fop/image/loader/batik/PreloaderSVG.java b/src/java/org/apache/fop/image/loader/batik/PreloaderSVG.java index e59e06b6b..20557a644 100644 --- a/src/java/org/apache/fop/image/loader/batik/PreloaderSVG.java +++ b/src/java/org/apache/fop/image/loader/batik/PreloaderSVG.java @@ -32,6 +32,7 @@ import org.w3c.dom.svg.SVGDocument; import org.apache.batik.bridge.BridgeContext; import org.apache.batik.bridge.UnitProcessor; +import org.apache.batik.bridge.UserAgent; import org.apache.batik.dom.svg.SAXSVGDocumentFactory; import org.apache.batik.dom.svg.SVGDOMImplementation; import org.apache.batik.dom.svg.SVGOMDocument; @@ -46,7 +47,7 @@ import org.apache.xmlgraphics.image.loader.impl.ImageXMLDOM; import org.apache.xmlgraphics.image.loader.util.ImageUtil; import org.apache.xmlgraphics.util.MimeConstants; -import org.apache.fop.svg.SVGUserAgent; +import org.apache.fop.svg.SimpleSVGUserAgent; import org.apache.fop.util.UnclosableInputStream; /** @@ -154,8 +155,15 @@ public class PreloaderSVG extends AbstractImagePreloader { private ImageInfo createImageInfo(String uri, ImageContext context, SVGDocument doc) { Element e = doc.getRootElement(); float pxUnitToMillimeter = 25.4f / context.getSourceResolution(); - SVGUserAgent userAg = new SVGUserAgent(pxUnitToMillimeter, - new AffineTransform()); + UserAgent userAg = new SimpleSVGUserAgent(pxUnitToMillimeter, + new AffineTransform()) { + + /** {@inheritDoc} */ + public void displayMessage(String message) { + log.debug(message); + } + + }; BridgeContext ctx = new BridgeContext(userAg); UnitProcessor.Context uctx = UnitProcessor.createContext(ctx, e); diff --git a/src/java/org/apache/fop/layoutmgr/AbstractBreaker.java b/src/java/org/apache/fop/layoutmgr/AbstractBreaker.java index 65d537bcd..4ef0579f2 100644 --- a/src/java/org/apache/fop/layoutmgr/AbstractBreaker.java +++ b/src/java/org/apache/fop/layoutmgr/AbstractBreaker.java @@ -198,11 +198,11 @@ public abstract class AbstractBreaker { } /** - * Returns a PageBreakingLayoutListener for the PageBreakingAlgorithm to notify about layout - * problems. + * Creates and returns a PageBreakingLayoutListener for the PageBreakingAlgorithm to + * notify about layout problems. * @return the listener instance or null if no notifications are needed */ - protected PageBreakingAlgorithm.PageBreakingLayoutListener getLayoutListener() { + protected PageBreakingAlgorithm.PageBreakingLayoutListener createLayoutListener() { return null; } @@ -320,7 +320,7 @@ public abstract class AbstractBreaker { log.debug("PLM> start of algorithm (" + this.getClass().getName() + "), flow BPD =" + flowBPD); PageBreakingAlgorithm alg = new PageBreakingAlgorithm(getTopLevelLM(), - getPageProvider(), getLayoutListener(), + getPageProvider(), createLayoutListener(), alignment, alignmentLast, footnoteSeparatorLength, isPartOverflowRecoveryActivated(), autoHeight, isSinglePartFavored()); int iOptPageCount; diff --git a/src/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java b/src/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java index e8ca88c1c..f01f0e12f 100644 --- a/src/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java @@ -35,7 +35,6 @@ import org.apache.fop.area.CTM; import org.apache.fop.area.Trait; import org.apache.fop.datatypes.FODimension; import org.apache.fop.datatypes.Length; -import org.apache.fop.fo.FONode; import org.apache.fop.fo.flow.BlockContainer; import org.apache.fop.fo.properties.CommonAbsolutePosition; import org.apache.fop.traits.MinOptMax; @@ -239,11 +238,11 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager int availableIPD = referenceIPD - getIPIndents(); if (getContentAreaIPD() > availableIPD) { - log.warn(FONode.decorateWithContextInfo( - "The extent in inline-progression-direction (width) of a block-container is" - + " bigger than the available space (" - + getContentAreaIPD() + "mpt > " + context.getRefIPD() + "mpt)", - getBlockContainerFO())); + BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get( + getBlockContainerFO().getUserAgent().getEventBroadcaster()); + eventProducer.objectTooWide(this, getBlockContainerFO().getName(), + getContentAreaIPD(), context.getRefIPD(), + getBlockContainerFO().getLocator()); } MinOptMax stackLimit = new MinOptMax(relDims.bpd); @@ -378,10 +377,12 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager */ if (contentOverflows) { - log.warn("Contents overflow block-container viewport: clipping"); - if (getBlockContainerFO().getOverflow() == EN_ERROR_IF_OVERFLOW) { - //TODO Throw layout exception - } + BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get( + getBlockContainerFO().getUserAgent().getEventBroadcaster()); + boolean canRecover = (getBlockContainerFO().getOverflow() != EN_ERROR_IF_OVERFLOW); + eventProducer.viewportOverflow(this, getBlockContainerFO().getName(), + breaker.getOverflowAmount(), needClip(), canRecover, + getBlockContainerFO().getLocator()); } } addKnuthElementsForBorderPaddingAfter(returnList, true); @@ -526,10 +527,12 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager //TODO Maybe check for page overflow when autoHeight=true if (!autoHeight & (contentOverflows)) { - log.warn("Contents overflow block-container viewport: clipping"); - if (getBlockContainerFO().getOverflow() == EN_ERROR_IF_OVERFLOW) { - //TODO Throw layout exception - } + BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get( + getBlockContainerFO().getUserAgent().getEventBroadcaster()); + boolean canRecover = (getBlockContainerFO().getOverflow() != EN_ERROR_IF_OVERFLOW); + eventProducer.viewportOverflow(this, getBlockContainerFO().getName(), + breaker.getOverflowAmount(), needClip(), canRecover, + getBlockContainerFO().getLocator()); } } @@ -602,7 +605,18 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager } public boolean isOverflow() { - return !isEmpty() && (deferredAlg.getPageBreaks().size() > 1); + if (isEmpty()) { + return false; + } else { + return (deferredAlg.getPageBreaks().size() > 1) + || (deferredAlg.totalWidth - deferredAlg.totalShrink) + > deferredAlg.getLineWidth(); + } + } + + public int getOverflowAmount() { + return (deferredAlg.totalWidth - deferredAlg.totalShrink) + - deferredAlg.getLineWidth(); } protected LayoutManager getTopLevelLM() { diff --git a/src/java/org/apache/fop/layoutmgr/BlockLevelEventProducer.java b/src/java/org/apache/fop/layoutmgr/BlockLevelEventProducer.java new file mode 100644 index 000000000..c31a70477 --- /dev/null +++ b/src/java/org/apache/fop/layoutmgr/BlockLevelEventProducer.java @@ -0,0 +1,168 @@ +/* + * 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.layoutmgr; + +import org.xml.sax.Locator; + +import org.apache.fop.apps.FOPException; +import org.apache.fop.events.EventBroadcaster; +import org.apache.fop.events.EventProducer; + +/** + * Event producer interface for block-level layout managers. + */ +public interface BlockLevelEventProducer extends EventProducer { + + /** + * Provider class for the event producer. + */ + class Provider { + + /** + * Returns an event producer. + * @param broadcaster the event broadcaster to use + * @return the event producer + */ + public static BlockLevelEventProducer get(EventBroadcaster broadcaster) { + return (BlockLevelEventProducer)broadcaster.getEventProducerFor( + BlockLevelEventProducer.class); + } + } + + /** + * The contents of a table-row are too big to fit in the constraints. + * @param source the event source + * @param row the row number + * @param effCellBPD the effective extent in block-progression direction of the cell + * @param maxCellBPD the maximum extent in block-progression direction of the cell + * @param loc the location of the error or null + * @event.severity WARN + */ + void rowTooTall(Object source, int row, int effCellBPD, int maxCellBPD, Locator loc); + + /** + * Auto-table layout is not supported, yet. + * @param source the event source + * @param loc the location of the error or null + * @event.severity INFO + */ + void tableFixedAutoWidthNotSupported(Object source, Locator loc); + + /** + * An formatting object is too wide. + * @param source the event source + * @param elementName the formatting object + * @param effIPD the effective extent in inline-progression direction of the table contents + * @param maxIPD the maximum extent in inline-progression direction available + * @param loc the location of the error or null + * @event.severity WARN + */ + void objectTooWide(Object source, String elementName, int effIPD, int maxIPD, Locator loc); + + /** + * An overconstrained geometry adjustment rule was triggered (5.3.4, XSL 1.0). + * @param source the event source + * @param elementName the formatting object + * @param amount the amount of the adjustment (in mpt) + * @param loc the location of the error or null + * @event.severity INFO + */ + void overconstrainedAdjustEndIndent(Object source, String elementName, int amount, Locator loc); + + /** + * Contents overflow a viewport. + * @param source the event source + * @param elementName the formatting object + * @param amount the amount by which the contents overflow (in mpt) + * @param clip true if the content will be clipped + * @param canRecover indicates whether FOP can recover from this problem and continue working + * @param loc the location of the error or null + * @throws LayoutException the layout error provoked by the method call + * @event.severity FATAL + */ + void viewportOverflow(Object source, String elementName, + int amount, boolean clip, boolean canRecover, + Locator loc) throws LayoutException; + + /** + * Contents overflow a region viewport. + * @param source the event source + * @param elementName the formatting object + * @param page the page number/name where the overflow happened + * @param amount the amount by which the contents overflow (in mpt) + * @param clip true if the content will be clipped + * @param canRecover indicates whether FOP can recover from this problem and continue working + * @param loc the location of the error or null + * @throws LayoutException the layout error provoked by the method call + * @event.severity FATAL + */ + void regionOverflow(Object source, String elementName, + String page, + int amount, boolean clip, boolean canRecover, + Locator loc) throws LayoutException; + + /** + * Indicates that FOP doesn't support flows that are not mapped to region-body, yet. + * @param source the event source + * @param flowName the flow name + * @param masterName the page master name + * @param loc the location of the error or null + * @throws UnsupportedOperationException the layout error provoked by the method call + * @event.severity FATAL + */ + void flowNotMappingToRegionBody(Object source, String flowName, String masterName, + Locator loc) throws UnsupportedOperationException; + + /** + * A page sequence master is exhausted. + * @param source the event source + * @param pageSequenceMasterName the name of the page sequence master + * @param canRecover indicates whether FOP can recover from this problem and continue working + * @param loc the location of the error or null + * @throws FOPException the error provoked by the method call + * @event.severity FATAL + */ + void pageSequenceMasterExhausted(Object source, String pageSequenceMasterName, + boolean canRecover, Locator loc) throws FOPException; + + /** + * No subsequences in page sequence master. + * @param source the event source + * @param pageSequenceMasterName the name of the page sequence master + * @param loc the location of the error or null + * @throws FOPException the error provoked by the method call + * @event.severity FATAL + */ + void missingSubsequencesInPageSequenceMaster(Object source, String pageSequenceMasterName, + Locator loc) throws FOPException; + + /** + * No single-page-master matching in page sequence master. + * @param source the event source + * @param pageSequenceMasterName the name of the page sequence master + * @param pageMasterName the name of the page master not matching + * @param loc the location of the error or null + * @throws FOPException the error provoked by the method call + * @event.severity FATAL + */ + void noMatchingPageMaster(Object source, String pageSequenceMasterName, + String pageMasterName, Locator loc) throws FOPException; + +} diff --git a/src/java/org/apache/fop/layoutmgr/BlockStackingLayoutManager.java b/src/java/org/apache/fop/layoutmgr/BlockStackingLayoutManager.java index 67ed1de9f..cb6db6b01 100644 --- a/src/java/org/apache/fop/layoutmgr/BlockStackingLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/BlockStackingLayoutManager.java @@ -201,6 +201,10 @@ public abstract class BlockStackingLayoutManager extends AbstractLayoutManager if (ipd < 0) { //5.3.4, XSL 1.0, Overconstrained Geometry log.debug("Adjusting end-indent based on overconstrained geometry rules for " + fobj); + BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get( + getFObj().getUserAgent().getEventBroadcaster()); + eventProducer.overconstrainedAdjustEndIndent(this, + getFObj().getName(), ipd, getFObj().getLocator()); endIndent += ipd; ipd = 0; //TODO Should we skip layout for a block that has ipd=0? @@ -212,7 +216,7 @@ public abstract class BlockStackingLayoutManager extends AbstractLayoutManager /** * Sets the content area IPD by directly supplying the value. * end-indent is adjusted based on overconstrained geometry rules, if necessary. - * + * @param contentIPD the IPD of the content * @return the resulting content area IPD */ protected int updateContentAreaIPDwithOverconstrainedAdjust(int contentIPD) { @@ -220,6 +224,10 @@ public abstract class BlockStackingLayoutManager extends AbstractLayoutManager if (ipd < 0) { //5.3.4, XSL 1.0, Overconstrained Geometry log.debug("Adjusting end-indent based on overconstrained geometry rules for " + fobj); + BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get( + getFObj().getUserAgent().getEventBroadcaster()); + eventProducer.overconstrainedAdjustEndIndent(this, + getFObj().getName(), ipd, getFObj().getLocator()); endIndent += ipd; } setContentAreaIPD(contentIPD); diff --git a/src/java/org/apache/fop/layoutmgr/ExternalDocumentLayoutManager.java b/src/java/org/apache/fop/layoutmgr/ExternalDocumentLayoutManager.java index 21856c781..086d91c31 100644 --- a/src/java/org/apache/fop/layoutmgr/ExternalDocumentLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/ExternalDocumentLayoutManager.java @@ -21,6 +21,7 @@ package org.apache.fop.layoutmgr; import java.awt.Dimension; import java.awt.Rectangle; +import java.io.FileNotFoundException; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; @@ -46,6 +47,7 @@ import org.apache.fop.area.inline.Image; import org.apache.fop.area.inline.Viewport; import org.apache.fop.datatypes.FODimension; import org.apache.fop.datatypes.URISpecification; +import org.apache.fop.events.ResourceEventProducer; import org.apache.fop.fo.Constants; import org.apache.fop.fo.extensions.ExternalDocument; import org.apache.fop.layoutmgr.inline.ImageLayout; @@ -140,17 +142,28 @@ public class ExternalDocumentLayoutManager extends AbstractPageSequenceLayoutMan pageIndex++; } } catch (URISyntaxException e) { - log.error("Error parsing or constructing URIs based on URI: " + uri); + getResourceEventProducer().uriError(this, uri, e, + getExternalDocument().getLocator()); return; } } + } catch (FileNotFoundException fnfe) { + getResourceEventProducer().imageNotFound(this, uri, fnfe, + getExternalDocument().getLocator()); } catch (IOException ioe) { - log.error("Image not available: " + uri, ioe); + getResourceEventProducer().imageIOError(this, uri, ioe, + getExternalDocument().getLocator()); } catch (ImageException ie) { - log.error("Error while inspecting image: " + uri + " (" + ie.getMessage() + ")"); + getResourceEventProducer().imageError(this, uri, ie, + getExternalDocument().getLocator()); } } + private ResourceEventProducer getResourceEventProducer() { + return ResourceEventProducer.Provider.get( + getExternalDocument().getUserAgent().getEventBroadcaster()); + } + private void makePageForImage(ImageInfo info, ImageLayout layout) { this.imageLayout = layout; curPage = makeNewPage(false, false); diff --git a/src/java/org/apache/fop/layoutmgr/LayoutException.java b/src/java/org/apache/fop/layoutmgr/LayoutException.java new file mode 100644 index 000000000..350cc758a --- /dev/null +++ b/src/java/org/apache/fop/layoutmgr/LayoutException.java @@ -0,0 +1,105 @@ +/* + * 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.layoutmgr; + +import java.util.Locale; + +import org.apache.fop.events.Event; +import org.apache.fop.events.EventFormatter; +import org.apache.fop.events.EventExceptionManager.ExceptionFactory; + +/** + * Exception thrown by FOP if an unrecoverable layout error occurs. An example: An area overflows + * a viewport that has overflow="error-if-overflow". + * + * @todo Discuss if this should become a checked exception. + */ +public class LayoutException extends RuntimeException { + + private static final long serialVersionUID = 5157080040923740433L; + + private String localizedMessage; + private LayoutManager layoutManager; + + /** + * Constructs a new layout exception with the specified detail message. + * @param message the detail message. + */ + public LayoutException(String message) { + this(message, null); + } + + /** + * Constructs a new layout exception with the specified detail message. + * @param message the detail message + * @param lm the layout manager that throws the exception + */ + public LayoutException(String message, LayoutManager lm) { + super(message); + this.layoutManager = lm; + } + + /** + * Sets the localized message for this exception. + * @param msg the localized message + */ + public void setLocalizedMessage(String msg) { + this.localizedMessage = msg; + } + + /** {@inheritDoc} */ + public String getLocalizedMessage() { + if (this.localizedMessage != null) { + return this.localizedMessage; + } else { + return super.getLocalizedMessage(); + } + } + + /** + * Returns the layout manager that detected the problem. + * @return the layout manager (or null) + */ + public LayoutManager getLayoutManager() { + return this.layoutManager; + } + + /** Exception factory for {@link LayoutException}. */ + public static class LayoutExceptionFactory implements ExceptionFactory { + + /** {@inheritDoc} */ + public Throwable createException(Event event) { + Object source = event.getSource(); + LayoutManager lm = (source instanceof LayoutManager) ? (LayoutManager)source : null; + String msg = EventFormatter.format(event, Locale.ENGLISH); + LayoutException ex = new LayoutException(msg, lm); + if (!Locale.ENGLISH.equals(Locale.getDefault())) { + ex.setLocalizedMessage(EventFormatter.format(event)); + } + return ex; + } + + /** {@inheritDoc} */ + public Class getExceptionClass() { + return LayoutException.class; + } + + } +} diff --git a/src/java/org/apache/fop/layoutmgr/PageBreaker.java b/src/java/org/apache/fop/layoutmgr/PageBreaker.java index 3e100cd50..d6be75758 100644 --- a/src/java/org/apache/fop/layoutmgr/PageBreaker.java +++ b/src/java/org/apache/fop/layoutmgr/PageBreaker.java @@ -27,7 +27,6 @@ import org.apache.fop.area.Block; import org.apache.fop.area.Footnote; import org.apache.fop.area.PageViewport; import org.apache.fop.fo.Constants; -import org.apache.fop.fo.FONode; import org.apache.fop.fo.FObj; import org.apache.fop.fo.pagination.PageSequence; import org.apache.fop.fo.pagination.Region; @@ -79,27 +78,25 @@ public class PageBreaker extends AbstractBreaker { return pslm.getPageProvider(); } - /** - * {@inheritDoc} - */ - protected PageBreakingLayoutListener getLayoutListener() { + /** {@inheritDoc} */ + protected PageBreakingLayoutListener createLayoutListener() { return new PageBreakingLayoutListener() { - public void notifyOverflow(int part, FObj obj) { + public void notifyOverflow(int part, int amount, FObj obj) { Page p = pageProvider.getPage( false, part, PageProvider.RELTO_CURRENT_ELEMENT_LIST); RegionBody body = (RegionBody)p.getSimplePageMaster().getRegion( Region.FO_REGION_BODY); - String err = FONode.decorateWithContextInfo( - "Content of the region-body on page " - + p.getPageViewport().getPageNumberString() - + " overflows the available area in block-progression dimension.", - obj); - if (body.getOverflow() == Constants.EN_ERROR_IF_OVERFLOW) { - throw new RuntimeException(err); - } else { - log.warn(err); - } + BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get( + body.getUserAgent().getEventBroadcaster()); + + boolean canRecover = (body.getOverflow() != Constants.EN_ERROR_IF_OVERFLOW); + boolean needClip = (body.getOverflow() == Constants.EN_HIDDEN + || body.getOverflow() == Constants.EN_ERROR_IF_OVERFLOW); + eventProducer.regionOverflow(this, body.getName(), + p.getPageViewport().getPageNumberString(), + amount, needClip, canRecover, + body.getLocator()); } }; @@ -297,7 +294,7 @@ public class PageBreaker extends AbstractBreaker { //Restart last page PageBreakingAlgorithm algRestart = new PageBreakingAlgorithm( getTopLevelLM(), - getPageProvider(), getLayoutListener(), + getPageProvider(), createLayoutListener(), alg.getAlignment(), alg.getAlignmentLast(), footnoteSeparatorLength, isPartOverflowRecoveryActivated(), false, false); @@ -356,7 +353,7 @@ public class PageBreaker extends AbstractBreaker { //Restart last page PageBreakingAlgorithm algRestart = new BalancingColumnBreakingAlgorithm( getTopLevelLM(), - getPageProvider(), getLayoutListener(), + getPageProvider(), createLayoutListener(), alignment, Constants.EN_START, footnoteSeparatorLength, isPartOverflowRecoveryActivated(), pslm.getCurrentPV().getBodyRegion().getColumnCount()); diff --git a/src/java/org/apache/fop/layoutmgr/PageBreakingAlgorithm.java b/src/java/org/apache/fop/layoutmgr/PageBreakingAlgorithm.java index d98d29b5c..5e3d0a887 100644 --- a/src/java/org/apache/fop/layoutmgr/PageBreakingAlgorithm.java +++ b/src/java/org/apache/fop/layoutmgr/PageBreakingAlgorithm.java @@ -27,7 +27,6 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.fop.fo.Constants; -import org.apache.fop.fo.FONode; import org.apache.fop.fo.FObj; import org.apache.fop.layoutmgr.AbstractBreaker.PageBreakPosition; import org.apache.fop.traits.MinOptMax; @@ -289,7 +288,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { private void resetFootnotes(LinkedList elementLists) { for (int i = 0; i < elementLists.size(); i++) { - LinkedList removedList = (LinkedList) footnotesList.remove(footnotesList.size() - 1); + /*LinkedList removedList = (LinkedList)*/footnotesList.remove(footnotesList.size() - 1); lengthList.remove(lengthList.size() - 1); // update totalFootnotesLength @@ -332,11 +331,13 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { actualWidth += allFootnotes; insertedFootnotesLength = pageNode.totalFootnotes + allFootnotes; footnoteListIndex = footnotesList.size() - 1; - footnoteElementIndex = ((LinkedList) footnotesList.get(footnoteListIndex)).size() - 1; - } else if (((canDeferOldFootnotes = checkCanDeferOldFootnotes(pageNode, elementIndex)) + footnoteElementIndex + = ((LinkedList) footnotesList.get(footnoteListIndex)).size() - 1; + } else if (((canDeferOldFootnotes + = checkCanDeferOldFootnotes(pageNode, elementIndex)) || newFootnotes) - && (footnoteSplit = getFootnoteSplit(pageNode, getLineWidth() - actualWidth, - canDeferOldFootnotes)) > 0) { + && (footnoteSplit = getFootnoteSplit(pageNode, + getLineWidth() - actualWidth, canDeferOldFootnotes)) > 0) { // it is allowed to break or even defer footnotes if either: // - there are new footnotes in the last piece of content, and // there is space to add at least a piece of the first one @@ -356,7 +357,8 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { actualWidth += allFootnotes; insertedFootnotesLength = pageNode.totalFootnotes + allFootnotes; footnoteListIndex = footnotesList.size() - 1; - footnoteElementIndex = ((LinkedList) footnotesList.get(footnoteListIndex)).size() - 1; + footnoteElementIndex + = ((LinkedList) footnotesList.get(footnoteListIndex)).size() - 1; } } else { // all footnotes have already been placed on previous pages @@ -381,7 +383,8 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { */ private boolean checkCanDeferOldFootnotes(KnuthPageNode node, int contentElementIndex) { return (noBreakBetween(node.position, contentElementIndex) - && deferredFootnotes(node.footnoteListIndex, node.footnoteElementIndex, node.totalFootnotes)); + && deferredFootnotes(node.footnoteListIndex, + node.footnoteElementIndex, node.totalFootnotes)); } /** @@ -455,7 +458,8 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { * @param availableLength available space for footnotes * @param canDeferOldFootnotes */ - private int getFootnoteSplit(KnuthPageNode activeNode, int availableLength, boolean canDeferOldFootnotes) { + private int getFootnoteSplit(KnuthPageNode activeNode, int availableLength, + boolean canDeferOldFootnotes) { return getFootnoteSplit(activeNode.footnoteListIndex, activeNode.footnoteElementIndex, activeNode.totalFootnotes, @@ -520,7 +524,8 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { } // try adding a split of the next note - noteListIterator = ((LinkedList) footnotesList.get(listIndex)).listIterator(elementIndex); + noteListIterator = ((LinkedList) footnotesList.get(listIndex)) + .listIterator(elementIndex); int prevSplitLength = 0; int prevIndex = -1; @@ -754,14 +759,6 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { } } - private int getPartCount() { - if (pageBreaks == null) { - return 0; - } else { - return pageBreaks.size(); - } - } - public void updateData1(int total, double demerits) { } @@ -774,12 +771,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { if (difference + bestActiveNode.availableShrink < 0) { if (!autoHeight) { if (layoutListener != null) { - layoutListener.notifyOverflow(bestActiveNode.line - 1, getFObj()); - } else if (log.isWarnEnabled()) { - log.warn(FONode.decorateWithContextInfo( - "Part/page " + (bestActiveNode.line - 1) - + " overflows the available area in block-progression dimension.", - getFObj())); + layoutListener.notifyOverflow(bestActiveNode.line - 1, -difference, getFObj()); } } } @@ -890,9 +882,10 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { /** * Issued when an overflow is detected * @param part the number of the part (page) this happens on + * @param amount the amount by which the area overflows (in mpt) * @param obj the root FO object where this happens */ - void notifyOverflow(int part, FObj obj); + void notifyOverflow(int part, int amount, FObj obj); } diff --git a/src/java/org/apache/fop/layoutmgr/PageProvider.java b/src/java/org/apache/fop/layoutmgr/PageProvider.java index e16c3396a..037f02094 100644 --- a/src/java/org/apache/fop/layoutmgr/PageProvider.java +++ b/src/java/org/apache/fop/layoutmgr/PageProvider.java @@ -23,6 +23,7 @@ import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.apache.fop.apps.FOPException; import org.apache.fop.area.AreaTreeHandler; import org.apache.fop.fo.Constants; @@ -74,6 +75,7 @@ public class PageProvider implements Constants { /** * Main constructor. + * @param ath the area tree handler * @param ps The page-sequence the provider operates on */ public PageProvider(AreaTreeHandler ath, PageSequence ps) { @@ -258,10 +260,10 @@ public class PageProvider implements Constants { if (!pageSeq.getMainFlow().getFlowName().equals(body.getRegionName())) { // this is fine by the XSL Rec (fo:flow's flow-name can be mapped to // any region), but we don't support it yet. - throw new FOPException("Flow '" + pageSeq.getMainFlow().getFlowName() - + "' does not map to the region-body in page-master '" - + spm.getMasterName() + "'. FOP presently " - + "does not support this."); + BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get( + pageSeq.getUserAgent().getEventBroadcaster()); + eventProducer.flowNotMappingToRegionBody(this, + pageSeq.getMainFlow().getFlowName(), spm.getMasterName(), spm.getLocator()); } Page page = new Page(spm, index, pageNumberString, isBlank); //Set unique key obtained from the AreaTreeHandler diff --git a/src/java/org/apache/fop/layoutmgr/StaticContentLayoutManager.java b/src/java/org/apache/fop/layoutmgr/StaticContentLayoutManager.java index b1e414527..763ddf58d 100644 --- a/src/java/org/apache/fop/layoutmgr/StaticContentLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/StaticContentLayoutManager.java @@ -29,10 +29,12 @@ import org.apache.commons.logging.LogFactory; import org.apache.fop.area.Area; import org.apache.fop.area.Block; import org.apache.fop.area.RegionReference; -import org.apache.fop.fo.FONode; +import org.apache.fop.fo.Constants; +import org.apache.fop.fo.FObj; import org.apache.fop.fo.pagination.PageSequence; import org.apache.fop.fo.pagination.SideRegion; import org.apache.fop.fo.pagination.StaticContent; +import org.apache.fop.layoutmgr.PageBreakingAlgorithm.PageBreakingLayoutListener; import org.apache.fop.layoutmgr.inline.InlineLevelLayoutManager; import org.apache.fop.layoutmgr.inline.TextLayoutManager; import org.apache.fop.traits.MinOptMax; @@ -240,12 +242,17 @@ public class StaticContentLayoutManager extends BlockStackingLayoutManager { breaker.doLayout(targetBPD, autoHeight); if (breaker.isOverflow()) { if (!autoHeight) { - //Overflow handling - if (regionFO.getOverflow() == EN_ERROR_IF_OVERFLOW) { - //TODO throw layout exception - } - log.warn(FONode.decorateWithContextInfo( - "static-content overflows the available area.", fobj)); + String page = getPSLM().getCurrentPage().getPageViewport().getPageNumberString(); + + BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get( + getStaticContentFO().getUserAgent().getEventBroadcaster()); + boolean canRecover = (regionFO.getOverflow() != EN_ERROR_IF_OVERFLOW); + boolean needClip = (regionFO.getOverflow() == Constants.EN_HIDDEN + || regionFO.getOverflow() == Constants.EN_ERROR_IF_OVERFLOW); + eventProducer.regionOverflow(this, regionFO.getName(), + page, + breaker.getOverflowAmount(), needClip, canRecover, + getStaticContentFO().getLocator()); } } } @@ -262,7 +269,7 @@ public class StaticContentLayoutManager extends BlockStackingLayoutManager { private StaticContentLayoutManager lm; private int displayAlign; private int ipd; - private boolean overflow = false; + private int overflow = 0; public StaticContentBreaker(StaticContentLayoutManager lm, int ipd, int displayAlign) { @@ -288,9 +295,26 @@ public class StaticContentLayoutManager extends BlockStackingLayoutManager { } public boolean isOverflow() { + return (this.overflow != 0); + } + + public int getOverflowAmount() { return this.overflow; } + /** {@inheritDoc} */ + protected PageBreakingLayoutListener createLayoutListener() { + return new PageBreakingLayoutListener() { + + public void notifyOverflow(int part, int amount, FObj obj) { + if (StaticContentBreaker.this.overflow == 0) { + StaticContentBreaker.this.overflow = amount; + } + } + + }; + } + protected LayoutManager getTopLevelLM() { return lm; } @@ -340,9 +364,6 @@ public class StaticContentLayoutManager extends BlockStackingLayoutManager { protected void doPhase3(PageBreakingAlgorithm alg, int partCount, BlockSequence originalList, BlockSequence effectiveList) { - if (partCount > 1) { - overflow = true; - } //Rendering all parts (not just the first) at once for the case where the parts that //overflow should be visible. alg.removeAllPageBreaks(); diff --git a/src/java/org/apache/fop/layoutmgr/inline/ContentLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/ContentLayoutManager.java index ff7c5b3ce..2af844c5c 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/ContentLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/inline/ContentLayoutManager.java @@ -60,7 +60,6 @@ public class ContentLayoutManager extends AbstractBaseLayoutManager */ private static Log log = LogFactory.getLog(ContentLayoutManager.class); - private FOUserAgent userAgent; private Area holder; private int stackSize; private LayoutManager parentLM; diff --git a/src/java/org/apache/fop/layoutmgr/inline/InlineLevelEventProducer.java b/src/java/org/apache/fop/layoutmgr/inline/InlineLevelEventProducer.java new file mode 100644 index 000000000..51d2720cb --- /dev/null +++ b/src/java/org/apache/fop/layoutmgr/inline/InlineLevelEventProducer.java @@ -0,0 +1,66 @@ +/* + * 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.layoutmgr.inline; + +import org.xml.sax.Locator; + +import org.apache.fop.events.EventBroadcaster; +import org.apache.fop.events.EventProducer; + +/** + * Event producer interface for inline-level layout managers. + */ +public interface InlineLevelEventProducer extends EventProducer { + + /** + * Provider class for the event producer. + */ + class Provider { + + /** + * Returns an event producer. + * @param broadcaster the event broadcaster to use + * @return the event producer + */ + public static InlineLevelEventProducer get(EventBroadcaster broadcaster) { + return (InlineLevelEventProducer)broadcaster.getEventProducerFor( + InlineLevelEventProducer.class); + } + } + + /** + * fo:leader with "use-content" but without children. + * @param source the event source + * @param loc the location of the error or null + * @event.severity ERROR + */ + void leaderWithoutContent(Object source, Locator loc); + + /** + * A line overflows. + * @param source the event source + * @param line the number number + * @param overflowLength the length by which the content overflows the available space (in mpt) + * @param loc the location of the error or null + * @event.severity WARN + */ + void lineOverflows(Object source, int line, int overflowLength, Locator loc); + +} diff --git a/src/java/org/apache/fop/layoutmgr/inline/LeaderLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/LeaderLayoutManager.java index f4bcde96f..1e7c793df 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/LeaderLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/inline/LeaderLayoutManager.java @@ -19,11 +19,15 @@ package org.apache.fop.layoutmgr.inline; +import java.util.LinkedList; +import java.util.List; + import org.apache.fop.area.Trait; import org.apache.fop.area.inline.FilledArea; import org.apache.fop.area.inline.InlineArea; import org.apache.fop.area.inline.Space; import org.apache.fop.area.inline.TextArea; +import org.apache.fop.fo.FObj; import org.apache.fop.fo.flow.Leader; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontInfo; @@ -41,10 +45,6 @@ import org.apache.fop.layoutmgr.PositionIterator; import org.apache.fop.layoutmgr.TraitSetter; import org.apache.fop.traits.MinOptMax; -import java.util.List; -import java.util.LinkedList; -import org.apache.fop.fo.FObj; - /** * LayoutManager for the fo:leader formatting object */ @@ -159,7 +159,9 @@ public class LeaderLayoutManager extends LeafNodeLayoutManager { leaderArea = fa; } else if (fobj.getLeaderPattern() == EN_USECONTENT) { if (fobj.getChildNodes() == null) { - fobj.getLogger().error("Leader use-content with no content"); + InlineLevelEventProducer eventProducer = InlineLevelEventProducer.Provider.get( + getFObj().getUserAgent().getEventBroadcaster()); + eventProducer.leaderWithoutContent(this, getFObj().getLocator()); return null; } @@ -339,9 +341,7 @@ public class LeaderLayoutManager extends LeafNodeLayoutManager { return returnList; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public int getBaseLength(int lengthBase, FObj fobj) { return getParent().getBaseLength(lengthBase, getParent().getFObj()); } diff --git a/src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java index 9a818232c..1258fbe18 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java @@ -34,7 +34,6 @@ import org.apache.fop.area.inline.InlineArea; import org.apache.fop.datatypes.Length; import org.apache.fop.datatypes.Numeric; import org.apache.fop.fo.Constants; -import org.apache.fop.fo.FONode; import org.apache.fop.fo.flow.Block; import org.apache.fop.fo.properties.CommonHyphenation; import org.apache.fop.fonts.Font; @@ -384,11 +383,12 @@ public class LineLayoutManager extends InlineStackingLayoutManager if (log.isWarnEnabled()) { int lack = difference + bestActiveNode.availableShrink; if (lack < 0) { + InlineLevelEventProducer eventProducer + = InlineLevelEventProducer.Provider.get( + getFObj().getUserAgent().getEventBroadcaster()); + eventProducer.lineOverflows(this, addedPositions + 1, + -lack, getFObj().getLocator()); String textDiff = (lack < -50000 ? "more than 50 points" : (-lack) + "mpt"); - log.warn(FONode.decorateWithContextInfo( - "Line " + (addedPositions + 1) - + " of a paragraph overflows the available area by " - + textDiff + ".", getFObj())); } } @@ -1430,7 +1430,9 @@ public class LineLayoutManager extends InlineStackingLayoutManager auxCount++; } } - log.trace(" Word to hyphenate: " + sbChars.toString()); + if (log.isTraceEnabled()) { + log.trace(" Word to hyphenate: " + sbChars.toString()); + } // find hyphenation points HyphContext hc = getHyphenContext(sbChars); // ask each LM to hyphenate its word fragment diff --git a/src/java/org/apache/fop/layoutmgr/table/ColumnSetup.java b/src/java/org/apache/fop/layoutmgr/table/ColumnSetup.java index 2ef2be908..bd032e610 100644 --- a/src/java/org/apache/fop/layoutmgr/table/ColumnSetup.java +++ b/src/java/org/apache/fop/layoutmgr/table/ColumnSetup.java @@ -25,9 +25,9 @@ import java.util.ListIterator; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.fop.datatypes.PercentBaseContext; -import org.apache.fop.datatypes.Length; +import org.apache.fop.datatypes.Length; +import org.apache.fop.datatypes.PercentBaseContext; import org.apache.fop.fo.FONode; import org.apache.fop.fo.flow.table.Table; import org.apache.fop.fo.flow.table.TableColumn; @@ -77,12 +77,14 @@ public class ColumnSetup { } } //Post-processing the list (looking for gaps) + //TODO The following block could possibly be removed int pos = 1; ListIterator ppIter = columns.listIterator(); while (ppIter.hasNext()) { TableColumn col = (TableColumn)ppIter.next(); if (col == null) { - log.error("Found a gap in the table-columns at position " + pos); + assert false; //Gaps are filled earlier by fo.flow.table.Table.finalizeColumns() + //log.error("Found a gap in the table-columns at position " + pos); } pos++; } @@ -100,7 +102,9 @@ public class ColumnSetup { if (index > size) { if (index > maxColIndexReferenced) { maxColIndexReferenced = index; - if (!(size == 1 && getColumn(1).isImplicitColumn())) { + TableColumn col = getColumn(1); + if (!(size == 1 && col.isImplicitColumn())) { + assert false; //TODO Seems to be removable as this is now done in the FO tree log.warn(FONode.decorateWithContextInfo( "There are fewer table-columns than are needed. " + "Column " + index + " was accessed, but only " @@ -193,8 +197,7 @@ public class ColumnSetup { if (colWidth != null) { sumCols += colWidth.getValue(tlm); if (colWidth instanceof TableColLength) { - factors += - ((TableColLength) colWidth).getTableUnits(); + factors += ((TableColLength) colWidth).getTableUnits(); } } } diff --git a/src/java/org/apache/fop/layoutmgr/table/RowGroupLayoutManager.java b/src/java/org/apache/fop/layoutmgr/table/RowGroupLayoutManager.java index 9c97ca827..72e78b84f 100644 --- a/src/java/org/apache/fop/layoutmgr/table/RowGroupLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/table/RowGroupLayoutManager.java @@ -24,6 +24,7 @@ import java.util.LinkedList; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.apache.fop.fo.Constants; import org.apache.fop.fo.flow.table.EffRow; import org.apache.fop.fo.flow.table.GridUnit; @@ -32,6 +33,7 @@ import org.apache.fop.fo.flow.table.TableColumn; import org.apache.fop.fo.flow.table.TableRow; import org.apache.fop.fo.properties.CommonBorderPaddingBackground; import org.apache.fop.fo.properties.LengthRangeProperty; +import org.apache.fop.layoutmgr.BlockLevelEventProducer; import org.apache.fop.layoutmgr.ElementListObserver; import org.apache.fop.layoutmgr.LayoutContext; import org.apache.fop.layoutmgr.MinOptMaxUtil; @@ -193,7 +195,9 @@ class RowGroupLayoutManager { row.setHeight(rowHeights[rgi]); row.setExplicitHeight(explicitRowHeight); // TODO re-enable and improve after clarification + //See http://markmail.org/message/h25ycwwu7qglr4k4 // if (maxCellBPD > row.getExplicitHeight().max) { +//old: // log.warn(FONode.decorateWithContextInfo( // "The contents of row " + (row.getIndex() + 1) // + " are taller than they should be (there is a" @@ -203,10 +207,12 @@ class RowGroupLayoutManager { // + " to " + maxCellBPD + " millipoints, but the row shouldn't get" // + " any taller than " + row.getExplicitHeight() + " millipoints.", // row.getTableRow())); +//new (with events): +// BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Factory.create( +// tableRow.getUserAgent().getEventBroadcaster()); +// eventProducer.rowTooTall(this, row.getIndex() + 1, +// maxCellBPD, row.getExplicitHeight().max, tableRow.getLocator()); // } - if (log.isDebugEnabled()) { - log.debug(" height=" + rowHeights[rgi] + " explicit=" + explicitRowHeight); - } } } } diff --git a/src/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java b/src/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java index d6bba5cb5..720ca5faa 100644 --- a/src/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java @@ -26,6 +26,7 @@ import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.apache.fop.area.Area; import org.apache.fop.area.Block; import org.apache.fop.datatypes.LengthBase; @@ -34,6 +35,7 @@ import org.apache.fop.fo.FONode; import org.apache.fop.fo.FObj; import org.apache.fop.fo.flow.table.Table; import org.apache.fop.fo.flow.table.TableColumn; +import org.apache.fop.layoutmgr.BlockLevelEventProducer; import org.apache.fop.layoutmgr.BlockStackingLayoutManager; import org.apache.fop.layoutmgr.BreakElement; import org.apache.fop.layoutmgr.ConditionalElementListener; @@ -189,9 +191,9 @@ public class TableLayoutManager extends BlockStackingLayoutManager updateContentAreaIPDwithOverconstrainedAdjust(contentIPD); } else { if (!getTable().isAutoLayout()) { - log.info("table-layout=\"fixed\" and width=\"auto\", " - + "but auto-layout not supported " - + "=> assuming width=\"100%\""); + BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get( + getTable().getUserAgent().getEventBroadcaster()); + eventProducer.tableFixedAutoWidthNotSupported(this, getTable().getLocator()); } updateContentAreaIPDwithOverconstrainedAdjust(); } @@ -204,11 +206,11 @@ public class TableLayoutManager extends BlockStackingLayoutManager } int availableIPD = referenceIPD - getIPIndents(); if (getContentAreaIPD() > availableIPD) { - log.warn(FONode.decorateWithContextInfo( - "The extent in inline-progression-direction (width) of a table is" - + " bigger than the available space (" - + getContentAreaIPD() + "mpt > " + context.getRefIPD() + "mpt)", - getTable())); + BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get( + getTable().getUserAgent().getEventBroadcaster()); + eventProducer.objectTooWide(this, getTable().getName(), + getContentAreaIPD(), context.getRefIPD(), + getTable().getLocator()); } /* initialize unit to determine computed values diff --git a/src/java/org/apache/fop/render/AbstractGenericSVGHandler.java b/src/java/org/apache/fop/render/AbstractGenericSVGHandler.java index c0c9ce88c..185aed817 100644 --- a/src/java/org/apache/fop/render/AbstractGenericSVGHandler.java +++ b/src/java/org/apache/fop/render/AbstractGenericSVGHandler.java @@ -30,12 +30,16 @@ import org.w3c.dom.Document; import org.apache.batik.bridge.BridgeContext; import org.apache.batik.bridge.GVTBuilder; +import org.apache.batik.dom.AbstractDocument; import org.apache.batik.dom.svg.SVGDOMImplementation; import org.apache.batik.gvt.GraphicsNode; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.xmlgraphics.java2d.Graphics2DImagePainter; + import org.apache.fop.render.RendererContext.RendererContextWrapper; +import org.apache.fop.svg.SVGEventProducer; import org.apache.fop.svg.SVGUserAgent; /** @@ -73,7 +77,7 @@ public abstract class AbstractGenericSVGHandler implements XMLHandler, RendererC //Prepare SVGUserAgent ua = new SVGUserAgent( - context.getUserAgent().getSourcePixelUnitToMillimeter(), + context.getUserAgent(), new AffineTransform()); GVTBuilder builder = new GVTBuilder(); final BridgeContext ctx = new BridgeContext(ua); @@ -83,7 +87,9 @@ public abstract class AbstractGenericSVGHandler implements XMLHandler, RendererC try { root = builder.build(ctx, doc); } catch (Exception e) { - log.error("SVG graphic could not be built: " + e.getMessage(), e); + SVGEventProducer eventProducer = SVGEventProducer.Provider.get( + context.getUserAgent().getEventBroadcaster()); + eventProducer.svgNotBuilt(this, e, getDocumentURI(doc)); return; } @@ -114,6 +120,20 @@ public abstract class AbstractGenericSVGHandler implements XMLHandler, RendererC x, y, wrappedContext.getWidth(), wrappedContext.getHeight()); } + /** + * Gets the document URI from a Document instance if possible. + * @param doc the Document + * @return the URI or null + */ + protected String getDocumentURI(Document doc) { + String docURI = null; + if (doc instanceof AbstractDocument) { + AbstractDocument level3Doc = (AbstractDocument)doc; + docURI = level3Doc.getDocumentURI(); + } + return docURI; + } + /** * Override this method to update the renderer context if it needs special settings for * certain conditions. diff --git a/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java b/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java index 9e13476f6..b38d973c5 100644 --- a/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java +++ b/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java @@ -31,6 +31,7 @@ import org.w3c.dom.Document; import org.apache.batik.parser.AWTTransformProducer; import org.apache.xmlgraphics.image.loader.ImageSize; +import org.apache.xmlgraphics.util.QName; import org.apache.fop.area.Area; import org.apache.fop.area.Block; @@ -45,7 +46,6 @@ import org.apache.fop.fo.Constants; import org.apache.fop.fo.extensions.ExtensionElementMapping; import org.apache.fop.fonts.FontMetrics; import org.apache.fop.traits.BorderProps; -import org.apache.fop.util.QName; /** * Abstract base class for renderers like PDF and PostScript where many painting operations diff --git a/src/java/org/apache/fop/render/AbstractRenderer.java b/src/java/org/apache/fop/render/AbstractRenderer.java index 32c4b33c4..ca3d007d0 100644 --- a/src/java/org/apache/fop/render/AbstractRenderer.java +++ b/src/java/org/apache/fop/render/AbstractRenderer.java @@ -67,6 +67,7 @@ import org.apache.fop.area.inline.SpaceArea; import org.apache.fop.area.inline.TextArea; import org.apache.fop.area.inline.Viewport; import org.apache.fop.area.inline.WordArea; +import org.apache.fop.events.ResourceEventProducer; import org.apache.fop.fo.Constants; import org.apache.fop.fonts.FontInfo; @@ -113,21 +114,15 @@ public abstract class AbstractRenderer private Set warnedXMLHandlers; - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public abstract void setupFontInfo(FontInfo fontInfo); - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void setUserAgent(FOUserAgent agent) { userAgent = agent; } - /** - * @return the associated FOUserAgent - */ + /** {@inheritDoc} */ public FOUserAgent getUserAgent() { return userAgent; } @@ -797,10 +792,11 @@ public abstract class AbstractRenderer = new XMLHandlerConfigurator(userAgent); configurator.configure(ctx, namespace); handler.handleXML(ctx, doc, namespace); - } catch (Throwable t) { + } catch (Exception e) { // could not handle document - log.error("Some XML content will be ignored. " - + "Could not render XML", t); + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + ctx.getUserAgent().getEventBroadcaster()); + eventProducer.foreignXMLProcessingError(this, doc, namespace, e); } } else { if (warnedXMLHandlers == null) { @@ -809,8 +805,9 @@ public abstract class AbstractRenderer if (!warnedXMLHandlers.contains(namespace)) { // no handler found for document warnedXMLHandlers.add(namespace); - log.warn("Some XML content will be ignored. " - + "No handler defined for XML: " + namespace); + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + ctx.getUserAgent().getEventBroadcaster()); + eventProducer.foreignXMLNoHandler(this, doc, namespace); } } } diff --git a/src/java/org/apache/fop/render/Renderer.java b/src/java/org/apache/fop/render/Renderer.java index b40eec0cf..03b4582f7 100644 --- a/src/java/org/apache/fop/render/Renderer.java +++ b/src/java/org/apache/fop/render/Renderer.java @@ -88,6 +88,12 @@ public interface Renderer { */ void setUserAgent(FOUserAgent agent); + /** + * Returns the associated user agent. + * @return the user agent + */ + FOUserAgent getUserAgent(); + /** * Set up the given FontInfo. * diff --git a/src/java/org/apache/fop/render/RendererEventProducer.java b/src/java/org/apache/fop/render/RendererEventProducer.java new file mode 100644 index 000000000..365c8f430 --- /dev/null +++ b/src/java/org/apache/fop/render/RendererEventProducer.java @@ -0,0 +1,53 @@ +/* + * 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.render; + +import java.io.IOException; + +import org.apache.fop.events.EventBroadcaster; +import org.apache.fop.events.EventProducer; + +/** + * Event producer interface for rendering-specific events. + */ +public interface RendererEventProducer extends EventProducer { + + /** Provider class for the event producer. */ + class Provider { + + /** + * Returns an event producer. + * @param broadcaster the event broadcaster to use + * @return the event producer + */ + public static RendererEventProducer get(EventBroadcaster broadcaster) { + return (RendererEventProducer)broadcaster.getEventProducerFor( + RendererEventProducer.class); + } + } + + /** + * I/O error while writing target file. + * @param source the event source + * @param ioe the original I/O error + * @event.severity ERROR + */ + void ioError(Object source, IOException ioe); +} diff --git a/src/java/org/apache/fop/render/afp/AFPEventProducer.java b/src/java/org/apache/fop/render/afp/AFPEventProducer.java new file mode 100644 index 000000000..615c54c32 --- /dev/null +++ b/src/java/org/apache/fop/render/afp/AFPEventProducer.java @@ -0,0 +1,63 @@ +/* + * 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.render.afp; + +import org.apache.fop.events.EventBroadcaster; +import org.apache.fop.events.EventProducer; +import org.apache.fop.events.model.AbstractEventModelFactory; +import org.apache.fop.events.model.EventModel; + +/** + * Event producer interface for AFP-specific events. + */ +public interface AFPEventProducer extends EventProducer { + + /** Provider class for the event producer. */ + class Provider { + + /** + * Returns an event producer. + * @param broadcaster the event broadcaster to use + * @return the event producer + */ + public static AFPEventProducer get(EventBroadcaster broadcaster) { + return (AFPEventProducer)broadcaster.getEventProducerFor( + AFPEventProducer.class); + } + } + + /** Event model factory for AFP. */ + public static class EventModelFactory extends AbstractEventModelFactory { + + /** {@inheritDoc} */ + public EventModel createEventModel() { + return loadModel(getClass(), "event-model.xml"); + } + + } + + /** + * Warn about using default font setup. + * @param source the event source + * @event.severity WARN + */ + void warnDefaultFontSetup(Object source); + +} diff --git a/src/java/org/apache/fop/render/afp/AFPEventProducer.xml b/src/java/org/apache/fop/render/afp/AFPEventProducer.xml new file mode 100644 index 000000000..8eec9b656 --- /dev/null +++ b/src/java/org/apache/fop/render/afp/AFPEventProducer.xml @@ -0,0 +1,4 @@ + + + No AFP fonts configured. Using default setup. + diff --git a/src/java/org/apache/fop/render/afp/AFPRenderer.java b/src/java/org/apache/fop/render/afp/AFPRenderer.java index e50e77830..629a7b62f 100644 --- a/src/java/org/apache/fop/render/afp/AFPRenderer.java +++ b/src/java/org/apache/fop/render/afp/AFPRenderer.java @@ -69,6 +69,7 @@ import org.apache.fop.area.inline.SpaceArea; import org.apache.fop.area.inline.TextArea; import org.apache.fop.area.inline.WordArea; import org.apache.fop.datatypes.URISpecification; +import org.apache.fop.events.ResourceEventProducer; import org.apache.fop.fo.Constants; import org.apache.fop.fo.extensions.ExtensionAttachment; import org.apache.fop.fonts.FontInfo; @@ -295,7 +296,9 @@ public class AFPRenderer extends AbstractPathOrientedRenderer { } } } else { - log.warn("No AFP fonts configured - using default setup"); + AFPEventProducer eventProducer = AFPEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.warnDefaultFontSetup(this); } if (this.fontInfo.fontLookup("sans-serif", "normal", 400) == null) { CharacterSet cs = new FopCharacterSet("T1V10500", "Cp500", "CZH200 ", @@ -908,7 +911,7 @@ public class AFPRenderer extends AbstractPathOrientedRenderer { RendererContext context; context = super.createRendererContext(x, y, width, height, foreignAttributes); context.setProperty(AFPRendererContextConstants.AFP_GRAYSCALE, - new Boolean(!this.colorImages)); + Boolean.valueOf(!this.colorImages)); context.setProperty(AFPRendererContextConstants.AFP_FONT_INFO, this.fontInfo); context.setProperty(AFPRendererContextConstants.AFP_RESOLUTION, @@ -1045,13 +1048,17 @@ public class AFPRenderer extends AbstractPathOrientedRenderer { } } catch (ImageException ie) { - log.error("Error while processing image: " - + (info != null ? info.toString() : uri), ie); + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageError(this, (info != null ? info.toString() : uri), ie, null); } catch (FileNotFoundException fe) { - log.error(fe.getMessage()); + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageNotFound(this, (info != null ? info.toString() : uri), fe, null); } catch (IOException ioe) { - log.error("I/O error while processing image: " - + (info != null ? info.toString() : uri), ioe); + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageIOError(this, (info != null ? info.toString() : uri), ioe, null); } finally { if (in != null) { IOUtils.closeQuietly(in); @@ -1217,7 +1224,9 @@ public class AFPRenderer extends AbstractPathOrientedRenderer { //memory consumption (see PostScript output) ImageEncodingHelper.encodeRenderedImageAsRGB(image, baout); } catch (IOException ioe) { - log.error("Error while serializing bitmap: " + ioe.getMessage(), ioe); + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageWritingError(this, ioe); return; } //int res = getResolution(); @@ -1363,8 +1372,9 @@ public class AFPRenderer extends AbstractPathOrientedRenderer { } catch (Throwable ex) { encoding = AFPConstants.EBCIDIC_ENCODING; log.warn( - "renderText():: Error getting encoding for font " - + " - using default encoding " + "renderText():: Error getting encoding for font '" + + tf.getFullName() + + "' - using default encoding " + encoding); } diff --git a/src/java/org/apache/fop/render/afp/AFPSVGHandler.java b/src/java/org/apache/fop/render/afp/AFPSVGHandler.java index 0d3b864b0..939892648 100644 --- a/src/java/org/apache/fop/render/afp/AFPSVGHandler.java +++ b/src/java/org/apache/fop/render/afp/AFPSVGHandler.java @@ -113,8 +113,7 @@ public class AFPSVGHandler extends AbstractGenericSVGHandler { if (cfg != null) { strokeText = cfg.getChild("stroke-text", true).getValueAsBoolean(strokeText); } - final float uaResolution = context.getUserAgent().getSourceResolution(); - SVGUserAgent svgUserAgent = new SVGUserAgent(25.4f / uaResolution, new AffineTransform()); + SVGUserAgent svgUserAgent = new SVGUserAgent(context.getUserAgent(), new AffineTransform()); BridgeContext ctx = new BridgeContext(svgUserAgent); AFPTextHandler afpTextHandler = null; diff --git a/src/java/org/apache/fop/render/bitmap/BitmapRendererEventProducer.java b/src/java/org/apache/fop/render/bitmap/BitmapRendererEventProducer.java new file mode 100644 index 000000000..7b26d0771 --- /dev/null +++ b/src/java/org/apache/fop/render/bitmap/BitmapRendererEventProducer.java @@ -0,0 +1,80 @@ +/* + * 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.render.bitmap; + +import java.io.IOException; + +import org.apache.fop.events.EventBroadcaster; +import org.apache.fop.events.EventProducer; +import org.apache.fop.events.model.AbstractEventModelFactory; +import org.apache.fop.events.model.EventModel; + +/** + * Event producer interface for events generated by the bitmap renderers. + */ +public interface BitmapRendererEventProducer extends EventProducer { + + /** Provider class for the event producer. */ + class Provider { + + /** + * Returns an event producer. + * @param broadcaster the event broadcaster to use + * @return the event producer + */ + public static BitmapRendererEventProducer get(EventBroadcaster broadcaster) { + return (BitmapRendererEventProducer)broadcaster.getEventProducerFor( + BitmapRendererEventProducer.class); + } + } + + /** Event model factory for this event producer. */ + public static class EventModelFactory extends AbstractEventModelFactory { + + /** {@inheritDoc} */ + public EventModel createEventModel() { + return loadModel(getClass(), "event-model.xml"); + } + + } + + /** + * No filename information available. Stopping early after the first page. + * @param source the event source + * @event.severity WARN + */ + void stoppingAfterFirstPageNoFilename(Object source); + + /** + * Image writer does not support multiple images. Only the first page has been produced. + * @param source the event source + * @event.severity WARN + */ + void stoppingAfterFirstPageNoMultiWriter(Object source); + + /** + * No ImageWriter found. + * @param source the event source + * @param mime the target MIME type + * @throws IOException the I/O error provoked by the method call + * @event.severity FATAL + */ + void noImageWriterFound(Object source, String mime) throws IOException; +} diff --git a/src/java/org/apache/fop/render/bitmap/BitmapRendererEventProducer.xml b/src/java/org/apache/fop/render/bitmap/BitmapRendererEventProducer.xml new file mode 100644 index 000000000..a05af3e21 --- /dev/null +++ b/src/java/org/apache/fop/render/bitmap/BitmapRendererEventProducer.xml @@ -0,0 +1,6 @@ + + + No filename information available. Stopping early after the first page. + Image writer does not support multiple images. Only the first page has been produced. + Could not get an ImageWriter to produce "{mime}". The most likely explanation for this is a class loading problem. + diff --git a/src/java/org/apache/fop/render/bitmap/PNGRenderer.java b/src/java/org/apache/fop/render/bitmap/PNGRenderer.java index bedd2c499..8613ef7b8 100644 --- a/src/java/org/apache/fop/render/bitmap/PNGRenderer.java +++ b/src/java/org/apache/fop/render/bitmap/PNGRenderer.java @@ -23,12 +23,12 @@ import java.awt.image.RenderedImage; import java.io.IOException; import java.io.OutputStream; +import org.apache.commons.io.IOUtils; + import org.apache.xmlgraphics.image.writer.ImageWriter; import org.apache.xmlgraphics.image.writer.ImageWriterParams; import org.apache.xmlgraphics.image.writer.ImageWriterRegistry; -import org.apache.commons.io.IOUtils; - import org.apache.fop.apps.MimeConstants; import org.apache.fop.area.PageViewport; import org.apache.fop.render.java2d.Java2DRenderer; @@ -74,8 +74,10 @@ public class PNGRenderer extends Java2DRenderer { OutputStream os = getCurrentOutputStream(i); if (os == null) { - log.warn("No filename information available." - + " Stopping early after the first page."); + BitmapRendererEventProducer eventProducer + = BitmapRendererEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.stoppingAfterFirstPageNoFilename(this); break; } try { @@ -104,9 +106,10 @@ public class PNGRenderer extends Java2DRenderer { // Encode PNG image ImageWriter writer = ImageWriterRegistry.getInstance().getWriterFor(getMimeType()); if (writer == null) { - throw new IOException("Could not get an ImageWriter to produce " - + getMimeType() + ". The most likely explanation for this is a class" - + " loading problem."); + BitmapRendererEventProducer eventProducer + = BitmapRendererEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.noImageWriterFound(this, getMimeType()); } if (log.isDebugEnabled()) { log.debug("Writing image using " + writer.getClass().getName()); diff --git a/src/java/org/apache/fop/render/bitmap/TIFFRenderer.java b/src/java/org/apache/fop/render/bitmap/TIFFRenderer.java index 4f64e45e1..9291427d2 100644 --- a/src/java/org/apache/fop/render/bitmap/TIFFRenderer.java +++ b/src/java/org/apache/fop/render/bitmap/TIFFRenderer.java @@ -129,7 +129,10 @@ public class TIFFRenderer extends Java2DRenderer { // Creates writer ImageWriter writer = ImageWriterRegistry.getInstance().getWriterFor(getMimeType()); if (writer == null) { - throw new NullPointerException("No ImageWriter for " + getMimeType() + " available!"); + BitmapRendererEventProducer eventProducer + = BitmapRendererEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.noImageWriterFound(this, getMimeType()); } if (writer.supportsMultiImageWriter()) { MultiImageWriter multiWriter = writer.createMultiImageWriter(outputStream); @@ -145,8 +148,10 @@ public class TIFFRenderer extends Java2DRenderer { } else { writer.writeImage((RenderedImage) pageImagesItr.next(), outputStream, writerParams); if (pageImagesItr.hasNext()) { - log.error("Image encoder does not support multiple images. Only the first page" - + " has been produced."); + BitmapRendererEventProducer eventProducer + = BitmapRendererEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.stoppingAfterFirstPageNoFilename(this); } } diff --git a/src/java/org/apache/fop/render/java2d/Java2DRenderer.java b/src/java/org/apache/fop/render/java2d/Java2DRenderer.java index 583d2ad2f..0ffe3307a 100644 --- a/src/java/org/apache/fop/render/java2d/Java2DRenderer.java +++ b/src/java/org/apache/fop/render/java2d/Java2DRenderer.java @@ -66,6 +66,7 @@ import org.apache.fop.area.inline.SpaceArea; import org.apache.fop.area.inline.TextArea; import org.apache.fop.area.inline.WordArea; import org.apache.fop.datatypes.URISpecification; +import org.apache.fop.events.ResourceEventProducer; import org.apache.fop.fo.Constants; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontInfo; @@ -930,13 +931,17 @@ public abstract class Java2DRenderer extends AbstractPathOrientedRenderer implem pos, foreignAttributes); } } catch (ImageException ie) { - log.error("Error while processing image: " - + (info != null ? info.toString() : uri), ie); - } catch (FileNotFoundException fnfe) { - log.error(fnfe.getMessage()); + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageError(this, (info != null ? info.toString() : uri), ie, null); + } catch (FileNotFoundException fe) { + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageNotFound(this, (info != null ? info.toString() : uri), fe, null); } catch (IOException ioe) { - log.error("I/O error while processing image: " - + (info != null ? info.toString() : uri), ioe); + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageIOError(this, (info != null ? info.toString() : uri), ioe, null); } } diff --git a/src/java/org/apache/fop/render/java2d/Java2DSVGHandler.java b/src/java/org/apache/fop/render/java2d/Java2DSVGHandler.java index 51b458cfe..64ac823fc 100644 --- a/src/java/org/apache/fop/render/java2d/Java2DSVGHandler.java +++ b/src/java/org/apache/fop/render/java2d/Java2DSVGHandler.java @@ -23,26 +23,25 @@ import java.awt.geom.AffineTransform; import org.w3c.dom.Document; -import org.apache.fop.render.Renderer; -import org.apache.fop.render.XMLHandler; -import org.apache.fop.render.RendererContext; -import org.apache.fop.svg.SVGUserAgent; - -// Commons-Logging +import org.apache.batik.bridge.BridgeContext; +import org.apache.batik.bridge.GVTBuilder; +import org.apache.batik.gvt.GraphicsNode; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.batik.bridge.GVTBuilder; -import org.apache.batik.bridge.BridgeContext; -import org.apache.batik.dom.svg.SVGDOMImplementation; -import org.apache.batik.gvt.GraphicsNode; +import org.apache.fop.render.AbstractGenericSVGHandler; +import org.apache.fop.render.Renderer; +import org.apache.fop.render.RendererContext; +import org.apache.fop.svg.SVGEventProducer; +import org.apache.fop.svg.SVGUserAgent; /** * Java2D XML handler for SVG (uses Apache Batik). * This handler handles XML for foreign objects when rendering to Java2D. * The properties from the Java2D renderer are subject to change. */ -public class Java2DSVGHandler implements XMLHandler, Java2DRendererContextConstants { +public class Java2DSVGHandler extends AbstractGenericSVGHandler + implements Java2DRendererContextConstants { /** logging instance */ private static Log log = LogFactory.getLog(Java2DSVGHandler.class); @@ -54,16 +53,6 @@ public class Java2DSVGHandler implements XMLHandler, Java2DRendererContextConsta //nop } - /** {@inheritDoc} */ - public void handleXML(RendererContext context, - Document doc, String ns) throws Exception { - Java2DInfo pdfi = getJava2DInfo(context); - - if (SVGDOMImplementation.SVG_NAMESPACE_URI.equals(ns)) { - renderSVGDocument(context, doc, pdfi); - } - } - /** * Get the pdf information from the render context. * @@ -106,23 +95,18 @@ public class Java2DSVGHandler implements XMLHandler, Java2DRendererContextConsta } } - /** - * Render the svg document. - * @param context the renderer context - * @param doc the svg document - * @param info the pdf information of the current context - */ + /** {@inheritDoc} */ protected void renderSVGDocument(RendererContext context, - Document doc, - Java2DInfo info) { + Document doc) { + Java2DInfo info = getJava2DInfo(context); + if (log.isDebugEnabled()) { + log.debug("renderSVGDocument(" + context + ", " + doc + ", " + info + ")"); + } - log.debug("renderSVGDocument(" + context + ", " + doc + ", " + info + ")"); - int x = info.currentXPosition; int y = info.currentYPosition; - float ptom = context.getUserAgent().getSourcePixelUnitToMillimeter(); - SVGUserAgent ua = new SVGUserAgent(ptom, new AffineTransform()); + SVGUserAgent ua = new SVGUserAgent(context.getUserAgent(), new AffineTransform()); GVTBuilder builder = new GVTBuilder(); BridgeContext ctx = new BridgeContext(ua); @@ -131,7 +115,9 @@ public class Java2DSVGHandler implements XMLHandler, Java2DRendererContextConsta try { root = builder.build(ctx, doc); } catch (Exception e) { - log.error("SVG graphic could not be built: " + e.getMessage(), e); + SVGEventProducer eventProducer = SVGEventProducer.Provider.get( + context.getUserAgent().getEventBroadcaster()); + eventProducer.svgNotBuilt(this, e, getDocumentURI(doc)); return; } @@ -158,7 +144,9 @@ public class Java2DSVGHandler implements XMLHandler, Java2DRendererContextConsta try { root.paint(info.state.getGraph()); } catch (Exception e) { - log.error("Error while painting SVG", e); + SVGEventProducer eventProducer = SVGEventProducer.Provider.get( + context.getUserAgent().getEventBroadcaster()); + eventProducer.svgRenderingError(this, e, getDocumentURI(doc)); } info.state.getGraph().setTransform(origTransform); @@ -169,9 +157,4 @@ public class Java2DSVGHandler implements XMLHandler, Java2DRendererContextConsta return (renderer instanceof Java2DRenderer); } - - /** {@inheritDoc} */ - public String getNamespace() { - return SVGDOMImplementation.SVG_NAMESPACE_URI; - } } diff --git a/src/java/org/apache/fop/render/pcl/PCLEventProducer.java b/src/java/org/apache/fop/render/pcl/PCLEventProducer.java new file mode 100644 index 000000000..3e72de293 --- /dev/null +++ b/src/java/org/apache/fop/render/pcl/PCLEventProducer.java @@ -0,0 +1,66 @@ +/* + * 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.render.pcl; + +import org.apache.fop.events.EventBroadcaster; +import org.apache.fop.events.EventProducer; +import org.apache.fop.events.model.AbstractEventModelFactory; +import org.apache.fop.events.model.EventModel; + +/** + * Event producer interface for events generated by the PCL renderer. + */ +public interface PCLEventProducer extends EventProducer { + + /** Provider class for the event producer. */ + class Provider { + + /** + * Returns an event producer. + * @param broadcaster the event broadcaster to use + * @return the event producer + */ + public static PCLEventProducer get(EventBroadcaster broadcaster) { + return (PCLEventProducer)broadcaster.getEventProducerFor( + PCLEventProducer.class); + } + } + + /** Event model factory for this event producer. */ + public static class EventModelFactory extends AbstractEventModelFactory { + + /** {@inheritDoc} */ + public EventModel createEventModel() { + return loadModel(getClass(), "event-model.xml"); + } + + } + + /** + * Paper type could not be determined. Falling back to another. + * @param source the event source + * @param pageWidth the page width (in millipoints) + * @param pageHeight the page height (in millipoints) + * @param fallbackPaper the paper type that will be used instead + * @event.severity WARN + */ + void paperTypeUnavailable(Object source, long pageWidth, long pageHeight, String fallbackPaper); + +} diff --git a/src/java/org/apache/fop/render/pcl/PCLEventProducer.xml b/src/java/org/apache/fop/render/pcl/PCLEventProducer.xml new file mode 100644 index 000000000..a3b36fd60 --- /dev/null +++ b/src/java/org/apache/fop/render/pcl/PCLEventProducer.xml @@ -0,0 +1,4 @@ + + + Paper type ({pageWidth} x {pageHeight} mpt) could not be determined. Falling back to: {fallbackPaper} + diff --git a/src/java/org/apache/fop/render/pcl/PCLGenerator.java b/src/java/org/apache/fop/render/pcl/PCLGenerator.java index 3eb8ec425..5ca9a8bf9 100644 --- a/src/java/org/apache/fop/render/pcl/PCLGenerator.java +++ b/src/java/org/apache/fop/render/pcl/PCLGenerator.java @@ -43,9 +43,11 @@ import java.text.DecimalFormatSymbols; import java.util.Locale; import org.apache.commons.io.output.ByteArrayOutputStream; -import org.apache.fop.util.UnitConv; + import org.apache.xmlgraphics.image.GraphicsUtil; +import org.apache.fop.util.UnitConv; + /** * This class provides methods for generating PCL print files. */ @@ -355,16 +357,16 @@ public class PCLGenerator { if (usePCLShades || Color.black.equals(col) || Color.white.equals(col)) { - writeCommand("*c" + formatDouble4(w / 100) + "h" - + formatDouble4(h / 100) + "V"); + writeCommand("*c" + formatDouble4(w / 100.0) + "h" + + formatDouble4(h / 100.0) + "V"); int lineshade = convertToPCLShade(col); writeCommand("*c" + lineshade + "G"); writeCommand("*c2P"); //Shaded fill } else { defineGrayscalePattern(col, 32, DITHER_MATRIX_4X4); - writeCommand("*c" + formatDouble4(w / 100) + "h" - + formatDouble4(h / 100) + "V"); + writeCommand("*c" + formatDouble4(w / 100.0) + "h" + + formatDouble4(h / 100.0) + "V"); writeCommand("*c32G"); writeCommand("*c4P"); //User-defined pattern } diff --git a/src/java/org/apache/fop/render/pcl/PCLRenderer.java b/src/java/org/apache/fop/render/pcl/PCLRenderer.java index 1d606e919..b89fba9c1 100644 --- a/src/java/org/apache/fop/render/pcl/PCLRenderer.java +++ b/src/java/org/apache/fop/render/pcl/PCLRenderer.java @@ -58,6 +58,7 @@ import org.apache.xmlgraphics.image.loader.impl.ImageXMLDOM; import org.apache.xmlgraphics.image.loader.util.ImageUtil; import org.apache.xmlgraphics.java2d.GraphicContext; import org.apache.xmlgraphics.java2d.Graphics2DImagePainter; +import org.apache.xmlgraphics.util.QName; import org.apache.fop.apps.FOPException; import org.apache.fop.apps.MimeConstants; @@ -77,6 +78,7 @@ import org.apache.fop.area.inline.TextArea; import org.apache.fop.area.inline.Viewport; import org.apache.fop.area.inline.WordArea; import org.apache.fop.datatypes.URISpecification; +import org.apache.fop.events.ResourceEventProducer; import org.apache.fop.fo.extensions.ExtensionElementMapping; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontInfo; @@ -85,12 +87,12 @@ import org.apache.fop.render.Graphics2DAdapter; import org.apache.fop.render.PrintRenderer; import org.apache.fop.render.RendererContext; import org.apache.fop.render.RendererContextConstants; +import org.apache.fop.render.RendererEventProducer; import org.apache.fop.render.java2d.FontMetricsMapper; import org.apache.fop.render.java2d.FontSetup; import org.apache.fop.render.java2d.Java2DRenderer; import org.apache.fop.render.pcl.extensions.PCLElementMapping; import org.apache.fop.traits.BorderProps; -import org.apache.fop.util.QName; import org.apache.fop.util.UnitConv; /* Note: @@ -208,7 +210,9 @@ public class PCLRenderer extends PrintRenderer { */ protected void handleIOTrouble(IOException ioe) { if (!ioTrouble) { - log.error("Error while writing to target file", ioe); + RendererEventProducer eventProducer = RendererEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.ioError(this, ioe); ioTrouble = true; } } @@ -417,11 +421,15 @@ public class PCLRenderer extends PrintRenderer { if (this.currentPageDefinition == null) { this.currentPageDefinition = PCLPageDefinition.getDefaultPageDefinition(); - log.warn("Paper type could not be determined. Falling back to: " - + this.currentPageDefinition.getName()); + PCLEventProducer eventProducer = PCLEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.paperTypeUnavailable(this, pagewidth, pageheight, + this.currentPageDefinition.getName()); + } + if (log.isDebugEnabled()) { + log.debug("page size: " + currentPageDefinition.getPhysicalPageSize()); + log.debug("logical page: " + currentPageDefinition.getLogicalPageRect()); } - log.debug("page size: " + currentPageDefinition.getPhysicalPageSize()); - log.debug("logical page: " + currentPageDefinition.getLogicalPageRect()); if (this.currentPageDefinition.isLandscapeFormat()) { gen.writeCommand("&l1O"); //Orientation } else { @@ -1107,12 +1115,17 @@ public class PCLRenderer extends PrintRenderer { } } catch (ImageException ie) { - log.error("Error while processing image: " - + (info != null ? info.toString() : uri), ie); + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageError(this, (info != null ? info.toString() : uri), ie, null); } catch (FileNotFoundException fe) { - log.error(fe.getMessage()); + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageNotFound(this, (info != null ? info.toString() : uri), fe, null); } catch (IOException ioe) { - handleIOTrouble(ioe); + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageIOError(this, (info != null ? info.toString() : uri), ioe, null); } } diff --git a/src/java/org/apache/fop/render/pcl/PCLRendererContext.java b/src/java/org/apache/fop/render/pcl/PCLRendererContext.java index 62d4bcaa4..422b9d51d 100644 --- a/src/java/org/apache/fop/render/pcl/PCLRendererContext.java +++ b/src/java/org/apache/fop/render/pcl/PCLRendererContext.java @@ -19,9 +19,10 @@ package org.apache.fop.render.pcl; +import org.apache.xmlgraphics.util.QName; + import org.apache.fop.fo.extensions.ExtensionElementMapping; import org.apache.fop.render.RendererContext; -import org.apache.fop.util.QName; /** * Wrapper on the RendererContext to access the information structure for drawing diff --git a/src/java/org/apache/fop/render/pdf/PDFEventProducer.java b/src/java/org/apache/fop/render/pdf/PDFEventProducer.java new file mode 100644 index 000000000..f8b1bbb33 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/PDFEventProducer.java @@ -0,0 +1,64 @@ +/* + * 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.render.pdf; + +import org.apache.fop.events.EventBroadcaster; +import org.apache.fop.events.EventProducer; +import org.apache.fop.events.model.AbstractEventModelFactory; +import org.apache.fop.events.model.EventModel; + +/** + * Event producer interface for events generated by the PDF renderer. + */ +public interface PDFEventProducer extends EventProducer { + + /** Provider class for the event producer. */ + class Provider { + + /** + * Returns an event producer. + * @param broadcaster the event broadcaster to use + * @return the event producer + */ + public static PDFEventProducer get(EventBroadcaster broadcaster) { + return (PDFEventProducer)broadcaster.getEventProducerFor( + PDFEventProducer.class); + } + } + + /** Event model factory for this event producer. */ + public static class EventModelFactory extends AbstractEventModelFactory { + + /** {@inheritDoc} */ + public EventModel createEventModel() { + return loadModel(getClass(), "event-model.xml"); + } + + } + + /** + * Some link targets haven't been fully resolved. + * @param source the event source + * @param count the number of unresolved links + * @event.severity WARN + */ + void nonFullyResolvedLinkTargets(Object source, int count); + +} diff --git a/src/java/org/apache/fop/render/pdf/PDFEventProducer.xml b/src/java/org/apache/fop/render/pdf/PDFEventProducer.xml new file mode 100644 index 000000000..fd57d5099 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/PDFEventProducer.xml @@ -0,0 +1,4 @@ + + + {count} link target{count,equals,1,,s} could not be fully resolved and now point{count,equals,1,,s} to the top of the page or {count,equals,1,is,are} dysfunctional. + diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderer.java b/src/java/org/apache/fop/render/pdf/PDFRenderer.java index 55524534e..d2c8446eb 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRenderer.java +++ b/src/java/org/apache/fop/render/pdf/PDFRenderer.java @@ -73,6 +73,7 @@ import org.apache.fop.area.inline.SpaceArea; import org.apache.fop.area.inline.TextArea; import org.apache.fop.area.inline.WordArea; import org.apache.fop.datatypes.URISpecification; +import org.apache.fop.events.ResourceEventProducer; import org.apache.fop.fo.Constants; import org.apache.fop.fo.extensions.ExtensionAttachment; import org.apache.fop.fo.extensions.xmp.XMPMetadata; @@ -490,13 +491,10 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { PDFGoTo gt = (PDFGoTo) unfinishedGoTos.get(0); finishIDGoTo(gt, defaultPos); } - boolean one = count == 1; - String pl = one ? "" : "s"; - String ww = one ? "was" : "were"; - String ia = one ? "is" : "are"; - log.warn("" + count + " link target" + pl + " could not be fully resolved and " - + ww + " now point to the top of the page or " - + ia + " dysfunctional."); // dysfunctional if pageref is null + PDFEventProducer eventProducer = PDFEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.nonFullyResolvedLinkTargets(this, count); + // dysfunctional if pageref is null } } @@ -555,16 +553,17 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { private void renderDestination(DestinationData dd) { String targetID = dd.getIDRef(); - if (targetID != null && targetID.length() > 0) { - PageViewport pv = dd.getPageViewport(); - if (pv == null) { - log.warn("Unresolved destination item received: " + dd.getIDRef()); - } + if (targetID == null || targetID.length() == 0) { + throw new IllegalArgumentException("DestinationData must contain a ID reference"); + } + PageViewport pv = dd.getPageViewport(); + if (pv != null) { PDFGoTo gt = getPDFGoToForID(targetID, pv.getKey()); pdfDoc.getFactory().makeDestination( dd.getIDRef(), gt.makeReference()); } else { - log.warn("DestinationData item with null or empty IDRef received."); + //Warning already issued by AreaTreeHandler (debug level is sufficient) + log.debug("Unresolved destination item received: " + dd.getIDRef()); } } @@ -584,22 +583,22 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { PDFOutline pdfOutline = null; String targetID = bookmarkItem.getIDRef(); - if (targetID != null && targetID.length() > 0) { - PageViewport pv = bookmarkItem.getPageViewport(); - if (pv != null) { - String pvKey = pv.getKey(); - PDFGoTo gt = getPDFGoToForID(targetID, pvKey); - // create outline object: - PDFOutline parent = parentBookmarkItem != null - ? parentBookmarkItem - : pdfDoc.getOutlineRoot(); - pdfOutline = pdfDoc.getFactory().makeOutline(parent, - bookmarkItem.getBookmarkTitle(), gt, bookmarkItem.showChildItems()); - } else { - log.warn("Bookmark with IDRef \"" + targetID + "\" has a null PageViewport."); - } + if (targetID == null || targetID.length() == 0) { + throw new IllegalArgumentException("DestinationData must contain a ID reference"); + } + PageViewport pv = bookmarkItem.getPageViewport(); + if (pv != null) { + String pvKey = pv.getKey(); + PDFGoTo gt = getPDFGoToForID(targetID, pvKey); + // create outline object: + PDFOutline parent = parentBookmarkItem != null + ? parentBookmarkItem + : pdfDoc.getOutlineRoot(); + pdfOutline = pdfDoc.getFactory().makeOutline(parent, + bookmarkItem.getBookmarkTitle(), gt, bookmarkItem.showChildItems()); } else { - log.warn("Bookmark item with null or empty IDRef received."); + //Warning already issued by AreaTreeHandler (debug level is sufficient) + log.debug("Bookmark with IDRef \"" + targetID + "\" has a null PageViewport."); } for (int i = 0; i < bookmarkItem.getCount(); i++) { @@ -1368,15 +1367,8 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { if (annotsAllowed) { action = getPDFGoToForID(idRef, pvKey); } - } else if (pvKeyOK) { - log.warn("Internal link trait with PageViewport key " + pvKey - + " contains no ID reference."); - } else if (idRefOK) { - log.warn("Internal link trait with ID reference " + idRef - + " contains no PageViewport key."); } else { - log.warn("Internal link trait received with neither PageViewport key" - + " nor ID reference."); + //Warnings already issued by AreaTreeHandler } } @@ -1671,7 +1663,9 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { x, y, posInt.width, posInt.height, foreignAttributes); handler.generateImage(context, img, origin, posInt); } catch (IOException ioe) { - log.error("I/O error while handling image: " + info, ioe); + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageWritingError(this, ioe); return; } } else { @@ -1680,13 +1674,17 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { + info + " (" + img.getClass().getName() + ")"); } } catch (ImageException ie) { - log.error("Error while processing image: " - + (info != null ? info.toString() : uri), ie); - } catch (FileNotFoundException fnfe) { - log.error(fnfe.getMessage()); + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageError(this, (info != null ? info.toString() : uri), ie, null); + } catch (FileNotFoundException fe) { + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageNotFound(this, (info != null ? info.toString() : uri), fe, null); } catch (IOException ioe) { - log.error("I/O error while processing image: " - + (info != null ? info.toString() : uri), ioe); + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageIOError(this, (info != null ? info.toString() : uri), ioe, null); } // output new data @@ -1811,18 +1809,34 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { return MIME_TYPE; } + /** + * Sets the PDF/A mode for the PDF renderer. + * @param mode the PDF/A mode + */ public void setAMode(PDFAMode mode) { this.pdfAMode = mode; } + /** + * Sets the PDF/X mode for the PDF renderer. + * @param mode the PDF/X mode + */ public void setXMode(PDFXMode mode) { this.pdfXMode = mode; } + /** + * Sets the output color profile for the PDF renderer. + * @param outputProfileURI the URI to the output color profile + */ public void setOutputProfileURI(String outputProfileURI) { this.outputProfileURI = outputProfileURI; } + /** + * Sets the filter map to be used by the PDF renderer. + * @param filterMap the filter map + */ public void setFilterMap(Map filterMap) { this.filterMap = filterMap; } diff --git a/src/java/org/apache/fop/render/pdf/PDFSVGHandler.java b/src/java/org/apache/fop/render/pdf/PDFSVGHandler.java index cbc0a8ec9..cb7c7cf89 100644 --- a/src/java/org/apache/fop/render/pdf/PDFSVGHandler.java +++ b/src/java/org/apache/fop/render/pdf/PDFSVGHandler.java @@ -36,6 +36,8 @@ import org.apache.batik.util.SVGConstants; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.xmlgraphics.util.QName; + import org.apache.fop.apps.FOUserAgent; import org.apache.fop.fo.extensions.ExtensionElementMapping; import org.apache.fop.fonts.FontInfo; @@ -51,8 +53,8 @@ import org.apache.fop.render.RendererContextConstants; import org.apache.fop.svg.PDFAElementBridge; import org.apache.fop.svg.PDFBridgeContext; import org.apache.fop.svg.PDFGraphics2D; +import org.apache.fop.svg.SVGEventProducer; import org.apache.fop.svg.SVGUserAgent; -import org.apache.fop.util.QName; /** * PDF XML handler for SVG (uses Apache Batik). @@ -144,8 +146,9 @@ public class PDFSVGHandler extends AbstractGenericSVGHandler try { super.renderSVGDocument(context, doc); } catch (IOException ioe) { - log.error("I/O error while rendering SVG graphic: " - + ioe.getMessage(), ioe); + SVGEventProducer eventProducer = SVGEventProducer.Provider.get( + context.getUserAgent().getEventBroadcaster()); + eventProducer.svgRenderingError(this, ioe, getDocumentURI(doc)); } return; } @@ -153,15 +156,13 @@ public class PDFSVGHandler extends AbstractGenericSVGHandler int yOffset = pdfInfo.currentYPosition; FOUserAgent userAgent = context.getUserAgent(); - log.debug("Generating SVG at " - + userAgent.getTargetResolution() - + "dpi."); final float deviceResolution = userAgent.getTargetResolution(); - log.debug("Generating SVG at " + deviceResolution + "dpi."); - log.debug("Generating SVG at " + deviceResolution + "dpi."); + if (log.isDebugEnabled()) { + log.debug("Generating SVG at " + deviceResolution + "dpi."); + } final float uaResolution = userAgent.getSourceResolution(); - SVGUserAgent ua = new SVGUserAgent(25.4f / uaResolution, new AffineTransform()); + SVGUserAgent ua = new SVGUserAgent(userAgent, new AffineTransform()); //Scale for higher resolution on-the-fly images from Batik double s = uaResolution / deviceResolution; @@ -188,8 +189,9 @@ public class PDFSVGHandler extends AbstractGenericSVGHandler root = builder.build(ctx, doc); builder = null; } catch (Exception e) { - log.error("svg graphic could not be built: " - + e.getMessage(), e); + SVGEventProducer eventProducer = SVGEventProducer.Provider.get( + context.getUserAgent().getEventBroadcaster()); + eventProducer.svgNotBuilt(this, e, getDocumentURI(doc)); return; } // get the 'width' and 'height' attributes of the SVG document @@ -261,8 +263,9 @@ public class PDFSVGHandler extends AbstractGenericSVGHandler root.paint(graphics); pdfInfo.currentStream.add(graphics.getString()); } catch (Exception e) { - log.error("svg graphic could not be rendered: " - + e.getMessage(), e); + SVGEventProducer eventProducer = SVGEventProducer.Provider.get( + context.getUserAgent().getEventBroadcaster()); + eventProducer.svgRenderingError(this, e, getDocumentURI(doc)); } pdfInfo.pdfState.pop(); renderer.restoreGraphicsState(); diff --git a/src/java/org/apache/fop/render/ps/PSEventProducer.java b/src/java/org/apache/fop/render/ps/PSEventProducer.java new file mode 100644 index 000000000..451ed1cea --- /dev/null +++ b/src/java/org/apache/fop/render/ps/PSEventProducer.java @@ -0,0 +1,65 @@ +/* + * 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.render.ps; + +import org.apache.fop.events.EventBroadcaster; +import org.apache.fop.events.EventProducer; +import org.apache.fop.events.model.AbstractEventModelFactory; +import org.apache.fop.events.model.EventModel; + +/** + * Event producer interface for events generated by the PostScript renderer. + */ +public interface PSEventProducer extends EventProducer { + + /** Provider class for the event producer. */ + class Provider { + + /** + * Returns an event producer. + * @param broadcaster the event broadcaster to use + * @return the event producer + */ + public static PSEventProducer get(EventBroadcaster broadcaster) { + return (PSEventProducer)broadcaster.getEventProducerFor( + PSEventProducer.class); + } + } + + /** Event model factory for this event producer. */ + public static class EventModelFactory extends AbstractEventModelFactory { + + /** {@inheritDoc} */ + public EventModel createEventModel() { + return loadModel(getClass(), "event-model.xml"); + } + + } + + /** + * A PostScript dictionary could not be parsed. + * @param source the event source + * @param content the PostScript content + * @param e the original exception + * @event.severity ERROR + */ + void postscriptDictionaryParseError(Object source, String content, Exception e); + +} diff --git a/src/java/org/apache/fop/render/ps/PSEventProducer.xml b/src/java/org/apache/fop/render/ps/PSEventProducer.xml new file mode 100644 index 000000000..a0078223a --- /dev/null +++ b/src/java/org/apache/fop/render/ps/PSEventProducer.xml @@ -0,0 +1,4 @@ + + + Failed to parse dictionary string. Reason: {e}, content = "{content}" + diff --git a/src/java/org/apache/fop/render/ps/PSRenderer.java b/src/java/org/apache/fop/render/ps/PSRenderer.java index 7e32977e6..e4d582ba2 100644 --- a/src/java/org/apache/fop/render/ps/PSRenderer.java +++ b/src/java/org/apache/fop/render/ps/PSRenderer.java @@ -85,6 +85,7 @@ import org.apache.fop.area.inline.SpaceArea; import org.apache.fop.area.inline.TextArea; import org.apache.fop.area.inline.WordArea; import org.apache.fop.datatypes.URISpecification; +import org.apache.fop.events.ResourceEventProducer; import org.apache.fop.fo.Constants; import org.apache.fop.fo.extensions.ExtensionAttachment; import org.apache.fop.fonts.Font; @@ -95,6 +96,7 @@ import org.apache.fop.render.AbstractPathOrientedRenderer; import org.apache.fop.render.Graphics2DAdapter; import org.apache.fop.render.ImageAdapter; import org.apache.fop.render.RendererContext; +import org.apache.fop.render.RendererEventProducer; import org.apache.fop.render.ps.extensions.PSCommentAfter; import org.apache.fop.render.ps.extensions.PSCommentBefore; import org.apache.fop.render.ps.extensions.PSExtensionAttachment; @@ -296,7 +298,9 @@ public class PSRenderer extends AbstractPathOrientedRenderer */ protected void handleIOTrouble(IOException ioe) { if (!ioTrouble) { - log.error("Error while writing to target file", ioe); + RendererEventProducer eventProducer = RendererEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.ioError(this, ioe); ioTrouble = true; } } @@ -522,12 +526,17 @@ public class PSRenderer extends AbstractPathOrientedRenderer } } catch (ImageException ie) { - log.error("Error while processing image: " - + (info != null ? info.toString() : uri), ie); + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageError(this, (info != null ? info.toString() : uri), ie, null); } catch (FileNotFoundException fe) { - log.error(fe.getMessage()); + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageNotFound(this, (info != null ? info.toString() : uri), fe, null); } catch (IOException ioe) { - handleIOTrouble(ioe); + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageIOError(this, (info != null ? info.toString() : uri), ioe, null); } } @@ -1070,8 +1079,9 @@ public class PSRenderer extends AbstractPathOrientedRenderer try { this.pageDeviceDictionary.putAll(PSDictionary.valueOf(content)); } catch (PSDictionaryFormatException e) { - log.error("Failed to parse dictionary string: " - + e.getMessage() + ", content = '" + content + "'"); + PSEventProducer eventProducer = PSEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.postscriptDictionaryParseError(this, content, e); } } } else if (attachment instanceof PSCommentBefore) { @@ -1169,8 +1179,9 @@ public class PSRenderer extends AbstractPathOrientedRenderer try { pageDeviceDictionary.putAll(PSDictionary.valueOf(content)); } catch (PSDictionaryFormatException e) { - log.error("failed to parse dictionary string: " - + e.getMessage() + ", [" + content + "]"); + PSEventProducer eventProducer = PSEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.postscriptDictionaryParseError(this, content, e); } } } diff --git a/src/java/org/apache/fop/render/ps/PSSVGHandler.java b/src/java/org/apache/fop/render/ps/PSSVGHandler.java index 5cfe617c8..ebe098282 100644 --- a/src/java/org/apache/fop/render/ps/PSSVGHandler.java +++ b/src/java/org/apache/fop/render/ps/PSSVGHandler.java @@ -23,31 +23,24 @@ package org.apache.fop.render.ps; import java.awt.geom.AffineTransform; import java.io.IOException; -// DOM import org.w3c.dom.Document; -import org.w3c.dom.svg.SVGDocument; -import org.w3c.dom.svg.SVGSVGElement; -// Batik import org.apache.avalon.framework.configuration.Configuration; -import org.apache.batik.bridge.GVTBuilder; import org.apache.batik.bridge.BridgeContext; -import org.apache.batik.bridge.ViewBox; -import org.apache.batik.dom.svg.SVGDOMImplementation; +import org.apache.batik.bridge.GVTBuilder; import org.apache.batik.gvt.GraphicsNode; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.xmlgraphics.java2d.ps.PSGraphics2D; +import org.apache.xmlgraphics.ps.PSGenerator; -// FOP import org.apache.fop.fonts.FontInfo; +import org.apache.fop.render.AbstractGenericSVGHandler; import org.apache.fop.render.Renderer; -import org.apache.fop.render.XMLHandler; import org.apache.fop.render.RendererContext; +import org.apache.fop.svg.SVGEventProducer; import org.apache.fop.svg.SVGUserAgent; -import org.apache.xmlgraphics.java2d.ps.PSGraphics2D; -import org.apache.xmlgraphics.ps.PSGenerator; - -// Commons-Logging -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; /** * PostScript XML handler for SVG. Uses Apache Batik for SVG processing. @@ -57,7 +50,8 @@ import org.apache.commons.logging.LogFactory; * * @version $Id$ */ -public class PSSVGHandler implements XMLHandler, PSRendererContextConstants { +public class PSSVGHandler extends AbstractGenericSVGHandler + implements PSRendererContextConstants { /** logging instance */ private static Log log = LogFactory.getLog(PSSVGHandler.class); @@ -68,16 +62,6 @@ public class PSSVGHandler implements XMLHandler, PSRendererContextConstants { public PSSVGHandler() { } - /** {@inheritDoc} */ - public void handleXML(RendererContext context, - Document doc, String ns) throws Exception { - PSInfo psi = getPSInfo(context); - - if (SVGDOMImplementation.SVG_NAMESPACE_URI.equals(ns)) { - renderSVGDocument(context, doc, psi); - } - } - /** * Get the pdf information from the render context. * @@ -234,10 +218,10 @@ public class PSSVGHandler implements XMLHandler, PSRendererContextConstants { * Render the svg document. * @param context the renderer context * @param doc the svg document - * @param psInfo the pdf information of the current context */ protected void renderSVGDocument(RendererContext context, - Document doc, PSInfo psInfo) { + Document doc) { + PSInfo psInfo = getPSInfo(context); int xOffset = psInfo.currentXPosition; int yOffset = psInfo.currentYPosition; PSGenerator gen = psInfo.psGenerator; @@ -250,9 +234,7 @@ public class PSSVGHandler implements XMLHandler, PSRendererContextConstants { } SVGUserAgent ua - = new SVGUserAgent( - context.getUserAgent().getSourcePixelUnitToMillimeter(), - new AffineTransform()); + = new SVGUserAgent(context.getUserAgent(), new AffineTransform()); PSGraphics2D graphics = new PSGraphics2D(strokeText, gen); graphics.setGraphicContext(new org.apache.xmlgraphics.java2d.GraphicContext()); @@ -273,8 +255,9 @@ public class PSSVGHandler implements XMLHandler, PSRendererContextConstants { try { root = builder.build(ctx, doc); } catch (Exception e) { - log.error("SVG graphic could not be built: " - + e.getMessage(), e); + SVGEventProducer eventProducer = SVGEventProducer.Provider.get( + context.getUserAgent().getEventBroadcaster()); + eventProducer.svgNotBuilt(this, e, getDocumentURI(doc)); return; } // get the 'width' and 'height' attributes of the SVG document @@ -305,10 +288,10 @@ public class PSSVGHandler implements XMLHandler, PSRendererContextConstants { // viewBox puts it. gen.concatMatrix(sx, 0, 0, sy, xOffset / 1000f, yOffset / 1000f); + /* SVGSVGElement svg = ((SVGDocument)doc).getRootElement(); AffineTransform at = ViewBox.getPreserveAspectRatioTransform(svg, - psInfo.getWidth() / 1000f, psInfo.getHeight() / 1000f); - /* + psInfo.getWidth() / 1000f, psInfo.getHeight() / 1000f, ctx); if (!at.isIdentity()) { double[] vals = new double[6]; at.getMatrix(vals); @@ -322,15 +305,17 @@ public class PSSVGHandler implements XMLHandler, PSRendererContextConstants { try { root.paint(graphics); } catch (Exception e) { - log.error("SVG graphic could not be rendered: " - + e.getMessage(), e); + SVGEventProducer eventProducer = SVGEventProducer.Provider.get( + context.getUserAgent().getEventBroadcaster()); + eventProducer.svgRenderingError(this, e, getDocumentURI(doc)); } gen.restoreGraphicsState(); gen.commentln("%FOPEndSVG"); } catch (IOException ioe) { - log.error("SVG graphic could not be rendered: " - + ioe.getMessage(), ioe); + SVGEventProducer eventProducer = SVGEventProducer.Provider.get( + context.getUserAgent().getEventBroadcaster()); + eventProducer.svgRenderingError(this, ioe, getDocumentURI(doc)); } } @@ -339,10 +324,5 @@ public class PSSVGHandler implements XMLHandler, PSRendererContextConstants { return (renderer instanceof PSRenderer); } - /** {@inheritDoc} */ - public String getNamespace() { - return SVGDOMImplementation.SVG_NAMESPACE_URI; - } - } diff --git a/src/java/org/apache/fop/render/ps/ResourceHandler.java b/src/java/org/apache/fop/render/ps/ResourceHandler.java index 0dfb8029f..1a363c90e 100644 --- a/src/java/org/apache/fop/render/ps/ResourceHandler.java +++ b/src/java/org/apache/fop/render/ps/ResourceHandler.java @@ -20,8 +20,8 @@ package org.apache.fop.render.ps; import java.awt.geom.Dimension2D; -import java.awt.image.RenderedImage; import java.awt.geom.Rectangle2D; +import java.awt.image.RenderedImage; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -68,6 +68,7 @@ import org.apache.xmlgraphics.ps.dsc.events.PostScriptComment; import org.apache.xmlgraphics.ps.dsc.tools.DSCTools; import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.events.ResourceEventProducer; import org.apache.fop.fonts.FontInfo; /** @@ -321,7 +322,10 @@ public class ResourceHandler implements DSCParserConstants, PSSupportedFlavors { throw new UnsupportedOperationException("Unsupported image type: " + img); } } catch (ImageException ie) { - throw new IOException("Error while generating form for image: " + ie.getMessage()); + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + userAgent.getEventBroadcaster()); + eventProducer.imageError(resTracker, (info != null ? info.toString() : uri), + ie, null); } } } diff --git a/src/java/org/apache/fop/render/ps/extensions/AbstractPSCommentElement.java b/src/java/org/apache/fop/render/ps/extensions/AbstractPSCommentElement.java index a6e77fb13..1eb1d9d13 100644 --- a/src/java/org/apache/fop/render/ps/extensions/AbstractPSCommentElement.java +++ b/src/java/org/apache/fop/render/ps/extensions/AbstractPSCommentElement.java @@ -22,7 +22,6 @@ package org.apache.fop.render.ps.extensions; import org.apache.fop.apps.FOPException; import org.apache.fop.fo.Constants; import org.apache.fop.fo.FONode; -import org.apache.fop.fo.ValidationException; /** * Base postscript commment element class @@ -46,8 +45,8 @@ public abstract class AbstractPSCommentElement extends AbstractPSExtensionElemen protected void startOfNode() throws FOPException { if (parent.getNameId() != Constants.FO_DECLARATIONS && parent.getNameId() != Constants.FO_SIMPLE_PAGE_MASTER) { - throw new ValidationException(getName() - + " must be a child of fo:declarations or fo:simple-page-master."); + invalidChildError(getLocator(), parent.getName(), getNamespaceURI(), getName(), + "rule.childOfSPMorDeclarations"); } } diff --git a/src/java/org/apache/fop/render/ps/extensions/AbstractPSExtensionElement.java b/src/java/org/apache/fop/render/ps/extensions/AbstractPSExtensionElement.java index a10bb7518..31e44d2d2 100644 --- a/src/java/org/apache/fop/render/ps/extensions/AbstractPSExtensionElement.java +++ b/src/java/org/apache/fop/render/ps/extensions/AbstractPSExtensionElement.java @@ -20,14 +20,14 @@ package org.apache.fop.render.ps.extensions; // FOP +import org.xml.sax.Locator; + import org.apache.fop.apps.FOPException; import org.apache.fop.fo.FONode; import org.apache.fop.fo.PropertyList; import org.apache.fop.fo.ValidationException; import org.apache.fop.fo.extensions.ExtensionAttachment; -import org.xml.sax.Locator; - /** * Base class for the PostScript-specific extension elements. */ @@ -98,7 +98,7 @@ public abstract class AbstractPSExtensionElement extends FONode { * @see org.apache.fop.fo.FONode#getNormalNamespacePrefix() */ public String getNormalNamespacePrefix() { - return "fox"; + return "ps"; } /** diff --git a/src/java/org/apache/fop/render/ps/extensions/AbstractPSExtensionObject.java b/src/java/org/apache/fop/render/ps/extensions/AbstractPSExtensionObject.java index 6823d75d9..78b2f91eb 100644 --- a/src/java/org/apache/fop/render/ps/extensions/AbstractPSExtensionObject.java +++ b/src/java/org/apache/fop/render/ps/extensions/AbstractPSExtensionObject.java @@ -20,13 +20,14 @@ package org.apache.fop.render.ps.extensions; // FOP +import org.xml.sax.Attributes; +import org.xml.sax.Locator; + import org.apache.fop.apps.FOPException; import org.apache.fop.fo.FONode; import org.apache.fop.fo.PropertyList; import org.apache.fop.fo.ValidationException; import org.apache.fop.fo.extensions.ExtensionAttachment; -import org.xml.sax.Attributes; -import org.xml.sax.Locator; /** * Base class for the PostScript-specific extension elements. @@ -36,15 +37,15 @@ public abstract class AbstractPSExtensionObject extends FONode { private PSSetupCode setupCode = new PSSetupCode(); /** + * Main constructor. + * @param parent the parent node * @see org.apache.fop.fo.FONode#FONode(FONode) */ public AbstractPSExtensionObject(FONode parent) { super(parent); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ protected void validateChildNode(Locator loc, String nsURI, String localName) throws ValidationException { if (FO_URI.equals(nsURI)) { @@ -71,7 +72,7 @@ public abstract class AbstractPSExtensionObject extends FONode { /**{@inheritDoc} */ public String getNormalNamespacePrefix() { - return "fox"; + return "ps"; } /** {@inheritDoc} */ diff --git a/src/java/org/apache/fop/render/ps/extensions/PSCommentAfterElement.java b/src/java/org/apache/fop/render/ps/extensions/PSCommentAfterElement.java index 4bbfa0c73..d03a0220c 100644 --- a/src/java/org/apache/fop/render/ps/extensions/PSCommentAfterElement.java +++ b/src/java/org/apache/fop/render/ps/extensions/PSCommentAfterElement.java @@ -27,6 +27,9 @@ import org.apache.fop.fo.extensions.ExtensionAttachment; */ public class PSCommentAfterElement extends AbstractPSCommentElement { + /** the element name */ + protected static final String ELEMENT = "ps-comment-after"; + /** * Main constructor * @param parent node diff --git a/src/java/org/apache/fop/render/ps/extensions/PSCommentBeforeElement.java b/src/java/org/apache/fop/render/ps/extensions/PSCommentBeforeElement.java index f05e7c7fe..d74d3a194 100644 --- a/src/java/org/apache/fop/render/ps/extensions/PSCommentBeforeElement.java +++ b/src/java/org/apache/fop/render/ps/extensions/PSCommentBeforeElement.java @@ -27,6 +27,9 @@ import org.apache.fop.fo.extensions.ExtensionAttachment; */ public class PSCommentBeforeElement extends AbstractPSCommentElement { + /** the element name */ + protected static final String ELEMENT = "ps-comment-before"; + /** * Main constructor * @param parent parent node diff --git a/src/java/org/apache/fop/render/ps/extensions/PSExtensionHandler.java b/src/java/org/apache/fop/render/ps/extensions/PSExtensionHandler.java index 456d97430..e69500736 100644 --- a/src/java/org/apache/fop/render/ps/extensions/PSExtensionHandler.java +++ b/src/java/org/apache/fop/render/ps/extensions/PSExtensionHandler.java @@ -19,13 +19,15 @@ package org.apache.fop.render.ps.extensions; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.apache.fop.util.ContentHandlerFactory; import org.apache.fop.util.ContentHandlerFactory.ObjectBuiltListener; -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.DefaultHandler; /** * ContentHandler (parser) for restoring PSExtension objects from XML. @@ -91,25 +93,19 @@ public class PSExtensionHandler extends DefaultHandler content.append(ch, start, length); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void endDocument() throws SAXException { if (listener != null) { listener.notifyObjectBuilt(getObject()); } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public Object getObject() { return returnedObject; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void setObjectBuiltListener(ObjectBuiltListener listener) { this.listener = listener; } diff --git a/src/java/org/apache/fop/render/ps/extensions/PSPageSetupCodeElement.java b/src/java/org/apache/fop/render/ps/extensions/PSPageSetupCodeElement.java index ad46b9364..207c11e76 100644 --- a/src/java/org/apache/fop/render/ps/extensions/PSPageSetupCodeElement.java +++ b/src/java/org/apache/fop/render/ps/extensions/PSPageSetupCodeElement.java @@ -22,13 +22,13 @@ package org.apache.fop.render.ps.extensions; import org.apache.fop.apps.FOPException; import org.apache.fop.fo.Constants; import org.apache.fop.fo.FONode; -import org.apache.fop.fo.ValidationException; /** * Extension element for fox:ps-page-setup-code. */ public class PSPageSetupCodeElement extends AbstractPSExtensionObject { + /** The element name */ protected static final String ELEMENT = "ps-page-setup-code"; /** @@ -43,7 +43,8 @@ public class PSPageSetupCodeElement extends AbstractPSExtensionObject { protected void startOfNode() throws FOPException { super.startOfNode(); if (parent.getNameId() != Constants.FO_SIMPLE_PAGE_MASTER) { - throw new ValidationException(getName() + " must be a child of fo:simple-page-master."); + invalidChildError(getLocator(), parent.getName(), getNamespaceURI(), getName(), + "rule.childOfSPM"); } } diff --git a/src/java/org/apache/fop/render/ps/extensions/PSSetPageDeviceElement.java b/src/java/org/apache/fop/render/ps/extensions/PSSetPageDeviceElement.java index b512c6888..21acc8001 100644 --- a/src/java/org/apache/fop/render/ps/extensions/PSSetPageDeviceElement.java +++ b/src/java/org/apache/fop/render/ps/extensions/PSSetPageDeviceElement.java @@ -19,20 +19,21 @@ package org.apache.fop.render.ps.extensions; +import org.xml.sax.Attributes; +import org.xml.sax.Locator; + import org.apache.fop.apps.FOPException; import org.apache.fop.fo.Constants; import org.apache.fop.fo.FONode; import org.apache.fop.fo.PropertyList; -import org.apache.fop.fo.ValidationException; import org.apache.fop.fo.extensions.ExtensionAttachment; -import org.xml.sax.Attributes; -import org.xml.sax.Locator; /** * Extension element for ps:ps-setpagedevice. */ public class PSSetPageDeviceElement extends AbstractPSExtensionElement { + /** The element name */ protected static final String ELEMENT = "ps-setpagedevice"; /** @@ -52,8 +53,8 @@ public class PSSetPageDeviceElement extends AbstractPSExtensionElement { super.startOfNode(); if ( !((parent.getNameId() == Constants.FO_DECLARATIONS) || (parent.getNameId() == Constants.FO_SIMPLE_PAGE_MASTER)) ) { - throw new ValidationException( getName() - + " must be a child of fo:declarations or fo:simple-page-master."); + invalidChildError(getLocator(), parent.getName(), getNamespaceURI(), getName(), + "rule.childOfSPMorDeclarations"); } } diff --git a/src/java/org/apache/fop/render/ps/extensions/PSSetupCodeElement.java b/src/java/org/apache/fop/render/ps/extensions/PSSetupCodeElement.java index ec7216c44..e76dfeb64 100644 --- a/src/java/org/apache/fop/render/ps/extensions/PSSetupCodeElement.java +++ b/src/java/org/apache/fop/render/ps/extensions/PSSetupCodeElement.java @@ -22,13 +22,13 @@ package org.apache.fop.render.ps.extensions; import org.apache.fop.apps.FOPException; import org.apache.fop.fo.Constants; import org.apache.fop.fo.FONode; -import org.apache.fop.fo.ValidationException; /** * Extension element for fox:ps-setup-code. */ public class PSSetupCodeElement extends AbstractPSExtensionObject { + /** The element name */ protected static final String ELEMENT = "ps-setup-code"; /** @@ -43,7 +43,8 @@ public class PSSetupCodeElement extends AbstractPSExtensionObject { protected void startOfNode() throws FOPException { super.startOfNode(); if (parent.getNameId() != Constants.FO_DECLARATIONS) { - throw new ValidationException(getName() + " must be a child of fo:declarations."); + invalidChildError(getLocator(), parent.getName(), getNamespaceURI(), getName(), + "rule.childOfDeclarations"); } } diff --git a/src/java/org/apache/fop/render/rtf/RTFEventProducer.java b/src/java/org/apache/fop/render/rtf/RTFEventProducer.java new file mode 100644 index 000000000..a2646af46 --- /dev/null +++ b/src/java/org/apache/fop/render/rtf/RTFEventProducer.java @@ -0,0 +1,94 @@ +/* + * 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.render.rtf; + +import org.xml.sax.Locator; + +import org.apache.fop.events.EventBroadcaster; +import org.apache.fop.events.EventProducer; +import org.apache.fop.events.model.AbstractEventModelFactory; +import org.apache.fop.events.model.EventModel; +import org.apache.fop.fo.FONode; + +/** + * Event producer interface for events generated by the RTF renderer. + */ +public interface RTFEventProducer extends EventProducer { + + /** Provider class for the event producer. */ + class Provider { + + /** + * Returns an event producer. + * @param broadcaster the event broadcaster to use + * @return the event producer + */ + public static RTFEventProducer get(EventBroadcaster broadcaster) { + return (RTFEventProducer)broadcaster.getEventProducerFor( + RTFEventProducer.class); + } + } + + /** Event model factory for this event producer. */ + public static class EventModelFactory extends AbstractEventModelFactory { + + /** {@inheritDoc} */ + public EventModel createEventModel() { + return loadModel(getClass(), "event-model.xml"); + } + + } + + /** + * The RTF handler only supports simple-page-masters. + * @param source the event source + * @param masterReference the reference page-master-set + * @param loc the location of the error or null + * @event.severity WARN + */ + void onlySPMSupported(Object source, String masterReference, Locator loc); + + /** + * No simple-page-master could be determined- + * @param source the event source + * @param loc the location of the error or null + * @event.severity WARN + */ + void noSPMFound(Object source, Locator loc); + + /** + * The RTF handler requires explicit table-columns for now. + * @param source the event source + * @param loc the location of the error or null + * @event.severity WARN + */ + void explicitTableColumnsRequired(Object source, Locator loc); + + /** + * The RTF handler ignored some deferred event (i.e. an unsupported element). + * @param source the event source + * @param node the FO tree node being ignored + * @param start true for start, false for end + * @param loc the location of the error or null + * @event.severity WARN + */ + void ignoredDeferredEvent(Object source, FONode node, boolean start, Locator loc); + +} diff --git a/src/java/org/apache/fop/render/rtf/RTFEventProducer.xml b/src/java/org/apache/fop/render/rtf/RTFEventProducer.xml new file mode 100644 index 000000000..8f1f21a81 --- /dev/null +++ b/src/java/org/apache/fop/render/rtf/RTFEventProducer.xml @@ -0,0 +1,8 @@ + + + [ (See position {loc})| (See {#gatherContextInfo})| (No context info available)] + Only simple-page-masters are supported on page-sequences. Using default simple-page-master from page-sequence-master "{masterReference}".{{locator}} + No simple-page-master could be determined. + No table-columns found on table. RTF output requires that all table-columns for a table are defined. Output will be incorrect.{{locator}} + Ignored deferred event for {node} ({start,if,start,end}).{{locator}} + diff --git a/src/java/org/apache/fop/render/rtf/RTFHandler.java b/src/java/org/apache/fop/render/rtf/RTFHandler.java index d2f2c4192..88e34e17a 100644 --- a/src/java/org/apache/fop/render/rtf/RTFHandler.java +++ b/src/java/org/apache/fop/render/rtf/RTFHandler.java @@ -21,6 +21,7 @@ package org.apache.fop.render.rtf; // Java import java.awt.geom.Point2D; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -51,6 +52,7 @@ import org.apache.fop.apps.FOPException; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.datatypes.LengthBase; import org.apache.fop.datatypes.SimplePercentBaseContext; +import org.apache.fop.events.ResourceEventProducer; import org.apache.fop.fo.Constants; import org.apache.fop.fo.FOEventHandler; import org.apache.fop.fo.FONode; @@ -88,6 +90,7 @@ import org.apache.fop.fo.properties.CommonBorderPaddingBackground; import org.apache.fop.fo.properties.FixedLength; import org.apache.fop.fonts.FontSetup; import org.apache.fop.render.DefaultFontResolver; +import org.apache.fop.render.RendererEventProducer; import org.apache.fop.render.rtf.rtflib.rtfdoc.IRtfAfterContainer; import org.apache.fop.render.rtf.rtflib.rtfdoc.IRtfBeforeContainer; import org.apache.fop.render.rtf.rtflib.rtfdoc.IRtfListContainer; @@ -158,6 +161,16 @@ public class RTFHandler extends FOEventHandler { FontSetup.setup(fontInfo, null, new DefaultFontResolver(userAgent)); } + /** + * Central exception handler for I/O exceptions. + * @param ioe IOException to handle + */ + protected void handleIOTrouble(IOException ioe) { + RendererEventProducer eventProducer = RendererEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.ioError(this, ioe); + } + /** * {@inheritDoc} */ @@ -195,9 +208,9 @@ public class RTFHandler extends FOEventHandler { this.pagemaster = pageSeq.getRoot().getLayoutMasterSet().getSimplePageMaster(reference); if (this.pagemaster == null) { - log.warn("Only simple-page-masters are supported on page-sequences: " - + reference); - log.warn("Using default simple-page-master from page-sequence-master..."); + RTFEventProducer eventProducer = RTFEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.onlySPMSupported(this, reference, pageSeq.getLocator()); PageSequenceMaster master = pageSeq.getRoot().getLayoutMasterSet().getPageSequenceMaster(reference); this.pagemaster = master.getNextSimplePageMaster( @@ -218,7 +231,9 @@ public class RTFHandler extends FOEventHandler { PageAttributesConverter.convertPageAttributes( pagemaster)); } else { - log.warn("No simple-page-master could be determined!"); + RTFEventProducer eventProducer = RTFEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.noSPMFound(this, pageSeq.getLocator()); } builderContext.pushContainer(sect); @@ -226,9 +241,7 @@ public class RTFHandler extends FOEventHandler { bHeaderSpecified = false; bFooterSpecified = false; } catch (IOException ioe) { - // TODO could we throw Exception in all FOEventHandler events? - log.error("startPageSequence: " + ioe.getMessage(), ioe); - //TODO throw new FOPException(ioe); + handleIOTrouble(ioe); } catch (FOPException fope) { // TODO could we throw Exception in all FOEventHandler events? log.error("startPageSequence: " + fope.getMessage(), fope); @@ -338,8 +351,7 @@ public class RTFHandler extends FOEventHandler { log.warn("A " + fl.getLocalName() + " has been skipped: " + fl.getFlowName()); } } catch (IOException ioe) { - log.error("startFlow: " + ioe.getMessage()); - throw new RuntimeException(ioe.getMessage()); + handleIOTrouble(ioe); } catch (Exception e) { log.error("startFlow: " + e.getMessage()); throw new RuntimeException(e.getMessage()); @@ -396,9 +408,7 @@ public class RTFHandler extends FOEventHandler { textrun.pushBlockAttributes(rtfAttr); textrun.addBookmark(bl.getId()); } catch (IOException ioe) { - // TODO could we throw Exception in all FOEventHandler events? - log.error("startBlock: " + ioe.getMessage()); - throw new RuntimeException("IOException: " + ioe); + handleIOTrouble(ioe); } catch (Exception e) { log.error("startBlock: " + e.getMessage()); throw new RuntimeException("Exception: " + e); @@ -427,8 +437,7 @@ public class RTFHandler extends FOEventHandler { textrun.popBlockAttributes(); } catch (IOException ioe) { - log.error("startBlock:" + ioe.getMessage()); - throw new RuntimeException(ioe.getMessage()); + handleIOTrouble(ioe); } catch (Exception e) { log.error("startBlock:" + e.getMessage()); throw new RuntimeException(e.getMessage()); @@ -457,9 +466,7 @@ public class RTFHandler extends FOEventHandler { textrun.addParagraphBreak(); textrun.pushBlockAttributes(rtfAttr); } catch (IOException ioe) { - // TODO could we throw Exception in all FOEventHandler events? - log.error("startBlock: " + ioe.getMessage()); - throw new RuntimeException("IOException: " + ioe); + handleIOTrouble(ioe); } catch (Exception e) { log.error("startBlock: " + e.getMessage()); throw new RuntimeException("Exception: " + e); @@ -486,8 +493,7 @@ public class RTFHandler extends FOEventHandler { textrun.popBlockAttributes(); } catch (IOException ioe) { - log.error("startBlock:" + ioe.getMessage()); - throw new RuntimeException(ioe.getMessage()); + handleIOTrouble(ioe); } catch (Exception e) { log.error("startBlock:" + e.getMessage()); throw new RuntimeException(e.getMessage()); @@ -530,6 +536,8 @@ public class RTFHandler extends FOEventHandler { table.setBorderAttributes(borderAttributes); builderContext.pushContainer(table); + } catch (IOException ioe) { + handleIOTrouble(ioe); } catch (Exception e) { log.error("startTable:" + e.getMessage()); throw new RuntimeException(e.getMessage()); @@ -590,7 +598,6 @@ public class RTFHandler extends FOEventHandler { log.error("startColumn: " + e.getMessage()); throw new RuntimeException(e.getMessage()); } - } /** @@ -649,8 +656,7 @@ public class RTFHandler extends FOEventHandler { textrun.pushInlineAttributes(rtfAttr); textrun.addBookmark(inl.getId()); } catch (IOException ioe) { - log.error("startInline:" + ioe.getMessage()); - throw new RuntimeException(ioe.getMessage()); + handleIOTrouble(ioe); } catch (FOPException fe) { log.error("startInline:" + fe.getMessage()); throw new RuntimeException(fe.getMessage()); @@ -677,8 +683,7 @@ public class RTFHandler extends FOEventHandler { RtfTextrun textrun = container.getTextrun(); textrun.popInlineAttributes(); } catch (IOException ioe) { - log.error("startInline:" + ioe.getMessage()); - throw new RuntimeException(ioe.getMessage()); + handleIOTrouble(ioe); } catch (Exception e) { log.error("startInline:" + e.getMessage()); throw new RuntimeException(e.getMessage()); @@ -698,6 +703,8 @@ public class RTFHandler extends FOEventHandler { RtfTable tbl = (RtfTable)builderContext.getContainer(RtfTable.class, true, this); tbl.setHeaderAttribs(atts); + } catch (IOException ioe) { + handleIOTrouble(ioe); } catch (Exception e) { log.error("startBody: " + e.getMessage()); throw new RuntimeException(e.getMessage()); @@ -715,6 +722,8 @@ public class RTFHandler extends FOEventHandler { try { RtfTable tbl = (RtfTable)builderContext.getContainer(RtfTable.class, true, this); tbl.setHeaderAttribs(null); + } catch (IOException ioe) { + handleIOTrouble(ioe); } catch (Exception e) { log.error("endBody: " + e.getMessage()); throw new RuntimeException(e.getMessage()); @@ -745,6 +754,8 @@ public class RTFHandler extends FOEventHandler { // reset column iteration index to correctly access column widths builderContext.getTableContext().selectFirstColumn(); + } catch (IOException ioe) { + handleIOTrouble(ioe); } catch (Exception e) { log.error("startRow: " + e.getMessage()); throw new RuntimeException(e.getMessage()); @@ -778,6 +789,8 @@ public class RTFHandler extends FOEventHandler { tctx.selectNextColumn(); } + } catch (IOException ioe) { + handleIOTrouble(ioe); } catch (Exception e) { log.error("endRow: " + e.getMessage()); throw new RuntimeException(e.getMessage()); @@ -870,6 +883,8 @@ public class RTFHandler extends FOEventHandler { } builderContext.pushContainer(cell); + } catch (IOException ioe) { + handleIOTrouble(ioe); } catch (Exception e) { log.error("startCell: " + e.getMessage()); throw new RuntimeException(e.getMessage()); @@ -906,8 +921,7 @@ public class RTFHandler extends FOEventHandler { ListAttributesConverter.convertAttributes(lb)); builderContext.pushContainer(newList); } catch (IOException ioe) { - log.error("startList: " + ioe.getMessage()); - throw new RuntimeException(ioe.getMessage()); + handleIOTrouble(ioe); } catch (FOPException fe) { log.error("startList: " + fe.getMessage()); throw new RuntimeException(fe.getMessage()); @@ -961,8 +975,7 @@ public class RTFHandler extends FOEventHandler { builderContext.pushContainer(list.newListItem()); } catch (IOException ioe) { - log.error("startList: " + ioe.getMessage()); - throw new RuntimeException(ioe.getMessage()); + handleIOTrouble(ioe); } catch (Exception e) { log.error("startList: " + e.getMessage()); throw new RuntimeException(e.getMessage()); @@ -995,8 +1008,7 @@ public class RTFHandler extends FOEventHandler { RtfListItemLabel label = item.new RtfListItemLabel(item); builderContext.pushContainer(label); } catch (IOException ioe) { - log.error("startPageNumber:" + ioe.getMessage()); - throw new RuntimeException(ioe.getMessage()); + handleIOTrouble(ioe); } catch (Exception e) { log.error("startPageNumber: " + e.getMessage()); throw new RuntimeException(e.getMessage()); @@ -1077,8 +1089,7 @@ public class RTFHandler extends FOEventHandler { builderContext.pushContainer(link); } catch (IOException ioe) { - log.error("startLink:" + ioe.getMessage()); - throw new RuntimeException(ioe.getMessage()); + handleIOTrouble(ioe); } catch (Exception e) { log.error("startLink: " + e.getMessage()); throw new RuntimeException(e.getMessage()); @@ -1104,21 +1115,28 @@ public class RTFHandler extends FOEventHandler { return; } + String uri = eg.getURL(); + ImageInfo info = null; try { - String uri = eg.getURL(); //set image data FOUserAgent userAgent = eg.getUserAgent(); ImageManager manager = userAgent.getFactory().getImageManager(); - ImageInfo info = manager.getImageInfo(uri, userAgent.getImageSessionContext()); - if (info == null) { - log.error("Image could not be found: " + uri); - return; - } + info = manager.getImageInfo(uri, userAgent.getImageSessionContext()); putGraphic(eg, info); - } catch (Exception e) { - log.error("Error while handling an external-graphic: " + e.getMessage(), e); + } catch (ImageException ie) { + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageError(this, (info != null ? info.toString() : uri), ie, null); + } catch (FileNotFoundException fe) { + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageNotFound(this, (info != null ? info.toString() : uri), fe, null); + } catch (IOException ioe) { + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageIOError(this, (info != null ? info.toString() : uri), ioe, null); } } @@ -1144,6 +1162,12 @@ public class RTFHandler extends FOEventHandler { // Set the image size to the size of the svg. Point2D csize = new Point2D.Float(-1, -1); Point2D intrinsicDimensions = child.getDimension(csize); + if (intrinsicDimensions == null) { + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.ifoNoIntrinsicSize(this, child.getLocator()); + return; + } size.setSizeInMillipoints( (int)Math.round(intrinsicDimensions.getX() * 1000), (int)Math.round(intrinsicDimensions.getY() * 1000)); @@ -1157,8 +1181,14 @@ public class RTFHandler extends FOEventHandler { Image converted = manager.convertImage(image, FLAVORS); putGraphic(ifo, converted); - } catch (Exception e) { - log.error("Error while handling an instream-foreign-object: " + e.getMessage(), e); + } catch (ImageException ie) { + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageError(this, null, ie, null); + } catch (IOException ioe) { + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageIOError(this, null, ioe, null); } } @@ -1183,7 +1213,9 @@ public class RTFHandler extends FOEventHandler { putGraphic(abstractGraphic, image); } catch (ImageException ie) { - log.error("Error while loading/processing image: " + info.getOriginalURI(), ie); + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageError(this, null, ie, null); } } @@ -1210,8 +1242,9 @@ public class RTFHandler extends FOEventHandler { } if (rawData == null) { - log.warn(FONode.decorateWithContextInfo("Image could not be embedded: " - + image, abstractGraphic)); + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageWritingError(this, null); return; } @@ -1322,9 +1355,7 @@ public class RTFHandler extends FOEventHandler { builderContext.pushContainer(rtfFootnote); } catch (IOException ioe) { - // TODO could we throw Exception in all FOEventHandler events? - log.error("startFootnote: " + ioe.getMessage()); - throw new RuntimeException("IOException: " + ioe); + handleIOTrouble(ioe); } catch (Exception e) { log.error("startFootnote: " + e.getMessage()); throw new RuntimeException("Exception: " + e); @@ -1358,9 +1389,7 @@ public class RTFHandler extends FOEventHandler { rtfFootnote.startBody(); } catch (IOException ioe) { - // TODO could we throw Exception in all FOEventHandler events? - log.error("startFootnoteBody: " + ioe.getMessage()); - throw new RuntimeException("IOException: " + ioe); + handleIOTrouble(ioe); } catch (Exception e) { log.error("startFootnoteBody: " + e.getMessage()); throw new RuntimeException("Exception: " + e); @@ -1383,9 +1412,7 @@ public class RTFHandler extends FOEventHandler { rtfFootnote.endBody(); } catch (IOException ioe) { - // TODO could we throw Exception in all FOEventHandler events? - log.error("endFootnoteBody: " + ioe.getMessage()); - throw new RuntimeException("IOException: " + ioe); + handleIOTrouble(ioe); } catch (Exception e) { log.error("endFootnoteBody: " + e.getMessage()); throw new RuntimeException("Exception: " + e); @@ -1421,10 +1448,8 @@ public class RTFHandler extends FOEventHandler { textrun.pushInlineAttributes(rtfAttr); textrun.addString(new String(data, start, length - start)); textrun.popInlineAttributes(); - } catch (IOException ioe) { - // FIXME could we throw Exception in all FOEventHandler events? - log.error("characters: " + ioe.getMessage()); - throw new RuntimeException(ioe.getMessage()); + } catch (IOException ioe) { + handleIOTrouble(ioe); } catch (Exception e) { log.error("characters:" + e.getMessage()); throw new RuntimeException(e.getMessage()); @@ -1452,8 +1477,7 @@ public class RTFHandler extends FOEventHandler { RtfTextrun textrun = container.getTextrun(); textrun.addPageNumber(rtfAttr); } catch (IOException ioe) { - log.error("startPageNumber:" + ioe.getMessage()); - throw new RuntimeException(ioe.getMessage()); + handleIOTrouble(ioe); } catch (Exception e) { log.error("startPageNumber: " + e.getMessage()); throw new RuntimeException(e.getMessage()); @@ -1611,7 +1635,9 @@ public class RTFHandler extends FOEventHandler { endCell( (TableCell) foNode); } } else { - log.warn("Ignored deferred event for " + foNode); + RTFEventProducer eventProducer = RTFEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.ignoredDeferredEvent(this, foNode, bStart, foNode.getLocator()); } } @@ -1655,8 +1681,9 @@ public class RTFHandler extends FOEventHandler { } } else { //TODO Implement implicit column setup handling! - log.warn("No table-columns found on table. RTF output requires that all" - + " table-columns for a table are defined. Output will be incorrect."); + RTFEventProducer eventProducer = RTFEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.explicitTableColumnsRequired(this, table.getLocator()); } //recurse table-header diff --git a/src/java/org/apache/fop/render/xml/XMLRenderer.java b/src/java/org/apache/fop/render/xml/XMLRenderer.java index 66cce0ae1..eb3c92e1b 100644 --- a/src/java/org/apache/fop/render/xml/XMLRenderer.java +++ b/src/java/org/apache/fop/render/xml/XMLRenderer.java @@ -41,6 +41,7 @@ import org.xml.sax.SAXException; import org.xml.sax.ext.LexicalHandler; import org.xml.sax.helpers.AttributesImpl; +import org.apache.xmlgraphics.util.QName; import org.apache.xmlgraphics.util.XMLizable; import org.apache.fop.apps.FOPException; @@ -90,7 +91,6 @@ import org.apache.fop.render.Renderer; import org.apache.fop.render.RendererContext; import org.apache.fop.render.XMLHandler; import org.apache.fop.util.ColorUtil; -import org.apache.fop.util.QName; /** * Renderer that renders areas to XML for debugging purposes. diff --git a/src/java/org/apache/fop/svg/SVGEventProducer.java b/src/java/org/apache/fop/svg/SVGEventProducer.java new file mode 100644 index 000000000..8894f3f58 --- /dev/null +++ b/src/java/org/apache/fop/svg/SVGEventProducer.java @@ -0,0 +1,89 @@ +/* + * 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.svg; + +import org.apache.fop.events.EventBroadcaster; +import org.apache.fop.events.EventProducer; + +/** + * Event producer interface for SVG-related events. + */ +public interface SVGEventProducer extends EventProducer { + + /** + * Provider class for the event producer. + */ + class Provider { + + /** + * Returns an event producer. + * @param broadcaster the event broadcaster to use + * @return the event producer + */ + public static SVGEventProducer get(EventBroadcaster broadcaster) { + return (SVGEventProducer)broadcaster.getEventProducerFor( + SVGEventProducer.class); + } + } + + /** + * Error during SVG processing. Either message or e must be set. + * @param source the event source + * @param message the error message (or null) + * @param e the exception (or null) + * @event.severity ERROR + */ + void error(Object source, String message, Exception e); + + /** + * Alert during SVG processing. + * @param source the event source + * @param message the error message + * @event.severity WARN + */ + void alert(Object source, String message); + + /** + * Info during SVG processing. + * @param source the event source + * @param message the error message + * @event.severity INFO + */ + void info(Object source, String message); + + /** + * SVG graphic could not be built due to an exception. + * @param source the event source + * @param e the original exception + * @param uri the URI of the SVG graphic + * @event.severity ERROR + */ + void svgNotBuilt(Object source, Exception e, String uri); + + /** + * SVG graphic could not be rendered due to an exception. + * @param source the event source + * @param e the original exception + * @param uri the URI of the SVG graphic + * @event.severity ERROR + */ + void svgRenderingError(Object source, Exception e, String uri); + +} diff --git a/src/java/org/apache/fop/svg/SVGUserAgent.java b/src/java/org/apache/fop/svg/SVGUserAgent.java index 540f490de..8d7754fcb 100644 --- a/src/java/org/apache/fop/svg/SVGUserAgent.java +++ b/src/java/org/apache/fop/svg/SVGUserAgent.java @@ -19,49 +19,42 @@ package org.apache.fop.svg; -import javax.xml.parsers.SAXParserFactory; -import org.apache.batik.bridge.UserAgentAdapter; - -// Java import java.awt.geom.AffineTransform; -import java.awt.geom.Dimension2D; -import java.awt.Dimension; -// Commons-Logging -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.apache.fop.apps.FOUserAgent; /** - * The SVG user agent. - * This is an implementation of the batik svg user agent - * for handling errors and getting user agent values. + * The SVG user agent. This is an implementation of the Batik SVG user agent. */ -public class SVGUserAgent extends UserAgentAdapter { - private AffineTransform currentTransform = null; - private float pixelUnitToMillimeter = 0.0f; +public class SVGUserAgent extends SimpleSVGUserAgent { + + private SVGEventProducer eventProducer; + private Exception lastException; /** - * logging instance + * Creates a new SVGUserAgent. + * @param foUserAgent the FO user agent to associate with this SVG user agent + * @param at the current transform */ - private Log logger = LogFactory.getLog(SVGUserAgent.class); + public SVGUserAgent(FOUserAgent foUserAgent, AffineTransform at) { + super(foUserAgent.getSourcePixelUnitToMillimeter(), at); + this.eventProducer = SVGEventProducer.Provider.get(foUserAgent.getEventBroadcaster()); + } /** * Creates a new SVGUserAgent. - * @param pixelUnitToMM the pixel to millimeter conversion factor - * currently in effect - * @param at the current transform + * @param foUserAgent the FO user agent to associate with this SVG user agent */ - public SVGUserAgent(float pixelUnitToMM, AffineTransform at) { - pixelUnitToMillimeter = pixelUnitToMM; - currentTransform = at; + public SVGUserAgent(FOUserAgent foUserAgent) { + this(foUserAgent, new AffineTransform()); } - + /** - * Returns the logger associated with this user agent. - * @return Logger the logger + * Returns the last exception sent to the {@link #displayError(Exception)} method. + * @return the last exception or null if no exception occurred */ - protected final Log getLogger() { - return logger; + public Exception getLastException() { + return this.lastException; } /** @@ -69,7 +62,7 @@ public class SVGUserAgent extends UserAgentAdapter { * @param message the message to display */ public void displayError(String message) { - logger.error(message); + this.eventProducer.error(this, message, null); } /** @@ -77,7 +70,8 @@ public class SVGUserAgent extends UserAgentAdapter { * @param ex the exception to display */ public void displayError(Exception ex) { - logger.error("SVG Error" + ex.getMessage(), ex); + this.lastException = ex; + this.eventProducer.error(this, ex.getLocalizedMessage(), ex); } /** @@ -86,7 +80,7 @@ public class SVGUserAgent extends UserAgentAdapter { * @param message the message to display */ public void displayMessage(String message) { - logger.info(message); + this.eventProducer.info(this, message); } /** @@ -94,78 +88,7 @@ public class SVGUserAgent extends UserAgentAdapter { * @param message the message to display */ public void showAlert(String message) { - logger.warn(message); - } - - /** - * Returns a customized the pixel to mm factor. - * @return the pixel unit to millimeter conversion factor - */ - public float getPixelUnitToMillimeter() { - return pixelUnitToMillimeter; + this.eventProducer.alert(this, message); } - /** - * Returns the language settings. - * @return the languages supported - */ - public String getLanguages() { - return "en"; // userLanguages; - } - - /** - * Returns the media type for this rendering. - * @return the media for fo documents is "print" - */ - public String getMedia() { - return "print"; - } - - /** - * Returns the user stylesheet uri. - * @return null if no user style sheet was specified. - */ - public String getUserStyleSheetURI() { - return null; // userStyleSheetURI; - } - - /** - * Returns the class name of the XML parser. - * @return the XML parser class name - */ - public String getXMLParserClassName() { - try { - SAXParserFactory factory = SAXParserFactory.newInstance(); - return factory.newSAXParser().getXMLReader().getClass().getName(); - } catch (Exception e) { - return null; - } - } - - /** - * Is the XML parser validating. - * @return true if the xml parser is validating - */ - public boolean isXMLParserValidating() { - return false; - } - - /** - * Get the transform of the svg document. - * @return the transform - */ - public AffineTransform getTransform() { - return currentTransform; - } - - /** - * Get the default viewport size for an svg document. - * This returns a default value of 100x100. - * @return the default viewport size - */ - public Dimension2D getViewportSize() { - return new Dimension(100, 100); - } - -} - +} \ No newline at end of file diff --git a/src/java/org/apache/fop/svg/SimpleSVGUserAgent.java b/src/java/org/apache/fop/svg/SimpleSVGUserAgent.java new file mode 100644 index 000000000..4df1af34e --- /dev/null +++ b/src/java/org/apache/fop/svg/SimpleSVGUserAgent.java @@ -0,0 +1,126 @@ +/* + * 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.svg; + +import java.awt.Dimension; +import java.awt.geom.AffineTransform; +import java.awt.geom.Dimension2D; + +import javax.xml.parsers.SAXParserFactory; + +import org.apache.batik.bridge.UserAgentAdapter; + +/** + * A simple SVG user agent. + * This is an implementation of the Batik SVG user agent. It ignores any message output sent + * by Batik. + */ +public class SimpleSVGUserAgent extends UserAgentAdapter { + + private AffineTransform currentTransform = null; + private float pixelUnitToMillimeter = 0.0f; + + /** + * Creates a new user agent. + * @param pixelUnitToMM the pixel to millimeter conversion factor currently in effect + * @param at the current transform + */ + public SimpleSVGUserAgent(float pixelUnitToMM, AffineTransform at) { + pixelUnitToMillimeter = pixelUnitToMM; + currentTransform = at; + } + + /** + * Returns a customized the pixel to mm factor. + * @return the pixel unit to millimeter conversion factor + */ + public float getPixelUnitToMillimeter() { + return pixelUnitToMillimeter; + } + + /** + * Returns the language settings. + * @return the languages supported + */ + public String getLanguages() { + return "en"; // userLanguages; + } + + /** + * Returns the media type for this rendering. + * @return the media for FO documents is "print" + */ + public String getMedia() { + return "print"; + } + + /** + * Returns the user stylesheet URI. + * @return null if no user style sheet was specified. + */ + public String getUserStyleSheetURI() { + return null; // userStyleSheetURI; + } + + /** + * Returns the class name of the XML parser. + * @return the XML parser class name + */ + public String getXMLParserClassName() { + try { + SAXParserFactory factory = SAXParserFactory.newInstance(); + return factory.newSAXParser().getXMLReader().getClass().getName(); + } catch (Exception e) { + return null; + } + } + + /** + * Is the XML parser validating. + * @return true if the XML parser is validating + */ + public boolean isXMLParserValidating() { + return false; + } + + /** + * Get the transform of the SVG document. + * @return the transform + */ + public AffineTransform getTransform() { + return currentTransform; + } + + /** {@inheritDoc} */ + public void setTransform(AffineTransform at) { + this.currentTransform = at; + } + + /** + * Get the default viewport size for an SVG document. + * This returns a default value of 100x100. + * @return the default viewport size + */ + public Dimension2D getViewportSize() { + return new Dimension(100, 100); + } + +} + diff --git a/src/java/org/apache/fop/util/QName.java b/src/java/org/apache/fop/util/QName.java index 390e29dd9..132f5b4dc 100644 --- a/src/java/org/apache/fop/util/QName.java +++ b/src/java/org/apache/fop/util/QName.java @@ -19,23 +19,17 @@ package org.apache.fop.util; -import java.io.Serializable; - /** * Represents a qualified name of an XML element or an XML attribute. *

* Note: This class allows to carry a namespace prefix but it is not used in the equals() and * hashCode() methods. + * @deprecated Use the XML Graphics Commons variant instead! */ -public class QName implements Serializable { +public class QName extends org.apache.xmlgraphics.util.QName { private static final long serialVersionUID = -5225376740044770690L; - private String namespaceURI; - private String localName; - private String prefix; - private int hashCode; - /** * Main constructor. * @param namespaceURI the namespace URI @@ -43,16 +37,7 @@ public class QName implements Serializable { * @param localName the local name */ public QName(String namespaceURI, String prefix, String localName) { - if (localName == null) { - throw new NullPointerException("Parameter localName must not be null"); - } - if (localName.length() == 0) { - throw new IllegalArgumentException("Parameter localName must not be empty"); - } - this.namespaceURI = namespaceURI; - this.prefix = prefix; - this.localName = localName; - this.hashCode = toHashString().hashCode(); + super(namespaceURI, prefix, localName); } /** @@ -61,78 +46,7 @@ public class QName implements Serializable { * @param qName the qualified name */ public QName(String namespaceURI, String qName) { - if (qName == null) { - throw new NullPointerException("Parameter localName must not be null"); - } - if (qName.length() == 0) { - throw new IllegalArgumentException("Parameter localName must not be empty"); - } - this.namespaceURI = namespaceURI; - int p = qName.indexOf(':'); - if (p > 0) { - this.prefix = qName.substring(0, p); - this.localName = qName.substring(p + 1); - } else { - this.prefix = null; - this.localName = qName; - } - this.hashCode = toHashString().hashCode(); + super(namespaceURI, qName); } - /** @return the namespace URI */ - public String getNamespaceURI() { - return this.namespaceURI; - } - - /** @return the namespace prefix */ - public String getPrefix() { - return this.prefix; - } - - /** @return the local name */ - public String getLocalName() { - return this.localName; - } - - /** @return the fully qualified name */ - public String getQName() { - return getPrefix() != null ? getPrefix() + ':' + getLocalName() : getLocalName(); - } - - /** {@inheritDoc} */ - public int hashCode() { - return this.hashCode; - } - - /** {@inheritDoc} */ - public boolean equals(Object obj) { - if (obj == null) { - return false; - } else if (obj == this) { - return true; - } else { - if (obj instanceof QName) { - QName other = (QName)obj; - if ((getNamespaceURI() == null && other.getNamespaceURI() == null) - || getNamespaceURI().equals(other.getNamespaceURI())) { - return getLocalName().equals(other.getLocalName()); - } - } - } - return false; - } - - /** {@inheritDoc} */ - public String toString() { - return prefix != null - ? (prefix + ":" + localName) - : toHashString(); - } - - private String toHashString() { - return (namespaceURI != null - ? ("{" + namespaceURI + "}" + localName) - : localName); - } - } diff --git a/src/java/org/apache/fop/util/XMLResourceBundle.java b/src/java/org/apache/fop/util/XMLResourceBundle.java new file mode 100644 index 000000000..1b320816b --- /dev/null +++ b/src/java/org/apache/fop/util/XMLResourceBundle.java @@ -0,0 +1,398 @@ +/* + * 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.util; + +import java.io.IOException; +import java.io.InputStream; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Locale; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.Properties; +import java.util.ResourceBundle; +import java.util.Stack; + +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.sax.SAXResult; +import javax.xml.transform.sax.SAXTransformerFactory; +import javax.xml.transform.stream.StreamSource; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import org.apache.xmlgraphics.util.QName; + +/** + * This class is a ResourceBundle that loads its contents from XML files instead of properties + * files (like PropertiesResourceBundle). + *

+ * The XML format for this resource bundle implementation is the following + * (the same as Apache Cocoon's XMLResourceBundle): + *

+ * <catalogue xml:lang="en">
+ *   <message key="key1">Message <br/> Value 1</message>
+ *   <message key="key2">Message <br/> Value 1</message>
+ *   ...
+ * </catalogue>
+ * 
+ */ +public class XMLResourceBundle extends ResourceBundle { + + //Note: Some code here has been copied and adapted from Apache Harmony! + + private Properties resources = new Properties(); + + private Locale locale; + + private static SAXTransformerFactory tFactory + = (SAXTransformerFactory)SAXTransformerFactory.newInstance(); + + /** + * Creates a resource bundle from an InputStream. + * @param in the stream to read from + * @throws IOException if an I/O error occurs + */ + public XMLResourceBundle(InputStream in) throws IOException { + try { + Transformer transformer = tFactory.newTransformer(); + StreamSource src = new StreamSource(in); + SAXResult res = new SAXResult(new CatalogueHandler()); + transformer.transform(src, res); + } catch (TransformerException e) { + throw new IOException("Error while parsing XML resource bundle: " + e.getMessage()); + } + } + + /** + * Gets a resource bundle using the specified base name, default locale, and class loader. + * @param baseName the base name of the resource bundle, a fully qualified class name + * @param loader the class loader from which to load the resource bundle + * @return a resource bundle for the given base name and the default locale + * @throws MissingResourceException if no resource bundle for the specified base name can be + * found + * @see java.util.ResourceBundle#getBundle(String) + */ + public static ResourceBundle getXMLBundle(String baseName, ClassLoader loader) + throws MissingResourceException { + return getXMLBundle(baseName, Locale.getDefault(), loader); + } + + /** + * Gets a resource bundle using the specified base name, locale, and class loader. + * @param baseName the base name of the resource bundle, a fully qualified class name + * @param locale the locale for which a resource bundle is desired + * @param loader the class loader from which to load the resource bundle + * @return a resource bundle for the given base name and locale + * @throws MissingResourceException if no resource bundle for the specified base name can be + * found + * @see java.util.ResourceBundle#getBundle(String, Locale, ClassLoader) + */ + public static ResourceBundle getXMLBundle(String baseName, Locale locale, ClassLoader loader) + throws MissingResourceException { + if (loader == null) { + throw new NullPointerException("loader must not be null"); + } + if (baseName == null) { + throw new NullPointerException("baseName must not be null"); + } + + ResourceBundle bundle; + if (!locale.equals(Locale.getDefault())) { + bundle = handleGetXMLBundle(baseName, "_" + locale, false, loader); + if (bundle != null) { + return bundle; + } + } + bundle = handleGetXMLBundle(baseName, "_" + Locale.getDefault(), true, loader); + if (bundle != null) { + return bundle; + } + throw new MissingResourceException( + baseName + " (" + locale + ")", baseName + '_' + locale, null); + } + + static class MissingBundle extends ResourceBundle { + public Enumeration getKeys() { + return null; + } + + public Object handleGetObject(String name) { + return null; + } + } + + private static final ResourceBundle MISSING = new MissingBundle(); + private static final ResourceBundle MISSINGBASE = new MissingBundle(); + + private static Map cache = new java.util.WeakHashMap(); + //> + + private static ResourceBundle handleGetXMLBundle(String base, String locale, + boolean loadBase, final ClassLoader loader) { + XMLResourceBundle bundle = null; + String bundleName = base + locale; + Object cacheKey = loader != null ? (Object) loader : (Object) "null"; + Hashtable loaderCache; // + synchronized (cache) { + loaderCache = (Hashtable)cache.get(cacheKey); + if (loaderCache == null) { + loaderCache = new Hashtable(); + cache.put(cacheKey, loaderCache); + } + } + ResourceBundle result = (ResourceBundle)loaderCache.get(bundleName); + if (result != null) { + if (result == MISSINGBASE) { + return null; + } + if (result == MISSING) { + if (!loadBase) { + return null; + } + String extension = strip(locale); + if (extension == null) { + return null; + } + return handleGetXMLBundle(base, extension, loadBase, loader); + } + return result; + } + + final String fileName = bundleName.replace('.', '/') + ".xml"; + InputStream stream = (InputStream)AccessController + .doPrivileged(new PrivilegedAction() { + public Object run() { + return loader == null + ? ClassLoader.getSystemResourceAsStream(fileName) + : loader.getResourceAsStream(fileName); + } + }); + if (stream != null) { + try { + try { + bundle = new XMLResourceBundle(stream); + } finally { + stream.close(); + } + bundle.setLocale(locale); + } catch (IOException e) { + throw new MissingResourceException(e.getMessage(), base, null); + } + } + + String extension = strip(locale); + if (bundle != null) { + if (extension != null) { + ResourceBundle parent = handleGetXMLBundle(base, extension, true, + loader); + if (parent != null) { + bundle.setParent(parent); + } + } + loaderCache.put(bundleName, bundle); + return bundle; + } + + if (extension != null) { + ResourceBundle fallback = handleGetXMLBundle(base, extension, loadBase, loader); + if (fallback != null) { + loaderCache.put(bundleName, fallback); + return fallback; + } + } + loaderCache.put(bundleName, loadBase ? MISSINGBASE : MISSING); + return null; + } + + private void setLocale(String name) { + String language = "", country = "", variant = ""; + if (name.length() > 1) { + int nextIndex = name.indexOf('_', 1); + if (nextIndex == -1) { + nextIndex = name.length(); + } + language = name.substring(1, nextIndex); + if (nextIndex + 1 < name.length()) { + int index = nextIndex; + nextIndex = name.indexOf('_', nextIndex + 1); + if (nextIndex == -1) { + nextIndex = name.length(); + } + country = name.substring(index + 1, nextIndex); + if (nextIndex + 1 < name.length()) { + variant = name.substring(nextIndex + 1, name.length()); + } + } + } + this.locale = new Locale(language, country, variant); + } + + private static String strip(String name) { + int index = name.lastIndexOf('_'); + if (index != -1) { + return name.substring(0, index); + } + return null; + } + + private Enumeration getLocalKeys() { + return (Enumeration)resources.propertyNames(); + } + + /** {@inheritDoc} */ + public Locale getLocale() { + return this.locale; + } + + /** {@inheritDoc} */ + public Enumeration getKeys() { + if (parent == null) { + return getLocalKeys(); + } + return new Enumeration() { + private Enumeration local = getLocalKeys(); + private Enumeration pEnum = parent.getKeys(); + + private Object nextElement; + + private boolean findNext() { + if (nextElement != null) { + return true; + } + while (pEnum.hasMoreElements()) { + Object next = pEnum.nextElement(); + if (!resources.containsKey(next)) { + nextElement = next; + return true; + } + } + return false; + } + + public boolean hasMoreElements() { + if (local.hasMoreElements()) { + return true; + } + return findNext(); + } + + public Object nextElement() { + if (local.hasMoreElements()) { + return local.nextElement(); + } + if (findNext()) { + Object result = nextElement; + nextElement = null; + return result; + } + // Cause an exception + return pEnum.nextElement(); + } + }; + } + + /** {@inheritDoc} */ + protected Object handleGetObject(String key) { + if (key == null) { + throw new NullPointerException("key must not be null"); + } + return resources.get(key); + } + + /** {@inheritDoc} */ + public String toString() { + return "XMLResourceBundle: " + getLocale(); + } + + private class CatalogueHandler extends DefaultHandler { + + private static final String CATALOGUE = "catalogue"; + private static final String MESSAGE = "message"; + + private StringBuffer valueBuffer = new StringBuffer(); + private Stack elementStack = new Stack(); + private String currentKey = null; + + private boolean isOwnNamespace(String uri) { + return ("".equals(uri)); + } + + private QName getParentElementName() { + return (QName)elementStack.peek(); + } + + /** {@inheritDoc} */ + public void startElement(String uri, String localName, String qName, + Attributes atts) throws SAXException { + super.startElement(uri, localName, qName, atts); + QName elementName = new QName(uri, qName); + if (isOwnNamespace(uri)) { + if (CATALOGUE.equals(localName)) { + //nop + } else if (MESSAGE.equals(localName)) { + if (!CATALOGUE.equals(getParentElementName().getLocalName())) { + throw new SAXException(MESSAGE + " must be a child of " + CATALOGUE); + } + this.currentKey = atts.getValue("key"); + } else { + throw new SAXException("Invalid element name: " + elementName); + } + } else { + //ignore + } + this.valueBuffer.setLength(0); + elementStack.push(elementName); + } + + /** {@inheritDoc} */ + public void endElement(String uri, String localName, String qName) throws SAXException { + super.endElement(uri, localName, qName); + elementStack.pop(); + if (isOwnNamespace(uri)) { + if (CATALOGUE.equals(localName)) { + //nop + } else if (MESSAGE.equals(localName)) { + if (this.currentKey == null) { + throw new SAXException( + "current key is null (attribute 'key' might be mistyped)"); + } + resources.put(this.currentKey, this.valueBuffer.toString()); + this.currentKey = null; + } + } else { + //ignore + } + this.valueBuffer.setLength(0); + } + + /** {@inheritDoc} */ + public void characters(char[] ch, int start, int length) throws SAXException { + super.characters(ch, start, length); + valueBuffer.append(ch, start, length); + } + + } + +} diff --git a/src/java/org/apache/fop/util/text/AdvancedMessageFormat.java b/src/java/org/apache/fop/util/text/AdvancedMessageFormat.java new file mode 100644 index 000000000..a2169156a --- /dev/null +++ b/src/java/org/apache/fop/util/text/AdvancedMessageFormat.java @@ -0,0 +1,487 @@ +/* + * 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.util.text; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import org.apache.xmlgraphics.util.Service; + + +/** + * Formats messages based on a template and with a set of named parameters. This is similar to + * {@link java.util.MessageFormat} but uses named parameters and supports conditional sub-groups. + *

+ * Example: + *

+ *

Missing field "{fieldName}"[ at location: {location}]!

+ *
    + *
  • Curly brackets ("{}") are used for fields.
  • + *
  • Square brackets ("[]") are used to delimit conditional sub-groups. A sub-group is + * conditional when all fields inside the sub-group have a null value. In the case, everything + * between the brackets is skipped.
  • + *
+ */ +public class AdvancedMessageFormat { + + /** Regex that matches "," but not "\," (escaped comma) */ + static final Pattern COMMA_SEPARATOR_REGEX = Pattern.compile("(? 0) { + parent.addChild(new TextPart(sb.toString())); + sb.setLength(0); + } + i++; + int nesting = 1; + while (i < len) { + ch = pattern.charAt(i); + if (ch == '{') { + nesting++; + } else if (ch == '}') { + nesting--; + if (nesting == 0) { + i++; + break; + } + } + sb.append(ch); + i++; + } + parent.addChild(parseField(sb.toString())); + sb.setLength(0); + break; + case ']': + i++; + break loop; //Current composite is finished + case '[': + if (sb.length() > 0) { + parent.addChild(new TextPart(sb.toString())); + sb.setLength(0); + } + i++; + CompositePart composite = new CompositePart(true); + parent.addChild(composite); + i += parseInnerPattern(pattern, composite, sb, i); + break; + case '|': + if (sb.length() > 0) { + parent.addChild(new TextPart(sb.toString())); + sb.setLength(0); + } + parent.newSection(); + i++; + break; + case '\\': + if (i < len - 1) { + i++; + ch = pattern.charAt(i); + } + //no break here! Must be right before "default" section + default: + sb.append(ch); + i++; + } + } + if (sb.length() > 0) { + parent.addChild(new TextPart(sb.toString())); + sb.setLength(0); + } + return i - start; + } + + private Part parseField(String field) { + String[] parts = COMMA_SEPARATOR_REGEX.split(field, 3); + String fieldName = parts[0]; + if (parts.length == 1) { + if (fieldName.startsWith("#")) { + return new FunctionPart(fieldName.substring(1)); + } else { + return new SimpleFieldPart(fieldName); + } + } else { + String format = parts[1]; + PartFactory factory = (PartFactory)PART_FACTORIES.get(format); + if (factory == null) { + throw new IllegalArgumentException( + "No PartFactory available under the name: " + format); + } + if (parts.length == 2) { + return factory.newPart(fieldName, null); + } else { + return factory.newPart(fieldName, parts[2]); + } + } + } + + private static Function getFunction(String functionName) { + return (Function)FUNCTIONS.get(functionName); + } + + /** + * Formats a message with the given parameters. + * @param params a Map of named parameters (Contents: ) + * @return the formatted message + */ + public String format(Map params) { + StringBuffer sb = new StringBuffer(); + format(params, sb); + return sb.toString(); + } + + /** + * Formats a message with the given parameters. + * @param params a Map of named parameters (Contents: ) + * @param target the target StringBuffer to write the formatted message to + */ + public void format(Map params, StringBuffer target) { + rootPart.write(target, params); + } + + /** + * Represents a message template part. This interface is implemented by various variants of + * the single curly braces pattern ({field}, {field,if,yes,no} etc.). + */ + public interface Part { + + /** + * Writes the formatted part to a string buffer. + * @param sb the target string buffer + * @param params the parameters to work with + */ + void write(StringBuffer sb, Map params); + + /** + * Indicates whether there is any content that is generated by this message part. + * @param params the parameters to work with + * @return true if the part has content + */ + boolean isGenerated(Map params); + } + + /** + * Implementations of this interface parse a field part and return message parts. + */ + public interface PartFactory { + + /** + * Creates a new part by parsing the values parameter to configure the part. + * @param fieldName the field name + * @param values the unparsed parameter values + * @return the new message part + */ + Part newPart(String fieldName, String values); + + /** + * Returns the name of the message part format. + * @return the name of the message part format + */ + String getFormat(); + } + + /** + * Implementations of this interface format certain objects to strings. + */ + public interface ObjectFormatter { + + /** + * Formats an object to a string and writes the result to a string buffer. + * @param sb the target string buffer + * @param obj the object to be formatted + */ + void format(StringBuffer sb, Object obj); + + /** + * Indicates whether a given object is supported. + * @param obj the object + * @return true if the object is supported by the formatter + */ + boolean supportsObject(Object obj); + } + + /** + * Implementations of this interface do some computation based on the message parameters + * given to it. Note: at the moment, this has to be done in a local-independent way since + * there is no locale information. + */ + public interface Function { + + /** + * Executes the function. + * @param params the message parameters + * @return the function result + */ + Object evaluate(Map params); + + /** + * Returns the name of the function. + * @return the name of the function + */ + Object getName(); + } + + private static class TextPart implements Part { + + private String text; + + public TextPart(String text) { + this.text = text; + } + + public void write(StringBuffer sb, Map params) { + sb.append(text); + } + + public boolean isGenerated(Map params) { + return true; + } + + /** {@inheritDoc} */ + public String toString() { + return this.text; + } + } + + private static class SimpleFieldPart implements Part { + + private String fieldName; + + public SimpleFieldPart(String fieldName) { + this.fieldName = fieldName; + } + + public void write(StringBuffer sb, Map params) { + if (!params.containsKey(fieldName)) { + throw new IllegalArgumentException( + "Message pattern contains unsupported field name: " + fieldName); + } + Object obj = params.get(fieldName); + formatObject(obj, sb); + } + + public boolean isGenerated(Map params) { + Object obj = params.get(fieldName); + return obj != null; + } + + /** {@inheritDoc} */ + public String toString() { + return "{" + this.fieldName + "}"; + } + } + + /** + * Formats an object to a string and writes the result to a string buffer. This method + * usually uses the object's toString() method unless there is an + * {@link ObjectFormatter} that supports the object. {@link ObjectFormatter}s are registered + * through the service provider mechanism defined by the JAR specification. + * @param obj the object to be formatted + * @param target the target string buffer + */ + public static void formatObject(Object obj, StringBuffer target) { + if (obj instanceof String) { + target.append(obj); + } else { + boolean handled = false; + Iterator iter = OBJECT_FORMATTERS.iterator(); + while (iter.hasNext()) { + ObjectFormatter formatter = (ObjectFormatter)iter.next(); + if (formatter.supportsObject(obj)) { + formatter.format(target, obj); + handled = true; + break; + } + } + if (!handled) { + target.append(String.valueOf(obj)); + } + } + } + + private static class FunctionPart implements Part { + + private Function function; + + public FunctionPart(String functionName) { + this.function = getFunction(functionName); + if (this.function == null) { + throw new IllegalArgumentException("Unknown function: " + functionName); + } + } + + public void write(StringBuffer sb, Map params) { + Object obj = this.function.evaluate(params); + formatObject(obj, sb); + } + + public boolean isGenerated(Map params) { + Object obj = this.function.evaluate(params); + return obj != null; + } + + /** {@inheritDoc} */ + public String toString() { + return "{#" + this.function.getName() + "}"; + } + } + + private static class CompositePart implements Part { + + protected List parts = new java.util.ArrayList(); + private boolean conditional; + private boolean hasSections = false; + + public CompositePart(boolean conditional) { + this.conditional = conditional; + } + + private CompositePart(List parts) { + this.parts.addAll(parts); + this.conditional = true; + } + + public void addChild(Part part) { + if (part == null) { + throw new NullPointerException("part must not be null"); + } + if (hasSections) { + CompositePart composite = (CompositePart)this.parts.get(this.parts.size() - 1); + composite.addChild(part); + } else { + this.parts.add(part); + } + } + + public void newSection() { + if (!hasSections) { + List p = this.parts; + //Dropping into a different mode... + this.parts = new java.util.ArrayList(); + this.parts.add(new CompositePart(p)); + hasSections = true; + } + this.parts.add(new CompositePart(true)); + } + + public void write(StringBuffer sb, Map params) { + if (hasSections) { + Iterator iter = this.parts.iterator(); + while (iter.hasNext()) { + CompositePart part = (CompositePart)iter.next(); + if (part.isGenerated(params)) { + part.write(sb, params); + break; + } + } + } else { + if (isGenerated(params)) { + Iterator iter = this.parts.iterator(); + while (iter.hasNext()) { + Part part = (Part)iter.next(); + part.write(sb, params); + } + } + } + } + + public boolean isGenerated(Map params) { + if (hasSections) { + Iterator iter = this.parts.iterator(); + while (iter.hasNext()) { + Part part = (Part)iter.next(); + if (part.isGenerated(params)) { + return true; + } + } + return false; + } else { + if (conditional) { + Iterator iter = this.parts.iterator(); + while (iter.hasNext()) { + Part part = (Part)iter.next(); + if (!part.isGenerated(params)) { + return false; + } + } + } + return true; + } + } + + /** {@inheritDoc} */ + public String toString() { + return this.parts.toString(); + } + } + + + static String unescapeComma(String string) { + return string.replaceAll("\\\\,", ","); + } +} diff --git a/src/java/org/apache/fop/util/text/ChoiceFieldPart.java b/src/java/org/apache/fop/util/text/ChoiceFieldPart.java new file mode 100644 index 000000000..df457a02b --- /dev/null +++ b/src/java/org/apache/fop/util/text/ChoiceFieldPart.java @@ -0,0 +1,91 @@ +/* + * 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.util.text; + +import java.text.ChoiceFormat; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.fop.util.text.AdvancedMessageFormat.Part; +import org.apache.fop.util.text.AdvancedMessageFormat.PartFactory; + +/** + * Defines a "choice" field part that works like {@link ChoiceFormat}. + */ +public class ChoiceFieldPart implements Part { + + private static final Pattern VARIABLE_REGEX = Pattern.compile("\\{([^\\}]+)\\}"); + + private String fieldName; + private ChoiceFormat choiceFormat; + + /** + * Creates a new choice part. + * @param fieldName the field name to work on + * @param choicesPattern the choices pattern (as used by {@link ChoiceFormat}) + */ + public ChoiceFieldPart(String fieldName, String choicesPattern) { + this.fieldName = fieldName; + this.choiceFormat = new ChoiceFormat(choicesPattern); + } + + /** {@inheritDoc} */ + public boolean isGenerated(Map params) { + Object obj = params.get(fieldName); + return obj != null; + } + + /** {@inheritDoc} */ + public void write(StringBuffer sb, Map params) { + Object obj = params.get(fieldName); + Number num = (Number)obj; + String result = this.choiceFormat.format(num.doubleValue()); + Matcher m = VARIABLE_REGEX.matcher(result); + if (m.find()) { + //Resolve inner variables + AdvancedMessageFormat f = new AdvancedMessageFormat(result); + f.format(params, sb); + } else { + sb.append(result); + } + } + + /** {@inheritDoc} */ + public String toString() { + return "{" + this.fieldName + ",choice, ....}"; + } + + /** Factory for ChoiceFieldPart. */ + public static class Factory implements PartFactory { + + /** {@inheritDoc} */ + public Part newPart(String fieldName, String values) { + return new ChoiceFieldPart(fieldName, values); + } + + /** {@inheritDoc} */ + public String getFormat() { + return "choice"; + } + + } + +} \ No newline at end of file diff --git a/src/java/org/apache/fop/util/text/EqualsFieldPart.java b/src/java/org/apache/fop/util/text/EqualsFieldPart.java new file mode 100644 index 000000000..2114b0d00 --- /dev/null +++ b/src/java/org/apache/fop/util/text/EqualsFieldPart.java @@ -0,0 +1,92 @@ +/* + * 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.util.text; + +import java.util.Map; + +import org.apache.fop.util.text.AdvancedMessageFormat.Part; +import org.apache.fop.util.text.AdvancedMessageFormat.PartFactory; + +/** + * Defines an "equals" field part that can compare a field's string value against another string. + * It returns either of two possible values attached as additional part parameters. Example: + * {field,equals,new,This is new!,This is old!} + */ +public class EqualsFieldPart extends IfFieldPart { + + private String equalsValue; + + /** + * Creates a new "equals" field part. + * @param fieldName the field name + * @param values the unparsed parameter values + */ + public EqualsFieldPart(String fieldName, String values) { + super(fieldName, values); + } + + /** {@inheritDoc} */ + protected void parseValues(String values) { + String[] parts = AdvancedMessageFormat.COMMA_SEPARATOR_REGEX.split(values, 3); + this.equalsValue = parts[0]; + if (parts.length == 1) { + throw new IllegalArgumentException( + "'equals' format must have at least 2 parameters"); + } + if (parts.length == 3) { + ifValue = AdvancedMessageFormat.unescapeComma(parts[1]); + elseValue = AdvancedMessageFormat.unescapeComma(parts[2]); + } else { + ifValue = AdvancedMessageFormat.unescapeComma(parts[1]); + } + } + + /** {@inheritDoc} */ + protected boolean isTrue(Map params) { + Object obj = params.get(fieldName); + if (obj != null) { + return String.valueOf(obj).equals(this.equalsValue); + } else { + return false; + } + } + + /** {@inheritDoc} */ + public String toString() { + return "{" + this.fieldName + ", equals " + this.equalsValue + "}"; + } + + /** + * Part factory for "equals". + */ + public static class Factory implements PartFactory { + + /** {@inheritDoc} */ + public Part newPart(String fieldName, String values) { + return new EqualsFieldPart(fieldName, values); + } + + /** {@inheritDoc} */ + public String getFormat() { + return "equals"; + } + + } +} \ No newline at end of file diff --git a/src/java/org/apache/fop/util/text/GlyphNameFieldPart.java b/src/java/org/apache/fop/util/text/GlyphNameFieldPart.java new file mode 100644 index 000000000..5d78cdfad --- /dev/null +++ b/src/java/org/apache/fop/util/text/GlyphNameFieldPart.java @@ -0,0 +1,89 @@ +/* + * 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.util.text; + +import java.util.Map; + +import org.apache.xmlgraphics.fonts.Glyphs; + +import org.apache.fop.util.text.AdvancedMessageFormat.Part; +import org.apache.fop.util.text.AdvancedMessageFormat.PartFactory; + +/** + * Function formatting a character to a glyph name. + */ +public class GlyphNameFieldPart implements Part { + + private String fieldName; + + /** + * Creates a new glyph name field part + * @param fieldName the field name + */ + public GlyphNameFieldPart(String fieldName) { + this.fieldName = fieldName; + } + + /** {@inheritDoc} */ + public boolean isGenerated(Map params) { + Object obj = params.get(fieldName); + return obj != null && getGlyphName(obj).length() > 0; + } + + private String getGlyphName(Object obj) { + if (obj instanceof Character) { + return Glyphs.charToGlyphName(((Character)obj).charValue()); + } else { + throw new IllegalArgumentException( + "Value for glyph name part must be a Character but was: " + + obj.getClass().getName()); + } + } + + /** {@inheritDoc} */ + public void write(StringBuffer sb, Map params) { + if (!params.containsKey(fieldName)) { + throw new IllegalArgumentException( + "Message pattern contains unsupported field name: " + fieldName); + } + Object obj = params.get(fieldName); + sb.append(getGlyphName(obj)); + } + + /** {@inheritDoc} */ + public String toString() { + return "{" + this.fieldName + ",glyph-name}"; + } + + /** Factory for {@link GlyphNameFieldPart}. */ + public static class Factory implements PartFactory { + + /** {@inheritDoc} */ + public Part newPart(String fieldName, String values) { + return new GlyphNameFieldPart(fieldName); + } + + /** {@inheritDoc} */ + public String getFormat() { + return "glyph-name"; + } + + } +} diff --git a/src/java/org/apache/fop/util/text/HexFieldPart.java b/src/java/org/apache/fop/util/text/HexFieldPart.java new file mode 100644 index 000000000..19f47f3d7 --- /dev/null +++ b/src/java/org/apache/fop/util/text/HexFieldPart.java @@ -0,0 +1,84 @@ +/* + * 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.util.text; + +import java.util.Map; + +import org.apache.fop.util.text.AdvancedMessageFormat.Part; +import org.apache.fop.util.text.AdvancedMessageFormat.PartFactory; + +/** + * Function formatting a number or character to a hex value. + */ +public class HexFieldPart implements Part { + + private String fieldName; + + /** + * Creates a new hex field part + * @param fieldName the field name + */ + public HexFieldPart(String fieldName) { + this.fieldName = fieldName; + } + + /** {@inheritDoc} */ + public boolean isGenerated(Map params) { + Object obj = params.get(fieldName); + return obj != null; + } + + /** {@inheritDoc} */ + public void write(StringBuffer sb, Map params) { + if (!params.containsKey(fieldName)) { + throw new IllegalArgumentException( + "Message pattern contains unsupported field name: " + fieldName); + } + Object obj = params.get(fieldName); + if (obj instanceof Character) { + sb.append(Integer.toHexString(((Character)obj).charValue())); + } else if (obj instanceof Number) { + sb.append(Integer.toHexString(((Number)obj).intValue())); + } else { + throw new IllegalArgumentException("Incompatible value for hex field part: " + + obj.getClass().getName()); + } + } + + /** {@inheritDoc} */ + public String toString() { + return "{" + this.fieldName + ",hex}"; + } + + /** Factory for {@link HexFieldPart}. */ + public static class Factory implements PartFactory { + + /** {@inheritDoc} */ + public Part newPart(String fieldName, String values) { + return new HexFieldPart(fieldName); + } + + /** {@inheritDoc} */ + public String getFormat() { + return "hex"; + } + + } +} diff --git a/src/java/org/apache/fop/util/text/IfFieldPart.java b/src/java/org/apache/fop/util/text/IfFieldPart.java new file mode 100644 index 000000000..31cd8f36c --- /dev/null +++ b/src/java/org/apache/fop/util/text/IfFieldPart.java @@ -0,0 +1,116 @@ +/* + * 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.util.text; + +import java.util.Map; + +import org.apache.fop.util.text.AdvancedMessageFormat.Part; +import org.apache.fop.util.text.AdvancedMessageFormat.PartFactory; + +/** + * Defines an "if" field part that checks if field's value is true or false. + * It returns either of two possible values attached as additional part parameters. Example: + * {field,if,Yes,No} + */ +public class IfFieldPart implements Part { + + /** the field name for the part */ + protected String fieldName; + /** the value being returned if the field is true */ + protected String ifValue; + /** the value being returned if the field is false */ + protected String elseValue; + + /** + * Creates a new "if" field part. + * @param fieldName the field name + * @param values the unparsed parameter values + */ + public IfFieldPart(String fieldName, String values) { + this.fieldName = fieldName; + parseValues(values); + } + + /** + * Parses the parameter values + * @param values the unparsed parameter values + */ + protected void parseValues(String values) { + String[] parts = AdvancedMessageFormat.COMMA_SEPARATOR_REGEX.split(values, 2); + if (parts.length == 2) { + ifValue = AdvancedMessageFormat.unescapeComma(parts[0]); + elseValue = AdvancedMessageFormat.unescapeComma(parts[1]); + } else { + ifValue = AdvancedMessageFormat.unescapeComma(values); + } + } + + /** {@inheritDoc} */ + public void write(StringBuffer sb, Map params) { + boolean isTrue = isTrue(params); + if (isTrue) { + sb.append(ifValue); + } else if (elseValue != null) { + sb.append(elseValue); + } + } + + /** + * Indicates whether the field's value is true. If the field is not a boolen, it is true + * if the field is not null. + * @param params the message parameters + * @return true the field's value as boolean + */ + protected boolean isTrue(Map params) { + Object obj = params.get(fieldName); + if (obj instanceof Boolean) { + return ((Boolean)obj).booleanValue(); + } else { + return (obj != null); + } + } + + /** {@inheritDoc} */ + public boolean isGenerated(Map params) { + return isTrue(params) || (elseValue != null); + } + + /** {@inheritDoc} */ + public String toString() { + return "{" + this.fieldName + ", if...}"; + } + + /** + * Part factory for "if". + */ + public static class Factory implements PartFactory { + + /** {@inheritDoc} */ + public Part newPart(String fieldName, String values) { + return new IfFieldPart(fieldName, values); + } + + /** {@inheritDoc} */ + public String getFormat() { + return "if"; + } + + } +} \ No newline at end of file diff --git a/src/java/org/apache/fop/util/text/LocatorFormatter.java b/src/java/org/apache/fop/util/text/LocatorFormatter.java new file mode 100644 index 000000000..d9532c66d --- /dev/null +++ b/src/java/org/apache/fop/util/text/LocatorFormatter.java @@ -0,0 +1,42 @@ +/* + * 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.util.text; + +import org.xml.sax.Locator; + +import org.apache.fop.util.text.AdvancedMessageFormat.ObjectFormatter; + +/** + * Object formatter for the SAX Locator object. + */ +public class LocatorFormatter implements ObjectFormatter { + + /** {@inheritDoc} */ + public void format(StringBuffer sb, Object obj) { + Locator loc = (Locator)obj; + sb.append(loc.getLineNumber()).append(":").append(loc.getColumnNumber()); + } + + /** {@inheritDoc} */ + public boolean supportsObject(Object obj) { + return obj instanceof Locator; + } + +} \ No newline at end of file diff --git a/test/java/META-INF/services/org.apache.fop.events.model.EventModelFactory b/test/java/META-INF/services/org.apache.fop.events.model.EventModelFactory new file mode 100644 index 000000000..a40d5658a --- /dev/null +++ b/test/java/META-INF/services/org.apache.fop.events.model.EventModelFactory @@ -0,0 +1 @@ +org.apache.fop.events.FOPTestEventModelFactory \ No newline at end of file diff --git a/test/java/org/apache/fop/UtilityCodeTestSuite.java b/test/java/org/apache/fop/UtilityCodeTestSuite.java index 86a3469ce..d2577c251 100644 --- a/test/java/org/apache/fop/UtilityCodeTestSuite.java +++ b/test/java/org/apache/fop/UtilityCodeTestSuite.java @@ -23,12 +23,14 @@ import junit.framework.Test; import junit.framework.TestSuite; import org.apache.fop.pdf.PDFObjectTestCase; +import org.apache.fop.events.BasicEventTestCase; import org.apache.fop.traits.BorderPropsTestCase; import org.apache.fop.util.DataURIResolverTestCase; import org.apache.fop.util.ElementListUtilsTestCase; import org.apache.fop.util.PDFNumberTestCase; import org.apache.fop.util.ColorUtilTestCase; import org.apache.fop.util.UnitConvTestCase; +import org.apache.fop.util.XMLResourceBundleTestCase; /** * Test suite for FOP's utility classes. @@ -50,6 +52,8 @@ public class UtilityCodeTestSuite { suite.addTest(new TestSuite(BorderPropsTestCase.class)); suite.addTest(new TestSuite(ElementListUtilsTestCase.class)); suite.addTest(new TestSuite(DataURIResolverTestCase.class)); + suite.addTest(new TestSuite(BasicEventTestCase.class)); + suite.addTest(new TestSuite(XMLResourceBundleTestCase.class)); //$JUnit-END$ return suite; } diff --git a/test/java/org/apache/fop/events/BasicEventTestCase.java b/test/java/org/apache/fop/events/BasicEventTestCase.java new file mode 100644 index 000000000..d365ca0ec --- /dev/null +++ b/test/java/org/apache/fop/events/BasicEventTestCase.java @@ -0,0 +1,95 @@ +/* + * 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.events; + +import junit.framework.TestCase; + +import org.apache.fop.events.model.EventSeverity; + +public class BasicEventTestCase extends TestCase { + + public void testBasics() throws Exception { + + MyEventListener listener = new MyEventListener(); + + EventBroadcaster broadcaster = new DefaultEventBroadcaster(); + broadcaster.addEventListener(listener); + assertTrue(broadcaster.hasEventListeners()); + + Event ev = new Event(this, "123", EventSeverity.INFO, + Event.paramsBuilder() + .param("reason", "I'm tired") + .param("blah", new Integer(23)) + .build()); + broadcaster.broadcastEvent(ev); + + ev = listener.event; + assertNotNull(ev); + assertEquals("123", listener.event.getEventID()); + assertEquals(EventSeverity.INFO, listener.event.getSeverity()); + assertEquals("I'm tired", ev.getParam("reason")); + assertEquals(new Integer(23), ev.getParam("blah")); + + broadcaster.removeEventListener(listener); + assertFalse(broadcaster.hasEventListeners()); + + //Just check that there are no NPEs + broadcaster.broadcastEvent(ev); + } + + public void testEventProducer() throws Exception { + MyEventListener listener = new MyEventListener(); + + EventBroadcaster broadcaster = new DefaultEventBroadcaster(); + broadcaster.addEventListener(listener); + assertTrue(broadcaster.hasEventListeners()); + + + TestEventProducer producer = TestEventProducer.Provider.get(broadcaster); + producer.complain(this, "I'm tired", 23); + + Event ev = listener.event; + assertNotNull(ev); + assertEquals("org.apache.fop.events.TestEventProducer.complain", + listener.event.getEventID()); + assertEquals(EventSeverity.WARN, listener.event.getSeverity()); + assertEquals("I'm tired", ev.getParam("reason")); + assertEquals(new Integer(23), ev.getParam("blah")); + + broadcaster.removeEventListener(listener); + assertFalse(broadcaster.hasEventListeners()); + + //Just check that there are no NPEs + broadcaster.broadcastEvent(ev); + } + + private class MyEventListener implements EventListener { + + private Event event; + + public void processEvent(Event event) { + if (this.event != null) { + fail("Multiple events received"); + } + this.event = event; + } + } + +} diff --git a/test/java/org/apache/fop/events/FOPTestEventModelFactory.java b/test/java/org/apache/fop/events/FOPTestEventModelFactory.java new file mode 100644 index 000000000..796a9cfb5 --- /dev/null +++ b/test/java/org/apache/fop/events/FOPTestEventModelFactory.java @@ -0,0 +1,37 @@ +/* + * 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.events; + +import org.apache.fop.events.model.AbstractEventModelFactory; +import org.apache.fop.events.model.EventModel; + +/** + * Factory for FOP's test event model. + */ +public class FOPTestEventModelFactory extends AbstractEventModelFactory { + + private static final String EVENT_MODEL_FILENAME = "test-event-model.xml"; + + /** {@inheritDoc} */ + public EventModel createEventModel() { + return loadModel(getClass(), EVENT_MODEL_FILENAME); + } + +} diff --git a/test/java/org/apache/fop/events/TestEventProducer.java b/test/java/org/apache/fop/events/TestEventProducer.java new file mode 100644 index 000000000..7dfba75ba --- /dev/null +++ b/test/java/org/apache/fop/events/TestEventProducer.java @@ -0,0 +1,48 @@ +/* + * 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.events; + +public interface TestEventProducer extends EventProducer { + + /** + * Complain about something. + * @param source the event source + * @param reason the reason for the complaint + * @param blah the complaint + * @event.severity WARN + */ + void complain(Object source, String reason, int blah); + + /** + * Express joy about something. + * @param source the event source + * @param what the cause for the joy + * @event.severity INFO + */ + void enjoy(Object source, String what); + + public class Provider { + + public static TestEventProducer get(EventBroadcaster broadcaster) { + return (TestEventProducer)broadcaster.getEventProducerFor(TestEventProducer.class); + } + } + +} diff --git a/test/java/org/apache/fop/util/AdvancedMessageFormatTestCase.java b/test/java/org/apache/fop/util/AdvancedMessageFormatTestCase.java new file mode 100644 index 000000000..c0e003573 --- /dev/null +++ b/test/java/org/apache/fop/util/AdvancedMessageFormatTestCase.java @@ -0,0 +1,183 @@ +/* + * 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.util; + +import java.util.Map; + +import junit.framework.TestCase; + +import org.xml.sax.helpers.LocatorImpl; + +import org.apache.fop.events.model.EventSeverity; +import org.apache.fop.util.text.AdvancedMessageFormat; + +/** + * Tests for EventFormatter. + */ +public class AdvancedMessageFormatTestCase extends TestCase { + + public void testFormatting() throws Exception { + String msg; + AdvancedMessageFormat format; + + String pattern + = "Element \"{elementName}\" is missing[ required property \"{propertyName}\"]!"; + format = new AdvancedMessageFormat(pattern); + + Map params = new java.util.HashMap(); + params.put("node", new Object()); + params.put("elementName", "fo:external-graphic"); + params.put("propertyName", "src"); + + msg = format.format(params); + assertEquals("Element \"fo:external-graphic\" is missing required property \"src\"!", msg); + + params.remove("propertyName"); + msg = format.format(params); + assertEquals("Element \"fo:external-graphic\" is missing!", msg); + + pattern + = "Testing \\{escaped \\[characters\\], now a normal field {elementName}!"; + format = new AdvancedMessageFormat(pattern); + msg = format.format(params); + assertEquals("Testing {escaped [characters], now a normal field fo:external-graphic!", msg); + + pattern = "Multi-conditional: [case1: {var1}|case2: {var2}|case3: {var3}]"; + format = new AdvancedMessageFormat(pattern); + + params = new java.util.HashMap(); + msg = format.format(params); + assertEquals("Multi-conditional: ", msg); + + params.put("var3", "value3"); + msg = format.format(params); + assertEquals("Multi-conditional: case3: value3", msg); + params.put("var1", "value1"); + msg = format.format(params); + assertEquals("Multi-conditional: case1: value1", msg); + } + + public void testObjectFormatting() throws Exception { + String msg; + AdvancedMessageFormat format; + + String pattern + = "Here's a Locator: {locator}"; + format = new AdvancedMessageFormat(pattern); + + Map params = new java.util.HashMap(); + LocatorImpl loc = new LocatorImpl(); + loc.setColumnNumber(7); + loc.setLineNumber(12); + params.put("locator", loc); + + msg = format.format(params); + assertEquals("Here\'s a Locator: 12:7", msg); + } + + public void testIfFormatting() throws Exception { + String msg; + AdvancedMessageFormat format; + + format = new AdvancedMessageFormat("You are{isBad,if, not} nice!"); + + Map params = new java.util.HashMap(); + + params.put("isBad", Boolean.FALSE); + msg = format.format(params); + assertEquals("You are nice!", msg); + + params.put("isBad", Boolean.TRUE); + msg = format.format(params); + assertEquals("You are not nice!", msg); + + format = new AdvancedMessageFormat("You are{isGood,if, very, not so} nice!"); + + params = new java.util.HashMap(); + + msg = format.format(params); //isGood is missing + assertEquals("You are not so nice!", msg); + + params.put("isGood", Boolean.FALSE); + msg = format.format(params); + assertEquals("You are not so nice!", msg); + + params.put("isGood", Boolean.TRUE); + msg = format.format(params); + assertEquals("You are very nice!", msg); + + format = new AdvancedMessageFormat("You are{isGood,if, very\\, very} nice!"); + + params = new java.util.HashMap(); + + msg = format.format(params); //isGood is missing + assertEquals("You are nice!", msg); + + params.put("isGood", Boolean.FALSE); + msg = format.format(params); + assertEquals("You are nice!", msg); + + params.put("isGood", Boolean.TRUE); + msg = format.format(params); + assertEquals("You are very, very nice!", msg); + } + + public void testEqualsFormatting() throws Exception { + String msg; + AdvancedMessageFormat format; + + format = new AdvancedMessageFormat( + "Error{severity,equals,EventSeverity:FATAL,,\nSome explanation!}"); + + Map params = new java.util.HashMap(); + + params.put("severity", EventSeverity.FATAL); + msg = format.format(params); + assertEquals("Error", msg); + + params.put("severity", EventSeverity.WARN); + msg = format.format(params); + assertEquals("Error\nSome explanation!", msg); + } + + public void testChoiceFormatting() throws Exception { + String msg; + AdvancedMessageFormat format; + + format = new AdvancedMessageFormat( + "You have {amount,choice,0#nothing|0<{amount} bucks|100 + + Hello World! + Untranslatable + \ No newline at end of file diff --git a/test/java/org/apache/fop/util/XMLResourceBundleTestCase_de.xml b/test/java/org/apache/fop/util/XMLResourceBundleTestCase_de.xml new file mode 100644 index 000000000..44cf5a5b4 --- /dev/null +++ b/test/java/org/apache/fop/util/XMLResourceBundleTestCase_de.xml @@ -0,0 +1,4 @@ + + + Hallo Welt! + \ No newline at end of file diff --git a/test/java/org/apache/fop/util/invalid-translation-file.xml b/test/java/org/apache/fop/util/invalid-translation-file.xml new file mode 100644 index 000000000..09306b7cc --- /dev/null +++ b/test/java/org/apache/fop/util/invalid-translation-file.xml @@ -0,0 +1,5 @@ + + + Hello World! + blah + \ No newline at end of file -- 2.39.5