FOP-3127: Allow XMP at PDF page level
diff --git a/src/main/java/org/apache/xmlgraphics/xmp/Metadata.java b/src/main/java/org/apache/xmlgraphics/xmp/Metadata.java
index d1feb75..5f8a44e 100644
--- a/src/main/java/org/apache/xmlgraphics/xmp/Metadata.java
+++ b/src/main/java/org/apache/xmlgraphics/xmp/Metadata.java
@@ -39,7 +39,7 @@
  */
 public class Metadata implements XMLizable, PropertyAccess {
 
-    private Map properties = new java.util.HashMap();
+    private Map<QName, XMPProperty> properties = new java.util.HashMap<>();
 
     /** {@inheritDoc} */
     public void setProperty(XMPProperty prop) {
@@ -53,13 +53,12 @@
 
     /** {@inheritDoc} */
     public XMPProperty getProperty(QName name) {
-        XMPProperty prop = (XMPProperty)properties.get(name);
-        return prop;
+        return properties.get(name);
     }
 
     /** {@inheritDoc} */
     public XMPProperty removeProperty(QName name) {
-        return (XMPProperty)properties.remove(name);
+        return properties.remove(name);
     }
 
     /** {@inheritDoc} */
@@ -102,6 +101,9 @@
         handler.startElement(XMPConstants.XMP_NAMESPACE, "xmpmeta", "x:xmpmeta", atts);
         handler.startPrefixMapping("rdf", XMPConstants.RDF_NAMESPACE);
         handler.startElement(XMPConstants.RDF_NAMESPACE, "RDF", "rdf:RDF", atts);
+
+        writeCustomDescription(handler);
+
         //Get all property namespaces
         Set namespaces = new java.util.HashSet();
         Iterator iter = properties.keySet().iterator();
@@ -121,7 +123,7 @@
 
             for (Object o : properties.values()) {
                 XMPProperty prop = (XMPProperty) o;
-                if (prop.getName().getNamespaceURI().equals(ns)) {
+                if (prop.getName().getNamespaceURI().equals(ns) && !prop.attribute) {
                     if (first) {
                         if (prefix == null) {
                             prefix = prop.getName().getPrefix();
@@ -154,4 +156,27 @@
         handler.endPrefixMapping("x");
     }
 
+    private void writeCustomDescription(ContentHandler handler) throws SAXException {
+        AttributesImpl atts = new AttributesImpl();
+        boolean empty = true;
+        for (XMPProperty prop : properties.values()) {
+            if (prop.attribute) {
+                atts.addAttribute(prop.getNamespace(), prop.getName().getLocalName(), prop.getName().getQName(),
+                        "CDATA", (String) prop.getValue());
+                if (prop.getName().getPrefix() != null) {
+                    handler.startPrefixMapping(prop.getName().getPrefix(), prop.getNamespace());
+                    handler.endPrefixMapping(prop.getName().getPrefix());
+                }
+                empty = false;
+            }
+        }
+        if (!empty) {
+            atts.addAttribute(XMPConstants.RDF_NAMESPACE,
+                    "about", "rdf:about", "CDATA", "");
+            handler.startElement(XMPConstants.RDF_NAMESPACE,
+                    "RDF", "rdf:Description", atts);
+            handler.endElement(XMPConstants.RDF_NAMESPACE, "RDF", "rdf:Description");
+        }
+    }
+
 }
diff --git a/src/main/java/org/apache/xmlgraphics/xmp/XMPArray.java b/src/main/java/org/apache/xmlgraphics/xmp/XMPArray.java
index 018768c..da79790 100644
--- a/src/main/java/org/apache/xmlgraphics/xmp/XMPArray.java
+++ b/src/main/java/org/apache/xmlgraphics/xmp/XMPArray.java
@@ -21,6 +21,7 @@
 
 import java.net.URI;
 
+import java.util.ArrayList;
 import java.util.List;
 
 import org.xml.sax.ContentHandler;
@@ -36,6 +37,7 @@
     private XMPArrayType type;
     private List values = new java.util.ArrayList();
     private List xmllang = new java.util.ArrayList();
+    private List<String> parseTypes = new ArrayList<>();
 
     /**
      * Main constructor
@@ -151,9 +153,23 @@
      * Adds a new value to the array
      * @param value the value
      */
-    public void add(Object value) {
+    public void add(Object value, String lang, String parseType) {
         values.add(value);
-        xmllang.add(null);
+        xmllang.add(lang);
+        parseTypes.add(parseType);
+    }
+
+    /**
+     * 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)
+     */
+    public void add(Object value, String lang) {
+        add(value, lang, null);
+    }
+
+    public void add(Object value) {
+        add(value, null, null);
     }
 
     /**
@@ -166,6 +182,7 @@
         if (idx >= 0) {
             values.remove(idx);
             xmllang.remove(idx);
+            parseTypes.remove(idx);
             return true;
         }
         return false;
@@ -173,16 +190,6 @@
     }
 
     /**
-     * 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)
-     */
-    public void add(String value, String lang) {
-        values.add(value);
-        xmllang.add(lang);
-    }
-
-    /**
      * Returns the current number of values in the array.
      * @return the current number of values in the array
      */
@@ -226,6 +233,10 @@
                 atts.addAttribute(XMPConstants.RDF_NAMESPACE, "resource",
                         "rdf:resource", "CDATA", ((URI)v).toString());
             }
+            String parseType = parseTypes.get(i);
+            if (parseType != null) {
+                atts.addAttribute(XMPConstants.RDF_NAMESPACE, "parseType", "rdf:parseType", "CDATA", parseType);
+            }
             handler.startElement(XMPConstants.RDF_NAMESPACE,
                     "li", "rdf:li", atts);
             if (v instanceof XMPComplexValue) {
diff --git a/src/main/java/org/apache/xmlgraphics/xmp/XMPHandler.java b/src/main/java/org/apache/xmlgraphics/xmp/XMPHandler.java
index b3b918f..2eed5df 100644
--- a/src/main/java/org/apache/xmlgraphics/xmp/XMPHandler.java
+++ b/src/main/java/org/apache/xmlgraphics/xmp/XMPHandler.java
@@ -153,6 +153,7 @@
                         String qn = attributes.getQName(i);
                         String v = attributes.getValue(i);
                         XMPProperty prop = new XMPProperty(new QName(ns, qn), v);
+                        prop.attribute = true;
                         getCurrentProperties().setProperty(prop);
                     }
                 }
@@ -204,6 +205,12 @@
         this.nestingInfoStack.push("struct");
     }
 
+    private void startThinStructure() {
+        XMPThinStructure struct = new XMPThinStructure();
+        contextStack.push(struct);
+        nestingInfoStack.push("struct");
+    }
+
     /** {@inheritDoc} */
     public void endElement(String uri, String localName, String qName) throws SAXException {
         Attributes atts = (Attributes)attributesStack.pop();
@@ -212,27 +219,24 @@
         } else if (XMPConstants.RDF_NAMESPACE.equals(uri) && !"value".equals(localName)) {
             if ("li".equals(localName)) {
                 XMPStructure struct = getCurrentStructure();
+                String parseType = atts.getValue("rdf:parseType");
                 if (struct != null) {
                     //Pop the structure
                     this.contextStack.pop();
                     this.nestingInfoStack.pop();
-                    getCurrentArray(true).add(struct);
+                    getCurrentArray(true).add(struct, null, parseType);
                 } 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);
-                        }
+                        getCurrentArray(true).add(s, lang, parseType);
                     } else {
                         String res = atts.getValue(XMPConstants.RDF_NAMESPACE,
                                 "resource");
                         if (res != null) {
                             try {
                                 URI resource = new URI(res);
-                                getCurrentArray(true).add(resource);
+                                getCurrentArray(true).add(resource, null, parseType);
                             } catch (URISyntaxException e) {
                                 throw new SAXException("rdf:resource value is not a well-formed URI", e);
                             }
@@ -288,7 +292,7 @@
                 throw new IllegalStateException("No content in XMP property");
             }
             if (getCurrentProperties() == null) {
-                startStructure();
+                startThinStructure();
             }
             getCurrentProperties().setProperty(prop);
         }
diff --git a/src/main/java/org/apache/xmlgraphics/xmp/XMPProperty.java b/src/main/java/org/apache/xmlgraphics/xmp/XMPProperty.java
index 5f2b5ed..d7718d6 100644
--- a/src/main/java/org/apache/xmlgraphics/xmp/XMPProperty.java
+++ b/src/main/java/org/apache/xmlgraphics/xmp/XMPProperty.java
@@ -40,6 +40,7 @@
     private Object value;
     private String xmllang;
     private Map qualifiers;
+    protected boolean attribute;
 
     /**
      * Creates a new XMP property.
@@ -186,6 +187,9 @@
         String prefix = getName().getPrefix();
         if (prefix == null || "".equals(prefix)) {
             XMPSchema schema = XMPSchemaRegistry.getInstance().getSchema(getNamespace());
+            if (schema == null) {
+                return getName().getLocalName();
+            }
             prefix = schema.getPreferredPrefix();
         }
         return prefix + ":" + getName().getLocalName();
diff --git a/src/main/java/org/apache/xmlgraphics/xmp/XMPStructure.java b/src/main/java/org/apache/xmlgraphics/xmp/XMPStructure.java
index 86f3d20..ba7bcd8 100644
--- a/src/main/java/org/apache/xmlgraphics/xmp/XMPStructure.java
+++ b/src/main/java/org/apache/xmlgraphics/xmp/XMPStructure.java
@@ -33,7 +33,7 @@
  */
 public class XMPStructure extends XMPComplexValue implements PropertyAccess {
 
-    private Map properties = new java.util.HashMap();
+    protected Map properties = new java.util.HashMap();
 
     /**
      * Main constructor
diff --git a/src/main/java/org/apache/xmlgraphics/xmp/XMPThinStructure.java b/src/main/java/org/apache/xmlgraphics/xmp/XMPThinStructure.java
new file mode 100644
index 0000000..afc015f
--- /dev/null
+++ b/src/main/java/org/apache/xmlgraphics/xmp/XMPThinStructure.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+package org.apache.xmlgraphics.xmp;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+
+public class XMPThinStructure extends XMPStructure {
+    public void toSAX(ContentHandler handler) throws SAXException {
+        for (Object o : properties.values()) {
+            XMPProperty prop = (XMPProperty) o;
+            prop.toSAX(handler);
+        }
+    }
+}
diff --git a/src/test/java/org/apache/xmlgraphics/xmp/XMPParserTestCase.java b/src/test/java/org/apache/xmlgraphics/xmp/XMPParserTestCase.java
index 76ecd81..922f148 100644
--- a/src/test/java/org/apache/xmlgraphics/xmp/XMPParserTestCase.java
+++ b/src/test/java/org/apache/xmlgraphics/xmp/XMPParserTestCase.java
@@ -33,6 +33,9 @@
 import org.xml.sax.Attributes;
 import org.xml.sax.helpers.DefaultHandler;
 
+import org.xml.sax.Attributes;
+import org.xml.sax.helpers.DefaultHandler;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
@@ -204,21 +207,44 @@
         final StringBuilder sb = new StringBuilder();
         meta.toSAX(new DefaultHandler() {
             public void startElement(String uri, String localName, String qName, Attributes attributes) {
-                sb.append(qName).append("\n");
+                sb.append("<").append(qName);
+                for (int i = 0; i < attributes.getLength(); i++) {
+                    sb.append(" ").append(attributes.getQName(i)).append("=").append(attributes.getValue(i));
+                }
+                sb.append(">\n");
             }
         });
-        assertEquals("x:xmpmeta\n"
-                + "rdf:RDF\n"
-                + "rdf:Description\n"
-                + "pdfaExtension:schemas\n"
-                + "rdf:Bag\n"
-                + "rdf:li\n"
-                + "rdf:Description\n"
-                + "pdfaSchema:property\n"
-                + "rdf:Seq\n"
-                + "rdf:li\n"
-                + "rdf:Description\n"
-                + "pdfaProperty:name\n", sb.toString());
+        assertEquals("<x:xmpmeta>\n"
+                + "<rdf:RDF>\n"
+                + "<rdf:Description abc:xyz=rty rdf:about=>\n"
+                + "<rdf:Description rdf:about=>\n"
+                + "<pdfaExtension:schemas>\n"
+                + "<rdf:Bag>\n"
+                + "<rdf:li rdf:parseType=Resource>\n"
+                + "<pdfaSchema:property>\n"
+                + "<rdf:Seq>\n"
+                + "<rdf:li rdf:parseType=Resource>\n"
+                + "<pdfaProperty:name>\n", sb.toString());
+    }
+
+    @Test
+    public void testNoNamespace() throws Exception {
+        URL url = getClass().getResource("test-no-namespace.xmp");
+        Metadata meta = XMPParser.parseXMP(url);
+        final StringBuilder sb = new StringBuilder();
+        meta.toSAX(new DefaultHandler() {
+            public void startElement(String uri, String localName, String qName, Attributes attributes) {
+                sb.append("<").append(qName);
+                for (int i = 0; i < attributes.getLength(); i++) {
+                    sb.append(" ").append(attributes.getQName(i)).append("=").append(attributes.getValue(i));
+                }
+                sb.append(">\n");
+            }
+        });
+        assertEquals("<x:xmpmeta>\n"
+                + "<rdf:RDF>\n"
+                + "<rdf:Description rdf:about=>\n"
+                + "<a>\n", sb.toString());
     }
 
     @Test
diff --git a/src/test/java/org/apache/xmlgraphics/xmp/test-no-namespace.xmp b/src/test/java/org/apache/xmlgraphics/xmp/test-no-namespace.xmp
new file mode 100644
index 0000000..30819a8
--- /dev/null
+++ b/src/test/java/org/apache/xmlgraphics/xmp/test-no-namespace.xmp
@@ -0,0 +1,3 @@
+<x:xmpmeta xmlns:x="adobe:ns:meta/">
+<a/>
+</x:xmpmeta>
\ No newline at end of file
diff --git a/src/test/java/org/apache/xmlgraphics/xmp/test-subproperty.xmp b/src/test/java/org/apache/xmlgraphics/xmp/test-subproperty.xmp
index 2384f54..492de77 100644
--- a/src/test/java/org/apache/xmlgraphics/xmp/test-subproperty.xmp
+++ b/src/test/java/org/apache/xmlgraphics/xmp/test-subproperty.xmp
@@ -1,5 +1,6 @@
 <x:xmpmeta xmlns:x="adobe:ns:meta/">
-    <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+    <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:abc="http://www.abc.de/abc/">
+        <rdf:Description rdf:about="" abc:xyz="rty"/>
         <rdf:Description xmlns:pdfaExtension="http://www.aiim.org/pdfa/ns/extension/"
                          xmlns:pdfaSchema="http://www.aiim.org/pdfa/ns/schema#"
                          xmlns:pdfaProperty="http://www.aiim.org/pdfa/ns/property#" rdf:about="">