]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
Initial support for XMP metadata (PDF 1.4) under fo:declarations. Both xmpmeta and...
authorJeremias Maerki <jeremias@apache.org>
Fri, 17 Feb 2006 10:57:15 +0000 (10:57 +0000)
committerJeremias Maerki <jeremias@apache.org>
Fri, 17 Feb 2006 10:57:15 +0000 (10:57 +0000)
Extracted DOM2SAX functionality from XMLXMLHandler into utility class since it is now reused elsewhere.
New DOMBuilderContentHandlerFactory is used to create ContentHandlers that build generic DOMs.
New DelegatingContentHandler is a convenience base class modelled after XMLFilterImpl but as passive SAX receiver. It is used by DOMBuilderContentHandlerFactory.
Refactored FOTreeBuilder. FO tree building is now in a special ContentHandler which can be replaced temporarily when handling foreign XML like SVG or XMP. Extension Elements wanting the set their own ContentHandlers (instead of using the standard FO tree building mechanism) return a non-null value in getContentHandlerFactory(). The old mechanism is still supported (MathML, Plan and Barcode4J still use that). However, SVG support is changed to use a ContentHandlerFactory.
Extension elements for xmpmeta and RDF elements making use of the new DOM build-up using ContentHandlerFactory.
XMP metadata is passed to the renderer as ExtensionAttachment to the document. Only PDFRenderer uses the XMP extension attachment at this time.
The PDFRenderer automatically builds XMP metadata based on the basic metadata information in the PDFInfo object if no explicit XMP metadata has been added to the XSL-FO document.
XMP metadata merging is not implemented because this would involve a more sophisticated XMP infrastructure. That also means that XMP metadata is not validated. It's passed into the PDF as is.
The PDF library now provides the PDFMetadata class to embed XMP metadata in PDFs version >=1.4. stream contents use a special filter list which is initially empty, so non-PDF-aware XMP readers can extract (and optionally modify) the XMP metadata.

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

26 files changed:
src/java/org/apache/fop/fo/ElementMapping.java
src/java/org/apache/fop/fo/ElementMappingRegistry.java
src/java/org/apache/fop/fo/FONode.java
src/java/org/apache/fop/fo/FOTreeBuilder.java
src/java/org/apache/fop/fo/XMLObj.java
src/java/org/apache/fop/fo/extensions/svg/SVGElement.java
src/java/org/apache/fop/fo/extensions/xmp/RDFElement.java [new file with mode: 0644]
src/java/org/apache/fop/fo/extensions/xmp/RDFElementMapping.java [new file with mode: 0644]
src/java/org/apache/fop/fo/extensions/xmp/XMPConstants.java [new file with mode: 0644]
src/java/org/apache/fop/fo/extensions/xmp/XMPElementMapping.java [new file with mode: 0644]
src/java/org/apache/fop/fo/extensions/xmp/XMPMetaElement.java [new file with mode: 0644]
src/java/org/apache/fop/fo/extensions/xmp/XMPMetadata.java [new file with mode: 0644]
src/java/org/apache/fop/pdf/PDFDocument.java
src/java/org/apache/fop/pdf/PDFFactory.java
src/java/org/apache/fop/pdf/PDFFilterList.java
src/java/org/apache/fop/pdf/PDFInfo.java
src/java/org/apache/fop/pdf/PDFMetadata.java [new file with mode: 0644]
src/java/org/apache/fop/pdf/PDFRoot.java
src/java/org/apache/fop/render/pdf/PDFRenderer.java
src/java/org/apache/fop/render/ps/extensions/PSExtensionHandler.java
src/java/org/apache/fop/render/xml/XMLXMLHandler.java
src/java/org/apache/fop/util/ContentHandlerFactory.java
src/java/org/apache/fop/util/DOM2SAX.java [new file with mode: 0644]
src/java/org/apache/fop/util/DOMBuilderContentHandlerFactory.java [new file with mode: 0644]
src/java/org/apache/fop/util/DelegatingContentHandler.java [new file with mode: 0644]
status.xml

index 092a411f030bd2dd3fa4e073ed54db340e790601..1142891b05f01b5969d776e50b0cbeb821825efe 100644 (file)
@@ -76,7 +76,7 @@ public abstract class ElementMapping {
     /**
      * @return the default DOMImplementation when no specialized DOM is necessary.
      */
-    protected DOMImplementation getDefaultDOMImplementation() {
+    public static DOMImplementation getDefaultDOMImplementation() {
         DocumentBuilderFactory fact = DocumentBuilderFactory.newInstance();
         fact.setNamespaceAware(true);
         fact.setValidating(false);
index 35224e7ae525c7bf5c50f16879143b35f03c25f9..ee8c7b86ecd16b212fd521531228d376727e2568 100644 (file)
@@ -78,6 +78,8 @@ public class ElementMappingRegistry {
         addElementMapping("org.apache.fop.fo.extensions.svg.SVGElementMapping");
         addElementMapping("org.apache.fop.fo.extensions.svg.BatikExtensionElementMapping");
         addElementMapping("org.apache.fop.fo.extensions.ExtensionElementMapping");
+        addElementMapping("org.apache.fop.fo.extensions.xmp.XMPElementMapping");
+        addElementMapping("org.apache.fop.fo.extensions.xmp.RDFElementMapping");
         addElementMapping("org.apache.fop.render.ps.extensions.PSExtensionElementMapping");
 
         // add mappings from available services
index 84d04b2c041f65ec400331e9f143bd2c831948c1..7663cb49b05f4d2d171efb2a0b23b84395c93f1b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 1999-2005 The Apache Software Foundation.
+ * Copyright 1999-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.
@@ -35,6 +35,7 @@ import org.apache.fop.fo.extensions.ExtensionElementMapping;
 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;
 
 /**
  * Base class for nodes in the XML tree
@@ -568,5 +569,15 @@ public abstract class FONode implements Cloneable {
         return null;
     }
     
+    /**
+     * This method is overridden by extension elements and allows the extension element to return
+     * a ContentHandlerFactory. This factory can create ContentHandler implementations that handle
+     * foreign XML content by either building up a specific DOM, a Java object or something else.
+     * @return the ContentHandlerFactory or null if not applicable
+     */
+    public ContentHandlerFactory getContentHandlerFactory() {
+        return null;
+    }
+    
 }
 
index f674d58c842be360a5197a7e922cb8305938bde7..d5d03b7b763afe3b73adae0afed5b32bad12f65b 100644 (file)
@@ -29,7 +29,11 @@ import org.apache.fop.area.AreaTreeHandler;
 import org.apache.fop.fo.ElementMapping.Maker;
 import org.apache.fop.fo.pagination.Root;
 import org.apache.fop.image.ImageFactory;
+import org.apache.fop.util.ContentHandlerFactory;
+import org.apache.fop.util.ContentHandlerFactory.ObjectSource;
+import org.apache.fop.util.ContentHandlerFactory.ObjectBuiltListener;
 import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
 import org.xml.sax.Locator;
 import org.xml.sax.SAXException;
 import org.xml.sax.SAXParseException;
@@ -56,16 +60,12 @@ public class FOTreeBuilder extends DefaultHandler {
      */
     protected Root rootFObj = null;
 
-    /**
-     * Current formatting object being handled
-     */
-    protected FONode currentFObj = null;
-
-    /**
-     * Current propertyList for the node being handled.
-     */
-    protected PropertyList currentPropertyList;
-
+    /** Main DefaultHandler that handles the FO namespace. */
+    protected MainFOHandler mainFOHandler;
+    
+    /** Current delegate ContentHandler to receive the SAX events */
+    protected ContentHandler delegate;
+    
     /**
      * The class that handles formatting and rendering to a stream
      * (mark-fop@inomial.com)
@@ -80,6 +80,8 @@ public class FOTreeBuilder extends DefaultHandler {
     
     private boolean used = false;
     
+    private int depth;
+    
     /**
      * FOTreeBuilder constructor
      * @param outputFormat the MIME type of the output format to use (ex. "application/pdf").
@@ -135,11 +137,8 @@ public class FOTreeBuilder extends DefaultHandler {
      * @see org.xml.sax.ContentHandler#characters(char[], int, int)
      */
     public void characters(char[] data, int start, int length) 
-        throws FOPException {
-            if (currentFObj != null) {
-                currentFObj.addCharacters(data, start, start + length, 
-                        currentPropertyList, getEffectiveLocator());
-            }
+                throws SAXException {
+        delegate.characters(data, start, length);
     }
 
     /**
@@ -157,6 +156,9 @@ public class FOTreeBuilder extends DefaultHandler {
             log.debug("Building formatting object tree");
         }
         foEventHandler.startDocument();
+        this.mainFOHandler = new MainFOHandler(); 
+        this.mainFOHandler.startDocument();
+        this.delegate = this.mainFOHandler;
     }
 
     /**
@@ -164,8 +166,8 @@ public class FOTreeBuilder extends DefaultHandler {
      * @see org.xml.sax.ContentHandler#endDocument()
      */
     public void endDocument() throws SAXException {
+        this.delegate.endDocument();
         rootFObj = null;
-        currentFObj = null;
         if (log.isDebugEnabled()) {
             log.debug("Parsing of document complete");
         }
@@ -181,55 +183,8 @@ public class FOTreeBuilder extends DefaultHandler {
      */
     public void startElement(String namespaceURI, String localName, String rawName,
                              Attributes attlist) throws SAXException {
-
-        /* the node found in the FO document */
-        FONode foNode;
-        PropertyList propertyList;
-
-        // Check to ensure first node encountered is an fo:root
-        if (rootFObj == null) {
-            if (!namespaceURI.equals(FOElementMapping.URI) 
-                || !localName.equals("root")) {
-                throw new SAXException(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."));
-            }
-        } else { // check that incoming node is valid for currentFObj
-            if (namespaceURI.equals(FOElementMapping.URI)) {
-                // currently no fox: elements to validate
-                // || namespaceURI.equals(ExtensionElementMapping.URI) */) {
-                try {
-                    currentFObj.validateChildNode(locator, namespaceURI, localName);
-                } catch (ValidationException e) {
-                    throw e;
-                }
-            }
-        }
-        
-        ElementMapping.Maker fobjMaker = findFOMaker(namespaceURI, localName);
-//      log.debug("found a " + fobjMaker.toString());
-
-        try {
-            foNode = fobjMaker.make(currentFObj);
-            propertyList = foNode.createPropertyList(currentPropertyList, foEventHandler);
-            foNode.processNode(localName, getEffectiveLocator(), attlist, propertyList);
-            foNode.startOfNode();
-        } catch (IllegalArgumentException e) {
-            throw new SAXException(e);
-        }
-
-        if (rootFObj == null) {
-            rootFObj = (Root) foNode;
-            rootFObj.setFOEventHandler(foEventHandler);
-        } else {
-            currentFObj.addChildNode(foNode);
-        }
-
-        currentFObj = foNode;
-        if (propertyList != null) {
-            currentPropertyList = propertyList;
-        }
+        this.depth++;
+        delegate.startElement(namespaceURI, localName, rawName, attlist);
     }
 
     /**
@@ -237,25 +192,17 @@ public class FOTreeBuilder extends DefaultHandler {
      * @see org.xml.sax.ContentHandler#endElement(String, String, String)
      */
     public void endElement(String uri, String localName, String rawName)
-                throws FOPException {
-        if (currentFObj == null) {
-            throw new IllegalStateException(
-                    "endElement() called for " + rawName + " where there is no current element.");
-        } else if (!currentFObj.getLocalName().equals(localName) 
-                || !currentFObj.getNamespaceURI().equals(uri)) {
-            log.warn("Mismatch: " + currentFObj.getLocalName() 
-                    + " (" + currentFObj.getNamespaceURI() 
-                    + ") vs. " + localName + " (" + uri + ")");
-        }
-        currentFObj.endOfNode();
-
-        if (currentPropertyList.getFObj() == currentFObj) {
-            currentPropertyList = currentPropertyList.getParentPropertyList();
-        }
-        if (currentFObj.getParent() == null) {
-            log.debug("endElement for top-level " + currentFObj.getName());
+                throws SAXException {
+        this.delegate.endElement(uri, localName, rawName);
+        this.depth--;
+        if (depth == 0) {
+            if (delegate != mainFOHandler) {
+                //Return from sub-handler back to main handler
+                delegate.endDocument();
+                delegate = mainFOHandler;
+                delegate.endElement(uri, localName, rawName);
+            }
         }
-        currentFObj = currentFObj.getParent();
     }
 
     /**
@@ -309,5 +256,137 @@ public class FOTreeBuilder extends DefaultHandler {
         }
     }
     
+    /**
+     * Main DefaultHandler implementation which builds the FO tree.
+     */
+    private class MainFOHandler extends DefaultHandler {
+        
+        /**
+         * Current formatting object being handled
+         */
+        protected FONode currentFObj = null;
+
+        /**
+         * Current propertyList for the node being handled.
+         */
+        protected PropertyList currentPropertyList;
+
+        /**
+         * SAX Handler for the start of an element
+         * @see org.xml.sax.ContentHandler#startElement(String, String, String, Attributes)
+         */
+        public void startElement(String namespaceURI, String localName, String rawName,
+                                 Attributes attlist) throws SAXException {
+
+            /* the node found in the FO document */
+            FONode foNode;
+            PropertyList propertyList;
+
+            // Check to ensure first node encountered is an fo:root
+            if (rootFObj == null) {
+                if (!namespaceURI.equals(FOElementMapping.URI) 
+                    || !localName.equals("root")) {
+                    throw new SAXException(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."));
+                }
+            } else { // check that incoming node is valid for currentFObj
+                if (namespaceURI.equals(FOElementMapping.URI)) {
+                    // currently no fox: elements to validate
+                    // || namespaceURI.equals(ExtensionElementMapping.URI) */) {
+                    try {
+                        currentFObj.validateChildNode(locator, namespaceURI, localName);
+                    } catch (ValidationException e) {
+                        throw e;
+                    }
+                }
+            }
+            
+            ElementMapping.Maker fobjMaker = findFOMaker(namespaceURI, localName);
+
+            try {
+                foNode = fobjMaker.make(currentFObj);
+                propertyList = foNode.createPropertyList(currentPropertyList, foEventHandler);
+                foNode.processNode(localName, getEffectiveLocator(), attlist, propertyList);
+                foNode.startOfNode();
+            } catch (IllegalArgumentException e) {
+                throw new SAXException(e);
+            }
+
+            ContentHandlerFactory chFactory = foNode.getContentHandlerFactory();
+            if (chFactory != null) {
+                ContentHandler subHandler = chFactory.createContentHandler();
+                if (subHandler instanceof ObjectSource 
+                        && foNode instanceof ObjectBuiltListener) {
+                    ((ObjectSource)subHandler).setObjectBuiltListener((ObjectBuiltListener)foNode);
+                }
+                
+                subHandler.startDocument();
+                subHandler.startElement(namespaceURI, localName, rawName, attlist);
+                depth = 1;
+                delegate = subHandler;
+            }
+            
+            if (rootFObj == null) {
+                rootFObj = (Root) foNode;
+                rootFObj.setFOEventHandler(foEventHandler);
+            } else {
+                currentFObj.addChildNode(foNode);
+            }
+
+            currentFObj = foNode;
+            if (propertyList != null) {
+                currentPropertyList = propertyList;
+            }
+        }
+
+        /**
+         * SAX Handler for the end of an element
+         * @see org.xml.sax.ContentHandler#endElement(String, String, String)
+         */
+        public void endElement(String uri, String localName, String rawName)
+                    throws SAXException {
+            if (currentFObj == null) {
+                throw new IllegalStateException(
+                        "endElement() called for " + rawName 
+                            + " where there is no current element.");
+            } else if (!currentFObj.getLocalName().equals(localName) 
+                    || !currentFObj.getNamespaceURI().equals(uri)) {
+                log.warn("Mismatch: " + currentFObj.getLocalName() 
+                        + " (" + currentFObj.getNamespaceURI() 
+                        + ") vs. " + localName + " (" + uri + ")");
+            }
+            currentFObj.endOfNode();
+
+            if (currentPropertyList.getFObj() == currentFObj) {
+                currentPropertyList = currentPropertyList.getParentPropertyList();
+            }
+            if (currentFObj.getParent() == null) {
+                log.debug("endElement for top-level " + currentFObj.getName());
+            }
+            currentFObj = currentFObj.getParent();
+        }
+
+        /**
+         * SAX Handler for characters
+         * @see org.xml.sax.ContentHandler#characters(char[], int, int)
+         */
+        public void characters(char[] data, int start, int length) 
+            throws FOPException {
+                if (currentFObj != null) {
+                    currentFObj.addCharacters(data, start, start + length, 
+                            currentPropertyList, getEffectiveLocator());
+                }
+        }
+
+        public void endDocument() throws SAXException {
+            currentFObj = null;
+        }
+
+        
+        
+    }
+    
 }
 
index 55d8c2988f32a40b25ecc74ccbe742d09fc149d2..87cb004c33ad844db69210251ddb3ce479d1bc9d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 1999-2005 The Apache Software Foundation.
+ * Copyright 1999-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.
@@ -26,6 +26,7 @@ import javax.xml.parsers.DocumentBuilderFactory;
 
 import org.apache.batik.dom.util.XMLSupport;
 import org.apache.fop.apps.FOPException;
+import org.apache.fop.util.ContentHandlerFactory.ObjectBuiltListener;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.xml.sax.Attributes;
@@ -35,7 +36,7 @@ import org.xml.sax.Locator;
  * Abstract class modelling generic, non-XSL-FO XML objects. Such objects are
  * stored in a DOM.
  */
-public abstract class XMLObj extends FONode {
+public abstract class XMLObj extends FONode implements ObjectBuiltListener {
 
     // temp reference for attributes
     private Attributes attr = null;
@@ -112,6 +113,12 @@ public abstract class XMLObj extends FONode {
         this.doc = doc;
         element = doc.createElementNS(getNamespaceURI(), name);
 
+        setAttributes(element, attr);
+        attr = null;
+        parent.appendChild(element);
+    }
+
+    private static void setAttributes(Element element, Attributes attr) {
         for (int count = 0; count < attr.getLength(); count++) {
             String rf = attr.getValue(count);
             String qname = attr.getQName(count);
@@ -128,10 +135,8 @@ public abstract class XMLObj extends FONode {
                 }
             }
         }
-        attr = null;
-        parent.appendChild(element);
     }
-
+    
     /**
      * Add the top-level element to the DOM document
      * @param doc DOM document
@@ -139,22 +144,7 @@ public abstract class XMLObj extends FONode {
      */
     public void buildTopLevel(Document doc, Element svgRoot) {
         // build up the info for the top level element
-        for (int count = 0; count < attr.getLength(); count++) {
-            String rf = attr.getValue(count);
-            String qname = attr.getQName(count);
-            int idx = qname.indexOf(":");
-            if (idx == -1) {
-                element.setAttribute(qname, rf);
-            } else {
-                String pref = qname.substring(0, idx);
-                String tail = qname.substring(idx + 1);
-                if (pref.equals("xmlns")) {
-                    ns.put(tail, rf);
-                } else {
-                    element.setAttributeNS((String)ns.get(pref), tail, rf);
-                }
-            }
-        }
+        setAttributes(element, attr);
     }
 
     /**
@@ -180,6 +170,7 @@ public abstract class XMLObj extends FONode {
             }
             
         } catch (Exception e) {
+            //TODO this is ugly because there may be subsequent failures like NPEs
             log.error("Error while trying to instantiate a DOM Document", e);
         }
         return doc;
@@ -214,5 +205,10 @@ public abstract class XMLObj extends FONode {
         element.appendChild(text);
     }
 
+    /** @see org.apache.fop.util.ContentHandlerFactory.ObjectBuiltListener */
+    public void notifyObjectBuilt(Object obj) {
+        this.doc = (Document)obj;
+    }
+    
 }
 
index c5d15fde720339b0824a0c2b6a4d7a1f13516a3f..47cc663405a7b8de5484b09eb7179a3ce098803b 100644 (file)
@@ -22,6 +22,8 @@ package org.apache.fop.fo.extensions.svg;
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.PropertyList;
+import org.apache.fop.util.ContentHandlerFactory;
+import org.apache.fop.util.DOMBuilderContentHandlerFactory;
 
 import org.apache.batik.dom.svg.SVGOMDocument;
 import org.apache.batik.dom.svg.SVGOMElement;
@@ -58,6 +60,14 @@ public class SVGElement extends SVGObj {
         super(parent);
     }
 
+    /**
+     * @see org.apache.fop.fo.FONode#getContentHandlerFactory()
+     */
+    public ContentHandlerFactory getContentHandlerFactory() {
+        return new DOMBuilderContentHandlerFactory(getNamespaceURI(), 
+                SVGDOMImplementation.getDOMImplementation());
+    }
+    
     /**
      * @see org.apache.fop.fo.FONode#processNode
      */
@@ -287,5 +297,6 @@ public class SVGElement extends SVGObj {
             return 100;
         }
     }
+
 }
 
diff --git a/src/java/org/apache/fop/fo/extensions/xmp/RDFElement.java b/src/java/org/apache/fop/fo/extensions/xmp/RDFElement.java
new file mode 100644 (file)
index 0000000..ea8e48b
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * 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.fo.extensions.xmp;
+
+import org.apache.fop.fo.ElementMapping;
+import org.apache.fop.fo.FONode;
+import org.apache.fop.fo.FObj;
+import org.apache.fop.fo.XMLObj;
+import org.apache.fop.fo.extensions.ExtensionAttachment;
+import org.apache.fop.util.ContentHandlerFactory;
+import org.apache.fop.util.DOMBuilderContentHandlerFactory;
+
+/**
+ * Represents the top-level "RDF" element used by XMP metadata.
+ */
+public class RDFElement extends XMLObj {
+
+    private XMPMetadata attachment;
+    
+    /**
+     * Main constructor.
+     * @param parent the parent formatting object
+     */
+    public RDFElement(FONode parent) {
+        super(parent);
+    }
+    
+    /** @see org.apache.fop.fo.FONode#getNormalNamespacePrefix() */
+    public String getNormalNamespacePrefix() {
+        return "rdf";
+    }
+
+    /** @see org.apache.fop.fo.FONode#getNamespaceURI() */
+    public String getNamespaceURI() {
+        return XMPConstants.RDF_NAMESPACE;
+    }
+
+    /**
+     * @see org.apache.fop.fo.FONode#getContentHandlerFactory()
+     */
+    public ContentHandlerFactory getContentHandlerFactory() {
+        return new DOMBuilderContentHandlerFactory(getNamespaceURI(), 
+                ElementMapping.getDefaultDOMImplementation());
+    }
+    
+    /** @see org.apache.fop.fo.FONode#getExtensionAttachment() */
+    public ExtensionAttachment getExtensionAttachment() {
+        if (parent instanceof FObj) {
+            if (attachment == null) {
+                attachment = new XMPMetadata(doc);
+            }
+            return attachment;
+        } else {
+            return super.getExtensionAttachment();
+        }
+    }
+
+    /**
+     * @see org.apache.fop.fo.XMLObj#notifyObjectBuilt(java.lang.Object)
+     */
+    public void notifyObjectBuilt(Object obj) {
+        super.notifyObjectBuilt(obj);
+        attachment.setDocument(doc);
+    }
+
+}
diff --git a/src/java/org/apache/fop/fo/extensions/xmp/RDFElementMapping.java b/src/java/org/apache/fop/fo/extensions/xmp/RDFElementMapping.java
new file mode 100644 (file)
index 0000000..566a6c6
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * 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.fo.extensions.xmp;
+
+import java.util.HashMap;
+
+import org.apache.fop.fo.FONode;
+import org.apache.fop.fo.ElementMapping;
+
+import org.w3c.dom.DOMImplementation;
+
+/**
+ * Setup the element mapping for XMP metadata.
+ */
+public class RDFElementMapping extends ElementMapping {
+    
+    /** Main constructor. */
+    public RDFElementMapping() {
+        namespaceURI = XMPConstants.RDF_NAMESPACE;
+    }
+
+    /** @see org.apache.fop.fo.ElementMapping#getDOMImplementation() */
+    public DOMImplementation getDOMImplementation() {
+        return getDefaultDOMImplementation();
+    }
+
+    /** @see org.apache.fop.fo.ElementMapping#initialize() */
+    protected void initialize() {
+        if (foObjs == null) {
+            foObjs = new HashMap();
+            foObjs.put("RDF", new RDFElementMaker());
+        }
+    }
+
+    static class RDFElementMaker extends ElementMapping.Maker {
+        public FONode make(FONode parent) {
+            return new RDFElement(parent);
+        }
+    }
+
+}
diff --git a/src/java/org/apache/fop/fo/extensions/xmp/XMPConstants.java b/src/java/org/apache/fop/fo/extensions/xmp/XMPConstants.java
new file mode 100644 (file)
index 0000000..b8b2998
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * 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.fo.extensions.xmp;
+
+/**
+ * Constants used in XMP metadata.
+ */
+public interface XMPConstants {
+
+    /** Namespace URI for XMP */
+    String XMP_NAMESPACE = "adobe:ns:meta/";
+    
+    /** Namespace URI for RDF */
+    String RDF_NAMESPACE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+
+    /** Namespace URI for Dublin Core */ 
+    String DUBLIN_CORE_NAMESPACE = "http://purl.org/dc/elements/1.1/";
+    
+    /** Namespace URI for the XMP Basic Schema */
+    String XMP_BASIC_NAMESPACE = "http://ns.adobe.com/xap/1.0/";
+    
+}
diff --git a/src/java/org/apache/fop/fo/extensions/xmp/XMPElementMapping.java b/src/java/org/apache/fop/fo/extensions/xmp/XMPElementMapping.java
new file mode 100644 (file)
index 0000000..230ea81
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * 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.fo.extensions.xmp;
+
+import java.util.HashMap;
+
+import org.apache.fop.fo.FONode;
+import org.apache.fop.fo.ElementMapping;
+
+import org.w3c.dom.DOMImplementation;
+
+/**
+ * Setup the element mapping for XMP metadata.
+ */
+public class XMPElementMapping extends ElementMapping {
+    
+    /** Main constructor. */
+    public XMPElementMapping() {
+        namespaceURI = XMPConstants.XMP_NAMESPACE;
+    }
+
+    /** @see org.apache.fop.fo.ElementMapping#getDOMImplementation() */
+    public DOMImplementation getDOMImplementation() {
+        return getDefaultDOMImplementation();
+    }
+
+    /** @see org.apache.fop.fo.ElementMapping#initialize() */
+    protected void initialize() {
+        if (foObjs == null) {
+            foObjs = new HashMap();
+            foObjs.put("xmpmeta", new XMPMetaElementMaker());
+        }
+    }
+
+    static class XMPMetaElementMaker extends ElementMapping.Maker {
+        public FONode make(FONode parent) {
+            return new XMPMetaElement(parent);
+        }
+    }
+
+}
diff --git a/src/java/org/apache/fop/fo/extensions/xmp/XMPMetaElement.java b/src/java/org/apache/fop/fo/extensions/xmp/XMPMetaElement.java
new file mode 100644 (file)
index 0000000..4f39fb1
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * 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.fo.extensions.xmp;
+
+import org.apache.fop.fo.ElementMapping;
+import org.apache.fop.fo.FONode;
+import org.apache.fop.fo.FObj;
+import org.apache.fop.fo.XMLObj;
+import org.apache.fop.fo.extensions.ExtensionAttachment;
+import org.apache.fop.util.ContentHandlerFactory;
+import org.apache.fop.util.DOMBuilderContentHandlerFactory;
+
+/**
+ * Represents the top-level "xmpmeta" element used by XMP metadata.
+ */
+public class XMPMetaElement extends XMLObj {
+
+    private XMPMetadata attachment;
+    
+    /**
+     * Main constructor.
+     * @param parent the parent formatting object
+     */
+    public XMPMetaElement(FONode parent) {
+        super(parent);
+    }
+    
+    /** @see org.apache.fop.fo.FONode#getNormalNamespacePrefix() */
+    public String getNormalNamespacePrefix() {
+        return "x";
+    }
+
+    /** @see org.apache.fop.fo.FONode#getNamespaceURI() */
+    public String getNamespaceURI() {
+        return XMPConstants.XMP_NAMESPACE;
+    }
+
+    /**
+     * @see org.apache.fop.fo.FONode#getContentHandlerFactory()
+     */
+    public ContentHandlerFactory getContentHandlerFactory() {
+        return new DOMBuilderContentHandlerFactory(getNamespaceURI(), 
+                ElementMapping.getDefaultDOMImplementation());
+    }
+    
+    /** @see org.apache.fop.fo.FONode#getExtensionAttachment() */
+    public ExtensionAttachment getExtensionAttachment() {
+        if (parent instanceof FObj) {
+            if (attachment == null) {
+                attachment = new XMPMetadata(doc);
+            }
+            return attachment;
+        } else {
+            return super.getExtensionAttachment();
+        }
+    }
+
+    /**
+     * @see org.apache.fop.fo.XMLObj#notifyObjectBuilt(java.lang.Object)
+     */
+    public void notifyObjectBuilt(Object obj) {
+        super.notifyObjectBuilt(obj);
+        attachment.setDocument(doc);
+    }
+
+}
diff --git a/src/java/org/apache/fop/fo/extensions/xmp/XMPMetadata.java b/src/java/org/apache/fop/fo/extensions/xmp/XMPMetadata.java
new file mode 100644 (file)
index 0000000..fd84b24
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * 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.fo.extensions.xmp;
+
+import java.io.Serializable;
+
+import org.apache.fop.fo.extensions.ExtensionAttachment;
+import org.apache.fop.util.DOM2SAX;
+import org.apache.fop.util.XMLizable;
+import org.w3c.dom.Document;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+
+/**
+ * This is the pass-through value object for the XMP metadata extension.
+ */
+public class XMPMetadata implements ExtensionAttachment, Serializable, XMLizable {
+
+    /** The category URI for this extension attachment. */
+    public static final String CATEGORY = XMPConstants.XMP_NAMESPACE;
+    
+    private Document doc;
+    private boolean readOnly = true;
+
+    /**
+     * No-argument contructor.
+     */
+    public XMPMetadata() {
+        //nop
+    }
+    
+    /**
+     * Default constructor.
+     * @param doc the DOM document containing the XMP metadata
+     */
+    public XMPMetadata(Document doc) {
+        this.doc = doc;
+    }
+    
+    /** @return the DOM document containing the XMP metadata */
+    public Document getDocument() {
+        return this.doc;
+    }
+    
+    /**
+     * Sets the DOM document containing the XMP metadata.
+     * @param document the DOM document
+     */
+    public void setDocument(Document document) {
+        this.doc = document;
+    }
+    
+    /** @return true if the XMP metadata is marked read-only. */
+    public boolean isReadOnly() {
+        return readOnly;
+    }
+
+    /**
+     * Sets the flag that decides whether a metadata packet may be modified.
+     * @param readOnly true if the XMP metadata packet should be marked read-only.
+     */
+    public void setReadOnly(boolean readOnly) {
+        this.readOnly = readOnly;
+    }
+
+    /** @see org.apache.fop.fo.extensions.ExtensionAttachment#getCategory() */
+    public String getCategory() {
+        return CATEGORY;
+    }
+    
+    /** @see org.apache.fop.util.XMLizable#toSAX(org.xml.sax.ContentHandler) */
+    public void toSAX(ContentHandler handler) throws SAXException {
+        DOM2SAX.writeDocument(getDocument(), handler);
+    }
+    
+}
index 4d16dfa740ad44e05724da45fd7dc011d10b242d..572e3be43a28f689b9ba20ee8398c592565efdfb 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 1999-2004 The Apache Software Foundation.
+ * Copyright 1999-2004,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.
@@ -62,7 +62,13 @@ public class PDFDocument {
      * the version of PDF supported which is 1.4
      */
     protected static final String PDF_VERSION = "1.4";
+    
+    /** Integer constant to represent PDF 1.3 */
+    public static final int PDF_VERSION_1_3 = 3;
 
+    /** Integer constant to represent PDF 1.4 */
+    public static final int PDF_VERSION_1_4 = 4;
+    
     /**
      * the encoding to use when converting strings to PDF commandos.
      */
@@ -98,6 +104,9 @@ public class PDFDocument {
      */
     protected int xref;
 
+    /** Indicates what PDF version is active */
+    protected int pdfVersion = PDF_VERSION_1_4;
+    
     /**
      * the /Root object
      */
@@ -234,6 +243,13 @@ public class PDFDocument {
         this.info = getFactory().makeInfo(prod);
     }
 
+    /**
+     * @return the integer representing the active PDF version (one of PDFDocument.PDF_VERSION_*)
+     */
+    public int getPDFVersion() {
+        return this.pdfVersion;
+    }
+    
     /**
      * Returns the factory for PDF objects.
      * @return PDFFactory the factory
index 39b172417d371d5c2d4bb840c2c5626385753d07..6222cf1f0e984a83514c73b6ec2f60f5e9e58ed8 100644 (file)
@@ -29,6 +29,9 @@ import java.util.Map;
 import javax.xml.transform.Source;
 import javax.xml.transform.stream.StreamSource;
 
+//W3C DOM
+import org.w3c.dom.Document;
+
 // Apache libs
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.logging.Log;
@@ -55,7 +58,7 @@ public class PDFFactory {
 
     private PDFDocument document;
 
-    private Log log = LogFactory.getLog("org.apache.fop.pdf");
+    private Log log = LogFactory.getLog(PDFFactory.class);
 
     /**
      * Creates a new PDFFactory.
@@ -86,6 +89,7 @@ public class PDFFactory {
     public PDFRoot makeRoot(PDFPages pages) {
         //Make a /Pages object. This object is written in the trailer.
         PDFRoot pdfRoot = new PDFRoot(++this.document.objectcount, pages);
+        pdfRoot.setDocument(getDocument());
         getDocument().addTrailerObject(pdfRoot);
         return pdfRoot;
     }
@@ -97,6 +101,7 @@ public class PDFFactory {
      */
     public PDFPages makePages() {
         PDFPages pdfPages = new PDFPages(++(this.document.objectcount));
+        pdfPages.setDocument(getDocument());
         getDocument().addTrailerObject(pdfPages);
         return pdfPages;
     }
@@ -108,6 +113,7 @@ public class PDFFactory {
      */
     public PDFResources makeResources() {
         PDFResources pdfResources = new PDFResources(++this.document.objectcount);
+        pdfResources.setDocument(getDocument());
         getDocument().addTrailerObject(pdfResources);
         return pdfResources;
     }
@@ -131,6 +137,18 @@ public class PDFFactory {
         return pdfInfo;
     }
 
+    /**
+     * Make a Metadata object.
+     * @param doc the DOM Document containing the XMP metadata.
+     * @param readOnly true if the metadata packet should be marked read-only
+     * @return the newly created Metadata object
+     */
+    public PDFMetadata makeMetadata(Document doc, boolean readOnly) {
+        PDFMetadata pdfMetadata = new PDFMetadata(doc, readOnly);
+        getDocument().registerObject(pdfMetadata);
+        return pdfMetadata;
+    }
+
     /**
      * Make a /Page object. The page is assigned an object number immediately
      * so references can already be made. The page must be added to the
@@ -1302,6 +1320,4 @@ public class PDFFactory {
         return obj;
     }
 
-
-
 }
index 4554ea6c668226ecc9a4eff40ea7d1955dc195d8..6ffda9da7e18779556d46abba858694c4b6f8944 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 1999-2005 The Apache Software Foundation.
+ * Copyright 1999-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.
@@ -49,6 +49,8 @@ public class PDFFilterList {
     public static final String TIFF_FILTER = "tiff";
     /** Key for the filter used for fonts */
     public static final String FONT_FILTER = "font";
+    /** Key for the filter used for metadata */
+    public static final String METADATA_FILTER = "metadata";
 
     private List filters = new java.util.ArrayList();
 
index ce926d4da922cb9efb9e1af584e6904b966a9dbf..7bf34675337b74d26d3369a574d9577e2fc7fed0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 1999-2005 The Apache Software Foundation.
+ * Copyright 1999-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.
@@ -53,6 +53,11 @@ public class PDFInfo extends PDFObject {
         this.producer = producer;
     }
 
+    /** @return the creator of the document or null if not set */
+    public String getCreator() {
+        return this.creator;
+    }
+    
     /**
      * set the creator string
      *
@@ -76,6 +81,11 @@ public class PDFInfo extends PDFObject {
         this.title = t;
     }
 
+    /** @return the author of the document or null if not set */
+    public String getAuthor() {
+        return this.author;
+    }
+    
     /**
      * set the author string
      *
@@ -85,6 +95,11 @@ public class PDFInfo extends PDFObject {
         this.author = a;
     }
 
+    /** @return the subject of the document or null if not set */
+    public String getSubject() {
+        return this.subject;
+    }
+    
     /**
      * set the subject string
      *
@@ -94,6 +109,11 @@ public class PDFInfo extends PDFObject {
         this.subject = s;
     }
 
+    /** @return the keywords for the document or null if not set */
+    public String getKeyword() {
+        return this.keywords;
+    }
+    
     /**
      * set the keywords string
      *
diff --git a/src/java/org/apache/fop/pdf/PDFMetadata.java b/src/java/org/apache/fop/pdf/PDFMetadata.java
new file mode 100644 (file)
index 0000000..d162ba0
--- /dev/null
@@ -0,0 +1,205 @@
+/*
+ * 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.pdf;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.apache.fop.apps.FOUserAgent;
+import org.apache.fop.fo.ElementMapping;
+import org.apache.fop.fo.extensions.xmp.XMPConstants;
+import org.w3c.dom.DOMImplementation;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+/**
+ * Special PDFStream for Metadata.
+ * @since PDF 1.4
+ */
+public class PDFMetadata extends PDFStream {
+    
+    private Document xmpMetadata;
+    private boolean readOnly = true;
+
+    /** @see org.apache.fop.pdf.PDFObject#PDFObject() */
+    public PDFMetadata(Document xmp, boolean readOnly) {
+        super();
+        if (xmp == null) {
+            throw new NullPointerException(
+                    "DOM Document representing the metadata must no be null");
+        }
+        this.xmpMetadata = xmp;
+        this.readOnly = readOnly;
+    }
+
+    /** @see org.apache.fop.pdf.AbstractPDFStream#setupFilterList() */
+    protected void setupFilterList() {
+        if (!getFilterList().isInitialized()) {
+            getFilterList().addDefaultFilters(
+                getDocumentSafely().getFilterMap(), 
+                PDFFilterList.METADATA_FILTER);
+        }
+        super.setupFilterList();
+    }
+
+    /** @see org.apache.fop.pdf.AbstractPDFStream#allowEncryption() */
+    protected boolean allowEncryption() {
+        return false; //XMP metadata packet must be scannable by non PDF-compatible readers
+    }
+
+    /**
+     * overload the base object method so we don't have to copy
+     * byte arrays around so much
+     * @see org.apache.fop.pdf.PDFObject#output(OutputStream)
+     */
+    protected int output(java.io.OutputStream stream)
+                throws java.io.IOException {
+        int length = super.output(stream);
+        this.xmpMetadata = null; //Release DOM when it's not used anymore
+        return length;
+    }
+    
+    /** @see org.apache.fop.pdf.AbstractPDFStream#outputRawStreamData(java.io.OutputStream) */
+    protected void outputRawStreamData(OutputStream out) throws IOException {
+        final String encoding = "UTF-8";
+        out.write("<?xpacket begin=\"\uFEFF\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>\n"
+                .getBytes(encoding));
+        try {
+            TransformerFactory tFactory = TransformerFactory.newInstance();
+            Transformer transformer = tFactory.newTransformer();
+            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
+            transformer.setOutputProperty(OutputKeys.ENCODING, encoding);
+            transformer.setOutputProperty(OutputKeys.INDENT, "no");
+            DOMSource src = new DOMSource(this.xmpMetadata);
+            StreamResult res = new StreamResult(out);
+            transformer.transform(src, res);
+        } catch (TransformerConfigurationException e) {
+            throw new IOException("Error setting up Transformer for XMP stream serialization: " 
+                    + e.getMessage());
+        } catch (TransformerException e) {
+            throw new IOException("Error while serializing XMP stream: " 
+                    + e.getMessage());
+        }
+        if (readOnly) {
+            out.write("\n<?xpacket end=\"r\"?>".getBytes(encoding));
+        } else {
+            //Create padding string (40 * 101 characters is more or less the recommended 4KB)
+            StringBuffer sb = new StringBuffer(101);
+            sb.append('\n');
+            for (int i = 0; i < 100; i++) {
+                sb.append(" ");
+            }
+            byte[] padding = sb.toString().getBytes(encoding);
+            for (int i = 0; i < 40; i++) {
+                out.write(padding);
+            }
+            out.write("\n<?xpacket end=\"w\"?>".getBytes(encoding));
+        }
+    }
+    
+    /** @see org.apache.fop.pdf.AbstractPDFStream#buildStreamDict(String) */
+    protected String buildStreamDict(String lengthEntry) {
+        final String filterEntry = getFilterList().buildFilterDictEntries();
+        final StringBuffer sb = new StringBuffer(128);
+        sb.append(getObjectID());
+        sb.append("<< ");
+        sb.append("/Type /Metadata");
+        sb.append("\n/Subtype /XML");
+        sb.append("\n/Length " + lengthEntry);
+        sb.append("\n" + filterEntry);
+        sb.append("\n>>\n");
+        return sb.toString();
+    }
+
+    /**
+     * Creates an XMP document based on the settings on the PDF Document.
+     * @param pdfDoc the PDF Document
+     * @return a DOM document representing the requested XMP metadata
+     */
+    public static Document createXMPFromUserAgent(PDFDocument pdfDoc) {
+        DOMImplementation domImplementation = ElementMapping.getDefaultDOMImplementation();
+        Document doc = domImplementation.createDocument(
+                XMPConstants.XMP_NAMESPACE, "x:xmpmeta", null);
+        Element rdf = doc.createElementNS(XMPConstants.RDF_NAMESPACE, "rdf:RDF");
+        doc.getDocumentElement().appendChild(rdf);
+        
+        Element desc, el;
+        PDFInfo info = pdfDoc.getInfo();
+        DateFormat pseudoISO8601DateFormat = new SimpleDateFormat(
+            "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'SSS");
+        
+        //Set creation date if not available, yet
+        if (info.getCreationDate() == null) {
+            Date d = new Date();
+            info.setCreationDate(d);
+        }
+
+        //Dublin Core
+        desc = doc.createElementNS(XMPConstants.RDF_NAMESPACE, "rdf:Description");
+        desc.setAttribute("about", "");
+        rdf.appendChild(desc);
+        if (info.getAuthor() != null) {
+            el = doc.createElementNS(XMPConstants.DUBLIN_CORE_NAMESPACE, "dc:creator");
+            desc.appendChild(el);
+            el.appendChild(doc.createTextNode(info.getAuthor()));
+        }
+        if (info.getTitle() != null) {
+            el = doc.createElementNS(XMPConstants.DUBLIN_CORE_NAMESPACE, "dc:title");
+            desc.appendChild(el);
+            el.appendChild(doc.createTextNode(info.getTitle()));
+        }
+        if (info.getSubject() != null) {
+            el = doc.createElementNS(XMPConstants.DUBLIN_CORE_NAMESPACE, "dc:subject");
+            desc.appendChild(el);
+            el.appendChild(doc.createTextNode(info.getSubject()));
+        }
+        el = doc.createElementNS(XMPConstants.DUBLIN_CORE_NAMESPACE, "dc:date");
+        desc.appendChild(el);
+        el.appendChild(doc.createTextNode(pseudoISO8601DateFormat.format(info.getCreationDate())));
+        
+        //XMP Basic Schema
+        desc = doc.createElementNS(XMPConstants.RDF_NAMESPACE, "rdf:Description");
+        desc.setAttribute("about", "");
+        rdf.appendChild(desc);
+        el = doc.createElementNS(XMPConstants.XMP_BASIC_NAMESPACE, "xmp:createDate");
+        desc.appendChild(el);
+        el.appendChild(doc.createTextNode(pseudoISO8601DateFormat.format(info.getCreationDate())));
+        if (info.getCreator() != null) {
+            el = doc.createElementNS(XMPConstants.XMP_BASIC_NAMESPACE, "xmp:creatorTool");
+            desc.appendChild(el);
+            el.appendChild(doc.createTextNode(info.getCreator()));
+        }
+        
+        
+        return doc;
+    }
+    
+    
+}
index 2ac00cdf1bd748324d2f1c26f3f53e9568ae82bf..3d483f58711999981e50fc7fe929cce1d1b644fd 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 1999-2004 The Apache Software Foundation.
+ * Copyright 1999-2004,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.
@@ -53,6 +53,9 @@ public class PDFRoot extends PDFObject {
      */
     private PDFOutline outline;
 
+    /** Optional Metadata object */
+    private PDFMetadata metadata;
+    
     private int pageMode = PAGEMODE_USENONE;
 
     /**
@@ -115,6 +118,23 @@ public class PDFRoot extends PDFObject {
     public PDFOutline getRootOutline() {
         return outline;
     }
+    
+    /**
+     * Set the optional Metadata object.
+     * @param meta the Metadata object
+     * @since PDF 1.4
+     */
+    public void setMetadata(PDFMetadata meta) {
+        this.metadata = meta;
+    }
+    
+    /**
+     * @return the Metadata object if set, null otherwise.
+     * @since PDF 1.4
+     */
+    public PDFMetadata getMetadata() {
+        return this.metadata;
+    }
 
     /**
      * @see org.apache.fop.pdf.PDFObject#toPDFString()
@@ -144,6 +164,10 @@ public class PDFRoot extends PDFObject {
                 break;
             }
         }
+        if (getMetadata() != null 
+                && getDocumentSafely().getPDFVersion() >= PDFDocument.PDF_VERSION_1_4) {
+            p.append("/Metadata " + getMetadata().referencePDF() + "\n");
+        }
         p.append(">>\nendobj\n");
         return p.toString();
     }
index ab2ae245b9653873790220c1e3802034d130e3d4..d0f65d16f7927c02ef6922227eb79b43a6b58387 100644 (file)
@@ -41,7 +41,7 @@ import org.apache.fop.apps.FOUserAgent;
 import org.apache.fop.apps.MimeConstants;
 import org.apache.fop.area.CTM;
 import org.apache.fop.area.LineArea;
-import org.apache.fop.area.Page;
+import org.apache.fop.area.OffDocumentExtensionAttachment;
 import org.apache.fop.area.PageViewport;
 import org.apache.fop.area.RegionViewport;
 import org.apache.fop.area.Trait;
@@ -69,6 +69,7 @@ import org.apache.fop.pdf.PDFEncryptionManager;
 import org.apache.fop.pdf.PDFFilterList;
 import org.apache.fop.pdf.PDFInfo;
 import org.apache.fop.pdf.PDFLink;
+import org.apache.fop.pdf.PDFMetadata;
 import org.apache.fop.pdf.PDFNumber;
 import org.apache.fop.pdf.PDFOutline;
 import org.apache.fop.pdf.PDFPage;
@@ -82,6 +83,8 @@ import org.apache.fop.render.AbstractPathOrientedRenderer;
 import org.apache.fop.render.Graphics2DAdapter;
 import org.apache.fop.render.RendererContext;
 import org.apache.fop.fo.Constants;
+import org.apache.fop.fo.extensions.ExtensionAttachment;
+import org.apache.fop.fo.extensions.xmp.XMPMetadata;
 
 /*
 todo:
@@ -296,6 +299,11 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
         // render Bookmark-Tree
         if (odi instanceof BookmarkData) {
             renderBookmarkTree((BookmarkData) odi);
+        } else if (odi instanceof OffDocumentExtensionAttachment) {
+            ExtensionAttachment attachment = ((OffDocumentExtensionAttachment)odi).getAttachment();
+            if (XMPMetadata.CATEGORY.equals(attachment.getCategory())) {
+                renderXMPMetadata((XMPMetadata)attachment);
+            }
         }
     }
 
@@ -340,6 +348,12 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
         }
     }
     
+    private void renderXMPMetadata(XMPMetadata metadata) {
+        PDFMetadata pdfMetadata = pdfDoc.getFactory().makeMetadata(
+                metadata.getDocument(), metadata.isReadOnly());
+        pdfDoc.getRoot().setMetadata(pdfMetadata);
+    }
+
     /** @see org.apache.fop.render.Renderer#getGraphics2DAdapter() */
     public Graphics2DAdapter getGraphics2DAdapter() {
         return new PDFGraphics2DAdapter(this);
@@ -400,6 +414,14 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
                 info.setTitle(str);
             }
         }
+        if (pdfDoc.getRoot().getMetadata() == null) {
+            //If at this time no XMP metadata for the overall document has been set, create it
+            //from the PDFInfo object.
+            Document xmp = PDFMetadata.createXMPFromUserAgent(pdfDoc);
+            PDFMetadata pdfMetadata = pdfDoc.getFactory().makeMetadata(
+                    xmp, true);
+            pdfDoc.getRoot().setMetadata(pdfMetadata);
+        }
     }
 
     /**
index e22951236c88d4aaf032ffee3556721652b363a0..401202dc0e1a9ba8901371b3da18defa3eff06b6 100644 (file)
@@ -20,8 +20,8 @@ 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.apache.fop.util.ContentHandlerFactory.ObjectBuiltListener;
 import org.xml.sax.Attributes;
 import org.xml.sax.SAXException;
 import org.xml.sax.helpers.DefaultHandler;
@@ -39,6 +39,7 @@ public class PSExtensionHandler extends DefaultHandler
     private Attributes lastAttributes;
     
     private PSSetupCode returnedObject;
+    private ObjectBuiltListener listener;
     
     /** @see org.xml.sax.helpers.DefaultHandler */
     public void startElement(String uri, String localName, String qName, Attributes attributes) 
@@ -80,9 +81,27 @@ public class PSExtensionHandler extends DefaultHandler
         content.append(ch, start, length);
     }
 
-    /** @see org.apache.fop.area.AreaTreeParser.ObjectSource#getObject() */
+    /**
+     * @see org.xml.sax.helpers.DefaultHandler#endDocument()
+     */
+    public void endDocument() throws SAXException {
+        if (listener != null) {
+            listener.notifyObjectBuilt(getObject());
+        }
+    }
+
+    /**
+     * @see org.apache.fop.util.ContentHandlerFactory.ObjectSource#getObject()
+     */
     public Object getObject() {
         return returnedObject;
     }
 
+    /**
+     * @see org.apache.fop.util.ContentHandlerFactory.ObjectSource
+     */
+    public void setObjectBuiltListener(ObjectBuiltListener listener) {
+        this.listener = listener;
+    }
+
 }
index eb1f4946b929890ae46ce5003e89b3708efcc299..da44bc725e0eac5de3b6682d8459eba87ac16faf 100644 (file)
 
 package org.apache.fop.render.xml;
 
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
 import org.apache.fop.render.Renderer;
 import org.apache.fop.render.XMLHandler;
 import org.apache.fop.render.RendererContext;
+import org.apache.fop.util.DOM2SAX;
 
-import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Attr;
-import org.xml.sax.SAXException;
 import org.xml.sax.ContentHandler;
-import org.xml.sax.ext.LexicalHandler;
-import org.xml.sax.helpers.AttributesImpl;
 
 /**
  * XML handler for the XML renderer.
@@ -41,104 +33,12 @@ public class XMLXMLHandler implements XMLHandler {
     /** Key for getting the TransformerHandler from the RendererContext */
     public static final String HANDLER = "handler";
 
-    /** Logging instance */
-    private static Log log = LogFactory.getLog(XMLXMLHandler.class);
-
-    private AttributesImpl atts = new AttributesImpl();
-
     /** @see org.apache.fop.render.XMLHandler */
     public void handleXML(RendererContext context,
                 org.w3c.dom.Document doc, String ns) throws Exception {
         ContentHandler handler = (ContentHandler) context.getProperty(HANDLER);
 
-        writeDocument(doc, handler);
-    }
-
-    /**
-     * Writes the given document using the given TransformerHandler.
-     * @param doc DOM document
-     * @param handler TransformerHandler to write to
-     * @throws SAXException In case of a problem while writing XML
-     */
-    public void writeDocument(Document doc,
-                                     ContentHandler handler) throws SAXException {
-        for (Node n = doc.getFirstChild(); n != null;
-                n = n.getNextSibling()) {
-            writeNode(n, handler);
-        }
-    }
-
-    /**
-     * Writes a node using the given writer.
-     * @param node node to serialize
-     * @param handler ContentHandler to write to
-     * @throws SAXException In case of a problem while writing XML
-     */
-    public void writeNode(Node node, ContentHandler handler) throws SAXException {
-        char[] ca;
-        switch (node.getNodeType()) {
-            case Node.ELEMENT_NODE:
-                atts.clear();
-
-                if (node.hasAttributes()) {
-                    NamedNodeMap attr = node.getAttributes();
-                    int len = attr.getLength();
-                    for (int i = 0; i < len; i++) {
-                        Attr a = (Attr) attr.item(i);
-                        atts.addAttribute("", a.getNodeName(), a.getNodeName(),
-                                "CDATA", a.getNodeValue());
-                    }
-                }
-                handler.startElement(node.getNamespaceURI(),
-                        node.getLocalName(), node.getLocalName(), atts);
-
-                Node c = node.getFirstChild();
-                if (c != null) {
-                    for (; c != null; c = c.getNextSibling()) {
-                        writeNode(c, handler);
-                    }
-                }
-                handler.endElement(node.getNamespaceURI(), node.getNodeName(), node.getNodeName());
-                break;
-            case Node.TEXT_NODE:
-                ca = node.getNodeValue().toCharArray();
-                handler.characters(ca, 0, ca.length);
-                break;
-            case Node.CDATA_SECTION_NODE:
-                ca = node.getNodeValue().toCharArray();
-                if (handler instanceof LexicalHandler) {
-                    LexicalHandler lh = (LexicalHandler)handler;
-                    lh.startCDATA();
-                    handler.characters(ca, 0, ca.length);
-                    lh.endCDATA();
-                } else {
-                    handler.characters(ca, 0, ca.length);
-                }
-                break;
-            case Node.ENTITY_REFERENCE_NODE:
-                log.warn("Ignoring ENTITY_REFERENCE_NODE. NYI");
-                /*
-                writer.write("&");
-                writer.write();
-                writer.write(";");
-                */
-                break;
-            case Node.PROCESSING_INSTRUCTION_NODE:
-                handler.processingInstruction(node.getNodeName(), node.getNodeValue());
-                break;
-            case Node.COMMENT_NODE:
-                ca = node.getNodeValue().toCharArray();
-                if (handler instanceof LexicalHandler) {
-                    LexicalHandler lh = (LexicalHandler)handler;
-                    lh.comment(ca, 0, ca.length);
-                }
-                break;
-            case Node.DOCUMENT_TYPE_NODE:
-                break;
-            default:
-                throw new IllegalArgumentException("Unexpected node type ("
-                        + node.getNodeType() + ")");
-        }
+        DOM2SAX.writeDocument(doc, handler);
     }
 
     /** @see org.apache.fop.render.XMLHandler#supportsRenderer(org.apache.fop.render.Renderer) */
index da615cc5439115241be865f89367b1b75e583051..a17d7a60f886781a5273f723e2f566e2ea56dee7 100644 (file)
 
 package org.apache.fop.util;
 
+import java.util.EventListener;
+
 import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
 
 /**
  * Factory interface implemented by classes that can instantiate ContentHandler subclasses which
@@ -33,8 +36,9 @@ public interface ContentHandlerFactory {
     
     /**
      * @return a new ContentHandler to handle a SAX stream
+     * @throws SAXException if there's an error while preparing the ContentHandler
      */
-    ContentHandler createContentHandler();
+    ContentHandler createContentHandler() throws SAXException;
     
     /**
      * Interface that ContentHandler implementations that parse Java objects from XML can implement
@@ -46,6 +50,25 @@ public interface ContentHandlerFactory {
          * @return the object parsed from the SAX stream (call valid after parsing)
          */
         Object getObject();
+     
+        /**
+         * Set a listener which gets notified when the object is fully built.
+         * @param listener the listener which gets notified
+         */
+        void setObjectBuiltListener(ObjectBuiltListener listener);
+    }
+    
+    /**
+     * EventListener interface for objects which want to get notified when ContentHandler
+     * implementing the ObjectSource interface has finished parsing.
+     */
+    public interface ObjectBuiltListener extends EventListener {
+        
+        /**
+         * Notifies the listener when the object is fully built.
+         * @param obj the newly built object
+         */
+        void notifyObjectBuilt(Object obj);
         
     }
     
diff --git a/src/java/org/apache/fop/util/DOM2SAX.java b/src/java/org/apache/fop/util/DOM2SAX.java
new file mode 100644 (file)
index 0000000..a26e714
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * 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.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.ext.LexicalHandler;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+ * Helper class that produces a SAX stream from a DOM Document.
+ */
+public class DOM2SAX {
+
+    /** Logging instance */
+    private static Log log = LogFactory.getLog(DOM2SAX.class);
+
+    /**
+     * Writes the given document using the given TransformerHandler.
+     * @param doc DOM document
+     * @param handler TransformerHandler to write to
+     * @throws SAXException In case of a problem while writing XML
+     */
+    public static void writeDocument(Document doc,
+                                     ContentHandler handler) throws SAXException {
+        AttributesImpl atts = new AttributesImpl();
+        for (Node n = doc.getFirstChild(); n != null;
+                n = n.getNextSibling()) {
+            writeNode(n, handler, atts);
+        }
+    }
+
+    /**
+     * Writes a node using the given writer.
+     * @param node node to serialize
+     * @param handler ContentHandler to write to
+     * @param atts AttributesImpl instance that is reused during SAX event generation
+     * @throws SAXException In case of a problem while writing XML
+     */
+    private static void writeNode(Node node, ContentHandler handler, AttributesImpl atts) 
+                throws SAXException {
+        char[] ca;
+        switch (node.getNodeType()) {
+            case Node.ELEMENT_NODE:
+                atts.clear();
+
+                if (node.hasAttributes()) {
+                    NamedNodeMap attr = node.getAttributes();
+                    int len = attr.getLength();
+                    for (int i = 0; i < len; i++) {
+                        Attr a = (Attr) attr.item(i);
+                        atts.addAttribute("", a.getNodeName(), a.getNodeName(),
+                                "CDATA", a.getNodeValue());
+                    }
+                }
+                handler.startElement(node.getNamespaceURI(),
+                        node.getLocalName(), node.getLocalName(), atts);
+
+                Node c = node.getFirstChild();
+                if (c != null) {
+                    for (; c != null; c = c.getNextSibling()) {
+                        writeNode(c, handler, atts);
+                    }
+                }
+                handler.endElement(node.getNamespaceURI(), node.getNodeName(), node.getNodeName());
+                break;
+            case Node.TEXT_NODE:
+                ca = node.getNodeValue().toCharArray();
+                handler.characters(ca, 0, ca.length);
+                break;
+            case Node.CDATA_SECTION_NODE:
+                ca = node.getNodeValue().toCharArray();
+                if (handler instanceof LexicalHandler) {
+                    LexicalHandler lh = (LexicalHandler)handler;
+                    lh.startCDATA();
+                    handler.characters(ca, 0, ca.length);
+                    lh.endCDATA();
+                } else {
+                    handler.characters(ca, 0, ca.length);
+                }
+                break;
+            case Node.ENTITY_REFERENCE_NODE:
+                log.warn("Ignoring ENTITY_REFERENCE_NODE. NYI");
+                /*
+                writer.write("&");
+                writer.write();
+                writer.write(";");
+                */
+                break;
+            case Node.PROCESSING_INSTRUCTION_NODE:
+                handler.processingInstruction(node.getNodeName(), node.getNodeValue());
+                break;
+            case Node.COMMENT_NODE:
+                ca = node.getNodeValue().toCharArray();
+                if (handler instanceof LexicalHandler) {
+                    LexicalHandler lh = (LexicalHandler)handler;
+                    lh.comment(ca, 0, ca.length);
+                }
+                break;
+            case Node.DOCUMENT_TYPE_NODE:
+                break;
+            default:
+                throw new IllegalArgumentException("Unexpected node type ("
+                        + node.getNodeType() + ")");
+        }
+    }
+
+    
+}
diff --git a/src/java/org/apache/fop/util/DOMBuilderContentHandlerFactory.java b/src/java/org/apache/fop/util/DOMBuilderContentHandlerFactory.java
new file mode 100644 (file)
index 0000000..5b5dd3b
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * 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 javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.sax.TransformerHandler;
+
+import org.w3c.dom.DOMImplementation;
+import org.w3c.dom.Document;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+
+/**
+ * ContentHandlerFactory which constructs ContentHandlers that build DOM Documents.
+ */
+public class DOMBuilderContentHandlerFactory implements ContentHandlerFactory {
+
+    private static SAXTransformerFactory tFactory 
+            = (SAXTransformerFactory)SAXTransformerFactory.newInstance();
+
+    private String namespaceURI;
+    private DOMImplementation domImplementation;
+    
+    /**
+     * Main Constructor
+     * @param namespaceURI the main namespace URI for the DOM to be parsed 
+     * @param domImplementation the DOMImplementation to use for build the DOM
+     */
+    public DOMBuilderContentHandlerFactory(String namespaceURI, 
+                DOMImplementation domImplementation) {
+        this.namespaceURI = namespaceURI;
+        this.domImplementation = domImplementation;
+    }
+    
+    /** @see org.apache.fop.util.ContentHandlerFactory#getSupportedNamespaces() */
+    public String[] getSupportedNamespaces() {
+        return new String[] {namespaceURI};
+    }
+
+    /** @see org.apache.fop.util.ContentHandlerFactory#createContentHandler() */
+    public ContentHandler createContentHandler() throws SAXException {
+        return new Handler();
+    }
+    
+    private class Handler extends DelegatingContentHandler
+                implements ContentHandlerFactory.ObjectSource {
+     
+        private Document doc;
+        private ObjectBuiltListener obListener;
+        
+        public Handler() throws SAXException {
+            super();
+        }
+
+        public Document getDocument() {
+            return this.doc;
+        }
+        
+        /**
+         * @see org.apache.fop.util.ContentHandlerFactory.ObjectSource#getObject()
+         */
+        public Object getObject() {
+            return getDocument();
+        }
+
+        /**
+         * @see org.apache.fop.util.ContentHandlerFactory.ObjectSource
+         */
+        public void setObjectBuiltListener(ObjectBuiltListener listener) {
+            this.obListener = listener;
+        }
+        
+        /**
+         * @see org.apache.fop.util.DelegatingContentHandler#startDocument()
+         */
+        public void startDocument() throws SAXException {
+            //Suppress startDocument() call if doc has not been set, yet. It will be done later.
+            if (doc != null) {
+                super.startDocument();
+            }
+        }
+
+        /**
+         * @see org.apache.fop.util.DelegatingContentHandler
+         */
+        public void startElement(String uri, String localName, String qName, Attributes atts) 
+                    throws SAXException {
+            if (doc == null) {
+                TransformerHandler handler;
+                try {
+                    handler = tFactory.newTransformerHandler();
+                } catch (TransformerConfigurationException e) {
+                    throw new SAXException("Error creating a new TransformerHandler", e);
+                }
+                doc = domImplementation.createDocument(namespaceURI, qName, null);
+                //It's easier to work with an empty document, so remove the root element
+                doc.removeChild(doc.getDocumentElement());
+                handler.setResult(new DOMResult(doc));
+                setDelegateContentHandler(handler);
+                setDelegateLexicalHandler(handler);
+                setDelegateDTDHandler(handler);
+                handler.startDocument();
+            }
+            super.startElement(uri, localName, qName, atts);
+        }
+
+        /**
+         * @see org.apache.fop.util.DelegatingContentHandler#endDocument()
+         */
+        public void endDocument() throws SAXException {
+            super.endDocument();
+            if (obListener != null) {
+                obListener.notifyObjectBuilt(getObject());
+            }
+        }
+
+    }
+
+}
diff --git a/src/java/org/apache/fop/util/DelegatingContentHandler.java b/src/java/org/apache/fop/util/DelegatingContentHandler.java
new file mode 100644 (file)
index 0000000..86cfb1e
--- /dev/null
@@ -0,0 +1,314 @@
+/*
+ * 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.io.IOException;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.DTDHandler;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.ext.LexicalHandler;
+
+/**
+ * SAX 2 Event Handler which simply delegates all calls to another ContentHandler. Subclasses can
+ * do additional processing. This class is the passive counterpart to XMLFilterImpl.
+ * <p>
+ * The ContentHandler is the only instance that is required. All others (DTDHandler,
+ * EntityResolver, LexicalHandler and ErrorHandler) may be ignored.
+ * 
+ */
+public class DelegatingContentHandler 
+        implements EntityResolver, DTDHandler, ContentHandler, LexicalHandler, ErrorHandler {
+
+    private ContentHandler delegate;
+    private EntityResolver entityResolver;
+    private DTDHandler dtdHandler;
+    private LexicalHandler lexicalHandler;
+    private ErrorHandler errorHandler;
+    
+    /**
+     * Main constructor.
+     */
+    public DelegatingContentHandler() {
+        //nop
+    }
+
+    /**
+     * @return the delegate that all ContentHandler events are forwarded to
+     */
+    public ContentHandler getDelegateContentHandler() {
+        return this.delegate;
+    }
+    
+    /**
+     * Sets the delegate ContentHandler that all events are forwarded to.
+     * @param handler the delegate instance
+     */
+    public void setDelegateContentHandler(ContentHandler handler) {
+        this.delegate = handler;
+    }
+    
+    /**
+     * Sets the delegate EntityResolver.
+     * @param resolver the delegate instance
+     */
+    public void setDelegateEntityResolver(EntityResolver resolver) {
+        this.entityResolver = resolver;
+    }
+    
+    /**
+     * Sets the delegate DTDHandler.
+     * @param handler the delegate instance
+     */
+    public void setDelegateDTDHandler(DTDHandler handler) {
+        this.dtdHandler = handler;
+    }
+    
+    /**
+     * Sets the delegate LexicalHandler.
+     * @param handler the delegate instance
+     */
+    public void setDelegateLexicalHandler(LexicalHandler handler) {
+        this.lexicalHandler = handler;
+    }
+    
+    /**
+     * Sets the delegate ErrorHandler.
+     * @param handler the delegate instance
+     */
+    public void setDelegateErrorHandler(ErrorHandler handler) {
+        this.errorHandler = handler;
+    }
+    
+    // ==== EntityResolver
+    
+    /**
+     * @see org.xml.sax.EntityResolver#resolveEntity(java.lang.String, java.lang.String)
+     */
+    public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
+        if (entityResolver != null) {
+            return entityResolver.resolveEntity(publicId, systemId);
+        } else {
+            return null;
+        }
+    }
+
+    // ==== DTDHandler
+
+    /**
+     * @see org.xml.sax.DTDHandler#notationDecl(java.lang.String, java.lang.String, java.lang.String)
+     */
+    public void notationDecl(String name, String publicId, String systemId) throws SAXException {
+        if (dtdHandler != null) {
+            dtdHandler.notationDecl(name, publicId, systemId);
+        }
+    }
+
+    /**
+     * @see org.xml.sax.DTDHandler#unparsedEntityDecl(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
+     */
+    public void unparsedEntityDecl(String name, String publicId, String systemId, 
+            String notationName) throws SAXException {
+        if (dtdHandler != null) {
+            dtdHandler.unparsedEntityDecl(name, publicId, systemId, notationName);
+        }
+    }
+
+    // ==== ContentHandler
+    
+    /**
+     * @see org.xml.sax.ContentHandler#setDocumentLocator(org.xml.sax.Locator)
+     */
+    public void setDocumentLocator(Locator locator) {
+        delegate.setDocumentLocator(locator);
+    }
+
+    /**
+     * @see org.xml.sax.ContentHandler#startDocument()
+     */
+    public void startDocument() throws SAXException {
+        delegate.startDocument();
+    }
+
+    /**
+     * @see org.xml.sax.ContentHandler#endDocument()
+     */
+    public void endDocument() throws SAXException {
+        delegate.endDocument();
+    }
+
+    /**
+     * @see org.xml.sax.ContentHandler#startPrefixMapping(java.lang.String, java.lang.String)
+     */
+    public void startPrefixMapping(String prefix, String uri) throws SAXException {
+        delegate.startPrefixMapping(prefix, uri);
+    }
+
+    /**
+     * @see org.xml.sax.ContentHandler#endPrefixMapping(java.lang.String)
+     */
+    public void endPrefixMapping(String prefix) throws SAXException {
+        delegate.endPrefixMapping(prefix);
+    }
+
+    /**
+     * @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
+     */
+    public void startElement(String uri, String localName, String qName, 
+                Attributes atts) throws SAXException {
+        delegate.startElement(uri, localName, qName, atts);
+    }
+
+    /**
+     * @see org.xml.sax.ContentHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
+     */
+    public void endElement(String uri, String localName, String qName) throws SAXException {
+        delegate.endElement(uri, localName, qName);
+    }
+
+    /**
+     * @see org.xml.sax.ContentHandler#characters(char[], int, int)
+     */
+    public void characters(char[] ch, int start, int length) throws SAXException {
+        delegate.characters(ch, start, length);
+    }
+
+    /**
+     * @see org.xml.sax.ContentHandler#ignorableWhitespace(char[], int, int)
+     */
+    public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
+        delegate.ignorableWhitespace(ch, start, length);
+    }
+
+    /**
+     * @see org.xml.sax.ContentHandler#processingInstruction(java.lang.String, java.lang.String)
+     */
+    public void processingInstruction(String target, String data) throws SAXException {
+        delegate.processingInstruction(target, data);
+    }
+
+    /**
+     * @see org.xml.sax.ContentHandler#skippedEntity(java.lang.String)
+     */
+    public void skippedEntity(String name) throws SAXException {
+        delegate.skippedEntity(name);
+    }
+
+    // ==== LexicalHandler
+    
+    /**
+     * @see org.xml.sax.ext.LexicalHandler#startDTD(java.lang.String, java.lang.String, java.lang.String)
+     */
+    public void startDTD(String name, String publicId, String systemId) throws SAXException {
+        if (lexicalHandler != null) {
+            lexicalHandler.startDTD(name, publicId, systemId);
+        }
+        
+    }
+
+    /**
+     * @see org.xml.sax.ext.LexicalHandler#endDTD()
+     */
+    public void endDTD() throws SAXException {
+        if (lexicalHandler != null) {
+            lexicalHandler.endDTD();
+        }
+    }
+
+    /**
+     * @see org.xml.sax.ext.LexicalHandler#startEntity(java.lang.String)
+     */
+    public void startEntity(String name) throws SAXException {
+        if (lexicalHandler != null) {
+            lexicalHandler.startEntity(name);
+        }
+    }
+
+    /**
+     * @see org.xml.sax.ext.LexicalHandler#endEntity(java.lang.String)
+     */
+    public void endEntity(String name) throws SAXException {
+        if (lexicalHandler != null) {
+            lexicalHandler.endEntity(name);
+        }
+    }
+
+    /**
+     * @see org.xml.sax.ext.LexicalHandler#startCDATA()
+     */
+    public void startCDATA() throws SAXException {
+        if (lexicalHandler != null) {
+            lexicalHandler.startCDATA();
+        }
+    }
+
+    /**
+     * @see org.xml.sax.ext.LexicalHandler#endCDATA()
+     */
+    public void endCDATA() throws SAXException {
+        if (lexicalHandler != null) {
+            lexicalHandler.endCDATA();
+        }
+    }
+
+    /**
+     * @see org.xml.sax.ext.LexicalHandler#comment(char[], int, int)
+     */
+    public void comment(char[] ch, int start, int length) throws SAXException {
+        if (lexicalHandler != null) {
+            lexicalHandler.comment(ch, start, length);
+        }
+    }
+    
+    // ==== ErrorHandler
+
+    /**
+     * @see org.xml.sax.ErrorHandler#warning(org.xml.sax.SAXParseException)
+     */
+    public void warning(SAXParseException exception) throws SAXException {
+        if (errorHandler != null) {
+            errorHandler.warning(exception);
+        }
+    }
+
+    /**
+     * @see org.xml.sax.ErrorHandler#error(org.xml.sax.SAXParseException)
+     */
+    public void error(SAXParseException exception) throws SAXException {
+        if (errorHandler != null) {
+            errorHandler.error(exception);
+        }
+    }
+
+    /**
+     * @see org.xml.sax.ErrorHandler#fatalError(org.xml.sax.SAXParseException)
+     */
+    public void fatalError(SAXParseException exception) throws SAXException {
+        if (errorHandler != null) {
+            errorHandler.fatalError(exception);
+        }
+    }
+
+}
index fa085c7104ff7bd2dd74c0dfb9a06e9b93235892..561f784144043790a235a40d78f6d7d44ba1b8b0 100644 (file)
@@ -27,6 +27,9 @@
 
   <changes>
     <release version="FOP Trunk">
+      <action context="Code" dev="JM" type="add">
+        Initial support for XMP metadata (PDF 1.4) under fo:declarations.
+      </action>
       <action context="Code" dev="AD" type="add">
         Added support for the background-position shorthand property.
       </action>