diff options
Diffstat (limited to 'src')
205 files changed, 8110 insertions, 1601 deletions
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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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$ --> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> + <xsl:output indent="yes" method="xml" encoding="UTF-8"/> + + <xsl:param name="generated-url" select="''"/> + + <xsl:template match="catalogue"> + <catalogue> + <xsl:attribute name="xml:lang"><xsl:value-of select="@xml:lang"/></xsl:attribute> + <xsl:apply-templates/> + <xsl:if test="$generated-url != ''"> + <xsl:variable name="generated" select="document($generated-url)"/> + <xsl:call-template name="add-new-messages"> + <xsl:with-param name="existing" select="."/> + <xsl:with-param name="new" select="$generated/catalogue"/> + </xsl:call-template> + </xsl:if> + </catalogue> + </xsl:template> + + <xsl:template name="add-new-messages"> + <xsl:param name="existing"/> + <xsl:param name="new"/> + <xsl:for-each select="$new/message"> + <xsl:variable name="k" select="@key"/> + <xsl:if test="not(boolean($existing/message[@key = $k]))"> + <xsl:apply-templates select="."></xsl:apply-templates> + </xsl:if> + </xsl:for-each> + </xsl:template> + + <xsl:template match="@*|node()"> + <xsl:copy> + <xsl:apply-templates select="@*|node()"/> + </xsl:copy> + </xsl:template> + +</xsl:stylesheet> 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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$ --> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> + <xsl:output indent="yes" method="xml" encoding="UTF-8"/> + + <xsl:template match="event-model"> + <catalogue> + <xsl:attribute name="xml:lang">en</xsl:attribute> + <xsl:apply-templates select="//method"></xsl:apply-templates> + </catalogue> + </xsl:template> + + <xsl:template match="method"> + <message> + <xsl:attribute name="key"><xsl:value-of select="../@name"/>.<xsl:value-of select="@name"/></xsl:attribute> + </message> + </xsl:template> + +</xsl:stylesheet> 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 @@ <fonts label="Fonts" href="fonts.html"/> <hyphenation label="Hyphenation" href="hyphenation.html"/> <extensions label="Extensions" href="extensions.html"/> + <events label="Events" href="events.html"/> </features> </trunk> 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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$ --> +<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V2.0//EN" "http://forrest.apache.org/dtd/document-v20.dtd"> +<document> + <header> + <title>Events/Processing Feedback</title> + <version>$Revision: 634267 $</version> + </header> + <body> + <section id="introduction"> + <title>Introduction</title> + <p> + In versions until 0.20.5, FOP used + <a href="http://excalibur.apache.org/framework/index.html">Avalon-style Logging</a> where + it was possible to supply a logger per processing run. During the redesign + the logging infrastructure was switched over to + <a href="http://commons.apache.org/logging/">Commons Logging</a> 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. + </p> + <p> + 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. + </p> + <p> + 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). + </p> + </section> + <section id="consumer"> + <title>The consumer side</title> + <p> + The event subsystem is located in the <code>org.apache.fop.events</code> package and its + base is the <code>Event</code> class. An instance is created for each event and is sent + to a set of <code>EventListener</code> instances by the <code>EventBroadcaster</code>. + An <code>Event</code> contains: + </p> + <ul> + <li>an event ID,</li> + <li>a source object (which generated the event),</li> + <li>a severity level (Info, Warning, Error and Fatal Error) and</li> + <li>a map of named parameters.</li> + </ul> + <p> + The <code>EventFormatter</code> class can be used to translate the events into + human-readable, localized messages. + </p> + <p> + A full example of what is shown here can be found in the + <code>examples/embedding/java/embedding/events</code> directory in the FOP distribution. + The example can also be accessed + <a href="http://svn.apache.org/viewvc/xmlgraphics/fop/trunk/examples/embedding/java/embedding/events/">via the web</a>. + </p> + <section id="write-listener"> + <title>Writing an EventListener</title> + <p> + 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. + </p> + <source><![CDATA[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; + +/** A simple event listener that writes the events to stdout and stderr. */ +public 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; + } + } +}]]></source> + <p> + You can see that for every event the method <code>processEvent</code> of the + <code>EventListener</code> will be called. Inside this method you can do whatever + processing you would like including throwing a <code>RuntimeException</code>, if you want + to abort the current processing run. + </p> + <p> + 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 <code>EventFormatter</code> class does + this for you. It provides additional methods if you'd like to explicitly specify + the locale. + </p> + <p> + 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! + </p> + </section> + <section id="add-listener"> + <title>Adding an EventListener</title> + <p> + To register the event listener with FOP, get the <code>EventBroadcaster</code> which + is associated with the user agent (<code>FOUserAgent</code>) and add it there: + </p> + <source><![CDATA[FOUserAgent foUserAgent = fopFactory.newFOUserAgent(); +foUserAgent.getEventBroadcaster().addEventListener(new SysOutEventListener());]]></source> + <p> + Please note that this is done separately for each processing run, i.e. for each + new user agent. + </p> + </section> + <section id="listener-example1"> + <title>An additional listener example</title> + <p> + Here's an additional example of an event listener: + </p> + <p> + 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: + </p> + <source><![CDATA[public 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"); + + throw new RuntimeException(EventFormatter.format(event), fnfe); + } else { + //ignore all other events (or do something of your choice) + } + } + +}]]></source> + <p> + This throws a <code>RuntimeException</code> with the <code>FileNotFoundException</code> + as the cause. Further processing effectively stops in FOP. You can catch the exception + in your code and react as you see necessary. + </p> + </section> + </section> + <section id="producer"> + <title>The producer side (for FOP developers)</title> + <p> + This section is primarily for FOP and FOP plug-in developers. It describes how to use + the event subsystem for producing events. + </p> + <note> + 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 + <a href="mailto:fop-dev@xmlgraphics.apache.org">us</a>. + </note> + <section id="basic-event-production"> + <title>Producing and sending an event</title> + <p> + The basics are very simple. Just instantiate an <code>Event</code> object and fill + it with the necessary parameters. Then pass it to the <code>EventBroadcaster</code> + which distributes the events to the interested listeneners. Here's a code example: + </p> + <source><![CDATA[Event ev = new Event(this, "complain", EventSeverity.WARN, + Event.paramsBuilder() + .param("reason", "I'm tired") + .param("blah", new Integer(23)) + .build()); +EventBroadcaster broadcaster = [get it from somewhere]; +broadcaster.broadcastEvent(ev); +]]></source> + <p> + The <code>Event.paramsBuilder()</code> is a + <a href="http://en.wikipedia.org/wiki/Fluent_interface">fluent interface</a> + to help with the build-up of the parameters. You could just as well instantiate a + <code>Map</code> (<code>Map<String, Object></code>) and fill it with values. + </p> + </section> + <section id="event-producer"> + <title>The EventProducer interface</title> + <p> + To simplify event production, the event subsystem provides the <code>EventProducer</code> + interface. You can create interfaces which extend <code>EventProducer</code>. 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. + </p> + <p> + The event producer interface does not need to have any implementation. The implementation + is produced at runtime by a dynamic proxy created by <code>DefaultEventBroadcaster</code>. + The dynamic proxy creates <code>Event</code> instances for each method call against + the event producer interface. Each parameter (except "source") is added to the event's + parameter map. + </p> + <p> + 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. + </p> + <p> + Here's an example of such an event producer interface: + </p> + <source><![CDATA[public interface MyEventProducer extends EventProducer { + + public class Provider { + + public static MyEventProducer get(EventBroadcaster broadcaster) { + return (MyEventProducer)broadcaster.getEventProducerFor(MyEventProducer.class); + } + } + + /** + * 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); + +}]]></source> + <p> + To produce the same event as in the first example above, you'd use the following code: + </p> + <source><![CDATA[EventBroadcaster broadcaster = [get it from somewhere]; +TestEventProducer producer = TestEventProducer.Provider.get(broadcaster); +producer.complain(this, "I'm tired", 23);]]></source> + </section> + <section id="event-model"> + <title>The event model</title> + <p> + 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 <code>Event</code>'s + parameter map we need to know the parameter names. These are retrieved from an + event object model. This is found in the <code>org.apache.fop.events.model</code> + 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 (<code>ant resourcegen</code>). The Ant task (found in + <code>src/codegen/java/org/apache/fop/tools/EventProducerCollectorTask.java</code>) + scans FOP's sources for descendants of the <code>EventProducer</code> interface and + uses <a href="http://qdox.codehaus.org/">QDox</a> to parse these interfaces. + </p> + <p> + 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. + </p> + <p> + 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 + <code>org.apache.fop.events.EventExceptionManager$ExceptionFactory</code> has to be + registered for the <code>EventExceptionManager</code> to be able to construct the + exception from an event. + </p> + <p> + 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 + <code>EventModelFactory</code>. This interface is implemented for each event model + and registered through the service provider mechanism + (see the <a href="#plug-ins">plug-ins section</a> for details). + </p> + </section> + <section id="event-severity"> + <title>Event severity</title> + <p> + Four different levels of severity for events has been defined: + </p> + <ol> + <li>INFO: informational only</li> + <li>WARN: a Warning</li> + <li>ERROR: an error condition from which FOP can recover. FOP will continue processing.</li> + <li>FATAL: a fatal error which causes an exception in the end and FOP will stop processing.</li> + </ol> + <p> + 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. + </p> + </section> + <section id="plug-ins"> + <title>Plug-ins to the event subsystem</title> + <p> + The event subsystem is extensible. There are a number of extension points: + </p> + <ul> + <li> + <strong><code>org.apache.fop.events.model.EventModelFactory</code>:</strong> Provides + an event model to the event subsystem. + </li> + <li> + <strong><code>org.apache.fop.events.EventExceptionManager$ExceptionFactory</code>:</strong> + Creates exceptions for events, i.e. turns an event into a specific exception. + </li> + </ul> + <p> + The names in bold above are used as filenames for the service provider files that + are placed in the <code>META-INF/services</code> directory. That way, they are + automatically detected. This is a mechanism defined by the + <a href="http://java.sun.com/j2se/1.4.2/docs/guide/jar/jar.html#Service%20Provider">JAR file specification</a>. + </p> + </section> + <section id="l10n"> + <title>Localization (L10n)</title> + <p> + One goal of the event subsystem was to have localized (translated) event messages. + The <code>EventFormatter</code> class can be used to convert an event to a + human-readable message. Each <code>EventProducer</code> 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 <code>EventFormatter</code> + class). + </p> + <p> + The XML format used by the <code>EventFormatter</code> is the same as + <a href="ext:cocoon">Apache Cocoon's</a> catalog format. Here's an example: + </p> + <source><![CDATA[<?xml version="1.0" encoding="UTF-8"?> +<catalogue xml:lang="en"> + <message key="locator"> + [ (See position {loc})| (See {#gatherContextInfo})| (No context info available)] + </message> + <message key="org.apache.fop.render.rtf.RTFEventProducer.explicitTableColumnsRequired"> + RTF output requires that all table-columns for a table are defined. Output will be incorrect.{{locator}} + </message> + <message key="org.apache.fop.render.rtf.RTFEventProducer.ignoredDeferredEvent"> + Ignored deferred event for {node} ({start,if,start,end}).{{locator}} + </message> +</catalogue> +]]></source> + <p> + 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 + <code>org.apache.fop.util.text.AdvancedMessageFormat</code> which is more powerful + than the <code>MessageFormat</code> classes provided by the Java class library + (<code>java.util.text</code> package). + </p> + <p> + "locator" is a template that is reused by the other message templates + by referencing it through "{{locator}}". This is some kind of include command. + </p> + <p> + Normal event parameters are accessed by name inside single curly braces, for example: + "{node}". For objects, this format just uses the <code>toString()</code> method to turn + the object into a string, unless there is an <code>ObjectFormatter</code> registered + for that type (there's an example for <code>org.xml.sax.Locator</code>). + </p> + <p> + 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). + </p> + <p> + Additional such modifiers can be added by implementing the + <code>AdvancedMessageFormat$Part</code> and <code>AdvancedMessageFormat$PartFactory</code> + interfaces. + </p> + <p> + 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). + </p> + <p> + Developers can also register a function (in the above example: + <code>{#gatherContextInfo})</code> + to do more complex information rendering. These functions are implementations of the + <code>AdvancedMessageFormat$Function</code> interface. Please take care that this is + done in a locale-independent way as there is no locale information available, yet. + </p> + </section> + </section> + </body> +</document> 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 { @@ -186,6 +170,33 @@ public class RenderPagesModel extends AreaTreeModel { } /** + * 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 * it and the page will be rendered later. 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/*<EventModel>*/ 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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$ --> +<catalogue xml:lang="en"> + <message key="locator">[ (See position {loc})| (See {#gatherContextInfo})| (No context info available)]</message> + <message key="rule.markerDescendantOfFlow">An fo:marker is permitted only as the descendant of an fo:flow.</message> + <message key="rule.retrieveMarkerDescendatOfStaticContent">An fo:retrieve-marker is permitted only as the descendant of an fo:static-content.</message> + <message key="rule.bidiOverrideContent">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.</message> + <message key="rule.inlineContent">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.</message> + <message key="rule.childOfSPM">The element must be a child of fo:simple-page-master.</message> + <message key="rule.childOfDeclarations">The element must be a child of fo:declarations.</message> + <message key="rule.childOfSPMorDeclarations">The element must be a child of fo:declarations or fo:simple-page-master.</message> + <message key="org.apache.fop.fo.FOValidationEventProducer.tooManyNodes">For "{elementName}", only one "{offendingNode}" may be declared.{{locator}}</message> + <message key="org.apache.fop.fo.FOValidationEventProducer.nodeOutOfOrder">For "{elementName}", "{tooLateNode}" must be declared before "{tooEarlyNode}"!{{locator}}</message> + <message key="org.apache.fop.fo.FOValidationEventProducer.invalidChild">"{offendingNode}" is not a valid child element of "{elementName}"![ {ruleViolated,lookup}]{{locator}}</message> + <message key="org.apache.fop.fo.FOValidationEventProducer.missingChildElement">"{elementName}" is missing child elements.[ +Required content model: {contentModel}]{{locator}}</message> + <message key="org.apache.fop.fo.FOValidationEventProducer.missingProperty">Element "{elementName}" is missing required property "{propertyName}"!{{locator}}</message> + <message key="org.apache.fop.fo.FOValidationEventProducer.idNotUnique">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}}</message> + <message key="org.apache.fop.fo.FOValidationEventProducer.markerNotInitialChild">fo:marker must be an initial child: {mcname}{{locator}}</message> + <message key="org.apache.fop.fo.FOValidationEventProducer.markerNotUniqueForSameParent">fo:marker "marker-class-name" must be unique for same parent: {mcname}{{locator}}</message> + <message key="org.apache.fop.fo.FOValidationEventProducer.invalidProperty">Invalid property encountered on "{elementName}": {attr}{{locator}}</message> + <message key="org.apache.fop.fo.FOValidationEventProducer.invalidPropertyValue">Invalid property value encountered in {propName}="{propValue}"[: {e}]{{locator}}</message> + <message key="org.apache.fop.fo.FOValidationEventProducer.unimplementedFeature">The following feature isn't implemented by Apache FOP, yet: {feature} (on {elementName}){{locator}}</message> + <message key="org.apache.fop.fo.FOValidationEventProducer.missingLinkDestination">Missing attribute on {elementName}: Either external-destination or internal-destination must be specified.{{locator}}</message> + <message key="org.apache.fop.fo.FOValidationEventProducer.markerCloningFailed">Unable to clone subtree of fo:marker (marker-class-name="{markerClassName}") for fo:retrieve-marker.{{locator}}</message> + <message key="org.apache.fop.fo.FOValidationEventProducer.colorProfileNameNotUnique">Duplicate color profile profile name: {name}{{locator}}</message> + <message key="org.apache.fop.fo.FOValidationEventProducer.regionNameMappedToMultipleRegionClasses">Region-name ("{regionName}") is being mapped to multiple region-classes ({defaultRegionClass1} and {defaultRegionClass2}).{{locator}}</message> + <message key="org.apache.fop.fo.FOValidationEventProducer.masterNameNotUnique">The page master name ("{name}") must be unique across page-masters and page-sequence-masters.{{locator}}</message> + <message key="org.apache.fop.fo.FOValidationEventProducer.duplicateFlowNameInPageSequence">Duplicate flow-name "{flowName}" found within {elementName}.{{locator}}</message> + <message key="org.apache.fop.fo.FOValidationEventProducer.flowNameNotMapped">The flow-name "{flowName}" on {elementName} could not be mapped to a region-name in the layout-master-set.{{locator}}</message> + <message key="org.apache.fop.fo.FOValidationEventProducer.masterNotFound">The master-reference "{masterReference}" on {elementName} matches no simple-page-master or page-sequence-master.{{locator}}</message> + <message key="org.apache.fop.fo.FOValidationEventProducer.illegalRegionName">The region-name "{regionName}" for {elementName} is not permitted.{{locator}}</message> + <message key="org.apache.fop.fo.FOValidationEventProducer.nonZeroBorderPaddingOnRegion">Border and padding for {elementName} "{regionName}" must be '0' (See 6.4.13 in XSL 1.0).{{locator}}</message> + <message key="org.apache.fop.fo.FOValidationEventProducer.columnCountErrorOnRegionBodyOverflowScroll">If overflow property is set to "scroll" on {elementName}, a column-count other than "1" may not be specified.{{locator}}</message> + <message key="org.apache.fop.fo.FOValidationEventProducer.invalidFORoot">First element must be the fo:root formatting object. Found {elementName} instead. Please make sure you're producing a valid XSL-FO document.</message> + <message key="org.apache.fop.fo.FOValidationEventProducer.emptyDocument">Document is empty (something might be wrong with your XSLT stylesheet).</message> + <message key="org.apache.fop.fo.FOValidationEventProducer.unknownFormattingObject">Unknown formatting object "{offendingNode}" encountered (a child of {elementName}}.{{locator}}</message> + <message key="org.apache.fop.fo.flow.table.TableEventProducer.nonAutoBPDOnTable">Only a value of "auto" for block-progression-dimension has a well-specified behavior on fo:table. Falling back to "auto".{{locator}}</message> + <message key="org.apache.fop.fo.flow.table.TableEventProducer.noTablePaddingWithCollapsingBorderModel">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}}</message> + <message key="org.apache.fop.fo.flow.table.TableEventProducer.noMixRowsAndCells">Either fo:table-rows or fo:table-cells may be children of an {elementName} but not both.{{locator}}</message> + <message key="org.apache.fop.fo.flow.table.TableEventProducer.footerOrderCannotRecover">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}}</message> + <message key="org.apache.fop.fo.flow.table.TableEventProducer.startEndRowUnderTableRowWarning">starts-row/ends-row for fo:table-cells non-applicable for children of an fo:table-row.{{locator}}</message> + <message key="org.apache.fop.fo.flow.table.TableEventProducer.tooManyCells">The column-number or number of cells in the row overflows the number of fo:table-columns specified for the table.{{locator}}</message> + <message key="org.apache.fop.fo.flow.table.TableEventProducer.valueMustBeBiggerGtEqOne">{propName} must be 1 or bigger, but got {actualValue}{{locator}}</message> + <message key="org.apache.fop.fo.flow.table.TableEventProducer.warnImplicitColumns">table-layout=\"fixed\" and column-width unspecified => falling back to proportional-column-width(1){{locator}}</message> + <message key="org.apache.fop.fo.flow.table.TableEventProducer.paddingNotApplicable">padding-* properties are not applicable to {elementName}, but a non-zero value for padding was found.{{locator}}</message> + <message key="org.apache.fop.fo.flow.table.TableEventProducer.cellOverlap">{elementName} overlaps in column {column}.<!-- no locator here, exception will be wrapped --></message> + <message key="org.apache.fop.fo.flow.table.TableEventProducer.breakIgnoredDueToRowSpanning">{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}}</message> + <message key="org.apache.fop.events.ResourceEventProducer.imageNotFound">Image not found.[ URI: {uri}.]{{locator}}</message> + <message key="org.apache.fop.events.ResourceEventProducer.imageError">Image not available.[ URI: {uri}.] Reason:[ {reason}][ {e}]{{locator}}</message> + <message key="org.apache.fop.events.ResourceEventProducer.imageIOError">I/O error while loading image.[ URI: {uri}.][ Reason: {ioe}]{{locator}}</message> + <message key="org.apache.fop.events.ResourceEventProducer.ifoNoIntrinsicSize">The intrinsic dimensions of an instream-foreign-object could not be determined.{{locator}}</message> + <message key="org.apache.fop.events.ResourceEventProducer.uriError">Error while handling URI: {uri}. Reason: {e}{{locator}}</message> + <message key="org.apache.fop.events.ResourceEventProducer.foreignXMLProcessingError">Some XML content will be ignored. Could not render XML in namespace "{namespaceURI}".[ Reason: {e}]</message> + <message key="org.apache.fop.events.ResourceEventProducer.foreignXMLNoHandler">Some XML content will be ignored. No handler defined for XML with namespace "{namespaceURI}".</message> + <message key="org.apache.fop.events.ResourceEventProducer.imageWritingError">Error while writing an image to the target file.[ Reason: {e}]</message> + <message key="org.apache.fop.events.ResourceEventProducer.cannotDeleteTempFile">Temporary file could not be deleted: {tempFile}</message> + <message key="org.apache.fop.layoutmgr.inline.InlineLevelEventProducer.leaderWithoutContent">fo:leader is set to "use-content" but has no content.{{locator}}</message> + <message key="org.apache.fop.layoutmgr.inline.InlineLevelEventProducer.lineOverflows">Line {line} of a paragraph overflows the available area by {overflowLength,choice,50000#{overflowLength} millipoints|50000<more than 50 points}.{{locator}}</message> + <message key="org.apache.fop.layoutmgr.BlockLevelEventProducer.rowTooTall">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}}</message> + <message key="org.apache.fop.layoutmgr.BlockLevelEventProducer.tableFixedAutoWidthNotSupported">table-layout="fixed" and width="auto", but auto-layout not supported => assuming width="100%".{{locator}}</message> + <message key="org.apache.fop.layoutmgr.BlockLevelEventProducer.objectTooWide">The extent in inline-progression-direction (width) of a {elementName} is bigger than the available space ({effIPD}mpt > {maxIPD}mpt).{{locator}}</message> + <message key="org.apache.fop.layoutmgr.BlockLevelEventProducer.overconstrainedAdjustEndIndent">Adjusting end-indent based on overconstrained geometry rules for {elementName}.{{locator}}</message> + <message key="org.apache.fop.layoutmgr.BlockLevelEventProducer.viewportOverflow">Content overflows the viewport of an {elementName} in block-progression direction by {amount} millipoints.{clip,if, Content will be clipped.}{{locator}}</message> + <message key="org.apache.fop.layoutmgr.BlockLevelEventProducer.regionOverflow">Content overflows the viewport of the {elementName} on page {page} in block-progression direction by {amount} millipoints.{clip,if, Content will be clipped.}{{locator}}</message> + <message key="org.apache.fop.layoutmgr.BlockLevelEventProducer.flowNotMappingToRegionBody">Flow "{flowName}" does not map to the region-body in page-master "{masterName}". FOP presently does not support this.{{locator}}</message> + <message key="org.apache.fop.layoutmgr.BlockLevelEventProducer.pageSequenceMasterExhausted">Subsequences exhausted in page-sequence-master "{pageSequenceMasterName}", {canRecover,if,using previous subsequence,cannot recover}.{{locator}}</message> + <message key="org.apache.fop.layoutmgr.BlockLevelEventProducer.missingSubsequencesInPageSequenceMaster">No subsequences in page-sequence-master "{pageSequenceMasterName}".{{locator}}</message> + <message key="org.apache.fop.layoutmgr.BlockLevelEventProducer.noMatchingPageMaster">No simple-page-master matching "{pageMasterName}" in page-sequence-master "{pageSequenceMasterName}".{{locator}}</message> + <message key="org.apache.fop.svg.SVGEventProducer.error">SVG error: {message}</message> + <message key="org.apache.fop.svg.SVGEventProducer.alert">SVG alert: {message}</message> + <message key="org.apache.fop.svg.SVGEventProducer.info">SVG info: {message}</message> + <message key="org.apache.fop.svg.SVGEventProducer.svgNotBuilt">SVG graphic could not be built. Reason: {e}</message> + <message key="org.apache.fop.svg.SVGEventProducer.svgRenderingError">SVG graphic could not be rendered. Reason: {e}</message> + <message key="org.apache.fop.render.RendererEventProducer.ioError">I/O error while writing to target file.[ Reason: {ioe}]</message> + <message key="org.apache.fop.area.AreaEventProducer.unresolvedIDReference">{type}: Unresolved ID reference "{id}" found.</message> + <message key="org.apache.fop.area.AreaEventProducer.unresolvedIDReferenceOnPage">Page {page}: Unresolved ID reference "{id}" found.</message> + <message key="org.apache.fop.area.AreaEventProducer.pageLoadError">Error while deserializing page {page}.[ Reason: {e}]</message> + <message key="org.apache.fop.area.AreaEventProducer.pageSaveError">Error while serializing page {page}.[ Reason: {e}]</message> + <message key="org.apache.fop.area.AreaEventProducer.pageRenderingError">Error while rendering page {page}.[ Reason: {e}]</message> + <message key="org.apache.fop.fonts.FontEventAdapter.fontSubstituted">Font "{requested}" not found. Substituting with "{effective}".</message> + <message key="org.apache.fop.fonts.FontEventAdapter.fontLoadingErrorAtAutoDetection">Unable to load font file: {fontURL}.[ Reason: {e}]</message> + <message key="org.apache.fop.fonts.FontEventAdapter.glyphNotAvailable">Glyph "{ch}" (0x{ch,hex}[, {ch,glyph-name}]) not available in font "{fontName}".</message> +</catalogue> 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 @@ +<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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$ -->
+<catalogue xml:lang="de">
+ <message key="locator">[ (Siehe Position {loc})| (Siehe {#gatherContextInfo})| (Keine Kontextinformationen verfügbar)]</message>
+ <message key="org.apache.fop.fo.FOValidationEventProducer.tooManyNodes">In "{elementName}" darf nur ein einziges "{offendingNode}" vorkommen!{{locator}}</message>
+ <message key="org.apache.fop.fo.FOValidationEventProducer.missingProperty">Dem Element "{elementName}" fehlt ein verlangtes Property "{propertyName}"!{{locator}}</message>
+</catalogue>
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: + * <ul> + * <li>The first parameter of each method must be: <code>Object source</code> + * </ul> + */ +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 <code>EventMethodModel</code>. + */ +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 0f436ae28..a47e6affa 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.HashMap; 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(); @@ -543,6 +575,54 @@ public abstract class FONode implements Cloneable { } /** + * 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 { * <code>(FONode) next();</code>) * @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 { * <code>(FONode) previous();</code>) * @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,17 +267,23 @@ 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 * return childNode) @@ -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); } } @@ -383,6 +373,33 @@ public abstract class FObj extends FONode implements Constants { } /** {@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) { return super.gatherContextInfo(); 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 { * <br>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/fo/properties/XMLLangShorthandParser.java b/src/java/org/apache/fop/fo/properties/XMLLangShorthandParser.java index 69ca372e5..5a5cf95c5 100644 --- a/src/java/org/apache/fop/fo/properties/XMLLangShorthandParser.java +++ b/src/java/org/apache/fop/fo/properties/XMLLangShorthandParser.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * -/* $Id:$ */ +/* $Id$ */ package org.apache.fop.fo.properties; 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 */ @@ -78,6 +80,15 @@ public class FontInfo { } /** + * 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.) * @return True if valid @@ -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<String> @@ -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 f74699fd5..2c47875d9 100644 --- a/src/java/org/apache/fop/render/AbstractGenericSVGHandler.java +++ b/src/java/org/apache/fop/render/AbstractGenericSVGHandler.java @@ -30,12 +30,14 @@ 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; /** @@ -46,9 +48,6 @@ import org.apache.fop.svg.SVGUserAgent; */ public abstract class AbstractGenericSVGHandler implements XMLHandler, RendererContextConstants { - /** logging instance */ - private static Log log = LogFactory.getLog(AbstractGenericSVGHandler.class); - /** {@inheritDoc} */ public void handleXML(RendererContext context, Document doc, String ns) throws Exception { @@ -73,7 +72,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 +82,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; } @@ -115,6 +116,20 @@ public abstract class AbstractGenericSVGHandler implements XMLHandler, RendererC } /** + * 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. * @param context the renderer context 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 <code>FOUserAgent</code> - */ + /** {@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 @@ -89,6 +89,12 @@ public interface Renderer { void setUserAgent(FOUserAgent agent); /** + * Returns the associated user agent. + * @return the user agent + */ + FOUserAgent getUserAgent(); + + /** * Set up the given FontInfo. * * @param fontInfo The font information 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<catalogue xml:lang="en"> + <message key="org.apache.fop.render.afp.AFPEventProducer.warnDefaultFontSetup">No AFP fonts configured. Using default setup.</message> +</catalogue> diff --git a/src/java/org/apache/fop/render/afp/AFPRenderer.java b/src/java/org/apache/fop/render/afp/AFPRenderer.java index 205873493..83b8048f9 100644 --- a/src/java/org/apache/fop/render/afp/AFPRenderer.java +++ b/src/java/org/apache/fop/render/afp/AFPRenderer.java @@ -70,6 +70,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; @@ -291,7 +292,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 ", @@ -909,7 +912,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)); return context; } @@ -1014,13 +1017,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); } /* @@ -1195,7 +1202,9 @@ public class AFPRenderer extends AbstractPathOrientedRenderer { image.getWidth(), image.getHeight(), this.bitsPerPixel); } } catch (IOException ioe) { - log.error("Error while serializing bitmap: " + ioe.getMessage(), ioe); + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageWritingError(this, ioe); } } @@ -1305,8 +1314,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/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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<catalogue xml:lang="en"> + <message key="org.apache.fop.render.bitmap.BitmapRendererEventProducer.stoppingAfterFirstPageNoFilename">No filename information available. Stopping early after the first page.</message> + <message key="org.apache.fop.render.bitmap.BitmapRendererEventProducer.stoppingAfterFirstPageNoMultiWriter">Image writer does not support multiple images. Only the first page has been produced.</message> + <message key="org.apache.fop.render.bitmap.BitmapRendererEventProducer.noImageWriterFound">Could not get an ImageWriter to produce "{mime}". The most likely explanation for this is a class loading problem.</message> +</catalogue> 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<catalogue xml:lang="en"> + <message key="org.apache.fop.render.pcl.PCLEventProducer.paperTypeUnavailable">Paper type ({pageWidth} x {pageHeight} mpt) could not be determined. Falling back to: {fallbackPaper}</message> +</catalogue> 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<catalogue xml:lang="en"> + <message key="org.apache.fop.render.pdf.PDFEventProducer.nonFullyResolvedLinkTargets">{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.</message> +</catalogue> diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderer.java b/src/java/org/apache/fop/render/pdf/PDFRenderer.java index 268791ac2..936bf4384 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 @@ -1810,18 +1808,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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<catalogue xml:lang="en"> + <message key="org.apache.fop.render.ps.PSEventProducer.postscriptDictionaryParseError">Failed to parse dictionary string. Reason: {e}, content = "{content}"</message> +</catalogue> 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 306cd7bda..1dd0f4be6 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,7 @@ import org.apache.fop.fo.extensions.ExtensionAttachment; */ public class PSCommentAfterElement extends AbstractPSCommentElement { + /** the element name */ protected static final String ELEMENT = "ps-comment-after"; /** 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 6058f355b..5d3c863f5 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,7 @@ import org.apache.fop.fo.extensions.ExtensionAttachment; */ public class PSCommentBeforeElement extends AbstractPSCommentElement { + /** the element name */ protected static final String ELEMENT = "ps-comment-before"; /** 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<catalogue xml:lang="en"> + <message key="locator">[ (See position {loc})| (See {#gatherContextInfo})| (No context info available)]</message> + <message key="org.apache.fop.render.rtf.RTFEventProducer.onlySPMSupported">Only simple-page-masters are supported on page-sequences. Using default simple-page-master from page-sequence-master "{masterReference}".{{locator}}</message> + <message key="org.apache.fop.render.rtf.RTFEventProducer.noSPMFound">No simple-page-master could be determined.</message> + <message key="org.apache.fop.render.rtf.RTFEventProducer.explicitTableColumnsRequired">No table-columns found on table. RTF output requires that all table-columns for a table are defined. Output will be incorrect.{{locator}}</message> + <message key="org.apache.fop.render.rtf.RTFEventProducer.ignoredDeferredEvent">Ignored deferred event for {node} ({start,if,start,end}).{{locator}}</message> +</catalogue> 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; @@ -159,6 +162,16 @@ public class RTFHandler extends FOEventHandler { } /** + * 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} */ public void startDocument() throws SAXException { @@ -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. * <p> * 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). + * <p> + * The XML format for this resource bundle implementation is the following + * (the same as Apache Cocoon's XMLResourceBundle): + * <pre> + * <catalogue xml:lang="en"> + * <message key="key1">Message <br/> Value 1</message> + * <message key="key2">Message <br/> Value 1</message> + * ... + * </catalogue> + * </pre> + */ +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(); + //<Object, Hashtable<String, ResourceBundle>> + + 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; //<String, ResourceBundle> + 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. + * <p> + * Example: + * </p> + * <p><code>Missing field "{fieldName}"[ at location: {location}]!</code></p> + * <ul> + * <li>Curly brackets ("{}") are used for fields.</li> + * <li>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.</li> + * </ul> + */ +public class AdvancedMessageFormat { + + /** Regex that matches "," but not "\," (escaped comma) */ + static final Pattern COMMA_SEPARATOR_REGEX = Pattern.compile("(?<!\\\\),"); + + private static final Map PART_FACTORIES = new java.util.HashMap(); + private static final List OBJECT_FORMATTERS = new java.util.ArrayList(); + private static final Map FUNCTIONS = new java.util.HashMap(); + + private CompositePart rootPart; + + static { + Iterator iter; + iter = Service.providers(PartFactory.class, true); + while (iter.hasNext()) { + PartFactory factory = (PartFactory)iter.next(); + PART_FACTORIES.put(factory.getFormat(), factory); + } + iter = Service.providers(ObjectFormatter.class, true); + while (iter.hasNext()) { + OBJECT_FORMATTERS.add((ObjectFormatter)iter.next()); + } + iter = Service.providers(Function.class, true); + while (iter.hasNext()) { + Function function = (Function)iter.next(); + FUNCTIONS.put(function.getName(), function); + } + } + + /** + * Construct a new message format. + * @param pattern the message format pattern. + */ + public AdvancedMessageFormat(CharSequence pattern) { + parsePattern(pattern); + } + + private void parsePattern(CharSequence pattern) { + rootPart = new CompositePart(false); + StringBuffer sb = new StringBuffer(); + parseInnerPattern(pattern, rootPart, sb, 0); + } + + private int parseInnerPattern(CharSequence pattern, CompositePart parent, + StringBuffer sb, int start) { + assert sb.length() == 0; + int i = start; + int len = pattern.length(); + loop: + while (i < len) { + char ch = pattern.charAt(i); + switch (ch) { + case '{': + if (sb.length() > 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: <String, Object>) + * @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: <String, Object>) + * @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 <code>toString()</code> 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: + * <code>{field,equals,new,This is new!,This is old!}</code> + */ +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: + * <code>{field,if,Yes,No}</code> + */ +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 |