]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
New interface XMLizable (copied from Apache Excalibur) to mark classes that can seria...
authorJeremias Maerki <jeremias@apache.org>
Thu, 19 Jan 2006 09:46:44 +0000 (09:46 +0000)
committerJeremias Maerki <jeremias@apache.org>
Thu, 19 Jan 2006 09:46:44 +0000 (09:46 +0000)
New interface ContentHandlerFactory (plus associated Factory with Service discovery) for sub-document parsing plug-ins. Used by the area tree but could be used independently.
ExtensionAttachments are now part of the area tree and are written by XMLRenderer and parsed by AreaTreeParser.
The PS Extensions are extended to support serialization to and deserialization from XML.
Added a test case that tests the PS Extension WRT the handling in the area tree XML.

git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@370452 13f79535-47bb-0310-9956-ffa450edef68

src/java/META-INF/services/org.apache.fop.util.ContentHandlerFactory [new file with mode: 0644]
src/java/org/apache/fop/area/AreaTreeParser.java
src/java/org/apache/fop/area/PageViewport.java
src/java/org/apache/fop/render/ps/extensions/PSExtensionHandler.java [new file with mode: 0644]
src/java/org/apache/fop/render/ps/extensions/PSExtensionHandlerFactory.java [new file with mode: 0644]
src/java/org/apache/fop/render/ps/extensions/PSSetupCode.java
src/java/org/apache/fop/render/xml/XMLRenderer.java
src/java/org/apache/fop/util/ContentHandlerFactory.java [new file with mode: 0644]
src/java/org/apache/fop/util/ContentHandlerFactoryRegistry.java [new file with mode: 0644]
src/java/org/apache/fop/util/XMLizable.java [new file with mode: 0644]
test/layoutengine/standard-testcases/ps-extension_1.xml [new file with mode: 0644]

diff --git a/src/java/META-INF/services/org.apache.fop.util.ContentHandlerFactory b/src/java/META-INF/services/org.apache.fop.util.ContentHandlerFactory
new file mode 100644 (file)
index 0000000..d7533c5
--- /dev/null
@@ -0,0 +1 @@
+org.apache.fop.render.ps.extensions.PSExtensionHandlerFactory
\ No newline at end of file
index 32e2bce1e65bca5fb42e304c51f59ac7dbaa518e..ad1017505fada5528b6ea6e434c5fb4f089782c9 100644 (file)
@@ -51,11 +51,14 @@ import org.apache.fop.area.inline.Viewport;
 import org.apache.fop.area.inline.WordArea;
 import org.apache.fop.fo.Constants;
 import org.apache.fop.fo.ElementMappingRegistry;
+import org.apache.fop.fo.extensions.ExtensionAttachment;
 import org.apache.fop.fonts.Font;
 import org.apache.fop.fonts.FontTriplet;
 import org.apache.fop.image.FopImage;
 import org.apache.fop.image.ImageFactory;
 import org.apache.fop.traits.BorderProps;
+import org.apache.fop.util.ContentHandlerFactory;
+import org.apache.fop.util.ContentHandlerFactoryRegistry;
 import org.apache.fop.util.DefaultErrorListener;
 
 import org.w3c.dom.DOMImplementation;
@@ -182,6 +185,7 @@ public class AreaTreeParser {
                 delegateStack.push(qName);
                 delegate.startElement(uri, localName, qName, attributes);
             } else if (domImplementation != null) {
+                //domImplementation is set so we need to start a new DOM building sub-process
                 TransformerHandler handler;
                 try {
                     handler = tFactory.newTransformerHandler();
@@ -196,6 +200,7 @@ public class AreaTreeParser {
                 ((ForeignObject)parent).setDocument(doc);
                 
                 //activate delegate for nested foreign document
+                domImplementation = null; //Not needed anymore now
                 this.delegate = handler;
                 delegateStack.push(qName);
                 delegate.startDocument();
@@ -405,15 +410,31 @@ public class AreaTreeParser {
                         setTraits(attributes, foreign);
                         getCurrentViewport().setContent(foreign);
                         areaStack.push(foreign);
+                    } else if ("extension-attachments".equals(localName)) {
+                        //TODO implement me
                     } else {
                         handled = false;
                     }
                 } else {
-                    handled = false;
+                    ContentHandlerFactory factory 
+                            = ContentHandlerFactoryRegistry.getInstance().getFactory(uri);
+                    if (factory != null) {
+                        delegate = factory.createContentHandler();
+                        delegateStack.push(qName);
+                        delegate.startDocument();
+                        delegate.startElement(uri, localName, qName, attributes);
+                    } else {
+                        handled = false;
+                    }
                 }
                 if (!handled) {
-                    throw new SAXException("Unhandled element " + localName 
-                            + " in namespace: " + uri);
+                    if (uri == null || uri.length() == 0) {
+                        throw new SAXException("Unhandled element " + localName 
+                                + " in namespace: " + uri);
+                    } else {
+                        log.warn("Unhandled element " + localName 
+                                + " in namespace: " + uri);
+                    }
                 }
             }
         }
@@ -438,6 +459,25 @@ public class AreaTreeParser {
             }
         }
         
+        /**
+         * Handles objects created by "sub-parsers" that implement the ObjectSource interface.
+         * An example of object handled here are ExtensionAttachments.
+         * @param obj the Object to be handled.
+         */
+        protected void handleExternallyGeneratedObject(Object obj) {
+            if (areaStack.size() == 0 && obj instanceof ExtensionAttachment) {
+                ExtensionAttachment attachment = (ExtensionAttachment)obj;
+                if (this.currentPageViewport == null) {
+                    this.treeModel.handleOffDocumentItem(
+                            new OffDocumentExtensionAttachment(attachment));
+                } else {
+                    this.currentPageViewport.addExtensionAttachment(attachment);
+                }
+            } else {
+                log.warn("Don't know how to handle externally generated object: " + obj);
+            }
+        }
+
         /** @see org.xml.sax.helpers.DefaultHandler */
         public void endElement(String uri, String localName, String qName) throws SAXException {
             if (delegate != null) {
@@ -445,8 +485,11 @@ public class AreaTreeParser {
                 delegateStack.pop();
                 if (delegateStack.size() == 0) {
                     delegate.endDocument();
-                    delegate = null;
-                    domImplementation = null;
+                    if (delegate instanceof ContentHandlerFactory.ObjectSource) {
+                        Object obj = ((ContentHandlerFactory.ObjectSource)delegate).getObject();
+                        handleExternallyGeneratedObject(obj);
+                    }
+                    delegate = null; //Sub-document is processed, return to normal processing
                 }
             } else {
                 if ("".equals(uri)) {
index b516b34465beb4959aff34ba651cd9d7016a1620..fedc4284f2cf9632671b923efa53bcb5d6b7282b 100644 (file)
@@ -32,6 +32,7 @@ import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
 import org.apache.fop.fo.Constants;
+import org.apache.fop.fo.extensions.ExtensionAttachment;
 import org.apache.fop.fo.pagination.SimplePageMaster;
 
 /**
@@ -492,6 +493,17 @@ public class PageViewport implements Resolvable, Cloneable {
         return this.simplePageMasterName;
     }
     
+    /**
+     * Adds a new ExtensionAttachment instance to this page.
+     * @param attachment the ExtensionAttachment
+     */
+    public void addExtensionAttachment(ExtensionAttachment attachment) {
+        if (this.extensionAttachments == null) {
+            this.extensionAttachments = new java.util.ArrayList();
+        }
+        extensionAttachments.add(attachment);
+    }
+    
     /** @return the list of extension attachments for this page */
     public List getExtensionAttachments() {
         return this.extensionAttachments;
diff --git a/src/java/org/apache/fop/render/ps/extensions/PSExtensionHandler.java b/src/java/org/apache/fop/render/ps/extensions/PSExtensionHandler.java
new file mode 100644 (file)
index 0000000..e229512
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.render.ps.extensions;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.fop.area.AreaTreeParser;
+import org.apache.fop.util.ContentHandlerFactory;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * ContentHandler (parser) for restoring PSExtension objects from XML.
+ */
+public class PSExtensionHandler extends DefaultHandler 
+            implements ContentHandlerFactory.ObjectSource {
+
+    /** Logger instance */
+    protected static Log log = LogFactory.getLog(PSExtensionHandler.class);
+
+    private StringBuffer content = new StringBuffer();
+    private Attributes lastAttributes;
+    
+    private PSSetupCode returnedObject;
+    
+    /** @see org.xml.sax.helpers.DefaultHandler */
+    public void startElement(String uri, String localName, String qName, Attributes attributes) 
+                throws SAXException {
+        boolean handled = false;
+        if (PSSetupCode.CATEGORY.equals(uri)) {
+            lastAttributes = attributes;
+            handled = true; 
+            if ("ps-setup-code".equals(localName)) {
+                //handled in endElement
+            } else {
+                handled = false;
+            }
+        }
+        if (!handled) {
+            if (PSSetupCode.CATEGORY.equals(uri)) {
+                throw new SAXException("Unhandled element " + localName 
+                        + " in namespace: " + uri);
+            } else {
+                log.warn("Unhandled element " + localName 
+                        + " in namespace: " + uri);
+            }
+        }
+    }
+
+    /** @see org.xml.sax.helpers.DefaultHandler */
+    public void endElement(String uri, String localName, String qName) throws SAXException {
+        if (PSSetupCode.CATEGORY.equals(uri)) {
+            if ("ps-setup-code".equals(localName)) {
+                String name = lastAttributes.getValue("name");
+                this.returnedObject = new PSSetupCode(name, content.toString());
+            }
+        }    
+        content.setLength(0); //Reset text buffer (see characters())
+    }
+
+    /** @see org.xml.sax.helpers.DefaultHandler#characters(char[], int, int) */
+    public void characters(char[] ch, int start, int length) throws SAXException {
+        content.append(ch, start, length);
+    }
+
+    /** @see org.apache.fop.area.AreaTreeParser.ObjectSource#getObject() */
+    public Object getObject() {
+        return returnedObject;
+    }
+
+}
diff --git a/src/java/org/apache/fop/render/ps/extensions/PSExtensionHandlerFactory.java b/src/java/org/apache/fop/render/ps/extensions/PSExtensionHandlerFactory.java
new file mode 100644 (file)
index 0000000..e2b85ad
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.render.ps.extensions;
+
+import org.apache.fop.util.ContentHandlerFactory;
+import org.xml.sax.ContentHandler;
+
+/**
+ * Factory for the ContentHandler that handles serialized PSSetupCode instances.
+ */
+public class PSExtensionHandlerFactory implements ContentHandlerFactory {
+
+    private static final String[] NAMESPACES = new String[] {PSSetupCode.CATEGORY};
+    
+    /** @see org.apache.fop.util.ContentHandlerFactory#getSupportedNamespaces() */
+    public String[] getSupportedNamespaces() {
+        return NAMESPACES;
+    }
+
+    /** @see org.apache.fop.util.ContentHandlerFactory#createContentHandler() */
+    public ContentHandler createContentHandler() {
+        return new PSExtensionHandler();
+    }
+
+}
index 846946f99d54033529089e10fc28cc996d52126f..49cb22979c73f68e1b38acbb9f436471285cace4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2005 The Apache Software Foundation.
+ * Copyright 2005-2006 The Apache Software Foundation.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -21,11 +21,15 @@ package org.apache.fop.render.ps.extensions;
 import java.io.Serializable;
 
 import org.apache.fop.fo.extensions.ExtensionAttachment;
+import org.apache.fop.util.XMLizable;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
 
 /**
  * This is the pass-through value object for the PostScript extension.
  */
-public class PSSetupCode implements ExtensionAttachment, Serializable {
+public class PSSetupCode implements ExtensionAttachment, Serializable, XMLizable {
 
     /** The category URI for this extension attachment. */
     public static final String CATEGORY = "apache:fop:extensions:postscript";
@@ -81,9 +85,26 @@ public class PSSetupCode implements ExtensionAttachment, Serializable {
         return CATEGORY;
     }
     
-    
     /** @see java.lang.Object#toString() */
     public String toString() {
         return "PSSetupCode(name=" + getName() + ")";
     }
+
+    private static final String ATT_NAME = "name";
+    private static final String ELEMENT = "ps-setup-code";
+    
+    /** @see org.apache.fop.util.XMLizable#toSAX(org.xml.sax.ContentHandler) */
+    public void toSAX(ContentHandler handler) throws SAXException {
+        AttributesImpl atts = new AttributesImpl();
+        if (name != null && name.length() > 0) {
+            atts.addAttribute(null, ATT_NAME, ATT_NAME, "CDATA", name);
+        }
+        handler.startElement(CATEGORY, ELEMENT, ELEMENT, atts);
+        if (content != null && content.length() > 0) {
+            char[] chars = content.toCharArray();
+            handler.characters(chars, 0, chars.length);
+        }
+        handler.endElement(CATEGORY, ELEMENT, ELEMENT);
+    }
+    
 }
index 1b9afc5663508e0c5d125bcc0606ddfff8fabf86..982d138bc53e6d7bdd71dfe0b56afc161bf0ec96 100644 (file)
@@ -43,6 +43,7 @@ import org.apache.fop.render.PrintRenderer;
 import org.apache.fop.render.Renderer;
 import org.apache.fop.render.RendererContext;
 import org.apache.fop.render.XMLHandler;
+import org.apache.fop.util.XMLizable;
 import org.apache.fop.apps.FOUserAgent;
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.apps.MimeConstants;
@@ -56,6 +57,8 @@ import org.apache.fop.area.NormalFlow;
 import org.apache.fop.area.Footnote;
 import org.apache.fop.area.LineArea;
 import org.apache.fop.area.MainReference;
+import org.apache.fop.area.OffDocumentExtensionAttachment;
+import org.apache.fop.area.OffDocumentItem;
 import org.apache.fop.area.PageViewport;
 import org.apache.fop.area.RegionReference;
 import org.apache.fop.area.RegionViewport;
@@ -75,6 +78,7 @@ import org.apache.fop.area.inline.TextArea;
 import org.apache.fop.area.inline.SpaceArea;
 import org.apache.fop.area.inline.WordArea;
 import org.apache.fop.fo.Constants;
+import org.apache.fop.fo.extensions.ExtensionAttachment;
 import org.apache.fop.fonts.FontInfo;
 import org.apache.fop.fonts.FontSetup;
 import org.apache.fop.fonts.FontTriplet;
@@ -114,6 +118,9 @@ public class XMLRenderer extends PrintRenderer {
     
     /** The OutputStream to write the generated XML to. */
     protected OutputStream out;
+
+    /** A list of ExtensionAttachements received through processOffDocumentItem() */
+    protected List extensionAttachments;
     
     /**
      * Creates a new XML renderer.
@@ -357,6 +364,28 @@ public class XMLRenderer extends PrintRenderer {
                   + (int) rect.getWidth() + " " + (int) rect.getHeight();
     }
 
+    private void handleDocumentExtensionAttachments() {
+        if (extensionAttachments != null && extensionAttachments.size() > 0) {
+            handleExtensionAttachments(extensionAttachments);
+            extensionAttachments.clear();
+        }
+    }
+    
+    /** @see org.apache.fop.render.AbstractRenderer#processOffDocumentItem() */
+    public void processOffDocumentItem(OffDocumentItem oDI) {
+        if (oDI instanceof OffDocumentExtensionAttachment) {
+            ExtensionAttachment attachment = ((OffDocumentExtensionAttachment)oDI).getAttachment();
+            if (extensionAttachments == null) {
+                extensionAttachments = new java.util.ArrayList();
+            }
+            extensionAttachments.add(attachment);
+        } else {
+            String warn = "Ignoring OffDocumentItem: " + oDI;
+            comment("WARNING: " + warn);
+            log.warn(warn);
+        }
+    }
+
     /**
      * @see org.apache.fop.render.Renderer#startRenderer(OutputStream)
      */
@@ -416,15 +445,45 @@ public class XMLRenderer extends PrintRenderer {
         addAttribute("nr", page.getPageNumberString());
         startElement("pageViewport", atts);
         startElement("page");
+        
+        handlePageExtensionAttachments(page);
         super.renderPage(page);
+        
         endElement("page");
         endElement("pageViewport");
     }
 
+    private void handleExtensionAttachments(List attachments) {
+        if (attachments != null && attachments.size() > 0) {
+            startElement("extension-attachments");
+            Iterator i = attachments.iterator();
+            while (i.hasNext()) {
+                ExtensionAttachment attachment = (ExtensionAttachment)i.next();
+                if (attachment instanceof XMLizable) {
+                    try {
+                        ((XMLizable)attachment).toSAX(this.handler);
+                    } catch (SAXException e) {
+                        log.error("Error while serializing Extension Attachment", e);
+                    }
+                } else {
+                    String warn = "Ignoring non-XMLizable ExtensionAttachment: " + attachment;
+                    comment("WARNING: " + warn);
+                    log.warn(warn);
+                }
+            }
+            endElement("extension-attachments");
+        }
+    }
+    
+    private void handlePageExtensionAttachments(PageViewport page) {
+        handleExtensionAttachments(page.getExtensionAttachments());
+    }
+    
     /**
      * @see org.apache.fop.render.Renderer#startPageSequence(LineArea)
      */
     public void startPageSequence(LineArea seqTitle) {
+        handleDocumentExtensionAttachments();
         if (startedSequence) {
             endElement("pageSequence");
         }
diff --git a/src/java/org/apache/fop/util/ContentHandlerFactory.java b/src/java/org/apache/fop/util/ContentHandlerFactory.java
new file mode 100644 (file)
index 0000000..da615cc
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.util;
+
+import org.xml.sax.ContentHandler;
+
+/**
+ * Factory interface implemented by classes that can instantiate ContentHandler subclasses which
+ * parse a SAX stream into Java objects.
+ */
+public interface ContentHandlerFactory {
+
+    /**
+     * @return an array of supported namespaces.
+     */
+    String[] getSupportedNamespaces();
+    
+    /**
+     * @return a new ContentHandler to handle a SAX stream
+     */
+    ContentHandler createContentHandler();
+    
+    /**
+     * Interface that ContentHandler implementations that parse Java objects from XML can implement
+     * to return these objects.
+     */
+    public interface ObjectSource {
+        
+        /**
+         * @return the object parsed from the SAX stream (call valid after parsing)
+         */
+        Object getObject();
+        
+    }
+    
+}
diff --git a/src/java/org/apache/fop/util/ContentHandlerFactoryRegistry.java b/src/java/org/apache/fop/util/ContentHandlerFactoryRegistry.java
new file mode 100644 (file)
index 0000000..68e215b
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.util;
+
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.util.Service;
+
+/**
+ * This class holds references to various XML handlers used by FOP. It also
+ * supports automatic discovery of additional XML handlers available through
+ * the class path.
+ */
+public class ContentHandlerFactoryRegistry {
+
+    /** the logger */
+    private static Log log = LogFactory.getLog(ContentHandlerFactoryRegistry.class);
+    
+    private static ContentHandlerFactoryRegistry instance;
+    
+    /** Map from namespace URIs to ContentHandlerFactories */
+    private Map factories = new java.util.HashMap();
+    
+    /**
+     * @return a singleton instance of the ContentHandlerFactoryRegistry.
+     */
+    public static ContentHandlerFactoryRegistry getInstance() {
+        if (instance == null) {
+            instance = new ContentHandlerFactoryRegistry();
+        }
+        return instance;
+    }
+    
+    /**
+     * Default constructor.
+     */
+    public ContentHandlerFactoryRegistry() {
+        discover();
+    }
+    
+    /**
+     * Add an XML handler. The handler itself is inspected to find out what it supports.
+     * @param classname the fully qualified class name
+     */
+    public void addContentHandlerFactory(String classname) {
+        try {
+            ContentHandlerFactory factory 
+                = (ContentHandlerFactory)Class.forName(classname).newInstance();
+            addContentHandlerFactory(factory);
+        } catch (ClassNotFoundException e) {
+            throw new IllegalArgumentException("Could not find "
+                                               + classname);
+        } catch (InstantiationException e) {
+            throw new IllegalArgumentException("Could not instantiate "
+                                               + classname);
+        } catch (IllegalAccessException e) {
+            throw new IllegalArgumentException("Could not access "
+                                               + classname);
+        } catch (ClassCastException e) {
+            throw new IllegalArgumentException(classname
+                                               + " is not an " 
+                                               + ContentHandlerFactory.class.getName());
+        }
+    }
+    
+    /**
+     * Add an ContentHandlerFactory. The instance is inspected to find out what it supports.
+     * @param factory the ContentHandlerFactory instance
+     */
+    public void addContentHandlerFactory(ContentHandlerFactory factory) {
+        String[] ns = factory.getSupportedNamespaces();
+        for (int i = 0; i < ns.length; i++) {
+            factories.put(ns[i], factory);
+        }
+    }
+    
+    /**
+     * Retrieves a ContentHandlerFactory instance of a given namespace URI.
+     * @param namespaceURI the namespace to be handled.
+     * @return the ContentHandlerFactory or null, if no suitable instance is available.
+     */
+    public ContentHandlerFactory getFactory(String namespaceURI) {
+        ContentHandlerFactory factory = (ContentHandlerFactory)factories.get(namespaceURI);
+        return factory;
+    }
+    
+    /**
+     * Discovers ContentHandlerFactory implementations through the classpath and dynamically
+     * registers them.
+     */
+    private void discover() {
+        // add mappings from available services
+        Iterator providers = Service.providers(ContentHandlerFactory.class);
+        if (providers != null) {
+            while (providers.hasNext()) {
+                String str = (String)providers.next();
+                try {
+                    if (log.isDebugEnabled()) {
+                        log.debug("Dynamically adding ContentHandlerFactory: " + str);
+                    }
+                    addContentHandlerFactory(str);
+                } catch (IllegalArgumentException e) {
+                    log.error("Error while adding ContentHandlerFactory", e);
+                }
+
+            }
+        }
+    }
+}
diff --git a/src/java/org/apache/fop/util/XMLizable.java b/src/java/org/apache/fop/util/XMLizable.java
new file mode 100644 (file)
index 0000000..ea02963
--- /dev/null
@@ -0,0 +1,43 @@
+/* 
+ * Copyright 2002-2004 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.util;
+
+/* 
+ * Copied from Apache Excalibur:
+ * https://svn.apache.org/repos/asf/excalibur/trunk/components/xmlutil/
+ *                                           src/java/org/apache/excalibur/xml/sax/XMLizable.java
+ */
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+
+/**
+ * This interface can be implemented by classes willing to provide an XML representation
+ * of their current state as SAX events.
+ */
+public interface XMLizable {
+    
+    /**
+     * Generates SAX events representing the object's state.
+     * @param handler ContentHandler instance to send the SAX events to
+     * @throws SAXException if there's a problem generating the SAX events
+     */
+    void toSAX(ContentHandler handler) throws SAXException;
+    
+}
diff --git a/test/layoutengine/standard-testcases/ps-extension_1.xml b/test/layoutengine/standard-testcases/ps-extension_1.xml
new file mode 100644 (file)
index 0000000..beadbef
--- /dev/null
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Copyright 2006 The Apache Software Foundation
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<!-- $Id$ -->
+<testcase>
+  <info>
+    <p>
+      This test checks the PostScript extension for custom setup code. The extension attachments need to show
+      up in the area tree XML so the AreaTreeParser can fully restore the area tree.
+    </p>
+  </info>
+  <fo>
+    <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:ps="http://xmlgraphics.apache.org/fop/postscript">
+      <fo:layout-master-set>
+        <fo:simple-page-master master-name="normal" page-width="5in" page-height="5in">
+          <fo:region-body/>
+          <ps:ps-page-setup-code name="media-dict">%FOPTestPSPageSetupCode: MediaDict!</ps:ps-page-setup-code>
+          <ps:ps-page-setup-code name="bla">%FOPTestPSPageSetupCode: Blah blah!</ps:ps-page-setup-code>
+        </fo:simple-page-master>
+      </fo:layout-master-set>
+      <fo:declarations>
+        <ps:ps-setup-code>%FOPTestPSSetupCode: General setup code here!</ps:ps-setup-code>
+        <ps:ps-setup-code name="multi-line">
+%FOPTestPSSetupCode: Line 1
+%FOPTestPSSetupCode: Line 2
+        </ps:ps-setup-code>
+      </fo:declarations>
+      <fo:page-sequence master-reference="normal">
+        <fo:flow flow-name="xsl-region-body">
+          <fo:block>Text on page <fo:page-number/>.</fo:block>
+          <fo:block break-before="page">Text on page <fo:page-number/>.</fo:block>
+        </fo:flow>
+      </fo:page-sequence>
+    </fo:root>
+  </fo>
+  <checks>
+    <eval expected="2" xpath="count(/areaTree/extension-attachments/child::*)"/>
+    <eval expected="%FOPTestPSSetupCode: General setup code here!" xpath="/areaTree/extension-attachments/child::*[1]"/>
+    <true xpath="contains(/areaTree/extension-attachments/child::*[2], '%FOPTestPSSetupCode: Line 1')"/>
+    <eval expected="multi-line" xpath="/areaTree/extension-attachments/child::*[2]/@name"/>
+
+    <eval expected="2" xpath="count(/areaTree/pageSequence/pageViewport[@nr=1]/page/extension-attachments/child::*)"/>
+    <eval expected="media-dict" xpath="/areaTree/pageSequence/pageViewport[@nr=1]/page/extension-attachments/child::*[1]/@name"/>
+    <eval expected="%FOPTestPSPageSetupCode: MediaDict!" xpath="/areaTree/pageSequence/pageViewport[@nr=1]/page/extension-attachments/child::*[1]"/>
+    <eval expected="bla" xpath="/areaTree/pageSequence/pageViewport[@nr=1]/page/extension-attachments/child::*[2]/@name"/>
+    <eval expected="%FOPTestPSPageSetupCode: Blah blah!" xpath="/areaTree/pageSequence/pageViewport[@nr=1]/page/extension-attachments/child::*[2]"/>
+
+    <eval expected="2" xpath="count(/areaTree/pageSequence/pageViewport[@nr=2]/page/extension-attachments/child::*)"/>
+    <eval expected="media-dict" xpath="/areaTree/pageSequence/pageViewport[@nr=2]/page/extension-attachments/child::*[1]/@name"/>
+    <eval expected="%FOPTestPSPageSetupCode: MediaDict!" xpath="/areaTree/pageSequence/pageViewport[@nr=2]/page/extension-attachments/child::*[1]"/>
+    <eval expected="bla" xpath="/areaTree/pageSequence/pageViewport[@nr=2]/page/extension-attachments/child::*[2]/@name"/>
+    <eval expected="%FOPTestPSPageSetupCode: Blah blah!" xpath="/areaTree/pageSequence/pageViewport[@nr=2]/page/extension-attachments/child::*[2]"/>
+  </checks>
+</testcase>