You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pdfbox.apache.org by ju...@apache.org on 2008/06/22 21:09:18 UTC

svn commit: r670399 [3/6] - in /incubator/pdfbox/trunk/jempbox: ./ lib/ licenses/ licenses/checkstyle/ licenses/jempbox/ licenses/junit/ src/ src/org/ src/org/jempbox/ src/org/jempbox/impl/ src/org/jempbox/xmp/ src/org/jempbox/xmp/pdfa/ src/test/ src/t...

Added: incubator/pdfbox/trunk/jempbox/src/org/jempbox/xmp/XMPMetadata.java
URL: http://svn.apache.org/viewvc/incubator/pdfbox/trunk/jempbox/src/org/jempbox/xmp/XMPMetadata.java?rev=670399&view=auto
==============================================================================
--- incubator/pdfbox/trunk/jempbox/src/org/jempbox/xmp/XMPMetadata.java (added)
+++ incubator/pdfbox/trunk/jempbox/src/org/jempbox/xmp/XMPMetadata.java Sun Jun 22 12:09:15 2008
@@ -0,0 +1,790 @@
+/**
+ * Copyright (c) 2006, www.jempbox.org
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of pdfbox; nor the names of its
+ *    contributors may be used to endorse or promote products derived from this
+ *    software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://www.jempbox.org
+ *
+ */
+package org.jempbox.xmp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.transform.TransformerException;
+
+import org.jempbox.impl.XMLUtil;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.ProcessingInstruction;
+import org.xml.sax.InputSource;
+
+/**
+ * This class represents the top level XMP data structure and gives access to
+ * the various schemas that are available as part of the XMP specification.
+ * 
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.10 $
+ */
+public class XMPMetadata
+{
+    /**
+     * Supported encoding for persisted XML.
+     */
+    public static final String ENCODING_UTF8 = "UTF-8";
+
+    /**
+     * Supported encoding for persisted XML.
+     */
+    public static final String ENCODING_UTF16BE = "UTF-16BE";
+
+    /**
+     * Supported encoding for persisted XML.
+     */
+    public static final String ENCODING_UTF16LE = "UTF-16LE";
+
+    /**
+     * The DOM representation of the metadata.
+     */
+    protected Document xmpDocument;
+
+    /**
+     * The encoding of the XMP document. Default is UTF8.
+     */
+    protected String encoding = ENCODING_UTF8;
+
+    /**
+     * A mapping of namespaces.
+     */
+    protected Map nsMappings = new HashMap();
+
+    /**
+     * Default constructor, creates blank XMP doc.
+     * 
+     * @throws IOException
+     *             If there is an error creating the initial document.
+     */
+    public XMPMetadata() throws IOException
+    {
+        xmpDocument = XMLUtil.newDocument();
+        ProcessingInstruction beginXPacket = xmpDocument
+                .createProcessingInstruction("xpacket",
+                        "begin=\"\uFEFF\" id=\"W5M0MpCehiHzreSzNTczkc9d\"");
+
+        xmpDocument.appendChild(beginXPacket);
+        Element xmpMeta = xmpDocument.createElementNS("adobe:ns:meta/",
+                "x:xmpmeta");
+        xmpMeta.setAttributeNS(XMPSchema.NS_NAMESPACE, "xmlns:x",
+                "adobe:ns:meta/");
+
+        xmpDocument.appendChild(xmpMeta);
+
+        Element rdf = xmpDocument.createElement("rdf:RDF");
+        rdf.setAttributeNS(XMPSchema.NS_NAMESPACE, "xmlns:rdf",
+                "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
+
+        xmpMeta.appendChild(rdf);
+
+        ProcessingInstruction endXPacket = xmpDocument
+                .createProcessingInstruction("xpacket", "end=\"w\"");
+        xmpDocument.appendChild(endXPacket);
+        init();
+    }
+
+    /**
+     * Constructor from an existing XML document.
+     * 
+     * @param doc
+     *            The root XMP document.
+     */
+    public XMPMetadata(Document doc)
+    {
+        xmpDocument = doc;
+        init();
+    }
+
+    private void init()
+    {
+        nsMappings.put(XMPSchemaPDF.NAMESPACE, XMPSchemaPDF.class);
+        nsMappings.put(XMPSchemaBasic.NAMESPACE, XMPSchemaBasic.class);
+        nsMappings
+                .put(XMPSchemaDublinCore.NAMESPACE, XMPSchemaDublinCore.class);
+        nsMappings.put(XMPSchemaMediaManagement.NAMESPACE,
+                XMPSchemaMediaManagement.class);
+        nsMappings.put(XMPSchemaRightsManagement.NAMESPACE,
+                XMPSchemaRightsManagement.class);
+        nsMappings.put(XMPSchemaBasicJobTicket.NAMESPACE,
+                XMPSchemaBasicJobTicket.class);
+        nsMappings.put(XMPSchemaDynamicMedia.NAMESPACE,
+                XMPSchemaDynamicMedia.class);
+        nsMappings.put(XMPSchemaPagedText.NAMESPACE, XMPSchemaPagedText.class);
+        nsMappings.put(XMPSchemaIptc4xmpCore.NAMESPACE,
+                XMPSchemaIptc4xmpCore.class);
+        nsMappings.put(XMPSchemaPhotoshop.NAMESPACE, XMPSchemaPhotoshop.class);
+    }
+
+    /**
+     * Will add a XMPSchema to the set of identified schemas.
+     * 
+     * The class needs to have a constructor with parameter Element
+     * 
+     * @param namespace
+     *            The namespace URI of the schmema for instance
+     *            http://purl.org/dc/elements/1.1/.
+     * @param xmpSchema
+     *            The schema to associated this identifier with.
+     */
+    public void addXMLNSMapping(String namespace, Class xmpSchema)
+    {
+
+        if (!(XMPSchema.class.isAssignableFrom(xmpSchema)))
+        {
+            throw new IllegalArgumentException(
+                    "Only XMPSchemas can be mapped to.");
+        }
+
+        nsMappings.put(namespace, xmpSchema);
+    }
+
+    /**
+     * Get the PDF Schema.
+     * 
+     * @return The first PDF schema in the list.
+     * 
+     * @throws IOException
+     *             If there is an error accessing the schema.
+     */
+    public XMPSchemaPDF getPDFSchema() throws IOException
+    {
+        return (XMPSchemaPDF) getSchemaByClass(XMPSchemaPDF.class);
+    }
+
+    /**
+     * Get the Basic Schema.
+     * 
+     * @return The first Basic schema in the list.
+     * 
+     * @throws IOException
+     *             If there is an error accessing the schema.
+     */
+    public XMPSchemaBasic getBasicSchema() throws IOException
+    {
+        return (XMPSchemaBasic) getSchemaByClass(XMPSchemaBasic.class);
+    }
+
+    /**
+     * Get the Dublin Core Schema.
+     * 
+     * @return The first Dublin schema in the list.
+     * 
+     * @throws IOException
+     *             If there is an error accessing the schema.
+     */
+    public XMPSchemaDublinCore getDublinCoreSchema() throws IOException
+    {
+        return (XMPSchemaDublinCore) getSchemaByClass(XMPSchemaDublinCore.class);
+    }
+
+    /**
+     * Get the Media Management Schema.
+     * 
+     * @return The first Media Management schema in the list.
+     * 
+     * @throws IOException
+     *             If there is an error accessing the schema.
+     */
+    public XMPSchemaMediaManagement getMediaManagementSchema()
+            throws IOException
+    {
+        return (XMPSchemaMediaManagement) getSchemaByClass(XMPSchemaMediaManagement.class);
+    }
+
+    /**
+     * Get the Schema Rights Schema.
+     * 
+     * @return The first Schema Rights schema in the list.
+     * 
+     * @throws IOException
+     *             If there is an error accessing the schema.
+     */
+    public XMPSchemaRightsManagement getRightsManagementSchema()
+            throws IOException
+    {
+        return (XMPSchemaRightsManagement) getSchemaByClass(XMPSchemaRightsManagement.class);
+    }
+
+    /**
+     * Get the Job Ticket Schema.
+     * 
+     * @return The first Job Ticket schema in the list.
+     * 
+     * @throws IOException
+     *             If there is an error accessing the schema.
+     */
+    public XMPSchemaBasicJobTicket getBasicJobTicketSchema() throws IOException
+    {
+        return (XMPSchemaBasicJobTicket) getSchemaByClass(XMPSchemaBasicJobTicket.class);
+    }
+
+    /**
+     * Get the Dynamic Media Schema.
+     * 
+     * @return The first Dynamic Media schema in the list.
+     * 
+     * @throws IOException
+     *             If there is an error accessing the schema.
+     */
+    public XMPSchemaDynamicMedia getDynamicMediaSchema() throws IOException
+    {
+        return (XMPSchemaDynamicMedia) getSchemaByClass(XMPSchemaDynamicMedia.class);
+    }
+
+    /**
+     * Get the Paged Text Schema.
+     * 
+     * @return The first Paged Text schema in the list.
+     * 
+     * @throws IOException
+     *             If there is an error accessing the schema.
+     */
+    public XMPSchemaPagedText getPagedTextSchema() throws IOException
+    {
+        return (XMPSchemaPagedText) getSchemaByClass(XMPSchemaPagedText.class);
+    }
+
+    /**
+     * Add a new Media Management schema.
+     * 
+     * @return The newly added schema.
+     */
+    public XMPSchemaMediaManagement addMediaManagementSchema()
+    {
+        XMPSchemaMediaManagement schema = new XMPSchemaMediaManagement(this);
+        return (XMPSchemaMediaManagement) basicAddSchema(schema);
+    }
+
+    /**
+     * Add a new Rights Managment schema.
+     * 
+     * @return The newly added schema.
+     */
+    public XMPSchemaRightsManagement addRightsManagementSchema()
+    {
+        XMPSchemaRightsManagement schema = new XMPSchemaRightsManagement(this);
+        return (XMPSchemaRightsManagement) basicAddSchema(schema);
+    }
+
+    /**
+     * Add a new Job Ticket schema.
+     * 
+     * @return The newly added schema.
+     */
+    public XMPSchemaBasicJobTicket addBasicJobTicketSchema()
+    {
+        XMPSchemaBasicJobTicket schema = new XMPSchemaBasicJobTicket(this);
+        return (XMPSchemaBasicJobTicket) basicAddSchema(schema);
+    }
+
+    /**
+     * Add a new Dynamic Media schema.
+     * 
+     * @return The newly added schema.
+     */
+    public XMPSchemaDynamicMedia addDynamicMediaSchema()
+    {
+        XMPSchemaDynamicMedia schema = new XMPSchemaDynamicMedia(this);
+        return (XMPSchemaDynamicMedia) basicAddSchema(schema);
+    }
+
+    /**
+     * Add a new Paged Text schema.
+     * 
+     * @return The newly added schema.
+     */
+    public XMPSchemaPagedText addPagedTextSchema()
+    {
+        XMPSchemaPagedText schema = new XMPSchemaPagedText(this);
+        return (XMPSchemaPagedText) basicAddSchema(schema);
+    }
+
+    /**
+     * Add a custom schema to the root rdf. The schema has to have been created
+     * as a child of this XMPMetadata.
+     * 
+     * @param schema
+     *            The schema to add.
+     */
+    public void addSchema(XMPSchema schema)
+    {
+        Element rdf = getRDFElement();
+        rdf.appendChild(schema.getElement());
+    }
+
+    /**
+     * Save the XMP document to a file.
+     * 
+     * @param file
+     *            The file to save the XMP document to.
+     * 
+     * @throws Exception
+     *             If there is an error while writing to the stream.
+     */
+    public void save(String file) throws Exception
+    {
+        XMLUtil.save(xmpDocument, file, encoding);
+    }
+
+    /**
+     * Save the XMP document to a stream.
+     * 
+     * @param outStream
+     *            The stream to save the XMP document to.
+     * 
+     * @throws TransformerException
+     *             If there is an error while writing to the stream.
+     */
+    public void save(OutputStream outStream) throws TransformerException
+    {
+        XMLUtil.save(xmpDocument, outStream, encoding);
+    }
+
+    /**
+     * Get the XML document as a byte array.
+     * 
+     * @return The metadata as an XML byte stream.
+     * @throws Exception
+     *             If there is an error creating the stream.
+     */
+    public byte[] asByteArray() throws Exception
+    {
+        return XMLUtil.asByteArray(xmpDocument, encoding);
+    }
+
+    /**
+     * Get the XML document from this object.
+     * 
+     * @return This object as an XML document.
+     */
+    public Document getXMPDocument()
+    {
+        return xmpDocument;
+    }
+
+    /**
+     * Generic add schema method.
+     * 
+     * @param schema
+     *            The schema to add.
+     * 
+     * @return The newly added schema.
+     */
+    protected XMPSchema basicAddSchema(XMPSchema schema)
+    {
+        Element rdf = getRDFElement();
+        rdf.appendChild(schema.getElement());
+        return schema;
+    }
+
+    /**
+     * Create and add a new PDF Schema to this metadata. Typically a XMP
+     * document will only have one PDF schema (but multiple are supported) so it
+     * is recommended that you first check the existence of a PDF scheme by
+     * using getPDFSchema()
+     * 
+     * @return A new blank PDF schema that is now part of the metadata.
+     */
+    public XMPSchemaPDF addPDFSchema()
+    {
+        XMPSchemaPDF schema = new XMPSchemaPDF(this);
+        return (XMPSchemaPDF) basicAddSchema(schema);
+    }
+
+    /**
+     * Create and add a new Dublin Core Schema to this metadata. Typically a XMP
+     * document will only have one schema for each type (but multiple are
+     * supported) so it is recommended that you first check the existence of a
+     * this scheme by using getDublinCoreSchema()
+     * 
+     * @return A new blank PDF schema that is now part of the metadata.
+     */
+    public XMPSchemaDublinCore addDublinCoreSchema()
+    {
+        XMPSchemaDublinCore schema = new XMPSchemaDublinCore(this);
+        return (XMPSchemaDublinCore) basicAddSchema(schema);
+    }
+
+    /**
+     * Create and add a new Basic Schema to this metadata. Typically a XMP
+     * document will only have one schema for each type (but multiple are
+     * supported) so it is recommended that you first check the existence of a
+     * this scheme by using getDublinCoreSchema()
+     * 
+     * @return A new blank PDF schema that is now part of the metadata.
+     */
+    public XMPSchemaBasic addBasicSchema()
+    {
+        XMPSchemaBasic schema = new XMPSchemaBasic(this);
+        return (XMPSchemaBasic) basicAddSchema(schema);
+    }
+
+    /**
+     * Create and add a new IPTC schema to this metadata.
+     * 
+     * @return A new blank IPTC schema that is now part of the metadata.
+     */
+    public XMPSchemaIptc4xmpCore addIptc4xmpCoreSchema()
+    {
+        XMPSchemaIptc4xmpCore schema = new XMPSchemaIptc4xmpCore(this);
+        return (XMPSchemaIptc4xmpCore) basicAddSchema(schema);
+    }
+
+    /**
+     * Create and add a new Photoshop schema to this metadata.
+     * 
+     * @return A new blank Photoshop schema that is now part of the metadata.
+     */
+    public XMPSchemaPhotoshop addPhotoshopSchema()
+    {
+        XMPSchemaPhotoshop schema = new XMPSchemaPhotoshop(this);
+        return (XMPSchemaPhotoshop) basicAddSchema(schema);
+    }
+
+    /**
+     * The encoding used to write the XML. Default value:UTF-8<br/> See the
+     * ENCODING_XXX constants
+     * 
+     * @param xmlEncoding
+     *            The encoding to write the XML as.
+     */
+    public void setEncoding(String xmlEncoding)
+    {
+        encoding = xmlEncoding;
+    }
+
+    /**
+     * Get the current encoding that will be used to write the XML.
+     * 
+     * @return The current encoding to write the XML to.
+     */
+    public String getEncoding()
+    {
+        return encoding;
+    }
+
+    /**
+     * Get the root RDF element.
+     * 
+     * @return The root RDF element.
+     */
+    private Element getRDFElement()
+    {
+        Element rdf = null;
+        NodeList nodes = xmpDocument.getElementsByTagName("rdf:RDF");
+        if (nodes.getLength() > 0)
+        {
+            rdf = (Element) nodes.item(0);
+        }
+        return rdf;
+    }
+
+    /**
+     * Load metadata from the filesystem.
+     * 
+     * @param file
+     *            The file to load the metadata from.
+     * 
+     * @return The loaded XMP document.
+     * 
+     * @throws IOException
+     *             If there is an error reading the data.
+     */
+    public static XMPMetadata load(String file) throws IOException
+    {
+        return new XMPMetadata(XMLUtil.parse(file));
+    }
+
+    /**
+     * Load a schema from an input source.
+     * 
+     * @param is
+     *            The input source to load the schema from.
+     * 
+     * @return The loaded/parsed schema.
+     * 
+     * @throws IOException
+     *             If there was an error while loading the schema.
+     */
+    public static XMPMetadata load(InputSource is) throws IOException
+    {
+        return new XMPMetadata(XMLUtil.parse(is));
+    }
+
+    /**
+     * Load metadata from the filesystem.
+     * 
+     * @param is
+     *            The stream to load the data from.
+     * 
+     * @return The loaded XMP document.
+     * 
+     * @throws IOException
+     *             If there is an error reading the data.
+     */
+    public static XMPMetadata load(InputStream is) throws IOException
+    {
+        return new XMPMetadata(XMLUtil.parse(is));
+    }
+
+    /**
+     * Test main program.
+     * 
+     * @param args
+     *            The command line arguments.
+     * @throws Exception
+     *             If there is an error.
+     */
+    public static void main(String[] args) throws Exception
+    {
+        XMPMetadata metadata = new XMPMetadata();
+        XMPSchemaPDF pdf = metadata.addPDFSchema();
+        pdf.setAbout("uuid:b8659d3a-369e-11d9-b951-000393c97fd8");
+        pdf.setKeywords("ben,bob,pdf");
+        pdf.setPDFVersion("1.3");
+        pdf.setProducer("Acrobat Distiller 6.0.1 for Macintosh");
+
+        XMPSchemaDublinCore dc = metadata.addDublinCoreSchema();
+        dc.addContributor("Ben Litchfield");
+        dc.addContributor("Solar Eclipse");
+        dc.addContributor("Some Other Guy");
+
+        XMPSchemaBasic basic = metadata.addBasicSchema();
+        Thumbnail t = new Thumbnail(metadata);
+        t.setFormat(Thumbnail.FORMAT_JPEG);
+        t.setImage("IMAGE_DATA");
+        t.setHeight(new Integer(100));
+        t.setWidth(new Integer(200));
+        basic.setThumbnail(t);
+        basic.setBaseURL("http://www.pdfbox.org/");
+
+        List schemas = metadata.getSchemas();
+        System.out.println("schemas=" + schemas);
+
+        metadata.save("test.xmp");
+    }
+
+    /**
+     * This will get a list of XMPSchema(or subclass) objects.
+     * 
+     * @return A non null read-only list of schemas that are part of this
+     *         metadata.
+     * 
+     * @throws IOException
+     *             If there is an error creating a specific schema.
+     */
+    public List getSchemas() throws IOException
+    {
+        NodeList schemaList = xmpDocument
+                .getElementsByTagName("rdf:Description");
+        List retval = new ArrayList(schemaList.getLength());
+        for (int i = 0; i < schemaList.getLength(); i++)
+        {
+            Element schema = (Element) schemaList.item(i);
+            boolean found = false;
+            NamedNodeMap attributes = schema.getAttributes();
+            for (int j = 0; j < attributes.getLength() && !found; j++)
+            {
+                Node attribute = attributes.item(j);
+                String name = attribute.getNodeName();
+                String value = attribute.getNodeValue();
+                if (name.startsWith("xmlns:") && nsMappings.containsKey(value))
+                {
+                    Class schemaClass = (Class) nsMappings.get(value);
+                    try
+                    {
+                        Constructor ctor = schemaClass
+                                .getConstructor(new Class[] { Element.class,
+                                        String.class });
+                        retval.add(ctor.newInstance(new Object[] { schema,
+                                name.substring(6) }));
+                        found = true;
+                    }
+                    catch(NoSuchMethodException e)
+                    {
+                        throw new IOException(
+                                "Error: Class "
+                                        + schemaClass.getName()
+                                        + " must have a constructor with the signature of "
+                                        + schemaClass.getName()
+                                        + "( org.w3c.dom.Element, java.lang.String )");
+                    }
+                    catch(Exception e)
+                    {
+                        e.printStackTrace();
+                        throw new IOException(e.getMessage());
+                    }
+                }
+            }
+            if (!found)
+            {
+                retval.add(new XMPSchema(schema, null));
+            }
+        }
+        return retval;
+    }
+
+    /**
+     * Will return all schemas that fit the given namespaceURI. Which is only
+     * done by using the namespace mapping (nsMapping) and not by actually
+     * checking the xmlns property.
+     * 
+     * @param namespaceURI
+     *            The namespaceURI to filter for.
+     * @return A list containing the found schemas or an empty list if non are
+     *         found or the namespaceURI could not be found in the namespace
+     *         mapping.
+     * @throws IOException
+     *             If an operation on the document fails.
+     */
+    public List getSchemasByNamespaceURI(String namespaceURI)
+            throws IOException
+    {
+
+        List l = getSchemas();
+        List result = new LinkedList();
+
+        Class schemaClass = (Class) nsMappings.get(namespaceURI);
+        if (schemaClass == null)
+        {
+            return result;
+        }
+
+        Iterator i = l.iterator();
+        while (i.hasNext())
+        {
+            XMPSchema schema = (XMPSchema) i.next();
+
+            if (schemaClass.isAssignableFrom(schema.getClass()))
+            {
+                result.add(schema);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * This will return true if the XMP contains an unknown schema.
+     * 
+     * @return True if an unknown schema is found, false otherwise
+     * 
+     * @throws IOException
+     *             If there is an error
+     */
+    public boolean hasUnknownSchema() throws IOException
+    {
+        NodeList schemaList = xmpDocument
+                .getElementsByTagName("rdf:Description");
+        for (int i = 0; i < schemaList.getLength(); i++)
+        {
+            Element schema = (Element) schemaList.item(i);
+            NamedNodeMap attributes = schema.getAttributes();
+            for (int j = 0; j < attributes.getLength(); j++)
+            {
+                Node attribute = attributes.item(j);
+                String name = attribute.getNodeName();
+                String value = attribute.getNodeValue();
+                if (name.startsWith("xmlns:") && !nsMappings.containsKey(value)
+                        && !value.equals(ResourceEvent.NAMESPACE))
+                {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Tries to retrieve a schema from this by classname.
+     * 
+     * @param targetSchema
+     *            Class for targetSchema.
+     * 
+     * @return XMPSchema or null if no target is found.
+     * 
+     * @throws IOException
+     *             if there was an error creating the schemas of this.
+     */
+    public XMPSchema getSchemaByClass(Class targetSchema) throws IOException
+    {
+        Iterator iter = getSchemas().iterator();
+        while (iter.hasNext())
+        {
+            XMPSchema element = (XMPSchema) iter.next();
+            if (element.getClass().getName().equals(targetSchema.getName()))
+            {
+                return element;
+            }
+        }
+        // not found
+        return null;
+    }
+
+    /**
+     * Merge this metadata with the given metadata object.
+     * 
+     * @param metadata The metadata to merge with this document.
+     * 
+     * @throws IOException If there is an error merging the data.
+     */
+    public void merge(XMPMetadata metadata) throws IOException
+    {
+        List schemas2 = metadata.getSchemas();
+        for (Iterator iterator = schemas2.iterator(); iterator.hasNext();)
+        {
+            XMPSchema schema2 = (XMPSchema) iterator.next();
+            XMPSchema schema1 = getSchemaByClass(schema2.getClass());
+            if (schema1 == null)
+            {
+                Element rdf = getRDFElement();
+                rdf.appendChild(xmpDocument.importNode(schema2.getElement(),
+                        true));
+            }
+            else
+            {
+                schema1.merge(schema2);
+            }
+        }
+    }
+}

Added: incubator/pdfbox/trunk/jempbox/src/org/jempbox/xmp/XMPSchema.java
URL: http://svn.apache.org/viewvc/incubator/pdfbox/trunk/jempbox/src/org/jempbox/xmp/XMPSchema.java?rev=670399&view=auto
==============================================================================
--- incubator/pdfbox/trunk/jempbox/src/org/jempbox/xmp/XMPSchema.java (added)
+++ incubator/pdfbox/trunk/jempbox/src/org/jempbox/xmp/XMPSchema.java Sun Jun 22 12:09:15 2008
@@ -0,0 +1,1094 @@
+/**
+ * Copyright (c) 2006, www.jempbox.org
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of pdfbox; nor the names of its
+ *    contributors may be used to endorse or promote products derived from this
+ *    software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://www.jempbox.org
+ *
+ */
+package org.jempbox.xmp;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.jempbox.impl.DateConverter;
+import org.jempbox.impl.XMLUtil;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * This class represents a metadata schema that can be stored in an XMP
+ * document. It handles all generic properties that are available. See
+ * subclasses for access to specific properties.
+ * 
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.8 $
+ */
+public class XMPSchema
+{
+    /**
+     * The standard xmlns namespace.
+     */
+    public static final String NS_NAMESPACE = "http://www.w3.org/2000/xmlns/";
+
+    /**
+     * The XML schema prefix.
+     */
+    protected String prefix;
+
+    /**
+     * The DOM representation of this object.
+     */
+    protected Element schema = null;
+
+    /**
+     * Create a new blank schema that can be populated.
+     * 
+     * @param parent
+     *            The parent XMP document that this schema will be part of.
+     * @param namespaceName
+     *            The name of the namespace, ie pdf,dc,...
+     * @param namespaceURI
+     *            The URI of the namespace, ie "http://ns.adobe.com/pdf/1.3/"
+     */
+    public XMPSchema(XMPMetadata parent, String namespaceName,
+            String namespaceURI)
+    {
+        schema = parent.xmpDocument.createElementNS(
+                "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
+                "rdf:Description");
+        prefix = namespaceName;
+        schema.setAttributeNS(NS_NAMESPACE, "xmlns:" + namespaceName,
+                namespaceURI);
+    }
+
+    /**
+     * Create schema from an existing XML element.
+     * 
+     * @param element
+     *            The existing XML element.
+     * @param aPrefix
+     *            The XML prefix.
+     */
+    public XMPSchema(Element element, String aPrefix)
+    {
+        schema = element;
+        if (aPrefix != null)
+        {
+            prefix = aPrefix;
+        }
+        else
+        {
+            prefix = "";
+        }
+    }
+
+    /**
+     * Get the XML element that is represented by this schema.
+     * 
+     * @return The root XML element of this schema.
+     */
+    public Element getElement()
+    {
+        return schema;
+    }
+
+    /**
+     * Get the RDF about attribute.
+     * 
+     * @return The RDF 'about' attribute.
+     */
+    public String getAbout()
+    {
+        return getTextProperty("rdf:about");
+    }
+
+    /**
+     * Set the RDF 'about' attribute. Passing in null will clear this attribute.
+     * 
+     * @param about
+     *            The new RFD about value.
+     */
+    public void setAbout(String about)
+    {
+        if (about == null)
+        {
+            schema.removeAttribute("rdf:about");
+        }
+        else
+        {
+            schema.setAttribute("rdf:about", about);
+        }
+    }
+
+    /**
+     * Set a simple text property on the schema.
+     * 
+     * @param propertyName
+     *            The name of the property, it must contain the namespace
+     *            prefix, ie "pdf:Keywords"
+     * @param propertyValue
+     *            The value for the property, can be any string. Passing null
+     *            will remove the property.
+     */
+    public void setTextProperty(String propertyName, String propertyValue)
+    {
+        if (propertyValue == null)
+        {
+            schema.removeAttribute(propertyName);
+            NodeList keywordList = schema.getElementsByTagName(propertyName);
+            for (int i = 0; i < keywordList.getLength(); i++)
+            {
+                schema.removeChild(keywordList.item(i));
+            }
+
+        }
+        else
+        {
+            if (schema.hasAttribute(propertyName))
+            {
+                schema.setAttribute(propertyName, propertyValue);
+            }
+            else
+            {
+                if (schema.hasChildNodes())
+                {
+                    NodeList nodeList = schema
+                            .getElementsByTagName(propertyName);
+                    if (nodeList.getLength() > 0)
+                    {
+                        Element node = (Element) nodeList.item(0);
+                        node.setNodeValue(propertyValue);
+                    }
+                    else
+                    {
+                        Element textNode = schema.getOwnerDocument()
+                                .createElement(propertyName);
+                        XMLUtil.setStringValue(textNode, propertyValue);
+                        schema.appendChild(textNode);
+                    }
+                }
+                else
+                {
+                    schema.setAttribute(propertyName, propertyValue);
+                }
+            }
+        }
+    }
+
+    /**
+     * Get the value of a simple text property.
+     * 
+     * @param propertyName
+     *            The name of the property to get, it must include the namespace
+     *            prefix. ie "pdf:Keywords".
+     * 
+     * @return The value of the text property or the null if there is no value.
+     */
+    public String getTextProperty(String propertyName)
+    {
+        // propertyValue == null does not work, since getAttribute returns the
+        // empty string if the attribute is not found
+
+        if (schema.hasAttribute(propertyName))
+        {
+            return schema.getAttribute(propertyName);
+        }
+        else
+        {
+            NodeList nodes = schema.getElementsByTagName(propertyName);
+            if (nodes.getLength() > 0)
+            {
+                Element node = (Element) nodes.item(0);
+                return XMLUtil.getStringValue(node);
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Get the value of the property as a date.
+     * 
+     * @param propertyName
+     *            The fully qualified property name for the date.
+     * 
+     * @return The value of the property as a date.
+     * 
+     * @throws IOException
+     *             If there is an error converting the value to a date.
+     */
+    public Calendar getDateProperty(String propertyName) throws IOException
+    {
+        return DateConverter.toCalendar(getTextProperty(propertyName));
+    }
+
+    /**
+     * Set the value of the property as a date.
+     * 
+     * @param propertyName
+     *            The fully qualified property name for the date.
+     * @param date
+     *            The date to set, or null to clear.
+     */
+    public void setDateProperty(String propertyName, Calendar date)
+    {
+        setTextProperty(propertyName, DateConverter.toISO8601(date));
+    }
+
+    /**
+     * Get the value of the property as a boolean.
+     * 
+     * @param propertyName
+     *            The fully qualified property name for the boolean.
+     * 
+     * @return The value of the property as a boolean.
+     */
+    public Boolean getBooleanProperty(String propertyName)
+    {
+        Boolean value = null;
+        String stringValue = getTextProperty(propertyName);
+        if (stringValue != null)
+        {
+            value = stringValue.equals("True") ? Boolean.TRUE : Boolean.FALSE;
+        }
+        return value;
+    }
+
+    /**
+     * Set the value of the property as a boolean.
+     * 
+     * @param propertyName
+     *            The fully qualified property name for the boolean.
+     * @param bool
+     *            The boolean to set, or null to clear.
+     */
+    public void setBooleanProperty(String propertyName, Boolean bool)
+    {
+        String value = null;
+        if (bool != null)
+        {
+            value = bool.booleanValue() ? "True" : "False";
+        }
+        setTextProperty(propertyName, value);
+    }
+
+    /**
+     * Get the value of the property as an integer.
+     * 
+     * @param propertyName
+     *            The fully qualified property name for the integer.
+     * 
+     * @return The value of the property as an integer.
+     */
+    public Integer getIntegerProperty(String propertyName)
+    {
+        Integer retval = null;
+        String intProperty = getTextProperty(propertyName);
+        if (intProperty.equals(""))
+        {
+            retval = new Integer(intProperty);
+        }
+        return retval;
+    }
+
+    /**
+     * Set the value of the property as an integer.
+     * 
+     * @param propertyName
+     *            The fully qualified property name for the integer.
+     * @param intValue
+     *            The int to set, or null to clear.
+     */
+    public void setIntegerProperty(String propertyName, Integer intValue)
+    {
+        String textValue = null;
+        if (intValue != null)
+        {
+            textValue = intValue.toString();
+        }
+        setTextProperty(propertyName, textValue);
+    }
+
+    /**
+     * Remove all matching entries with the given value from the bag.
+     * 
+     * @param bagName
+     *            The name of the bag, it must include the namespace prefix. ie
+     *            "pdf:Keywords".
+     * @param bagValue
+     *            The value to remove from the bagList.
+     */
+    public void removeBagValue(String bagName, String bagValue)
+    {
+        Element bagElement = null;
+        NodeList nodes = schema.getElementsByTagName(bagName);
+        if (nodes.getLength() > 0)
+        {
+            Element contElement = (Element) nodes.item(0);
+            NodeList bagList = contElement.getElementsByTagName("rdf:Bag");
+            if (bagList.getLength() > 0)
+            {
+                bagElement = (Element) bagList.item(0);
+                NodeList items = bagElement.getElementsByTagName("rdf:li");
+                for (int i = items.getLength() - 1; i >= 0; i--)
+                {
+                    Element li = (Element) items.item(i);
+                    String value = XMLUtil.getStringValue(li);
+                    if (value.equals(bagValue))
+                    {
+                        bagElement.removeChild(li);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Add an entry to a bag property.
+     * 
+     * @param bagName
+     *            The name of the bag, it must include the namespace prefix. ie
+     *            "pdf:Keywords".
+     * @param bagValue
+     *            The value to add to the bagList.
+     */
+    public void addBagValue(String bagName, String bagValue)
+    {
+        Element bagElement = null;
+        NodeList nodes = schema.getElementsByTagName(bagName);
+        if (nodes.getLength() > 0)
+        {
+            Element contElement = (Element) nodes.item(0);
+            NodeList bagList = contElement.getElementsByTagName("rdf:Bag");
+            if (bagList.getLength() > 0)
+            {
+                bagElement = (Element) bagList.item(0);
+            }
+        }
+        else
+        {
+            Element contElement = schema.getOwnerDocument().createElement(
+                    bagName);
+            schema.appendChild(contElement);
+            bagElement = schema.getOwnerDocument().createElement("rdf:Bag");
+            contElement.appendChild(bagElement);
+        }
+        Element liElement = schema.getOwnerDocument().createElement("rdf:li");
+        XMLUtil.setStringValue(liElement, bagValue);
+        bagElement.appendChild(liElement);
+    }
+
+    /**
+     * Get all the values of the bag property. This will return a list of
+     * java.lang.String objects, this is a read-only list.
+     * 
+     * @param bagName
+     *            The name of the bag property to get, it must include the
+     *            namespace prefix. ie "pdf:Keywords"
+     * 
+     * @return All of the values of the bag property in a list.
+     */
+    public List getBagList(String bagName)
+    {
+        List retval = null;
+        NodeList nodes = schema.getElementsByTagName(bagName);
+        if (nodes.getLength() > 0)
+        {
+            Element contributor = (Element) nodes.item(0);
+            NodeList bagList = contributor.getElementsByTagName("rdf:Bag");
+            if (bagList.getLength() > 0)
+            {
+                Element bag = (Element) bagList.item(0);
+                retval = new ArrayList();
+                NodeList items = bag.getElementsByTagName("rdf:li");
+                for (int i = 0; i < items.getLength(); i++)
+                {
+                    Element li = (Element) items.item(i);
+                    retval.add(XMLUtil.getStringValue(li));
+                }
+                retval = Collections.unmodifiableList(retval);
+            }
+        }
+
+        return retval;
+    }
+
+    /**
+     * Remove all matching values from a sequence property.
+     * 
+     * @param seqName
+     *            The name of the sequence property. It must include the
+     *            namespace prefix. ie "pdf:Keywords".
+     * @param seqValue
+     *            The value to remove from the list.
+     */
+    public void removeSequenceValue(String seqName, String seqValue)
+    {
+        Element bagElement = null;
+        NodeList nodes = schema.getElementsByTagName(seqName);
+        if (nodes.getLength() > 0)
+        {
+            Element contElement = (Element) nodes.item(0);
+            NodeList bagList = contElement.getElementsByTagName("rdf:Seq");
+            if (bagList.getLength() > 0)
+            {
+                bagElement = (Element) bagList.item(0);
+                NodeList items = bagElement.getElementsByTagName("rdf:li");
+                for (int i = items.getLength() - 1; i >= 0; i--)
+                {
+                    Element li = (Element) items.item(i);
+                    String value = XMLUtil.getStringValue(li);
+                    if (value.equals(seqValue))
+                    {
+                        bagElement.removeChild(li);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Remove a value from a sequence property. This will remove all entries
+     * from the list.
+     * 
+     * @param seqName
+     *            The name of the sequence property. It must include the
+     *            namespace prefix. ie "pdf:Keywords".
+     * @param seqValue
+     *            The value to remove from the list.
+     */
+    public void removeSequenceValue(String seqName, Elementable seqValue)
+    {
+        Element bagElement = null;
+        NodeList nodes = schema.getElementsByTagName(seqName);
+        if (nodes.getLength() > 0)
+        {
+            Element contElement = (Element) nodes.item(0);
+            NodeList bagList = contElement.getElementsByTagName("rdf:Seq");
+            if (bagList.getLength() > 0)
+            {
+                bagElement = (Element) bagList.item(0);
+                NodeList items = bagElement.getElementsByTagName("rdf:li");
+                for (int i = 0; i < items.getLength(); i++)
+                {
+                    Element li = (Element) items.item(i);
+                    if (li == seqValue.getElement())
+                    {
+                        bagElement.removeChild(li);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Add a new value to a sequence property.
+     * 
+     * @param seqName
+     *            The name of the sequence property, it must include the
+     *            namespace prefix. ie "pdf:Keywords"
+     * @param seqValue
+     *            The value to add to the sequence.
+     */
+    public void addSequenceValue(String seqName, String seqValue)
+    {
+        Element bagElement = null;
+        NodeList nodes = schema.getElementsByTagName(seqName);
+        if (nodes.getLength() > 0)
+        {
+            Element contElement = (Element) nodes.item(0);
+            NodeList bagList = contElement.getElementsByTagName("rdf:Seq");
+            if (bagList.getLength() > 0)
+            {
+                bagElement = (Element) bagList.item(0);
+            }
+            else
+            {
+                // xml is crap discard it
+                schema.removeChild(nodes.item(0));
+            }
+        }
+        if (bagElement == null)
+        {
+            Element contElement = schema.getOwnerDocument().createElement(
+                    seqName);
+            schema.appendChild(contElement);
+            bagElement = schema.getOwnerDocument().createElement("rdf:Seq");
+            contElement.appendChild(bagElement);
+        }
+        Element liElement = schema.getOwnerDocument().createElement("rdf:li");
+        liElement.appendChild(schema.getOwnerDocument()
+                .createTextNode(seqValue));
+        bagElement.appendChild(liElement);
+    }
+
+    /**
+     * Add a new value to a sequence property.
+     * 
+     * @param seqName
+     *            The name of the sequence property, it must include the
+     *            namespace prefix. ie "pdf:Keywords"
+     * @param seqValue
+     *            The value to add to the sequence.
+     */
+    public void addSequenceValue(String seqName, Elementable seqValue)
+    {
+        Element bagElement = null;
+        NodeList nodes = schema.getElementsByTagName(seqName);
+        if (nodes.getLength() > 0)
+        {
+            Element contElement = (Element) nodes.item(0);
+            NodeList bagList = contElement.getElementsByTagName("rdf:Seq");
+            if (bagList.getLength() > 0)
+            {
+                bagElement = (Element) bagList.item(0);
+            }
+        }
+        else
+        {
+            Element contElement = schema.getOwnerDocument().createElement(
+                    seqName);
+            schema.appendChild(contElement);
+            bagElement = schema.getOwnerDocument().createElement("rdf:Seq");
+            contElement.appendChild(bagElement);
+        }
+        bagElement.appendChild(seqValue.getElement());
+    }
+
+    /**
+     * Get all the values in a sequence property.
+     * 
+     * @param seqName
+     *            The name of the sequence property, it must include the
+     *            namespace prefix. ie "pdf:Keywords".
+     * 
+     * @return A read-only list of java.lang.String objects or null if the
+     *         property does not exist.
+     */
+    public List getSequenceList(String seqName)
+    {
+        List retval = null;
+        NodeList nodes = schema.getElementsByTagName(seqName);
+        if (nodes.getLength() > 0)
+        {
+            Element contributor = (Element) nodes.item(0);
+            NodeList bagList = contributor.getElementsByTagName("rdf:Seq");
+            if (bagList.getLength() > 0)
+            {
+                Element bag = (Element) bagList.item(0);
+                retval = new ArrayList();
+                NodeList items = bag.getElementsByTagName("rdf:li");
+                for (int i = 0; i < items.getLength(); i++)
+                {
+                    Element li = (Element) items.item(i);
+                    retval.add(XMLUtil.getStringValue(li));
+                }
+                retval = Collections.unmodifiableList(retval);
+            }
+        }
+
+        return retval;
+    }
+
+    /**
+     * Get a list of ResourceEvent objects.
+     * 
+     * @param seqName
+     *            The name of the sequence to retrieve.
+     * 
+     * @return A list of ResourceEvent objects or null if they do not exist.
+     */
+    public List getEventSequenceList(String seqName)
+    {
+        List retval = null;
+        NodeList nodes = schema.getElementsByTagName(seqName);
+        if (nodes.getLength() > 0)
+        {
+            Element contributor = (Element) nodes.item(0);
+            NodeList bagList = contributor.getElementsByTagName("rdf:Seq");
+            if (bagList.getLength() > 0)
+            {
+                Element bag = (Element) bagList.item(0);
+                retval = new ArrayList();
+                NodeList items = bag.getElementsByTagName("rdf:li");
+                for (int i = 0; i < items.getLength(); i++)
+                {
+                    Element li = (Element) items.item(i);
+                    retval.add(new ResourceEvent(li));
+                }
+                retval = Collections.unmodifiableList(retval);
+            }
+        }
+
+        return retval;
+    }
+
+    /**
+     * Remove a date sequence value from the list.
+     * 
+     * @param seqName
+     *            The name of the sequence property, it must include the
+     *            namespace prefix. ie "pdf:Keywords"
+     * @param date
+     *            The date to remove from the sequence property.
+     */
+    public void removeSequenceDateValue(String seqName, Calendar date)
+    {
+        String dateAsString = DateConverter.toISO8601(date);
+        removeSequenceValue(seqName, dateAsString);
+    }
+
+    /**
+     * Add a date sequence value to the list.
+     * 
+     * @param seqName
+     *            The name of the sequence property, it must include the
+     *            namespace prefix. ie "pdf:Keywords"
+     * @param date
+     *            The date to add to the sequence property.
+     */
+    public void addSequenceDateValue(String seqName, Calendar date)
+    {
+        String dateAsString = DateConverter.toISO8601(date);
+        addSequenceValue(seqName, dateAsString);
+    }
+
+    /**
+     * Get all the date values in a sequence property.
+     * 
+     * @param seqName
+     *            The name of the sequence property, it must include the
+     *            namespace prefix. ie "pdf:Keywords".
+     * 
+     * @return A read-only list of java.util.Calendar objects or null if the
+     *         property does not exist.
+     * 
+     * @throws IOException
+     *             If there is an error converting the value to a date.
+     */
+    public List getSequenceDateList(String seqName) throws IOException
+    {
+        List strings = getSequenceList(seqName);
+        List retval = null;
+        if (strings != null)
+        {
+            retval = new ArrayList();
+            for (int i = 0; i < strings.size(); i++)
+            {
+                retval.add(DateConverter.toCalendar((String) strings.get(i)));
+            }
+        }
+        return retval;
+    }
+
+    /**
+     * Set the value of a multi-lingual property.
+     * 
+     * @param propertyName
+     *            The name of the property, it must include the namespace
+     *            prefix. ie "pdf:Keywords"
+     * @param language
+     *            The language code of the value. If null then "x-default" is
+     *            assumed.
+     * @param value
+     *            The value of the property in the specified language.
+     */
+    public void setLanguageProperty(String propertyName, String language,
+            String value)
+    {
+        NodeList nodes = schema.getElementsByTagName(propertyName);
+        Element property = null;
+        if (nodes.getLength() == 0)
+        {
+            if (value == null)
+            {
+                // value is null, it doesn't already exist so there
+                // is nothing to do.
+                return;
+            }
+            property = schema.getOwnerDocument().createElement(propertyName);
+            schema.appendChild(property);
+        }
+        else
+        {
+            property = (Element) nodes.item(0);
+        }
+        Element alt = null;
+        NodeList altList = property.getElementsByTagName("rdf:Alt");
+        if (altList.getLength() == 0)
+        {
+            if (value == null)
+            {
+                // value is null, it doesn't already exist so there
+                // is nothing to do.
+                return;
+            }
+            alt = schema.getOwnerDocument().createElement("rdf:Alt");
+            property.appendChild(alt);
+        }
+        else
+        {
+            alt = (Element) altList.item(0);
+        }
+        NodeList items = alt.getElementsByTagName("rdf:li");
+        if (language == null)
+        {
+            language = "x-default";
+        }
+        boolean foundValue = false;
+        for (int i = 0; i < items.getLength(); i++)
+        {
+            Element li = (Element) items.item(i);
+            if (value == null)
+            {
+                alt.removeChild(li);
+            }
+            else if (language.equals(li.getAttribute("xml:lang")))
+            {
+                foundValue = true;
+                XMLUtil.setStringValue(li, value);
+            }
+        }
+        if (value != null && !foundValue)
+        {
+            Element li = schema.getOwnerDocument().createElement("rdf:li");
+            li.setAttribute("xml:lang", language);
+            XMLUtil.setStringValue(li, value);
+            if (language.equals("x-default"))
+            {
+                // default should be first element, see XMP spec
+                alt.insertBefore(li, alt.getFirstChild());
+            }
+            else
+            {
+                alt.appendChild(li);
+            }
+
+        }
+    }
+
+    /**
+     * Get the value of a multi-lingual property.
+     * 
+     * @param propertyName
+     *            The name of the property, it must include the namespace
+     *            prefix. ie "pdf:Keywords"
+     * @param language
+     *            The language code of the value. If null then "x-default" is
+     *            assumed.
+     * 
+     * @return The value of the language property.
+     */
+    public String getLanguageProperty(String propertyName, String language)
+    {
+        String retval = null;
+        if (language == null)
+        {
+            language = "x-default";
+        }
+
+        NodeList nodes = schema.getElementsByTagName(propertyName);
+        if (nodes.getLength() > 0)
+        {
+            Element property = (Element) nodes.item(0);
+            NodeList altList = property.getElementsByTagName("rdf:Alt");
+            if (altList.getLength() > 0)
+            {
+                Element alt = (Element) altList.item(0);
+                NodeList items = alt.getElementsByTagName("rdf:li");
+                for (int i = 0; i < items.getLength() && retval == null; i++)
+                {
+                    Element li = (Element) items.item(i);
+                    String elementLanguage = li.getAttribute("xml:lang");
+                    if (language.equals(elementLanguage))
+                    {
+                        retval = XMLUtil.getStringValue(li);
+                    }
+                }
+            }
+        }
+        return retval;
+    }
+
+    /**
+     * Set the value of a multi-lingual property.
+     * 
+     * @param propertyName
+     *            The name of the property, it must include the namespace
+     *            prefix. ie "pdf:Keywords"
+     * @param language
+     *            The language code of the value. If null then "x-default" is
+     *            assumed.
+     * @param value
+     *            The value of the property in the specified language.
+     */
+    public void setThumbnailProperty(String propertyName, String language,
+            Thumbnail value)
+    {
+        NodeList nodes = schema.getElementsByTagName(propertyName);
+        Element property = null;
+        if (nodes.getLength() == 0)
+        {
+            if (value == null)
+            {
+                // value is null, it doesn't already exist so there
+                // is nothing to do.
+                return;
+            }
+            property = schema.getOwnerDocument().createElement(propertyName);
+            schema.appendChild(property);
+        }
+        else
+        {
+            property = (Element) nodes.item(0);
+        }
+        Element alt = null;
+        NodeList altList = property.getElementsByTagName("rdf:Alt");
+        if (altList.getLength() == 0)
+        {
+            if (value == null)
+            {
+                // value is null, it doesn't already exist so there
+                // is nothing to do.
+                return;
+            }
+            alt = schema.getOwnerDocument().createElement("rdf:Alt");
+            property.appendChild(alt);
+        }
+        else
+        {
+            alt = (Element) altList.item(0);
+        }
+        NodeList items = alt.getElementsByTagName("rdf:li");
+        if (language == null)
+        {
+            language = "x-default";
+        }
+        boolean foundValue = false;
+        for (int i = 0; i < items.getLength(); i++)
+        {
+            Element li = (Element) items.item(i);
+            if (value == null)
+            {
+                alt.removeChild(li);
+            }
+            else if (language.equals(li.getAttribute("xml:lang")))
+            {
+                foundValue = true;
+                alt.replaceChild(li, value.getElement());
+            }
+        }
+        if (value != null && !foundValue)
+        {
+            Element li = value.getElement();
+            li.setAttribute("xml:lang", language);
+            if (language.equals("x-default"))
+            {
+                // default should be first element, see XMP spec
+                alt.insertBefore(li, alt.getFirstChild());
+            }
+            else
+            {
+                alt.appendChild(li);
+            }
+
+        }
+    }
+
+    /**
+     * Get the value of a multi-lingual property.
+     * 
+     * @param propertyName
+     *            The name of the property, it must include the namespace
+     *            prefix. ie "pdf:Keywords"
+     * @param language
+     *            The language code of the value. If null then "x-default" is
+     *            assumed.
+     * 
+     * @return The value of the language property.
+     */
+    public Thumbnail getThumbnailProperty(String propertyName, String language)
+    {
+        Thumbnail retval = null;
+        if (language == null)
+        {
+            language = "x-default";
+        }
+
+        NodeList nodes = schema.getElementsByTagName(propertyName);
+        if (nodes.getLength() > 0)
+        {
+            Element property = (Element) nodes.item(0);
+            NodeList altList = property.getElementsByTagName("rdf:Alt");
+            if (altList.getLength() > 0)
+            {
+                Element alt = (Element) altList.item(0);
+                NodeList items = alt.getElementsByTagName("rdf:li");
+                for (int i = 0; i < items.getLength() && retval == null; i++)
+                {
+                    Element li = (Element) items.item(i);
+                    String elementLanguage = li.getAttribute("xml:lang");
+                    if (language.equals(elementLanguage))
+                    {
+                        retval = new Thumbnail(li);
+                    }
+                }
+            }
+        }
+        return retval;
+    }
+
+    /**
+     * Get a list of all languages that are currently defined for a specific
+     * property.
+     * 
+     * @param propertyName
+     *            The name of the property, it must include the namespace
+     *            prefix. ie "pdf:Keywords"
+     * 
+     * @return A list of all languages, this will return an non-null empty list
+     *         if none have been defined.
+     */
+    public List getLanguagePropertyLanguages(String propertyName)
+    {
+        List retval = new ArrayList();
+
+        NodeList nodes = schema.getElementsByTagName(propertyName);
+        if (nodes.getLength() > 0)
+        {
+            Element property = (Element) nodes.item(0);
+            NodeList altList = property.getElementsByTagName("rdf:Alt");
+            if (altList.getLength() > 0)
+            {
+                Element alt = (Element) altList.item(0);
+                NodeList items = alt.getElementsByTagName("rdf:li");
+                for (int i = 0; i < items.getLength(); i++)
+                {
+                    Element li = (Element) items.item(i);
+                    String elementLanguage = li.getAttribute("xml:lang");
+                    if (elementLanguage == null)
+                    {
+                        retval.add("x-default");
+                    }
+                    else
+                    {
+                        retval.add(elementLanguage);
+                    }
+                }
+            }
+        }
+        return retval;
+    }
+
+    /**
+     * A basic schema merge, it merges bags and sequences and replace everything
+     * else.
+     * 
+     * @param xmpSchema The schema to merge.
+     * @throws IOException If there is an error during the merge.
+     */
+    public void merge(XMPSchema xmpSchema) throws IOException
+    {
+        if (!xmpSchema.getClass().equals(this.getClass()))
+        {
+            throw new IOException("Can only merge schemas of the same type.");
+        }
+
+        NamedNodeMap attributes = xmpSchema.getElement().getAttributes();
+        for (int i = 0; i < attributes.getLength(); i++)
+        {
+            Node a = attributes.item(i);
+            String name = a.getNodeName();
+            if (name.startsWith(prefix))
+            {
+                String newValue = xmpSchema.getTextProperty(name);
+                setTextProperty(name, newValue);
+            }
+        }
+        NodeList nodes = xmpSchema.getElement().getChildNodes();
+
+        for (int i = 0; i < nodes.getLength(); i++)
+        {
+            Node a = nodes.item(i);
+            String name = a.getNodeName();
+            if (name.startsWith(prefix))
+            {
+                if (a instanceof Element)
+                {
+                    Element e = (Element) a;
+                    if (nodes.getLength() > 0)
+                    {
+                        NodeList seqList = e.getElementsByTagName("rdf:Seq");
+                        if (seqList.getLength() > 0)
+                        {
+                            List newList = xmpSchema.getSequenceList(name);
+                            List oldList = getSequenceList(name);
+
+                            Iterator it = newList.iterator();
+
+                            while (it.hasNext())
+                            {
+                                Object object = it.next();
+                                if (oldList == null
+                                        || !oldList.contains(object))
+                                {
+                                    addSequenceValue(name, (String) object);
+                                }
+                            }
+                            continue;
+                        }
+                        NodeList bagList = e.getElementsByTagName("rdf:Bag");
+                        if (bagList.getLength() > 0)
+                        {
+                            List newList = xmpSchema.getBagList(name);
+                            List oldList = getBagList(name);
+
+                            Iterator it = newList.iterator();
+
+                            while (it.hasNext())
+                            {
+                                Object object = it.next();
+                                if (oldList == null
+                                        || !oldList.contains(object))
+                                {
+                                    addBagValue(name, (String) object);
+                                }
+                            }
+                            continue;
+                        }
+                    }
+                }
+                String newValue = xmpSchema.getTextProperty(name);
+                setTextProperty(name, newValue);
+            }
+        }
+    }
+}
\ No newline at end of file

Added: incubator/pdfbox/trunk/jempbox/src/org/jempbox/xmp/XMPSchemaBasic.java
URL: http://svn.apache.org/viewvc/incubator/pdfbox/trunk/jempbox/src/org/jempbox/xmp/XMPSchemaBasic.java?rev=670399&view=auto
==============================================================================
--- incubator/pdfbox/trunk/jempbox/src/org/jempbox/xmp/XMPSchemaBasic.java (added)
+++ incubator/pdfbox/trunk/jempbox/src/org/jempbox/xmp/XMPSchemaBasic.java Sun Jun 22 12:09:15 2008
@@ -0,0 +1,388 @@
+/**
+ * Copyright (c) 2006, www.jempbox.org
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of pdfbox; nor the names of its
+ *    contributors may be used to endorse or promote products derived from this
+ *    software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://www.jempbox.org
+ *
+ */
+package org.jempbox.xmp;
+
+import java.io.IOException;
+import java.util.Calendar;
+import java.util.List;
+
+import org.w3c.dom.Element;
+
+/**
+ * Define XMP properties that are common to all schemas.
+ * 
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class XMPSchemaBasic extends XMPSchema
+{
+    /**
+     * The namespace of this schema.
+     */
+    public static final String NAMESPACE = "http://ns.adobe.com/xap/1.0/";
+    
+    /**
+     * Construct a new blank PDF schema.
+     *
+     * @param parent The parent metadata schema that this will be part of.
+     */
+    public XMPSchemaBasic( XMPMetadata parent )
+    {
+        super( parent, "xmp", NAMESPACE );
+        schema.setAttributeNS( 
+            NS_NAMESPACE, 
+            "xmlns:xapGImg", 
+            "http://ns.adobe.com/xap/1.0/g/img/" );
+    }
+    
+    /**
+     * Constructor from existing XML element.
+     * 
+     * @param element The existing element.
+     * @param prefix The schema prefix.
+     */
+    public XMPSchemaBasic( Element element, String prefix )
+    {
+        super( element, prefix );
+        if( schema.getAttribute( "xmlns:xapGImg" ) == null )
+        {
+            schema.setAttributeNS( 
+                NS_NAMESPACE, 
+                "xmlns:xapGImg", 
+                "http://ns.adobe.com/xap/1.0/g/img/" );
+        }
+    }
+    
+    /**
+     * Remove an Advisory xpath expression.
+     *
+     * @param advisory An xpath expression specifying properties that
+     * were edited outside of the authoring application.
+     */
+    public void removeAdvisory( String advisory )
+    {
+        removeBagValue( prefix + ":Advisory", advisory );
+    }
+    
+    /**
+     * Add an advisory to the list.
+     *
+     * @param advisory The new advisory xpath expression.
+     */
+    public void addAdvisory( String advisory )
+    {
+        addBagValue( prefix + ":Advisory", advisory );
+    }
+    
+    /**
+     * Get the complete list of advisories.
+     *
+     * @return The list of advisories.
+     */
+    public List getAdvisories()
+    {
+        return getBagList( prefix + ":Advisory" );
+    }
+    
+    /**
+     * The base URL of the resource, for relative URLs in the document.
+     *
+     * @param url The base URL.
+     */
+    public void setBaseURL( String url )
+    {
+        setTextProperty( prefix + ":BaseURL", url );
+    }
+    
+    /**
+     * Get the base URL of the resource.
+     *
+     * @return The base URL.
+     */
+    public String getBaseURL()
+    {
+        return getTextProperty( prefix + ":BaseURL" );
+    }
+    
+    /**
+     * Set the creation date of the resource.
+     *
+     * @param date The creation date of the resource.
+     */
+    public void setCreateDate( Calendar date )
+    {
+        setDateProperty( prefix + ":CreateDate", date );
+    }
+    
+    /**
+     * Get the creation date of the resource.
+     *
+     * @return The creation date of the resource.
+     * 
+     * @throws IOException If there is an error while converting this property to
+     * a date.
+     */
+    public Calendar getCreateDate() throws IOException
+    {
+        return getDateProperty( prefix + ":CreateDate" );
+    }
+    
+    /**
+     * The creator tool for the resource.  In the form of "vendor app version", ie
+     * "Adobe Acrobat Distiller 5.0"
+     *
+     * @param creator The tool that was used to create the resource.
+     */
+    public void setCreatorTool( String creator )
+    {
+        setTextProperty( prefix + ":CreatorTool", creator );
+    }
+    
+    /**
+     * Get the tool that created this resource, in the form of "vendor app version", ie
+     * "Adobe Acrobat Distiller 5.0".
+     *
+     * @return The creator tool.
+     */
+    public String getCreatorTool()
+    {
+        return getTextProperty( prefix + ":CreatorTool" );
+    }
+    
+    /**
+     * Remove an identifier to this resource.
+     *
+     * @param id An identifier to this resource.
+     */
+    public void removeIdentifier( String id )
+    {
+        removeBagValue( prefix + ":Identifier", id );
+    }
+    
+    /**
+     * Add a new identifier for this resource.
+     *
+     * @param id A new identifier for this resource.
+     */
+    public void addIdentifier( String id )
+    {
+        addBagValue( prefix + ":Identifier", id );
+    }
+    
+    /**
+     * Get the complete list of identifiers.
+     *
+     * @return The list of identifiers.
+     */
+    public List getIdentifiers()
+    {
+        return getBagList( prefix + ":Identifier" );
+    }
+    
+    /**
+     * Set a short phrase that identifies this resource.
+     *
+     * @param label A short description of this resource.
+     */
+    public void setLabel( String label )
+    {
+        setTextProperty( prefix + ":Label", label );
+    }
+    
+    /**
+     * Get the short phrase that describes this resource.
+     *
+     * @return The label for this resource.
+     */
+    public String getLabel()
+    {
+        return getTextProperty( prefix + "p:Label" );
+    }
+    
+    /**
+     * Set a Title for this resource.
+     *
+     * @param title A title denoting this resource
+     */
+    public void setTitle( String title )
+    {
+        setTextProperty( prefix + ":Title", title);
+    }
+    
+    /**
+     * Get the title for this resource.
+     *
+     * @return The titled denoting this resource.
+     */
+    public String getTitle()
+    {
+        return getTextProperty( prefix + ":Title" );
+    }
+    
+    /**
+     * Set the date that any metadata was updated.
+     *
+     * @param date The metadata change date for this resource.
+     */
+    public void setMetadataDate( Calendar date )
+    {
+        setDateProperty( prefix + ":MetadataDate", date );
+    }
+    
+    /**
+     * Get the metadata change date for this resource.
+     *
+     * @return The metadata change date of the resource.
+     * 
+     * @throws IOException If there is an error while converting this property to
+     * a date.
+     */
+    public Calendar getMetadataDate() throws IOException
+    {
+        return getDateProperty( prefix + ":MetadataDate" );
+    }
+    
+    /**
+     * Set the date that the resource was last modified.
+     *
+     * @param date The modify date for this resource.
+     */
+    public void setModifyDate( Calendar date )
+    {
+        setDateProperty( prefix + ":ModifyDate", date );
+    }
+    
+    /**
+     * Get the date the resource was last modified.
+     *
+     * @return The modify date of the resource.
+     * 
+     * @throws IOException If there is an error while converting this property to
+     * a date.
+     */
+    public Calendar getModifyDate() throws IOException
+    {
+        return getDateProperty( prefix + ":ModifyDate" );
+    }
+    
+    /**
+     * Set a short informal name for the resource.
+     *
+     * @param nickname A short name of this resource.
+     */
+    public void setNickname( String nickname )
+    {
+        setTextProperty( prefix + ":Nickname", nickname );
+    }
+    
+    /**
+     * Get the short informal name for this resource.
+     *
+     * @return The short name for this resource.
+     */
+    public String getNickname()
+    {
+        return getTextProperty( prefix + ":Nickname" );
+    }
+    
+    /**
+     * Get a number that indicates the documents status.
+     * 
+     * @return The rating of the document.
+     */
+    public Integer getRating()
+    {
+        return getIntegerProperty( prefix + ":Rating" );
+    }
+    
+    /**
+     * Set the document status.
+     * 
+     * @param rating A number indicating status relative to other documents.
+     */
+    public void setRating( Integer rating )
+    {
+        setIntegerProperty( prefix + ":Rating", rating );
+    }
+    
+    /**
+     * Set the default value for the thumbnail.
+     *
+     * @param thumbnail The thumbnail of this resource.
+     */
+    public void setThumbnail( Thumbnail thumbnail )
+    {
+        setThumbnailProperty( prefix + ":Thumbnails", null, thumbnail );
+    }
+    
+    /**
+     * Get the default value for the thumbnail.
+     *
+     * @return The thumbnail of this resource.
+     */
+    public Thumbnail getThumbnail()
+    {
+        return getThumbnailProperty( prefix + ":Thumnails", null );
+    }
+    
+    /**
+     * Set the thumbnail of this resource in a specific language.
+     *
+     * @param language The language code.
+     * @param thumbnail The thumbnail in a specific language.
+     */
+    public void setThumbnail( String language, Thumbnail thumbnail )
+    {
+        setThumbnailProperty( prefix + ":Thumbnails", language, thumbnail );
+    }
+    
+    /**
+     * Get the thumbnail in a specific language.
+     *
+     * @param language The language code to get the description for.
+     *
+     * @return The thumbnail in the specified language or null if it does not exist.
+     */
+    public Thumbnail getThumbnail( String language )
+    {
+        return getThumbnailProperty( prefix + ":Thumbnails", language );
+    }
+    
+    /**
+     * Get a list of all languages that a thumbnail exists for.
+     *
+     * @return A non-null list of languages, potentially an empty list.
+     */
+    public List getThumbnailLanguages()
+    {
+        return getLanguagePropertyLanguages( prefix + ":Thumbnails" );
+    }
+}
\ No newline at end of file

Added: incubator/pdfbox/trunk/jempbox/src/org/jempbox/xmp/XMPSchemaBasicJobTicket.java
URL: http://svn.apache.org/viewvc/incubator/pdfbox/trunk/jempbox/src/org/jempbox/xmp/XMPSchemaBasicJobTicket.java?rev=670399&view=auto
==============================================================================
--- incubator/pdfbox/trunk/jempbox/src/org/jempbox/xmp/XMPSchemaBasicJobTicket.java (added)
+++ incubator/pdfbox/trunk/jempbox/src/org/jempbox/xmp/XMPSchemaBasicJobTicket.java Sun Jun 22 12:09:15 2008
@@ -0,0 +1,67 @@
+/**
+ * Copyright (c) 2006, www.jempbox.org
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of pdfbox; nor the names of its
+ *    contributors may be used to endorse or promote products derived from this
+ *    software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://www.jempbox.org
+ *
+ */
+package org.jempbox.xmp;
+
+import org.w3c.dom.Element;
+
+/**
+ * Implementation of Basic Job Ticket Schema.
+ * 
+ * @author Karsten Krieg (kkrieg@intarsys.de)
+ * @version $Revision: 1.2 $
+ */
+public class XMPSchemaBasicJobTicket extends XMPSchema
+{
+    /**
+     * The namespace for this schema.
+     */
+    public static final String NAMESPACE = "http://ns.adobe.com/xap/1.0/bj/";
+    
+    /**
+     *
+     * @param parent The parent metadata schema that this will be part of.
+     */
+    public XMPSchemaBasicJobTicket( XMPMetadata parent )
+    {
+        super( parent, "xmpBJ", NAMESPACE );
+    }
+    
+    /**
+     * Constructor from existing XML element.
+     * 
+     * @param element The existing element.
+     * @param prefix The schema prefix.
+     */
+    public XMPSchemaBasicJobTicket( Element element, String prefix )
+    {
+        super( element , prefix);
+    }   
+}
\ No newline at end of file