You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@xmlgraphics.apache.org by je...@apache.org on 2007/12/23 16:42:30 UTC

svn commit: r606564 - in /xmlgraphics/commons/trunk: ./ src/java/org/apache/xmlgraphics/xmp/ test/java/org/apache/xmlgraphics/xmp/

Author: jeremias
Date: Sun Dec 23 07:42:28 2007
New Revision: 606564

URL: http://svn.apache.org/viewvc?rev=606564&view=rev
Log:
XMP: Added support for structured properties.
Bugfix for XMP serialization: arrays with only one entry were serialized as simple property which could lead to information loss for merge operations.

Added:
    xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/PropertyAccess.java   (with props)
    xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/XMPStructure.java   (with props)
    xmlgraphics/commons/trunk/test/java/org/apache/xmlgraphics/xmp/
    xmlgraphics/commons/trunk/test/java/org/apache/xmlgraphics/xmp/XMPParserTest.java   (with props)
    xmlgraphics/commons/trunk/test/java/org/apache/xmlgraphics/xmp/test-basics.xmp
    xmlgraphics/commons/trunk/test/java/org/apache/xmlgraphics/xmp/test-structures.xmp
    xmlgraphics/commons/trunk/test/java/org/apache/xmlgraphics/xmp/unknown-schema.xmp
Modified:
    xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/Metadata.java
    xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/XMPArray.java
    xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/XMPConstants.java
    xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/XMPHandler.java
    xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/XMPProperty.java
    xmlgraphics/commons/trunk/status.xml

Modified: xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/Metadata.java
URL: http://svn.apache.org/viewvc/xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/Metadata.java?rev=606564&r1=606563&r2=606564&view=diff
==============================================================================
--- xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/Metadata.java (original)
+++ xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/Metadata.java Sun Dec 23 07:42:28 2007
@@ -23,55 +23,54 @@
 import java.util.Map;
 import java.util.Set;
 
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
 import org.apache.xmlgraphics.util.QName;
 import org.apache.xmlgraphics.util.XMLizable;
 import org.apache.xmlgraphics.xmp.merge.MergeRuleSet;
 import org.apache.xmlgraphics.xmp.merge.PropertyMerger;
-import org.xml.sax.ContentHandler;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.AttributesImpl;
 
 /**
  * This class represents the root of an XMP metadata tree. It's more or less equivalent to the
  * x:xmpmeta element together with its nested rdf:RDF element.
  */
-public class Metadata implements XMLizable {
+public class Metadata implements XMLizable, PropertyAccess {
 
     private Map properties = new java.util.HashMap();
 
-    /**
-     * Sets a property.
-     * @param prop the property
-     */
+    /** {@inheritDoc} */
     public void setProperty(XMPProperty prop) {
         properties.put(prop.getName(), prop);
     }
     
-    /**
-     * Returns a property
-     * @param uri the namespace URI of the property
-     * @param localName the local name of the property
-     * @return the requested property or null if it's not available
-     */
+    /** {@inheritDoc} */
     public XMPProperty getProperty(String uri, String localName) {
         return getProperty(new QName(uri, localName));
     }
     
-    /**
-     * Returns a property.
-     * @param name the name of the property
-     * @return the requested property or null if it's not available
-     */
+    /** {@inheritDoc} */
     public XMPProperty getProperty(QName name) {
         XMPProperty prop = (XMPProperty)properties.get(name);
         return prop;
     }
     
-    /** @return the number of properties in this metadata object. */
+    /** {@inheritDoc} */
+    public XMPProperty getValueProperty() {
+        return getProperty(XMPConstants.RDF_VALUE);
+    }
+    
+    /** {@inheritDoc} */
     public int getPropertyCount() {
         return this.properties.size();
     }
     
+    /** {@inheritDoc} */
+    public Iterator iterator() {
+        return this.properties.keySet().iterator();
+    }
+    
     /**
      * Merges this metadata object into a given target metadata object. The merge rule set provided
      * by each schema is used for the merge.
@@ -89,7 +88,7 @@
         }
     }
     
-    /** @see org.apache.xmlgraphics.util.XMLizable#toSAX(org.xml.sax.ContentHandler) */
+    /** {@inheritDoc} */
     public void toSAX(ContentHandler handler) throws SAXException {
         AttributesImpl atts = new AttributesImpl();
         handler.startElement(XMPConstants.XMP_NAMESPACE, "xmpmeta", "x:xmpmeta", atts);
@@ -98,7 +97,8 @@
         Set namespaces = new java.util.HashSet();
         Iterator iter = properties.keySet().iterator();
         while (iter.hasNext()) {
-            namespaces.add(((QName)iter.next()).getNamespaceURI());
+            QName n = ((QName)iter.next());
+            namespaces.add(n.getNamespaceURI());
         }
         //One Description element per namespace
         iter = namespaces.iterator();
@@ -106,28 +106,42 @@
             String ns = (String)iter.next();
             XMPSchema schema = XMPSchemaRegistry.getInstance().getSchema(ns);
             String prefix = (schema != null ? schema.getPreferredPrefix() : null);
-            if (prefix != null) {
-                handler.startPrefixMapping(prefix, ns);
-            }
-            
-            atts.clear();
-            atts.addAttribute(XMPConstants.RDF_NAMESPACE, "about", "rdf:about", "CDATA", "");
-            handler.startElement(XMPConstants.RDF_NAMESPACE, "RDF", "rdf:Description", atts);
+
+            boolean first = true;
+            boolean empty = true;
             
             Iterator props = properties.values().iterator();
             while (props.hasNext()) {
                 XMPProperty prop = (XMPProperty)props.next();
                 if (prop.getName().getNamespaceURI().equals(ns)) {
+                    if (first) {
+                        if (prefix == null) {
+                            prefix = prop.getName().getPrefix();
+                        }
+                        atts.clear();
+                        atts.addAttribute(XMPConstants.RDF_NAMESPACE,
+                                "about", "rdf:about", "CDATA", "");
+                        if (prefix != null) {
+                            handler.startPrefixMapping(prefix, ns);
+                        }
+                        handler.startElement(XMPConstants.RDF_NAMESPACE,
+                                "RDF", "rdf:Description", atts);
+                        empty = false;
+                        first = false;
+                    }
                     prop.toSAX(handler);
                 }
             }
-            handler.endElement(XMPConstants.RDF_NAMESPACE, "RDF", "rdf:Description");
-            if (prefix != null) {
-                handler.endPrefixMapping(prefix);
+            if (!empty) {
+                handler.endElement(XMPConstants.RDF_NAMESPACE, "RDF", "rdf:Description");
+                if (prefix != null) {
+                    handler.endPrefixMapping(prefix);
+                }
             }
         }
         
         handler.endElement(XMPConstants.RDF_NAMESPACE, "RDF", "rdf:RDF");
         handler.endElement(XMPConstants.XMP_NAMESPACE, "xmpmeta", "x:xmpmeta");
     }
+
 }

Added: xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/PropertyAccess.java
URL: http://svn.apache.org/viewvc/xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/PropertyAccess.java?rev=606564&view=auto
==============================================================================
--- xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/PropertyAccess.java (added)
+++ xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/PropertyAccess.java Sun Dec 23 07:42:28 2007
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.xmlgraphics.xmp;
+
+import java.util.Iterator;
+
+import org.apache.xmlgraphics.util.QName;
+
+/**
+ * This interface is implemented by the top-level Metadata class and stuctured properties.
+ */
+public interface PropertyAccess {
+
+    /**
+     * Sets a property.
+     * @param prop the property
+     */
+    void setProperty(XMPProperty prop);
+
+    /**
+     * Returns a property
+     * @param uri the namespace URI of the property
+     * @param localName the local name of the property
+     * @return the requested property or null if it's not available
+     */
+    XMPProperty getProperty(String uri, String localName);
+
+    /**
+     * Returns a property.
+     * @param name the name of the property
+     * @return the requested property or null if it's not available
+     */
+    XMPProperty getProperty(QName name);
+
+    /**
+     * Returns the rdf:value property. This is a shortcut for getProperty(XMPConstants.RDF_VALUE).
+     * @return the rdf:value property or null if it's no available
+     */
+    XMPProperty getValueProperty();
+    
+    /**
+     * Returns the number of properties.
+     * @return the number of properties in this metadata object.
+     */
+    int getPropertyCount();
+
+    /**
+     * Returns an Iterator over all properties in this structured property.
+     * @return an Iterator over all properties
+     */
+    Iterator iterator();
+
+}
\ No newline at end of file

Propchange: xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/PropertyAccess.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/PropertyAccess.java
------------------------------------------------------------------------------
    svn:keywords = Id

Modified: xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/XMPArray.java
URL: http://svn.apache.org/viewvc/xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/XMPArray.java?rev=606564&r1=606563&r2=606564&view=diff
==============================================================================
--- xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/XMPArray.java (original)
+++ xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/XMPArray.java Sun Dec 23 07:42:28 2007
@@ -49,7 +49,7 @@
     }
     
     /**
-     * Returns the value at a given position
+     * Returns the value at a given position.
      * @param idx the index of the requested value
      * @return the value at the given position
      */
@@ -57,28 +57,64 @@
         return this.values.get(idx);
     }
 
-    /** @see org.apache.xmlgraphics.xmp.XMPComplexValue#getSimpleValue() */
+    /**
+     * Returns the structure at a given position. If the value is not a structure a
+     * ClassCastException is thrown.
+     * @param idx the index of the requested value
+     * @return the structure at the given position
+     */
+    public XMPStructure getStructure(int idx) {
+        return (XMPStructure)this.values.get(idx);
+    }
+
+    /** {@inheritDoc} */
     public Object getSimpleValue() {
         if (values.size() == 1) {
             return getValue(0);
+        } else if (values.size() > 1) {
+            return getLangValue(XMPConstants.DEFAULT_LANGUAGE);
         } else {
             return null;
         }
     }
 
+    private String getParentLanguage(String lang) {
+        if (lang == null) {
+            return null;
+        }
+        int pos = lang.indexOf('-');
+        if (pos > 0) {
+            String parent = lang.substring(0, pos);
+            return parent;
+        }
+        return null;
+    }
+    
     /**
-     * Returns a language-dependant values (available for alternative arrays).
+     * Returns a language-dependent values (available for alternative arrays).
      * @param lang the language ("x-default" for the default value)
      * @return the requested value
      */
     public String getLangValue(String lang) {
         String v = null;
+        String valueForParentLanguage = null;
         for (int i = 0, c = values.size(); i < c; i++) {
             String l = (String)xmllang.get(i);
             if ((l == null && lang == null) || (l != null && l.equals(lang))) {
                 v = values.get(i).toString();
                 break;
             }
+            if (l != null && lang != null) {
+                //Check for "parent" language, too ("en" matches "en-GB")
+                String parent = getParentLanguage(l);
+                if (parent != null && parent.equals(lang)) {
+                    valueForParentLanguage = values.get(i).toString();
+                }
+            }
+        }
+        if (lang != null & v == null && valueForParentLanguage != null) {
+            //Use value found for parent language
+            v = valueForParentLanguage;
         }
         if (lang == null && v == null) {
             v = getLangValue(XMPConstants.DEFAULT_LANGUAGE);
@@ -90,7 +126,7 @@
     }
     
     /**
-     * Removes a language-dependant value
+     * Removes a language-dependent value
      * @param lang the language ("x-default" for the default value)
      */
     public void removeLangValue(String lang) {
@@ -117,7 +153,7 @@
     }
 
     /**
-     * Adds a language-dependant value to the array. Make sure not to add the same language twice.
+     * Adds a language-dependent value to the array. Make sure not to add the same language twice.
      * @param value the value
      * @param lang the language ("x-default" for the default value)
      */
@@ -143,13 +179,12 @@
         return res;
     }
 
-    /** @see org.apache.xmlgraphics.util.XMLizable#toSAX(org.xml.sax.ContentHandler) */
+    /** {@inheritDoc} */
     public void toSAX(ContentHandler handler) throws SAXException {
         AttributesImpl atts = new AttributesImpl();
         handler.startElement(XMPConstants.RDF_NAMESPACE, 
                 type.getName(), "rdf:" + type.getName(), atts);
         for (int i = 0, c = values.size(); i < c; i++) {
-            String value = (String)values.get(i);
             String lang = (String)xmllang.get(i);
             atts.clear();
             if (lang != null) {
@@ -157,8 +192,14 @@
             }
             handler.startElement(XMPConstants.RDF_NAMESPACE, 
                     "li", "rdf:li", atts);
-            char[] chars = value.toCharArray();
-            handler.characters(chars, 0, chars.length);
+            Object v = values.get(i);
+            if (v instanceof XMPComplexValue) {
+                ((XMPComplexValue)v).toSAX(handler);
+            } else {
+                String value = (String)values.get(i);
+                char[] chars = value.toCharArray();
+                handler.characters(chars, 0, chars.length);
+            }
             handler.endElement(XMPConstants.RDF_NAMESPACE, 
                     "li", "rdf:li");
         }
@@ -166,7 +207,7 @@
                 type.getName(), "rdf:" + type.getName());
     }
 
-    /** @see java.lang.Object#toString() */
+    /** {@inheritDoc} */
     public String toString() {
         return "XMP array: " + type + ", " + getSize();
     }

Modified: xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/XMPConstants.java
URL: http://svn.apache.org/viewvc/xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/XMPConstants.java?rev=606564&r1=606563&r2=606564&view=diff
==============================================================================
--- xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/XMPConstants.java (original)
+++ xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/XMPConstants.java Sun Dec 23 07:42:28 2007
@@ -19,6 +19,8 @@
 
 package org.apache.xmlgraphics.xmp;
 
+import org.apache.xmlgraphics.util.QName;
+
 /**
  * Constants used in XMP metadata.
  */
@@ -27,6 +29,9 @@
     /** Namespace URI for the xml: prefix */
     String XML_NS = "http://www.w3.org/XML/1998/namespace";
     
+    /** Namespace URI for the xmlns: prefix */
+    String XMLNS_NAMESPACE = "http://www.w3.org/2000/xmlns/";
+
     /** Namespace URI for XMP */
     String XMP_NAMESPACE = "adobe:ns:meta/";
     
@@ -58,5 +63,8 @@
 
     /** Default language for the xml:lang property */
     String DEFAULT_LANGUAGE = "x-default";
+    
+    /** QName for rdf:value */
+    QName RDF_VALUE = new QName(RDF_NAMESPACE, "value");
     
 }

Modified: xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/XMPHandler.java
URL: http://svn.apache.org/viewvc/xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/XMPHandler.java?rev=606564&r1=606563&r2=606564&view=diff
==============================================================================
--- xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/XMPHandler.java (original)
+++ xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/XMPHandler.java Sun Dec 23 07:42:28 2007
@@ -21,12 +21,13 @@
 
 import java.util.Stack;
 
-import org.apache.xmlgraphics.util.QName;
 import org.xml.sax.Attributes;
 import org.xml.sax.SAXException;
 import org.xml.sax.helpers.AttributesImpl;
 import org.xml.sax.helpers.DefaultHandler;
 
+import org.apache.xmlgraphics.util.QName;
+
 /**
  * Passive XMP parser implemented as a SAX DefaultHandler. After the XML document has been parsed
  * the Metadata object can be retrieved.
@@ -38,17 +39,87 @@
     private StringBuffer content = new StringBuffer();
     //private Attributes lastAttributes;
     private Stack attributesStack = new Stack();
-    //private Stack contextStack = new Stack();
+    private Stack nestingInfoStack = new Stack();
+    private Stack contextStack = new Stack();
     
-    private QName currentPropertyName;
-    private XMPProperty currentProperty;
-    private XMPComplexValue currentComplexValue;
+    //private QName currentPropertyName;
+    //private XMPProperty currentProperty;
+    //private XMPComplexValue currentComplexValue;
+    //private PropertyAccess currentProperties;
     
     /** @return the parsed metadata, available after the parsing. */
     public Metadata getMetadata() {
         return this.meta;
     }
 
+    private boolean hasComplexContent() {
+        Object obj = this.contextStack.peek();
+        if (obj instanceof QName) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+    
+    private PropertyAccess getCurrentProperties() {
+        Object obj = this.contextStack.peek();
+        if (obj instanceof PropertyAccess) {
+            return (PropertyAccess)obj;
+        } else {
+            return null;
+        }
+    }
+    
+    private QName getCurrentPropName() {
+        Object obj = this.contextStack.peek();
+        if (obj instanceof QName) {
+            return (QName)obj;
+        } else {
+            return null;
+        }
+    }
+    
+    private QName popCurrentPropName() throws SAXException {
+        Object obj = this.contextStack.pop();
+        this.nestingInfoStack.pop();
+        if (obj instanceof QName) {
+            return (QName)obj;
+        } else {
+            throw new SAXException("Invalid XMP structure. Property name expected");
+        }
+    }
+    
+    private XMPComplexValue getCurrentComplexValue() {
+        Object obj = this.contextStack.peek();
+        if (obj instanceof XMPComplexValue) {
+            return (XMPComplexValue)obj;
+        } else {
+            return null;
+        }
+    }
+    
+    private XMPStructure getCurrentStructure() {
+        Object obj = this.contextStack.peek();
+        if (obj instanceof XMPStructure) {
+            return (XMPStructure)obj;
+        } else {
+            return null;
+        }
+    }
+    
+    private XMPArray getCurrentArray(boolean required) throws SAXException {
+        Object obj = this.contextStack.peek();
+        if (obj instanceof XMPArray) {
+            return (XMPArray)obj;
+        } else {
+            if (required) {
+                throw new SAXException("Invalid XMP structure. Not in array");
+            } else {
+                return null;
+            }
+        }
+    }
+
     // --- Overrides ---
     
     /**
@@ -69,28 +140,61 @@
                 throw new SAXException("Invalid XMP document. Root already received earlier.");
             }
             this.meta = new Metadata();
+            //this.currentProperties = this.meta;
+            this.contextStack.push(this.meta);
+            this.nestingInfoStack.push("metadata");
         } else if (XMPConstants.RDF_NAMESPACE.equals(uri)) {
             if ("RDF".equals(localName)) {
                 if (this.meta == null) {
                     this.meta = new Metadata();
+                    //this.currentProperties = this.meta;
+                    this.contextStack.push(this.meta);
+                    this.nestingInfoStack.push("metadata");
                 }
             } else if ("Description".equals(localName)) {
-                if (currentPropertyName == null) {
+                String about = attributes.getValue(XMPConstants.RDF_NAMESPACE, "about");
+                if (this.contextStack.peek().equals(this.meta)) {
                     //rdf:RDF is the parent
-                    String about = attributes.getValue(XMPConstants.RDF_NAMESPACE, "about");
                 } else {
+                    if (about != null) {
+                        throw new SAXException(
+                                "Nested rdf:Description elements may not have an about property");
+                    }
                     //a structured property is the parent
+                    XMPStructure struct = new XMPStructure();
+                    //this.currentComplexValue = struct;
+                    this.contextStack.push(struct);
+                    //this.currentProperties = struct;
+                    this.nestingInfoStack.push("struct");
                 }
             } else if ("Seq".equals(localName)) {
-                this.currentComplexValue = new XMPArray(XMPArrayType.SEQ);
+                XMPArray array = new XMPArray(XMPArrayType.SEQ);
+                //this.currentComplexValue = array;
+                this.contextStack.push(array);
+                this.nestingInfoStack.push("Seq");
             } else if ("Bag".equals(localName)) {
-                this.currentComplexValue = new XMPArray(XMPArrayType.BAG);
+                XMPArray array = new XMPArray(XMPArrayType.BAG);
+                //this.currentComplexValue = array;
+                this.contextStack.push(array);
+                this.nestingInfoStack.push("Bag");
             } else if ("Alt".equals(localName)) {
-                this.currentComplexValue = new XMPArray(XMPArrayType.ALT);
+                XMPArray array = new XMPArray(XMPArrayType.ALT);
+                //this.currentComplexValue = array;
+                this.contextStack.push(array);
+                this.nestingInfoStack.push("Alt");
             } else if ("li".equals(localName)) {
+                //nop, handle in endElement()
+            } else if ("value".equals(localName)) {
+                QName name = new QName(uri, qName);
+                this.contextStack.push(name);
+                this.nestingInfoStack.push("prop:" + name);
+            } else {
+                throw new SAXException("Unexpected element in the RDF namespace: " + localName);
             }
         } else {
-            this.currentPropertyName = new QName(uri, qName);
+            QName name = new QName(uri, qName);
+            this.contextStack.push(name);
+            this.nestingInfoStack.push("prop:" + name);
         }
     }
     
@@ -102,39 +206,81 @@
         Attributes atts = (Attributes)attributesStack.pop();
         if (XMPConstants.XMP_NAMESPACE.equals(uri)) {
             //nop
-        } else if (XMPConstants.RDF_NAMESPACE.equals(uri)) {
+        } else if (XMPConstants.RDF_NAMESPACE.equals(uri) && !"value".equals(localName)) {
             if ("li".equals(localName)) {
-                String s = content.toString().trim();
-                if (s.length() > 0) {
-                    getCurrentArray().add(s);
+                XMPStructure struct = getCurrentStructure();
+                if (struct != null) {
+                    //Pop the structure
+                    Object obj = this.contextStack.pop();
+                    this.nestingInfoStack.pop();
+                    getCurrentArray(true).add(struct);
+                } else {
+                    String s = content.toString().trim();
+                    if (s.length() > 0) {
+                        String lang = atts.getValue(XMPConstants.XML_NS, "lang");
+                        if (lang != null) {
+                            getCurrentArray(true).add(s, lang);
+                        } else {
+                            getCurrentArray(true).add(s);
+                        }
+                    }
                 }
+            } else if ("Description".equals(localName)) {
+                /*
+                if (isInStructure()) {
+                    //Description is indicating a structure
+                    //this.currentProperties = (PropertyAccess)propertiesStack.pop();
+                    this.nestingInfoStack.pop();
+                }*/
             } else {
-                //nop
+                //nop, don't pop stack so the parent element has access
+                //this.contextStack.pop();
+                //this.nestingInfoStack.pop();
             }
         } else {
-            if (this.currentComplexValue != null) {
-                this.currentProperty = new XMPProperty(this.currentPropertyName, 
-                                this.currentComplexValue);
-                this.currentComplexValue = null;
+            XMPProperty prop;
+            QName name;
+            if (hasComplexContent()) {
+                //Pop content of property
+                Object obj = this.contextStack.pop();
+                this.nestingInfoStack.pop();
+                
+                name = popCurrentPropName();
+                
+                if (obj instanceof XMPComplexValue) {
+                    XMPComplexValue complexValue = (XMPComplexValue)obj;
+                    prop = new XMPProperty(name, complexValue);
+                } else {
+                    throw new UnsupportedOperationException("NYI");
+                }
             } else {
+                name = popCurrentPropName();
+
                 String s = content.toString().trim();
-                this.currentProperty = new XMPProperty(this.currentPropertyName, s);
+                prop = new XMPProperty(name, s);
                 String lang = atts.getValue(XMPConstants.XML_NS, "lang");
                 if (lang != null) {
-                    this.currentProperty.setXMLLang(lang);
+                    prop.setXMLLang(lang);
                 }
             }
-            this.meta.setProperty(this.currentProperty);
-            this.currentProperty = null;
-            this.currentPropertyName = null;
+            if (prop.getName() == null) {
+                throw new IllegalStateException("No content in XMP property");
+            }
+            getCurrentProperties().setProperty(prop);
+            //this.currentProperties.setProperty(this.currentProperty);
+            //this.currentProperty = null;
+            //this.currentPropertyName = null;
         }
+
         content.setLength(0); //Reset text buffer (see characters())
         super.endElement(uri, localName, qName);
     }
 
-    private XMPArray getCurrentArray() {
-        return (XMPArray)this.currentComplexValue;
+    /*
+    private boolean isInStructure() {
+        return !propertiesStack.isEmpty();
     }
+    */
 
     /** @see org.xml.sax.ContentHandler#characters(char[], int, int) */
     public void characters(char[] ch, int start, int length) throws SAXException {

Modified: xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/XMPProperty.java
URL: http://svn.apache.org/viewvc/xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/XMPProperty.java?rev=606564&r1=606563&r2=606564&view=diff
==============================================================================
--- xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/XMPProperty.java (original)
+++ xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/XMPProperty.java Sun Dec 23 07:42:28 2007
@@ -19,12 +19,16 @@
 
 package org.apache.xmlgraphics.xmp;
 
-import org.apache.xmlgraphics.util.QName;
-import org.apache.xmlgraphics.util.XMLizable;
+import java.util.Iterator;
+import java.util.Map;
+
 import org.xml.sax.ContentHandler;
 import org.xml.sax.SAXException;
 import org.xml.sax.helpers.AttributesImpl;
 
+import org.apache.xmlgraphics.util.QName;
+import org.apache.xmlgraphics.util.XMLizable;
+
 /**
  * This class is the base class for all XMP properties.
  */
@@ -33,6 +37,7 @@
     private QName name;
     private Object value;
     private String xmllang;
+    private Map qualifiers;
 
     /**
      * Creates a new XMP property.
@@ -108,6 +113,61 @@
         }
     }
     
+    /** @return the XMPStructure for a structure or null if the value is not a structure. */
+    public PropertyAccess getStructureValue() {
+        return (value instanceof XMPStructure ? (XMPStructure)value : null);
+    }
+    
+    private boolean hasPropertyQualifiers() {
+        return (this.qualifiers == null) || (this.qualifiers.size() == 0);
+    }
+
+    /**
+     * Indicates whether this property is actually not a structure, but a normal property with
+     * property qualifiers. If this method returns true, this structure can be converted to
+     * an simple XMPProperty using the simplify() method.
+     * @return true if this property is a structure property with property qualifiers
+     */
+    public boolean isQualifiedProperty() {
+        PropertyAccess props = getStructureValue();
+        if (props != null) {
+            XMPProperty rdfValue = props.getProperty(XMPConstants.RDF_VALUE);
+            return (rdfValue != null);
+        } else {
+            return hasPropertyQualifiers();
+        }
+    }
+    
+    public void simplify() {
+        PropertyAccess props = getStructureValue();
+        if (props != null) {
+            XMPProperty rdfValue = props.getProperty(XMPConstants.RDF_VALUE);
+            if (rdfValue != null) {
+                if (hasPropertyQualifiers()) {
+                    throw new IllegalStateException("Illegal internal state"
+                            + " (qualifiers present on non-simplified property)");
+                }
+                Object value = props.getProperty(XMPConstants.RDF_VALUE);
+                XMPProperty prop = new XMPProperty(getName(), value);
+                Iterator iter = props.iterator();
+                while (iter.hasNext()) {
+                    QName name = (QName)iter.next();
+                    if (!XMPConstants.RDF_VALUE.equals(name)) {
+                        prop.setPropertyQualifier(name, props.getProperty(name));
+                    }
+                }
+            }
+        }
+    }
+    
+    
+    private void setPropertyQualifier(QName name, XMPProperty property) {
+        if (this.qualifiers == null) {
+            this.qualifiers = new java.util.HashMap();
+        }
+        this.qualifiers.put(name, property);
+    }
+
     private String getEffectiveQName() {
         String prefix = getName().getPrefix();
         if (prefix == null || "".equals(prefix)) {
@@ -125,13 +185,7 @@
                 getName().getLocalName(), qName, atts);
         if (value instanceof XMPComplexValue) {
             XMPComplexValue cv = ((XMPComplexValue)value);
-            Object obj = cv.getSimpleValue();
-            if (obj != null) {
-                char[] chars = obj.toString().toCharArray();
-                handler.characters(chars, 0, chars.length);
-            } else {
-                cv.toSAX(handler);
-            }
+            cv.toSAX(handler);
         } else {
             char[] chars = value.toString().toCharArray();
             handler.characters(chars, 0, chars.length);
@@ -139,4 +193,13 @@
         handler.endElement(getName().getNamespaceURI(), 
                 getName().getLocalName(), qName);
     }
+
+    /** @see java.lang.Object#toString() */
+    public String toString() {
+        StringBuffer sb = new StringBuffer("XMP Property ");
+        sb.append(getName()).append(": ");
+        sb.append(getValue());
+        return sb.toString();
+    }
+    
 }

Added: xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/XMPStructure.java
URL: http://svn.apache.org/viewvc/xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/XMPStructure.java?rev=606564&view=auto
==============================================================================
--- xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/XMPStructure.java (added)
+++ xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/XMPStructure.java Sun Dec 23 07:42:28 2007
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.xmlgraphics.xmp;
+
+import java.util.Iterator;
+import java.util.Map;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import org.apache.xmlgraphics.util.QName;
+
+/**
+ * Represents an XMP structure as defined by the XMP specification.
+ */
+public class XMPStructure extends XMPComplexValue implements PropertyAccess {
+
+    private Map properties = new java.util.HashMap();
+    
+    /**
+     * Main constructor
+     */
+    public XMPStructure() {
+    }
+    
+    /** {@inheritDoc} */
+    public Object getSimpleValue() {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    public void setProperty(XMPProperty prop) {
+        properties.put(prop.getName(), prop);
+    }
+    
+    /** {@inheritDoc} */
+    public XMPProperty getProperty(String uri, String localName) {
+        return getProperty(new QName(uri, localName));
+    }
+    
+    /** {@inheritDoc} */
+    public XMPProperty getValueProperty() {
+        return getProperty(XMPConstants.RDF_VALUE);
+    }
+    
+    /** {@inheritDoc} */
+    public XMPProperty getProperty(QName name) {
+        XMPProperty prop = (XMPProperty)properties.get(name);
+        return prop;
+    }
+    
+    /** {@inheritDoc} */
+    public int getPropertyCount() {
+        return this.properties.size();
+    }
+    
+    /** {@inheritDoc} */
+    public Iterator iterator() {
+        return this.properties.keySet().iterator();
+    }
+
+    /** {@inheritDoc} */
+    public void toSAX(ContentHandler handler) throws SAXException {
+        AttributesImpl atts = new AttributesImpl();
+        atts.clear();
+        handler.startElement(XMPConstants.RDF_NAMESPACE, "RDF", "rdf:Description", atts);
+        
+        Iterator props = properties.values().iterator();
+        while (props.hasNext()) {
+            XMPProperty prop = (XMPProperty)props.next();
+            //if (prop.getName().getNamespaceURI().equals(ns)) {
+                prop.toSAX(handler);
+            //}
+        }
+        handler.endElement(XMPConstants.RDF_NAMESPACE, "RDF", "rdf:Description");
+    }
+
+    /** {@inheritDoc} */
+    public String toString() {
+        return "XMP structure: " + getPropertyCount();
+    }
+
+    
+}

Propchange: xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/XMPStructure.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/xmp/XMPStructure.java
------------------------------------------------------------------------------
    svn:keywords = Id

Modified: xmlgraphics/commons/trunk/status.xml
URL: http://svn.apache.org/viewvc/xmlgraphics/commons/trunk/status.xml?rev=606564&r1=606563&r2=606564&view=diff
==============================================================================
--- xmlgraphics/commons/trunk/status.xml (original)
+++ xmlgraphics/commons/trunk/status.xml Sun Dec 23 07:42:28 2007
@@ -25,6 +25,13 @@
 	</todo>
 	<changes>
 		<release version="Trunk" date="n/a">
+		  <action context="Code" dev="JM" type="add">
+		    XMP: Added support for structured properties.
+		  </action>
+		  <action context="Code" dev="JM" type="fix">
+		    Bugfix for XMP serialization: arrays with only one entry were serialized as
+		    simple property which could lead to information loss for merge operations.
+		  </action>
 		  <action context="Code" dev="JM" type="update">
 		    PostScript: Improved PSImageUtils by introducing the ImageEncoder interface and
 		    allowing for streams instead of byte arrays for data transfer which can lower

Added: xmlgraphics/commons/trunk/test/java/org/apache/xmlgraphics/xmp/XMPParserTest.java
URL: http://svn.apache.org/viewvc/xmlgraphics/commons/trunk/test/java/org/apache/xmlgraphics/xmp/XMPParserTest.java?rev=606564&view=auto
==============================================================================
--- xmlgraphics/commons/trunk/test/java/org/apache/xmlgraphics/xmp/XMPParserTest.java (added)
+++ xmlgraphics/commons/trunk/test/java/org/apache/xmlgraphics/xmp/XMPParserTest.java Sun Dec 23 07:42:28 2007
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.xmlgraphics.xmp;
+
+import java.net.URL;
+
+import junit.framework.TestCase;
+
+import org.apache.xmlgraphics.xmp.schemas.DublinCoreAdapter;
+import org.apache.xmlgraphics.xmp.schemas.DublinCoreSchema;
+import org.apache.xmlgraphics.xmp.schemas.XMPBasicAdapter;
+import org.apache.xmlgraphics.xmp.schemas.XMPBasicSchema;
+import org.apache.xmlgraphics.xmp.schemas.pdf.AdobePDFAdapter;
+import org.apache.xmlgraphics.xmp.schemas.pdf.AdobePDFSchema;
+
+/**
+ * Tests for the XMP parser.
+ */
+public class XMPParserTest extends TestCase {
+
+    public void testParseBasics() throws Exception {
+        URL url = getClass().getResource("test-basics.xmp");
+        Metadata meta = XMPParser.parseXMP(url);
+        
+        DublinCoreAdapter dcAdapter = DublinCoreSchema.getAdapter(meta);
+        XMPBasicAdapter basicAdapter = XMPBasicSchema.getAdapter(meta);
+        AdobePDFAdapter pdfAdapter = AdobePDFSchema.getAdapter(meta);
+        
+        XMPProperty prop;
+        prop = meta.getProperty(XMPConstants.DUBLIN_CORE_NAMESPACE, "creator");
+        XMPArray array;
+        array = prop.getArrayValue();
+        assertEquals(1, array.getSize());
+        assertEquals("John Doe", array.getValue(0).toString());
+        assertEquals("John Doe", dcAdapter.getCreators()[0]);
+               
+        prop = meta.getProperty(XMPConstants.DUBLIN_CORE_NAMESPACE, "title");
+        assertEquals("Example document", prop.getValue().toString());
+        assertEquals("Example document", dcAdapter.getTitle());
+        prop = meta.getProperty(XMPConstants.XMP_BASIC_NAMESPACE, "CreateDate");
+        //System.out.println("Creation Date: " + prop.getValue() + " " + prop.getClass().getName());
+        prop = meta.getProperty(XMPConstants.XMP_BASIC_NAMESPACE, "CreatorTool");
+        assertEquals("An XML editor", prop.getValue().toString());
+        assertEquals("An XML editor", basicAdapter.getCreatorTool());
+        prop = meta.getProperty(XMPConstants.ADOBE_PDF_NAMESPACE, "Producer");
+        assertEquals("Apache FOP Version SVN trunk", prop.getValue().toString());
+        assertEquals("Apache FOP Version SVN trunk", pdfAdapter.getProducer());
+        prop = meta.getProperty(XMPConstants.ADOBE_PDF_NAMESPACE, "PDFVersion");
+        assertEquals("1.4", prop.getValue().toString());
+        assertEquals("1.4", pdfAdapter.getPDFVersion());
+    }
+    
+    public void testParse1() throws Exception {
+        URL url = getClass().getResource("unknown-schema.xmp");
+        Metadata meta = XMPParser.parseXMP(url);
+        
+        DublinCoreAdapter dcAdapter = DublinCoreSchema.getAdapter(meta);
+        
+        XMPProperty prop;
+        //Access through the known schema as reference
+        prop = meta.getProperty(XMPConstants.DUBLIN_CORE_NAMESPACE, "title");
+        assertEquals("Unknown Schema", prop.getValue().toString());
+        assertEquals("Unknown Schema", dcAdapter.getTitle());
+        
+        //Access through a schema unknown to the XMP framework
+        prop = meta.getProperty("http://unknown.org/something", "dummy");
+        assertEquals("Dummy!", prop.getValue().toString());
+    }
+    
+    public void testParseStructures() throws Exception {
+        URL url = getClass().getResource("test-structures.xmp");
+        Metadata meta = XMPParser.parseXMP(url);
+        
+        XMPProperty prop;
+        
+        String testns = "http://foo.bar/test/";
+        prop = meta.getProperty(testns, "something");
+        assertEquals("blablah", prop.getValue().toString());
+        
+        prop = meta.getProperty(testns, "ingredients");
+        XMPArray array = prop.getArrayValue();
+        assertEquals(3, array.getSize());
+        XMPStructure struct = array.getStructure(0);
+        assertEquals(2, struct.getPropertyCount());
+        prop = struct.getValueProperty();
+        assertEquals("Apples", prop.getValue());
+        prop = struct.getProperty(testns, "amount");
+        assertEquals("4", prop.getValue());
+        
+        prop = meta.getProperty(testns, "villain");
+        XMPProperty prop1;
+        prop1 = prop.getStructureValue().getProperty(testns, "name");
+        assertEquals("Darth Sidious", prop1.getValue());
+        prop1 = prop.getStructureValue().getProperty(testns, "other-name");
+        assertEquals("Palpatine", prop1.getValue());
+        
+    }
+    
+}

Propchange: xmlgraphics/commons/trunk/test/java/org/apache/xmlgraphics/xmp/XMPParserTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: xmlgraphics/commons/trunk/test/java/org/apache/xmlgraphics/xmp/XMPParserTest.java
------------------------------------------------------------------------------
    svn:keywords = Id

Added: xmlgraphics/commons/trunk/test/java/org/apache/xmlgraphics/xmp/test-basics.xmp
URL: http://svn.apache.org/viewvc/xmlgraphics/commons/trunk/test/java/org/apache/xmlgraphics/xmp/test-basics.xmp?rev=606564&view=auto
==============================================================================
--- xmlgraphics/commons/trunk/test/java/org/apache/xmlgraphics/xmp/test-basics.xmp (added)
+++ xmlgraphics/commons/trunk/test/java/org/apache/xmlgraphics/xmp/test-basics.xmp Sun Dec 23 07:42:28 2007
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<x:xmpmeta xmlns:x="adobe:ns:meta/">
+  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+    <rdf:Description xmlns:dc="http://purl.org/dc/elements/1.1/" rdf:about="">
+      <dc:creator>
+        <rdf:Seq>
+          <rdf:li>John Doe</rdf:li>
+        </rdf:Seq>
+      </dc:creator>
+      <dc:title>Example document</dc:title>
+      <dc:date>2006-06-02T10:36:40+02:00</dc:date>
+    </rdf:Description>
+    <rdf:Description xmlns:xmp="http://ns.adobe.com/xap/1.0/" rdf:about="">
+      <xmp:CreateDate>2006-06-02T10:36:40+02:00</xmp:CreateDate>
+      <xmp:CreatorTool>An XML editor</xmp:CreatorTool>
+    </rdf:Description>
+    <rdf:Description xmlns:pdf="http://ns.adobe.com/pdf/1.3/" rdf:about="">
+      <pdf:Producer>Apache FOP Version SVN trunk</pdf:Producer>
+      <pdf:PDFVersion>1.4</pdf:PDFVersion>
+    </rdf:Description>
+  </rdf:RDF>
+</x:xmpmeta>

Added: xmlgraphics/commons/trunk/test/java/org/apache/xmlgraphics/xmp/test-structures.xmp
URL: http://svn.apache.org/viewvc/xmlgraphics/commons/trunk/test/java/org/apache/xmlgraphics/xmp/test-structures.xmp?rev=606564&view=auto
==============================================================================
--- xmlgraphics/commons/trunk/test/java/org/apache/xmlgraphics/xmp/test-structures.xmp (added)
+++ xmlgraphics/commons/trunk/test/java/org/apache/xmlgraphics/xmp/test-structures.xmp Sun Dec 23 07:42:28 2007
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<x:xmpmeta xmlns:x="adobe:ns:meta/">
+  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+    <rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">
+      <xmp:CreateDate>2007-12-23T16:22:04+01:00</xmp:CreateDate>
+    </rdf:Description>
+    <rdf:Description rdf:about="" xmlns:test="http://foo.bar/test/">
+
+      <test:something>blablah</test:something>
+      <test:ingredients>
+        <rdf:Bag>
+          <rdf:li>
+            <rdf:Description>
+              <rdf:value>Apples</rdf:value>
+              <test:amount>4</test:amount>
+            </rdf:Description>
+          </rdf:li>
+          <rdf:li>
+            <rdf:Description>
+              <rdf:value>Nuts</rdf:value>
+              <test:amount>12</test:amount>
+            </rdf:Description>
+          </rdf:li>
+          <rdf:li>
+            <rdf:Description>
+              <rdf:value>Hamburger</rdf:value>
+              <test:amount>1</test:amount>
+            </rdf:Description>
+          </rdf:li>
+        </rdf:Bag>
+      </test:ingredients>
+      <test:villain>
+        <rdf:Description>
+          <test:name>Darth Sidious</test:name>
+          <test:other-name>Palpatine</test:other-name>
+        </rdf:Description>
+      </test:villain>
+    </rdf:Description>
+  </rdf:RDF>
+</x:xmpmeta>

Added: xmlgraphics/commons/trunk/test/java/org/apache/xmlgraphics/xmp/unknown-schema.xmp
URL: http://svn.apache.org/viewvc/xmlgraphics/commons/trunk/test/java/org/apache/xmlgraphics/xmp/unknown-schema.xmp?rev=606564&view=auto
==============================================================================
--- xmlgraphics/commons/trunk/test/java/org/apache/xmlgraphics/xmp/unknown-schema.xmp (added)
+++ xmlgraphics/commons/trunk/test/java/org/apache/xmlgraphics/xmp/unknown-schema.xmp Sun Dec 23 07:42:28 2007
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<x:xmpmeta xmlns:x="adobe:ns:meta/">
+  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+    <rdf:Description xmlns:dc="http://purl.org/dc/elements/1.1/" rdf:about="">
+      <dc:title>Unknown Schema</dc:title>
+    </rdf:Description>
+    <rdf:Description xmlns:some="http://unknown.org/something" rdf:about="">
+      <some:dummy>Dummy!</some:dummy>
+    </rdf:Description>
+  </rdf:RDF>
+</x:xmpmeta>



---------------------------------------------------------------------
Apache XML Graphics Project URL: http://xmlgraphics.apache.org/
To unsubscribe, e-mail: commits-unsubscribe@xmlgraphics.apache.org
For additional commands, e-mail: commits-help@xmlgraphics.apache.org