Merge commit '25f0eb884d66f7eb0d16953fe3f54f96df544e28' into jb-mr1-dev-plus-aosp
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..3cc93fb
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,2 @@
+# Include the makefiles under this directory.
+include $(call all-makefiles-under,$(call my-dir))
diff --git a/MODULE_LICENSE_BSD b/MODULE_LICENSE_BSD
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_BSD
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..43029dd
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,32 @@
+The BSD License
+
+Copyright (c) 1999 - 2010, Adobe Systems Incorporated
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or 
+without modification, are permitted provided that the following 
+conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, 
+this list of conditions and the following disclaimer.
+ 
+* 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.
+ 
+* Neither the name of Adobe Systems Incorporated, 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 COPYRIGHT OWNER 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.
+
diff --git a/XMPCore/Android.mk b/XMPCore/Android.mk
new file mode 100644
index 0000000..955d296
--- /dev/null
+++ b/XMPCore/Android.mk
@@ -0,0 +1,13 @@
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+# Include all the java files.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := 8
+
+# The name of the jar file to create.
+LOCAL_MODULE := xmp_toolkit
+
+# Build a static jar file.
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/XMPCore/src/com/adobe/xmp/XMPConst.java b/XMPCore/src/com/adobe/xmp/XMPConst.java
new file mode 100644
index 0000000..61a5ca8
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/XMPConst.java
@@ -0,0 +1,167 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp;
+
+
+/**
+ * Common constants for the XMP Toolkit. 
+ * 
+ * @since 20.01.2006
+ */
+public interface XMPConst
+{
+	// ---------------------------------------------------------------------------------------------
+	// Standard namespace URI constants
+
+
+	// Standard namespaces
+
+	/** The XML namespace for XML. */
+	String NS_XML = "http://www.w3.org/XML/1998/namespace";
+	/** The XML namespace for RDF. */
+	String NS_RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+	/** The XML namespace for the Dublin Core schema. */
+	String NS_DC = "http://purl.org/dc/elements/1.1/";
+	/** The XML namespace for the IPTC Core schema. */
+	String NS_IPTCCORE = "http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/";
+	
+	// Adobe standard namespaces
+
+	/** The XML namespace Adobe XMP Metadata. */
+	String NS_X = "adobe:ns:meta/";
+	/** */
+	String NS_IX = "http://ns.adobe.com/iX/1.0/";
+	/** The XML namespace for the XMP "basic" schema. */
+	String NS_XMP = "http://ns.adobe.com/xap/1.0/";
+	/** The XML namespace for the XMP copyright schema. */
+	String NS_XMP_RIGHTS = "http://ns.adobe.com/xap/1.0/rights/";
+	/** The XML namespace for the XMP digital asset management schema. */
+	String NS_XMP_MM = "http://ns.adobe.com/xap/1.0/mm/";
+	/** The XML namespace for the job management schema. */
+	String NS_XMP_BJ = "http://ns.adobe.com/xap/1.0/bj/";
+	/** The XML namespace for the job management schema. */
+	String NS_XMP_NOTE = "http://ns.adobe.com/xmp/note/";
+	
+	/** The XML namespace for the PDF schema. */
+	String NS_PDF = "http://ns.adobe.com/pdf/1.3/";
+	/** The XML namespace for the PDF schema. */
+	String NS_PDFX = "http://ns.adobe.com/pdfx/1.3/";
+	/** */
+	String NS_PDFX_ID = "http://www.npes.org/pdfx/ns/id/";
+	/** */
+	String NS_PDFA_SCHEMA = "http://www.aiim.org/pdfa/ns/schema#";
+	/** */
+	String NS_PDFA_PROPERTY = "http://www.aiim.org/pdfa/ns/property#";
+	/** */
+	String NS_PDFA_TYPE = "http://www.aiim.org/pdfa/ns/type#";
+	/** */
+	String NS_PDFA_FIELD = "http://www.aiim.org/pdfa/ns/field#";
+	/** */
+	String NS_PDFA_ID = "http://www.aiim.org/pdfa/ns/id/";
+	/** */
+	String NS_PDFA_EXTENSION = "http://www.aiim.org/pdfa/ns/extension/";
+	/** The XML namespace for the Photoshop custom schema. */
+	String NS_PHOTOSHOP = "http://ns.adobe.com/photoshop/1.0/";
+	/** The XML namespace for the Photoshop Album schema. */
+	String NS_PSALBUM = "http://ns.adobe.com/album/1.0/";
+	/** The XML namespace for Adobe's EXIF schema. */
+	String NS_EXIF = "http://ns.adobe.com/exif/1.0/";
+	/** */
+	String NS_EXIF_AUX = "http://ns.adobe.com/exif/1.0/aux/";
+	/** The XML namespace for Adobe's TIFF schema. */
+	String NS_TIFF = "http://ns.adobe.com/tiff/1.0/";
+	/** */
+	String NS_PNG = "http://ns.adobe.com/png/1.0/";
+	/** */
+	String NS_JPEG = "http://ns.adobe.com/jpeg/1.0/";
+	/** */
+	String NS_JP2K = "http://ns.adobe.com/jp2k/1.0/";
+	/** */
+	String NS_CAMERARAW = "http://ns.adobe.com/camera-raw-settings/1.0/";
+	/** */
+	String NS_ADOBESTOCKPHOTO = "http://ns.adobe.com/StockPhoto/1.0/";
+	/** */
+	String NS_CREATOR_ATOM = "http://ns.adobe.com/creatorAtom/1.0/";
+	/** */
+	String NS_ASF = "http://ns.adobe.com/asf/1.0/";
+	/** */
+	String NS_WAV = "http://ns.adobe.com/xmp/wav/1.0/";
+	
+	
+	// XMP namespaces that are Adobe private
+	
+	/** */
+	String NS_DM = "http://ns.adobe.com/xmp/1.0/DynamicMedia/";
+	/** */
+	String NS_TRANSIENT = "http://ns.adobe.com/xmp/transient/1.0/";	
+	/** legaciy dublin core NS, will be converted to NS_DC */
+	String NS_DC_DEPRECATED = "http://purl.org/dc/1.1/";
+	
+	
+	// XML namespace constants for qualifiers and structured property fields.
+
+	/** The XML namespace for qualifiers of the xmp:Identifier property. */
+	String TYPE_IDENTIFIERQUAL = "http://ns.adobe.com/xmp/Identifier/qual/1.0/";
+	/** The XML namespace for fields of the Dimensions type. */
+	String TYPE_DIMENSIONS = "http://ns.adobe.com/xap/1.0/sType/Dimensions#";
+	/** */
+	String TYPE_TEXT = "http://ns.adobe.com/xap/1.0/t/";
+	/** */
+	String TYPE_PAGEDFILE = "http://ns.adobe.com/xap/1.0/t/pg/";
+	/** */
+	String TYPE_GRAPHICS = "http://ns.adobe.com/xap/1.0/g/";
+	/** The XML namespace for fields of a graphical image. Used for the Thumbnail type. */
+	String TYPE_IMAGE = "http://ns.adobe.com/xap/1.0/g/img/";
+	/** */
+	String TYPE_FONT = "http://ns.adobe.com/xap/1.0/sType/Font#";
+	/** The XML namespace for fields of the ResourceEvent type. */
+	String TYPE_RESOURCEEVENT = "http://ns.adobe.com/xap/1.0/sType/ResourceEvent#";
+	/** The XML namespace for fields of the ResourceRef type. */
+	String TYPE_RESOURCEREF = "http://ns.adobe.com/xap/1.0/sType/ResourceRef#";
+	/** The XML namespace for fields of the Version type. */
+	String TYPE_ST_VERSION = "http://ns.adobe.com/xap/1.0/sType/Version#";
+	/** The XML namespace for fields of the JobRef type. */
+	String TYPE_ST_JOB = "http://ns.adobe.com/xap/1.0/sType/Job#";
+	/** */
+	String TYPE_MANIFESTITEM = "http://ns.adobe.com/xap/1.0/sType/ManifestItem#";
+
+	
+
+	// ---------------------------------------------------------------------------------------------
+	// Basic types and constants
+
+	/**
+	 * The canonical true string value for Booleans in serialized XMP. Code that converts from the
+	 * string to a bool should be case insensitive, and even allow "1".
+	 */
+	String TRUESTR = "True";
+	/**
+	 * The canonical false string value for Booleans in serialized XMP. Code that converts from the
+	 * string to a bool should be case insensitive, and even allow "0".
+	 */
+	String FALSESTR = "False";
+	/** Index that has the meaning to be always the last item in an array. */
+	int ARRAY_LAST_ITEM = -1;
+	/** Node name of an array item. */
+	String ARRAY_ITEM_NAME = "[]";
+	/** The x-default string for localized properties */
+	String X_DEFAULT = "x-default";
+	/** xml:lang qualfifier */
+	String XML_LANG = "xml:lang";
+	/** rdf:type qualfifier */
+	String RDF_TYPE = "rdf:type";
+	
+	/** Processing Instruction (PI) for xmp packet */
+	String XMP_PI = "xpacket";
+	/** XMP meta tag version new */
+	String TAG_XMPMETA = "xmpmeta";
+	/** XMP meta tag version old */
+	String TAG_XAPMETA = "xapmeta";
+}
\ No newline at end of file
diff --git a/XMPCore/src/com/adobe/xmp/XMPDateTime.java b/XMPCore/src/com/adobe/xmp/XMPDateTime.java
new file mode 100644
index 0000000..505d580
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/XMPDateTime.java
@@ -0,0 +1,102 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp;
+
+import java.util.Calendar;
+import java.util.TimeZone;
+
+
+/**
+ * The <code>XMPDateTime</code>-class represents a point in time up to a resolution of nano
+ * seconds. Dates and time in the serialized XMP are ISO 8601 strings. There are utility functions
+ * to convert to the ISO format, a <code>Calendar</code> or get the Timezone. The fields of
+ * <code>XMPDateTime</code> are:
+ * <ul>
+ * <li> month - The month in the range 1..12.
+ * <li> day - The day of the month in the range 1..31.
+ * <li> minute - The minute in the range 0..59.
+ * <li> hour - The time zone hour in the range 0..23.
+ * <li> minute - The time zone minute in the range 0..59.
+ * <li> nanoSecond - The nano seconds within a second. <em>Note:</em> if the XMPDateTime is
+ * converted into a calendar, the resolution is reduced to milli seconds.
+ * <li> timeZone - a <code>TimeZone</code>-object.
+ * </ul>
+ * DateTime values are occasionally used in cases with only a date or only a time component. A date
+ * without a time has zeros for all the time fields. A time without a date has zeros for all date
+ * fields (year, month, and day).
+ */
+public interface XMPDateTime extends Comparable
+{
+	/** @return Returns the year, can be negative. */
+	int getYear();
+	
+	/** @param year Sets the year */
+	void setYear(int year);
+
+	/** @return Returns The month in the range 1..12. */
+	int getMonth();
+
+	/** @param month Sets the month 1..12 */
+	void setMonth(int month);
+	
+	/** @return Returns the day of the month in the range 1..31. */
+	int getDay();
+	
+	/** @param day Sets the day 1..31 */
+	void setDay(int day);
+
+	/** @return Returns hour - The hour in the range 0..23. */
+	int getHour();
+
+	/** @param hour Sets the hour in the range 0..23. */
+	void setHour(int hour);
+	
+	/** @return Returns the minute in the range 0..59. */ 
+	int getMinute();
+
+	/** @param minute Sets the minute in the range 0..59. */
+	void setMinute(int minute);
+	
+	/** @return Returns the second in the range 0..59. */
+	int getSecond();
+
+	/** @param second Sets the second in the range 0..59. */
+	void setSecond(int second);
+	
+	/**
+	 * @return Returns milli-, micro- and nano seconds.
+	 * 		   Nanoseconds within a second, often left as zero?
+	 */
+	int getNanoSecond();
+
+	/**
+	 * @param nanoSecond Sets the milli-, micro- and nano seconds.
+	 *		Granularity goes down to milli seconds. 		   
+	 */
+	void setNanoSecond(int nanoSecond);
+	
+	/** @return Returns the time zone. */
+	TimeZone getTimeZone();
+
+	/** @param tz a time zone to set */
+	void setTimeZone(TimeZone tz);
+	
+	/** 
+	 * @return Returns a <code>Calendar</code> (only with milli second precision). <br>
+	 *  		<em>Note:</em> the dates before Oct 15th 1585 (which normally fall into validity of 
+	 *  		the Julian calendar) are also rendered internally as Gregorian dates. 
+	 */
+	Calendar getCalendar();
+	
+	/**
+	 * @return Returns the ISO 8601 string representation of the date and time.
+	 */
+	String getISO8601String();
+}
\ No newline at end of file
diff --git a/XMPCore/src/com/adobe/xmp/XMPDateTimeFactory.java b/XMPCore/src/com/adobe/xmp/XMPDateTimeFactory.java
new file mode 100644
index 0000000..ec0a116
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/XMPDateTimeFactory.java
@@ -0,0 +1,153 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+import com.adobe.xmp.impl.XMPDateTimeImpl;
+
+
+/**
+ * A factory to create <code>XMPDateTime</code>-instances from a <code>Calendar</code> or an
+ * ISO 8601 string or for the current time.
+ * 
+ * @since 16.02.2006
+ */
+public final class XMPDateTimeFactory
+{
+	/** The UTC TimeZone */
+	private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
+
+	
+
+	/** Private constructor */
+	private XMPDateTimeFactory()
+	{
+		// EMPTY
+	}
+
+
+	/**
+	 * Creates an <code>XMPDateTime</code> from a <code>Calendar</code>-object.
+	 * 
+	 * @param calendar a <code>Calendar</code>-object.
+	 * @return An <code>XMPDateTime</code>-object.
+	 */
+	public static XMPDateTime createFromCalendar(Calendar calendar)
+	{
+		return new XMPDateTimeImpl(calendar);
+	}
+
+	
+	/**
+	 * Creates an <code>XMPDateTime</code>-object from initial values.
+	 * @param year years
+	 * @param month months from 1 to 12<br>
+	 * <em>Note:</em> Remember that the month in {@link Calendar} is defined from 0 to 11.
+	 * @param day days
+	 * @param hour hours
+	 * @param minute minutes
+	 * @param second seconds
+	 * @param nanoSecond nanoseconds 
+	 * @return Returns an <code>XMPDateTime</code>-object.
+	 */
+	public static XMPDateTime create(int year, int month, int day, 
+		int hour, int minute, int second, int nanoSecond)
+	{
+		XMPDateTime dt = new XMPDateTimeImpl();
+		dt.setYear(year);
+		dt.setMonth(month);
+		dt.setDay(day);
+		dt.setHour(hour);
+		dt.setMinute(minute);
+		dt.setSecond(second);
+		dt.setNanoSecond(nanoSecond);
+		return dt;
+	}
+	
+
+	/**
+	 * Creates an <code>XMPDateTime</code> from an ISO 8601 string.
+	 * 
+	 * @param strValue The ISO 8601 string representation of the date/time.
+	 * @return An <code>XMPDateTime</code>-object.
+	 * @throws XMPException When the ISO 8601 string is non-conform
+	 */
+	public static XMPDateTime createFromISO8601(String strValue) throws XMPException
+	{
+		return new XMPDateTimeImpl(strValue);
+	}
+
+
+	/**
+	 * Obtain the current date and time.
+	 * 
+	 * @return Returns The returned time is UTC, properly adjusted for the local time zone. The
+	 *         resolution of the time is not guaranteed to be finer than seconds.
+	 */
+	public static XMPDateTime getCurrentDateTime()
+	{
+		return new XMPDateTimeImpl(new GregorianCalendar());
+	}
+
+
+	/**
+	 * Sets the local time zone without touching any other Any existing time zone value is replaced,
+	 * the other date/time fields are not adjusted in any way.
+	 * 
+	 * @param dateTime the <code>XMPDateTime</code> variable containing the value to be modified.
+	 * @return Returns an updated <code>XMPDateTime</code>-object.
+	 */
+	public static XMPDateTime setLocalTimeZone(XMPDateTime dateTime)
+	{
+		Calendar cal = dateTime.getCalendar();
+		cal.setTimeZone(TimeZone.getDefault());
+		return new XMPDateTimeImpl(cal);
+	}
+
+
+	/**
+	 * Make sure a time is UTC. If the time zone is not UTC, the time is
+	 * adjusted and the time zone set to be UTC.
+	 * 
+	 * @param dateTime
+	 *            the <code>XMPDateTime</code> variable containing the time to
+	 *            be modified.
+	 * @return Returns an updated <code>XMPDateTime</code>-object.
+	 */
+	public static XMPDateTime convertToUTCTime(XMPDateTime dateTime)
+	{
+		long timeInMillis = dateTime.getCalendar().getTimeInMillis();
+		GregorianCalendar cal = new GregorianCalendar(UTC);
+		cal.setGregorianChange(new Date(Long.MIN_VALUE));		
+		cal.setTimeInMillis(timeInMillis);
+		return new XMPDateTimeImpl(cal);
+	}
+
+
+	/**
+	 * Make sure a time is local. If the time zone is not the local zone, the time is adjusted and
+	 * the time zone set to be local.
+	 * 
+	 * @param dateTime the <code>XMPDateTime</code> variable containing the time to be modified.
+	 * @return Returns an updated <code>XMPDateTime</code>-object.
+	 */
+	public static XMPDateTime convertToLocalTime(XMPDateTime dateTime)
+	{
+		long timeInMillis = dateTime.getCalendar().getTimeInMillis();
+		// has automatically local timezone
+		GregorianCalendar cal = new GregorianCalendar(); 
+		cal.setTimeInMillis(timeInMillis);
+		return new XMPDateTimeImpl(cal);
+	}
+}
\ No newline at end of file
diff --git a/XMPCore/src/com/adobe/xmp/XMPError.java b/XMPCore/src/com/adobe/xmp/XMPError.java
new file mode 100644
index 0000000..c04c834
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/XMPError.java
@@ -0,0 +1,44 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp;
+
+
+/**
+ * @since   21.09.2006
+ */
+public interface XMPError
+{
+	/** */
+	int UNKNOWN = 0;
+	/** */
+	int BADPARAM = 4;
+	/** */
+	int BADVALUE = 5;
+	/** */
+	int INTERNALFAILURE = 9;
+	/** */
+	int BADSCHEMA = 101;
+	/** */
+	int BADXPATH = 102;
+	/** */
+	int BADOPTIONS = 103;
+	/** */
+	int BADINDEX = 104;
+	/** */
+	int BADSERIALIZE = 107;
+	/** */
+	int BADXML = 201;
+	/** */
+	int BADRDF = 202;
+	/** */
+	int BADXMP = 203;
+	/** <em>Note:</em> This is an error code introduced by Java. */
+	int BADSTREAM = 204;
+}
diff --git a/XMPCore/src/com/adobe/xmp/XMPException.java b/XMPCore/src/com/adobe/xmp/XMPException.java
new file mode 100644
index 0000000..087b0e8
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/XMPException.java
@@ -0,0 +1,55 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp;
+
+/**
+ * This exception wraps all errors that occur in the XMP Toolkit.
+ * 
+ * @since   16.02.2006
+ */
+public class XMPException extends Exception
+{
+	/** the errorCode of the XMP toolkit */
+	private int errorCode;
+
+
+	/**
+	 * Constructs an exception with a message and an error code. 
+	 * @param message the message
+	 * @param errorCode the error code
+	 */
+	public XMPException(String message, int errorCode)
+	{
+		super(message);
+		this.errorCode = errorCode;
+	}
+
+
+	/**
+	 * Constructs an exception with a message, an error code and a <code>Throwable</code>
+	 * @param message the error message.
+	 * @param errorCode the error code
+	 * @param t the exception source
+	 */
+	public XMPException(String message, int errorCode, Throwable t)
+	{
+		super(message, t);
+		this.errorCode = errorCode;
+	}
+
+
+	/**
+	 * @return Returns the errorCode.
+	 */
+	public int getErrorCode()
+	{
+		return errorCode;
+	}
+}
\ No newline at end of file
diff --git a/XMPCore/src/com/adobe/xmp/XMPIterator.java b/XMPCore/src/com/adobe/xmp/XMPIterator.java
new file mode 100644
index 0000000..fa4edf6
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/XMPIterator.java
@@ -0,0 +1,82 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp;
+
+import java.util.Iterator;
+
+
+/**
+ * Interface for the <code>XMPMeta</code> iteration services.
+ * <code>XMPIterator</code> provides a uniform means to iterate over the
+ * schema and properties within an XMP object.
+ * <p>
+ * The iteration over the schema and properties within an XMP object is very
+ * complex. It is helpful to have a thorough understanding of the XMP data tree.
+ * One way to learn this is to create some complex XMP and examine the output of
+ * <code>XMPMeta#toString</code>. This is also described in the XMP
+ * Specification, in the XMP Data Model chapter.
+ * <p>
+ * The top of the XMP data tree is a single root node. This does not explicitly
+ * appear in the dump and is never visited by an iterator (that is, it is never
+ * returned from <code>XMPIterator#next()</code>). Beneath the root are
+ * schema nodes. These are just collectors for top level properties in the same
+ * namespace. They are created and destroyed implicitly. Beneath the schema
+ * nodes are the property nodes. The nodes below a property node depend on its
+ * type (simple, struct, or array) and whether it has qualifiers.
+ * <p>
+ * An <code>XMPIterator</code> is created by XMPMeta#interator() constructor
+ * defines a starting point for the iteration and options that control how it
+ * proceeds. By default the iteration starts at the root and visits all nodes
+ * beneath it in a depth first manner. The root node is not visited, the first
+ * visited node is a schema node. You can provide a schema name or property path
+ * to select a different starting node. By default this visits the named root
+ * node first then all nodes beneath it in a depth first manner.
+ * <p>
+ * The <code>XMPIterator#next()</code> method delivers the schema URI, path,
+ * and option flags for the node being visited. If the node is simple it also
+ * delivers the value. Qualifiers for this node are visited next. The fields of
+ * a struct or items of an array are visited after the qualifiers of the parent.
+ * <p>
+ * The options to control the iteration are:
+ * <ul>
+ * <li>JUST_CHILDREN - Visit just the immediate children of the root. Skip
+ * the root itself and all nodes below the immediate children. This omits the
+ * qualifiers of the immediate children, the qualifier nodes being below what
+ * they qualify, default is to visit the complete subtree.
+ * <li>UST_LEAFNODES - Visit just the leaf property nodes and their
+ * qualifiers.
+ * <li>JUST_LEAFNAME - Return just the leaf component of the node names.
+ * The default is to return the full xmp path.
+ * <li>OMIT_QUALIFIERS - Do not visit the qualifiers.
+ * <li>INCLUDE_ALIASES - Adds known alias properties to the properties in the iteration.
+ * 		<em>Note:</em> Not supported in Java XMPCore! 
+ * </ul>
+ * <p>
+ * <code>next()</code> returns <code>XMPPropertyInfo</code>-objects and throws
+ * a <code>NoSuchElementException</code> if there are no more properties to
+ * return.
+ * 
+ * @since 25.01.2006
+ */
+public interface XMPIterator extends Iterator
+{
+	/**
+	 * Skip the subtree below the current node when <code>next()</code> is
+	 * called.
+	 */
+	void skipSubtree();
+
+
+	/**
+	 * Skip the subtree below and remaining siblings of the current node when
+	 * <code>next()</code> is called.
+	 */
+	void skipSiblings();
+}
\ No newline at end of file
diff --git a/XMPCore/src/com/adobe/xmp/XMPMeta.java b/XMPCore/src/com/adobe/xmp/XMPMeta.java
new file mode 100644
index 0000000..dab0502
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/XMPMeta.java
@@ -0,0 +1,1176 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp;
+
+import java.util.Calendar;
+
+import com.adobe.xmp.options.IteratorOptions;
+import com.adobe.xmp.options.ParseOptions;
+import com.adobe.xmp.options.PropertyOptions;
+import com.adobe.xmp.properties.XMPProperty;
+
+
+/**
+ * This class represents the set of XMP metadata as a DOM representation. It has methods to read and
+ * modify all kinds of properties, create an iterator over all properties and serialize the metadata
+ * to a String, byte-array or <code>OutputStream</code>.
+ * 
+ * @since 20.01.2006
+ */
+public interface XMPMeta extends Cloneable
+{
+	// ---------------------------------------------------------------------------------------------
+	// Basic property manipulation functions
+
+	/**
+	 * The property value getter-methods all take a property specification: the first two parameters
+	 * are always the top level namespace URI (the &quot;schema&quot; namespace) and the basic name
+	 * of the property being referenced. See the introductory discussion of path expression usage
+	 * for more information.
+	 * <p>
+	 * All of the functions return an object inherited from <code>PropertyBase</code> or
+	 * <code>null</code> if the property does not exists. The result object contains the value of
+	 * the property and option flags describing the property. Arrays and the non-leaf levels of
+	 * nodes do not have values.
+	 * <p>
+	 * See {@link PropertyOptions} for detailed information about the options.
+	 * <p>
+	 * This is the simplest property getter, mainly for top level simple properties or after using
+	 * the path composition functions in XMPPathFactory.
+	 * 
+	 * @param schemaNS The namespace URI for the property. May be <code>null</code> or the empty
+	 *        string if the first component of the propName path contains a namespace prefix. The
+	 *        URI must be for a registered namespace.
+	 * @param propName The name of the property. May be a general path expression, must not be
+	 *        <code>null</code> or the empty string. Using a namespace prefix on the first
+	 *        component is optional. If present without a schemaNS value then the prefix specifies
+	 *        the namespace. The prefix must be for a registered namespace. If both a schemaNS URI
+	 *        and propName prefix are present, they must be corresponding parts of a registered
+	 *        namespace.
+	 * @return Returns a <code>XMPProperty</code> containing the value and the options or
+	 *         <code>null</code> if the property does not exist.
+	 * @throws XMPException Wraps all errors and exceptions that may occur.
+	 */
+	XMPProperty getProperty(String schemaNS, String propName) throws XMPException;
+
+	
+	/**
+	 * Provides access to items within an array. The index is passed as an integer, you need not
+	 * worry about the path string syntax for array items, convert a loop index to a string, etc.
+	 * 
+	 * @param schemaNS The namespace URI for the array. Has the same usage as in getProperty.
+	 * @param arrayName The name of the array. May be a general path expression, must not be
+	 *        <code>null</code> or the empty string. Has the same namespace prefix usage as
+	 *        propName in <code>getProperty()</code>.
+	 * @param itemIndex The index of the desired item. Arrays in XMP are indexed from 1. The
+	 *        constant {@link XMPConst#ARRAY_LAST_ITEM} always refers to the last existing array
+	 *        item.
+	 * @return Returns a <code>XMPProperty</code> containing the value and the options or
+	 *         <code>null</code> if the property does not exist.
+	 * @throws XMPException Wraps all errors and exceptions that may occur.
+	 */
+	XMPProperty getArrayItem(String schemaNS, String arrayName, int itemIndex) throws XMPException;
+
+
+	/**
+	 * Returns the number of items in the array.
+	 * 
+	 * @param schemaNS The namespace URI for the array. Has the same usage as in getProperty.
+	 * @param arrayName The name of the array. May be a general path expression, must not be
+	 *        <code>null</code> or the empty string. Has the same namespace prefix usage as
+	 *        propName in <code>getProperty()</code>.
+	 * @return Returns the number of items in the array.
+	 * @throws XMPException Wraps all errors and exceptions that may occur.
+	 */
+	int countArrayItems(String schemaNS, String arrayName) throws XMPException;
+
+
+	/**
+	 * Provides access to fields within a nested structure. The namespace for the field is passed as
+	 * a URI, you need not worry about the path string syntax.
+	 * <p>
+	 * The names of fields should be XML qualified names, that is within an XML namespace. The path
+	 * syntax for a qualified name uses the namespace prefix. This is unreliable since the prefix is
+	 * never guaranteed. The URI is the formal name, the prefix is just a local shorthand in a given
+	 * sequence of XML text.
+	 * 
+	 * @param schemaNS The namespace URI for the struct. Has the same usage as in getProperty.
+	 * @param structName The name of the struct. May be a general path expression, must not be
+	 *        <code>null</code> or the empty string. Has the same namespace prefix usage as
+	 *        propName in <code>getProperty()</code>.
+	 * @param fieldNS The namespace URI for the field. Has the same URI and prefix usage as the
+	 *        schemaNS parameter.
+	 * @param fieldName The name of the field. Must be a single XML name, must not be
+	 *        <code>null</code> or the empty string. Has the same namespace prefix usage as the
+	 *        structName parameter.
+	 * @return Returns a <code>XMPProperty</code> containing the value and the options or
+	 *         <code>null</code> if the property does not exist. Arrays and non-leaf levels of
+	 *         structs do not have values.
+	 * @throws XMPException Wraps all errors and exceptions that may occur.
+	 */
+	XMPProperty getStructField(
+		String schemaNS,
+		String structName,
+		String fieldNS,
+		String fieldName) throws XMPException;
+
+
+	/**
+	 * Provides access to a qualifier attached to a property. The namespace for the qualifier is
+	 * passed as a URI, you need not worry about the path string syntax. In many regards qualifiers
+	 * are like struct fields. See the introductory discussion of qualified properties for more
+	 * information.
+	 * <p>
+	 * The names of qualifiers should be XML qualified names, that is within an XML namespace. The
+	 * path syntax for a qualified name uses the namespace prefix. This is unreliable since the
+	 * prefix is never guaranteed. The URI is the formal name, the prefix is just a local shorthand
+	 * in a given sequence of XML text.
+	 * <p>
+	 * <em>Note:</em> Qualifiers are only supported for simple leaf properties at this time.
+	 * 
+	 * @param schemaNS The namespace URI for the struct. Has the same usage as in getProperty.
+	 * @param propName The name of the property to which the qualifier is attached. May be a general
+	 *        path expression, must not be <code>null</code> or the empty string. Has the same
+	 *        namespace prefix usage as in <code>getProperty()</code>.
+	 * @param qualNS The namespace URI for the qualifier. Has the same URI and prefix usage as the
+	 *        schemaNS parameter.
+	 * @param qualName The name of the qualifier. Must be a single XML name, must not be
+	 *        <code>null</code> or the empty string. Has the same namespace prefix usage as the
+	 *        propName parameter.
+	 * @return Returns a <code>XMPProperty</code> containing the value and the options of the
+	 *         qualifier or <code>null</code> if the property does not exist. The name of the
+	 *         qualifier must be a single XML name, must not be <code>null</code> or the empty
+	 *         string. Has the same namespace prefix usage as the propName parameter.
+	 *         <p>
+	 *         The value of the qualifier is only set if it has one (Arrays and non-leaf levels of
+	 *         structs do not have values).
+	 * @throws XMPException Wraps all errors and exceptions that may occur.
+	 */
+	XMPProperty getQualifier(
+		String schemaNS,
+		String propName,
+		String qualNS,
+		String qualName) throws XMPException;
+
+	
+
+	// ---------------------------------------------------------------------------------------------
+	// Functions for setting property values
+
+	/**
+	 * The property value <code>setters</code> all take a property specification, their
+	 * differences are in the form of this. The first two parameters are always the top level
+	 * namespace URI (the <code>schema</code> namespace) and the basic name of the property being
+	 * referenced. See the introductory discussion of path expression usage for more information.
+	 * <p>
+	 * All of the functions take a string value for the property and option flags describing the
+	 * property. The value must be Unicode in UTF-8 encoding. Arrays and non-leaf levels of structs
+	 * do not have values. Empty arrays and structs may be created using appropriate option flags.
+	 * All levels of structs that is assigned implicitly are created if necessary. appendArayItem
+	 * implicitly creates the named array if necessary.
+	 * <p>
+	 * See {@link PropertyOptions} for detailed information about the options.
+	 * <p>
+	 * This is the simplest property setter, mainly for top level simple properties or after using
+	 * the path composition functions in {@link XMPPathFactory}.
+	 * 
+	 * @param schemaNS The namespace URI for the property. Has the same usage as in getProperty.
+	 * @param propName The name of the property. 
+	 * 				   Has the same usage as in <code>getProperty()</code>.
+	 * @param propValue the value for the property (only leaf properties have a value). 
+	 *        Arrays and non-leaf levels of structs do not have values. 
+	 *        Must be <code>null</code> if the value is not relevant.<br/>
+	 *        The value is automatically detected: Boolean, Integer, Long, Double, XMPDateTime and
+	 *        byte[] are handled, on all other <code>toString()</code> is called.   
+	 *        
+	 * @param options Option flags describing the property. See the earlier description.
+	 * @throws XMPException Wraps all errors and exceptions that may occur.
+	 */
+	void setProperty(
+		String schemaNS,
+		String propName, 
+		Object propValue, 
+		PropertyOptions options) throws XMPException;
+
+	
+	/**
+	 * @see XMPMeta#setProperty(String, String, Object, PropertyOptions)
+	 *  
+	 * @param schemaNS The namespace URI
+	 * @param propName The name of the property 
+	 * @param propValue the value for the property   
+	 * @throws XMPException Wraps all errors and exceptions
+	 */
+	void setProperty(
+			String schemaNS,
+			String propName, 
+			Object propValue) throws XMPException;
+
+	
+	/**
+	 * Replaces an item within an array. The index is passed as an integer, you need not worry about
+	 * the path string syntax for array items, convert a loop index to a string, etc. The array
+	 * passed must already exist. In normal usage the selected array item is modified. A new item is
+	 * automatically appended if the index is the array size plus 1.
+	 * 
+	 * @param schemaNS The namespace URI for the array. Has the same usage as in getProperty.
+	 * @param arrayName The name of the array. May be a general path expression, must not be
+	 *        <code>null</code> or the empty string. Has the same namespace prefix usage as
+	 *        propName in getProperty.
+	 * @param itemIndex The index of the desired item. Arrays in XMP are indexed from 1. To address
+	 *        the last existing item, use {@link XMPMeta#countArrayItems(String, String)} to find
+	 *        out the length of the array.
+	 * @param itemValue the new value of the array item. Has the same usage as propValue in
+	 *        <code>setProperty()</code>.
+	 * @param options the set options for the item. 
+	 * @throws XMPException Wraps all errors and exceptions that may occur.
+	 */
+	void setArrayItem(
+		String schemaNS,
+		String arrayName,
+		int itemIndex,
+		String itemValue,
+		PropertyOptions options) throws XMPException;	
+
+	
+	/**
+	 * @see XMPMeta#setArrayItem(String, String, int, String, PropertyOptions)
+	 *  
+	 * @param schemaNS The namespace URI
+	 * @param arrayName The name of the array
+	 * @param itemIndex The index to insert the new item
+	 * @param itemValue the new value of the array item
+	 * @throws XMPException Wraps all errors and exceptions
+	 */
+	void setArrayItem(
+			String schemaNS,
+			String arrayName,
+			int itemIndex,
+			String itemValue) throws XMPException;	
+
+	
+	/**
+	 * Inserts an item into an array previous to the given index. The index is passed as an integer,
+	 * you need not worry about the path string syntax for array items, convert a loop index to a
+	 * string, etc. The array passed must already exist. In normal usage the selected array item is
+	 * modified. A new item is automatically appended if the index is the array size plus 1.
+	 * 
+	 * @param schemaNS The namespace URI for the array. Has the same usage as in getProperty.
+	 * @param arrayName The name of the array. May be a general path expression, must not be
+	 *        <code>null</code> or the empty string. Has the same namespace prefix usage as
+	 *        propName in getProperty.
+	 * @param itemIndex The index to insert the new item. Arrays in XMP are indexed from 1. Use
+	 * 		  <code>XMPConst.ARRAY_LAST_ITEM</code> to append items.
+	 * @param itemValue the new value of the array item. Has the same usage as
+	 *        propValue in <code>setProperty()</code>.
+	 * @param options the set options that decide about the kind of the node.
+ 	 * @throws XMPException Wraps all errors and exceptions that may occur.
+ 	 */
+	void insertArrayItem(
+		String schemaNS,
+		String arrayName,
+		int itemIndex,
+		String itemValue,
+		PropertyOptions options) throws XMPException;	
+	
+	
+	/**
+	 * @see XMPMeta#insertArrayItem(String, String, int, String, PropertyOptions)
+	 * 
+	 * @param schemaNS The namespace URI for the array
+	 * @param arrayName The name of the array
+	 * @param itemIndex The index to insert the new item
+	 * @param itemValue the value of the array item
+	 * @throws XMPException Wraps all errors and exceptions
+	 */
+	void insertArrayItem(
+			String schemaNS,
+			String arrayName,
+			int itemIndex,
+			String itemValue) throws XMPException;	
+
+	
+	/**
+	 * Simplifies the construction of an array by not requiring that you pre-create an empty array.
+	 * The array that is assigned is created automatically if it does not yet exist. Each call to
+	 * appendArrayItem() appends an item to the array. The corresponding parameters have the same
+	 * use as setArrayItem(). The arrayOptions parameter is used to specify what kind of array. If
+	 * the array exists, it must have the specified form.
+	 * 
+	 * @param schemaNS The namespace URI for the array. Has the same usage as in getProperty.
+	 * @param arrayName The name of the array. May be a general path expression, must not be null or
+	 *        the empty string. Has the same namespace prefix usage as propPath in getProperty.
+	 * @param arrayOptions Option flags describing the array form. The only valid options are
+	 *        <ul>
+	 *        <li> {@link PropertyOptions#ARRAY},
+	 *        <li> {@link PropertyOptions#ARRAY_ORDERED},
+	 *        <li> {@link PropertyOptions#ARRAY_ALTERNATE} or
+	 *        <li> {@link PropertyOptions#ARRAY_ALT_TEXT}.
+	 *        </ul>
+	 *        <em>Note:</em> the array options only need to be provided if the array is not 
+	 *        already existing, otherwise you can set them to <code>null</code> or use
+	 *        {@link XMPMeta#appendArrayItem(String, String, String)}.
+	 * @param itemValue the value of the array item. Has the same usage as propValue in getProperty.
+	 * @param itemOptions Option flags describing the item to append ({@link PropertyOptions})
+	 * @throws XMPException Wraps all errors and exceptions that may occur.
+	 */
+	void appendArrayItem(
+		String schemaNS,
+		String arrayName,
+		PropertyOptions arrayOptions,
+		String itemValue,
+		PropertyOptions itemOptions) throws XMPException;
+
+	
+	/**
+	 * @see XMPMeta#appendArrayItem(String, String, PropertyOptions, String, PropertyOptions)
+	 * 
+	 * @param schemaNS The namespace URI for the array
+	 * @param arrayName The name of the array
+	 * @param itemValue the value of the array item
+	 * @throws XMPException Wraps all errors and exceptions
+	 */
+	void appendArrayItem(
+			String schemaNS,
+			String arrayName,
+			String itemValue) throws XMPException;
+	
+
+	/**
+	 * Provides access to fields within a nested structure. The namespace for the field is passed as
+	 * a URI, you need not worry about the path string syntax. The names of fields should be XML
+	 * qualified names, that is within an XML namespace. The path syntax for a qualified name uses
+	 * the namespace prefix, which is unreliable because the prefix is never guaranteed. The URI is
+	 * the formal name, the prefix is just a local shorthand in a given sequence of XML text.
+	 * 
+	 * @param schemaNS The namespace URI for the struct. Has the same usage as in getProperty.
+	 * @param structName The name of the struct. May be a general path expression, must not be null
+	 *        or the empty string. Has the same namespace prefix usage as propName in getProperty.
+	 * @param fieldNS The namespace URI for the field. Has the same URI and prefix usage as the
+	 *        schemaNS parameter.
+	 * @param fieldName The name of the field. Must be a single XML name, must not be null or the
+	 *        empty string. Has the same namespace prefix usage as the structName parameter.
+	 * @param fieldValue the value of thefield, if the field has a value. 
+	 *        Has the same usage as propValue in getProperty.
+	 * @param options Option flags describing the field. See the earlier description.
+	 * @throws XMPException Wraps all errors and exceptions that may occur.
+	 */
+	void setStructField(
+		String schemaNS,
+		String structName,
+		String fieldNS,
+		String fieldName,
+		String fieldValue,
+		PropertyOptions options) throws XMPException;
+
+	
+	/**
+	 * @see XMPMeta#setStructField(String, String, String, String, String, PropertyOptions)
+	 * 
+	 * @param schemaNS The namespace URI for the struct
+	 * @param structName The name of the struct
+	 * @param fieldNS The namespace URI for the field
+	 * @param fieldName The name of the field
+	 * @param fieldValue the value of the field
+	 * @throws XMPException Wraps all errors and exceptions
+	 */
+	void setStructField(
+			String schemaNS,
+			String structName,
+			String fieldNS,
+			String fieldName,
+			String fieldValue) throws XMPException;
+	
+
+	/**
+	 * Provides access to a qualifier attached to a property. The namespace for the qualifier is
+	 * passed as a URI, you need not worry about the path string syntax. In many regards qualifiers
+	 * are like struct fields. See the introductory discussion of qualified properties for more
+	 * information. The names of qualifiers should be XML qualified names, that is within an XML
+	 * namespace. The path syntax for a qualified name uses the namespace prefix, which is
+	 * unreliable because the prefix is never guaranteed. The URI is the formal name, the prefix is
+	 * just a local shorthand in a given sequence of XML text. The property the qualifier
+	 * will be attached has to exist.
+	 * 
+	 * @param schemaNS The namespace URI for the struct. Has the same usage as in getProperty.
+	 * @param propName The name of the property to which the qualifier is attached. Has the same
+	 *        usage as in getProperty.
+	 * @param qualNS The namespace URI for the qualifier. Has the same URI and prefix usage as the
+	 *        schemaNS parameter.
+	 * @param qualName The name of the qualifier. Must be a single XML name, must not be
+	 *        <code>null</code> or the empty string. Has the same namespace prefix usage as the
+	 *        propName parameter.
+	 * @param qualValue A pointer to the <code>null</code> terminated UTF-8 string that is the
+	 *        value of the qualifier, if the qualifier has a value. Has the same usage as propValue
+	 *        in getProperty.
+	 * @param options Option flags describing the qualifier. See the earlier description.
+	 * @throws XMPException Wraps all errors and exceptions that may occur.
+	 */
+	void setQualifier(
+		String schemaNS,
+		String propName,
+		String qualNS,
+		String qualName,
+		String qualValue,
+		PropertyOptions options) throws XMPException;
+
+	
+	/**
+	 * @see XMPMeta#setQualifier(String, String, String, String, String, PropertyOptions)
+	 * 
+	 * @param schemaNS The namespace URI for the struct
+	 * @param propName The name of the property to which the qualifier is attached
+	 * @param qualNS The namespace URI for the qualifier
+	 * @param qualName The name of the qualifier
+	 * @param qualValue the value of the qualifier
+	 * @throws XMPException Wraps all errors and exceptions
+	 */
+	void setQualifier(
+			String schemaNS,
+			String propName,
+			String qualNS,
+			String qualName,
+			String qualValue) throws XMPException;
+	
+	
+	
+	// ---------------------------------------------------------------------------------------------
+	// Functions for deleting and detecting properties. These should be obvious from the
+	// descriptions of the getters and setters.
+
+	/**
+	 * Deletes the given XMP subtree rooted at the given property. It is not an error if the
+	 * property does not exist.
+	 * 
+	 * @param schemaNS The namespace URI for the property. Has the same usage as in
+	 *        <code>getProperty()</code>.
+	 * @param propName The name of the property. Has the same usage as in getProperty.
+	 */
+	void deleteProperty(String schemaNS, String propName);
+
+
+	/**
+	 * Deletes the given XMP subtree rooted at the given array item. It is not an error if the array
+	 * item does not exist.
+	 * 
+	 * @param schemaNS The namespace URI for the array. Has the same usage as in getProperty.
+	 * @param arrayName The name of the array. May be a general path expression, must not be
+	 *        <code>null</code> or the empty string. Has the same namespace prefix usage as
+	 *        propName in <code>getProperty()</code>.
+	 * @param itemIndex The index of the desired item. Arrays in XMP are indexed from 1. The
+	 *        constant <code>XMPConst.ARRAY_LAST_ITEM</code> always refers to the last
+	 *        existing array item.
+	 */
+	void deleteArrayItem(String schemaNS, String arrayName, int itemIndex);
+
+
+	/**
+	 * Deletes the given XMP subtree rooted at the given struct field. It is not an error if the
+	 * field does not exist.
+	 * 
+	 * @param schemaNS The namespace URI for the struct. Has the same usage as in
+	 *        <code>getProperty()</code>.
+	 * @param structName The name of the struct. May be a general path expression, must not be
+	 *        <code>null</code> or the empty string. Has the same namespace prefix usage as
+	 *        propName in getProperty.
+	 * @param fieldNS The namespace URI for the field. Has the same URI and prefix usage as the
+	 *        schemaNS parameter.
+	 * @param fieldName The name of the field. Must be a single XML name, must not be
+	 *        <code>null</code> or the empty string. Has the same namespace prefix usage as the
+	 *        structName parameter.
+	 */
+	void deleteStructField(String schemaNS, String structName, String fieldNS, String fieldName);
+
+
+	/**
+	 * Deletes the given XMP subtree rooted at the given qualifier. It is not an error if the
+	 * qualifier does not exist.
+	 * 
+	 * @param schemaNS The namespace URI for the struct. Has the same usage as in
+	 *        <code>getProperty()</code>.
+	 * @param propName The name of the property to which the qualifier is attached. Has the same
+	 *        usage as in getProperty.
+	 * @param qualNS The namespace URI for the qualifier. Has the same URI and prefix usage as the
+	 *        schemaNS parameter.
+	 * @param qualName The name of the qualifier. Must be a single XML name, must not be
+	 *        <code>null</code> or the empty string. Has the same namespace prefix usage as the
+	 *        propName parameter.
+	 */
+	void deleteQualifier(String schemaNS, String propName, String qualNS, String qualName);
+
+
+	/**
+	 * Returns whether the property exists.
+	 * 
+	 * @param schemaNS The namespace URI for the property. Has the same usage as in
+	 *        <code>getProperty()</code>.
+	 * @param propName The name of the property. 
+	 * 		  Has the same usage as in <code>getProperty()</code>.
+	 * @return Returns true if the property exists.
+	 */
+	boolean doesPropertyExist(String schemaNS, String propName);
+
+
+	/**
+	 * Tells if the array item exists.
+	 * 
+	 * @param schemaNS The namespace URI for the array. Has the same usage as in
+	 *        <code>getProperty()</code>.
+	 * @param arrayName The name of the array. May be a general path expression, must not be
+	 *        <code>null</code> or the empty string. Has the same namespace prefix usage as
+	 *        propName in <code>getProperty()</code>.
+	 * @param itemIndex The index of the desired item. Arrays in XMP are indexed from 1. The
+	 *        constant <code>XMPConst.ARRAY_LAST_ITEM</code> always refers to the last
+	 *        existing array item.
+	 * @return Returns <code>true</code> if the array exists, <code>false</code> otherwise.
+	 */
+	boolean doesArrayItemExist(String schemaNS, String arrayName, int itemIndex);
+
+
+	/**
+	 * DoesStructFieldExist tells if the struct field exists.
+	 * 
+	 * @param schemaNS The namespace URI for the struct. Has the same usage as in
+	 *        <code>getProperty()</code>.
+	 * @param structName The name of the struct. May be a general path expression, must not be
+	 *        <code>null</code> or the empty string. Has the same namespace prefix usage as
+	 *        propName in <code>getProperty()</code>.
+	 * @param fieldNS The namespace URI for the field. Has the same URI and prefix usage as the
+	 *        schemaNS parameter.
+	 * @param fieldName The name of the field. Must be a single XML name, must not be
+	 *        <code>null</code> or the empty string. Has the same namespace prefix usage as the
+	 *        structName parameter.
+	 * @return Returns true if the field exists.
+	 */
+	boolean doesStructFieldExist(
+		String schemaNS, 
+		String structName, 
+		String fieldNS,
+		String fieldName);
+
+
+	/**
+	 * DoesQualifierExist tells if the qualifier exists.
+	 * 
+	 * @param schemaNS The namespace URI for the struct. Has the same usage as in
+	 *        <code>getProperty()</code>.
+	 * @param propName The name of the property to which the qualifier is attached. Has the same
+	 *        usage as in <code>getProperty()</code>.
+	 * @param qualNS The namespace URI for the qualifier. Has the same URI and prefix usage as the
+	 *        schemaNS parameter.
+	 * @param qualName The name of the qualifier. Must be a single XML name, must not be
+	 *        <code>null</code> or the empty string. Has the same namespace prefix usage as the
+	 *        propName parameter.
+	 * @return Returns true if the qualifier exists.
+	 */
+	boolean doesQualifierExist(String schemaNS, String propName, String qualNS, String qualName);
+
+
+	// ---------------------------------------------------------------------------------------------
+	// Specialized Get and Set functions
+
+	/**
+	 * These functions provide convenient support for localized text properties, including a number
+	 * of special and obscure aspects. Localized text properties are stored in alt-text arrays. They
+	 * allow multiple concurrent localizations of a property value, for example a document title or
+	 * copyright in several languages. The most important aspect of these functions is that they
+	 * select an appropriate array item based on one or two RFC 3066 language tags. One of these
+	 * languages, the "specific" language, is preferred and selected if there is an exact match. For
+	 * many languages it is also possible to define a "generic" language that may be used if there
+	 * is no specific language match. The generic language must be a valid RFC 3066 primary subtag,
+	 * or the empty string. For example, a specific language of "en-US" should be used in the US,
+	 * and a specific language of "en-UK" should be used in England. It is also appropriate to use
+	 * "en" as the generic language in each case. If a US document goes to England, the "en-US"
+	 * title is selected by using the "en" generic language and the "en-UK" specific language. It is
+	 * considered poor practice, but allowed, to pass a specific language that is just an RFC 3066
+	 * primary tag. For example "en" is not a good specific language, it should only be used as a
+	 * generic language. Passing "i" or "x" as the generic language is also considered poor practice
+	 * but allowed. Advice from the W3C about the use of RFC 3066 language tags can be found at:
+	 * http://www.w3.org/International/articles/language-tags/
+	 * <p>
+	 * <em>Note:</em> RFC 3066 language tags must be treated in a case insensitive manner. The XMP
+	 * Toolkit does this by normalizing their capitalization:
+	 * <ul>
+	 * <li> The primary subtag is lower case, the suggested practice of ISO 639.
+	 * <li> All 2 letter secondary subtags are upper case, the suggested practice of ISO 3166.
+	 * <li> All other subtags are lower case. The XMP specification defines an artificial language,
+	 * <li>"x-default", that is used to explicitly denote a default item in an alt-text array.
+	 * </ul>
+	 * The XMP toolkit normalizes alt-text arrays such that the x-default item is the first item.
+	 * The SetLocalizedText function has several special features related to the x-default item, see
+	 * its description for details. The selection of the array item is the same for GetLocalizedText
+	 * and SetLocalizedText:
+	 * <ul>
+	 * <li> Look for an exact match with the specific language.
+	 * <li> If a generic language is given, look for a partial match.
+	 * <li> Look for an x-default item.
+	 * <li> Choose the first item.
+	 * </ul>
+	 * A partial match with the generic language is where the start of the item's language matches
+	 * the generic string and the next character is '-'. An exact match is also recognized as a
+	 * degenerate case. It is fine to pass x-default as the specific language. In this case,
+	 * selection of an x-default item is an exact match by the first rule, not a selection by the
+	 * 3rd rule. The last 2 rules are fallbacks used when the specific and generic languages fail to
+	 * produce a match. <code>getLocalizedText</code> returns information about a selected item in
+	 * an alt-text array. The array item is selected according to the rules given above.
+	 * 
+	 * <em>Note:</em> In a future version of this API a method 
+	 * 		using Java <code>java.lang.Locale</code> will be added.
+	 * 
+	 * @param schemaNS The namespace URI for the alt-text array. Has the same usage as in
+	 *        <code>getProperty()</code>.
+	 * @param altTextName The name of the alt-text array. May be a general path expression, must not
+	 *        be <code>null</code> or the empty string. Has the same namespace prefix usage as
+	 *        propName in <code>getProperty()</code>.
+	 * @param genericLang The name of the generic language as an RFC 3066 primary subtag. May be
+	 *        <code>null</code> or the empty string if no generic language is wanted.
+	 * @param specificLang The name of the specific language as an RFC 3066 tag. Must not be
+	 *        <code>null</code> or the empty string.
+	 * @return Returns an <code>XMPProperty</code> containing the value, the actual language and 
+	 * 		   the options if an appropriate alternate collection item exists, <code>null</code> 
+	 * 		  if the property.
+	 *         does not exist.
+	 * @throws XMPException Wraps all errors and exceptions that may occur.
+	 */
+	XMPProperty getLocalizedText(
+		String schemaNS, 
+		String altTextName,
+		String genericLang,
+		String specificLang) throws XMPException;
+
+
+	/**
+	 * Modifies the value of a selected item in an alt-text array. Creates an appropriate array item
+	 * if necessary, and handles special cases for the x-default item. If the selected item is from
+	 * a match with the specific language, the value of that item is modified. If the existing value
+	 * of that item matches the existing value of the x-default item, the x-default item is also
+	 * modified. If the array only has 1 existing item (which is not x-default), an x-default item
+	 * is added with the given value. If the selected item is from a match with the generic language
+	 * and there are no other generic matches, the value of that item is modified. If the existing
+	 * value of that item matches the existing value of the x-default item, the x-default item is
+	 * also modified. If the array only has 1 existing item (which is not x-default), an x-default
+	 * item is added with the given value. If the selected item is from a partial match with the
+	 * generic language and there are other partial matches, a new item is created for the specific
+	 * language. The x-default item is not modified. If the selected item is from the last 2 rules
+	 * then a new item is created for the specific language. If the array only had an x-default
+	 * item, the x-default item is also modified. If the array was empty, items are created for the
+	 * specific language and x-default.
+	 * 
+	 * <em>Note:</em> In a future version of this API a method 
+	 * 		using Java <code>java.lang.Locale</code> will be added.
+	 * 
+	 * 
+	 * @param schemaNS The namespace URI for the alt-text array. Has the same usage as in
+	 *        <code>getProperty()</code>.
+	 * @param altTextName The name of the alt-text array. May be a general path expression, must not
+	 *        be <code>null</code> or the empty string. Has the same namespace prefix usage as
+	 *        propName in <code>getProperty()</code>.
+	 * @param genericLang The name of the generic language as an RFC 3066 primary subtag. May be
+	 *        <code>null</code> or the empty string if no generic language is wanted.
+	 * @param specificLang The name of the specific language as an RFC 3066 tag. Must not be
+	 *        <code>null</code> or the empty string.
+	 * @param itemValue A pointer to the <code>null</code> terminated UTF-8 string that is the new
+	 *        value for the appropriate array item.
+	 * @param options Option flags, none are defined at present.
+	 * @throws XMPException Wraps all errors and exceptions that may occur.
+	 */
+	void setLocalizedText(
+		String schemaNS,
+		String altTextName,
+		String genericLang,
+		String specificLang,
+		String itemValue,
+		PropertyOptions options) throws XMPException;
+
+	
+	/**
+	 * @see XMPMeta#setLocalizedText(String, String, String, String, String, PropertyOptions)
+	 * 
+	 * @param schemaNS The namespace URI for the alt-text array
+	 * @param altTextName The name of the alt-text array
+	 * @param genericLang The name of the generic language
+	 * @param specificLang The name of the specific language
+	 * @param itemValue the new value for the appropriate array item
+	 * @throws XMPException Wraps all errors and exceptions
+	 */
+	void setLocalizedText(
+			String schemaNS,
+			String altTextName,
+			String genericLang,
+			String specificLang,
+			String itemValue) throws XMPException;
+
+	
+	
+	// ---------------------------------------------------------------------------------------------
+	// Functions accessing properties as binary values.
+
+	
+	/**
+	 * These are very similar to <code>getProperty()</code> and <code>SetProperty()</code> above, 
+	 * but the value is returned or provided in a literal form instead of as a UTF-8 string. 
+	 * The path composition functions in <code>XMPPathFactory</code> may be used to compose an path 
+	 * expression for fields in nested structures, items in arrays, or qualifiers.
+	 * 
+	 * @param  schemaNS The namespace URI for the property. Has the same usage as in
+	 *         <code>getProperty()</code>.
+	 * @param  propName The name of the property.
+	 * 		   Has the same usage as in <code>getProperty()</code>.
+	 * @return Returns a <code>Boolean</code> value or <code>null</code> 
+	 * 		   if the property does not exist.
+	 * @throws XMPException Wraps all exceptions that may occur, 
+	 * 		   especially conversion errors.
+	 */
+	Boolean getPropertyBoolean(String schemaNS, String propName) throws XMPException;
+	
+
+	/**
+	 * Convenience method to retrieve the literal value of a property.
+	 * 
+	 * @param  schemaNS The namespace URI for the property. Has the same usage as in
+	 *         <code>getProperty()</code>.
+	 * @param  propName The name of the property.
+	 * 		   Has the same usage as in <code>getProperty()</code>.
+	 * @return Returns an <code>Integer</code> value or <code>null</code> 
+	 * 		   if the property does not exist.
+	 * @throws XMPException Wraps all exceptions that may occur, 
+	 * 		   especially conversion errors.
+	 */
+	Integer getPropertyInteger(String schemaNS, String propName) throws XMPException;
+	
+
+	/**
+	 * Convenience method to retrieve the literal value of a property.
+	 * 
+	 * @param  schemaNS The namespace URI for the property. Has the same usage as in
+	 *         <code>getProperty()</code>.
+	 * @param  propName The name of the property.
+	 * 		   Has the same usage as in <code>getProperty()</code>.
+	 * @return Returns a <code>Long</code> value or <code>null</code> 
+	 * 		   if the property does not exist.
+	 * @throws XMPException Wraps all exceptions that may occur, 
+	 * 		   especially conversion errors.
+	 */
+	Long getPropertyLong(String schemaNS, String propName) throws XMPException;
+	
+
+	/**
+	 * Convenience method to retrieve the literal value of a property.
+	 * 
+	 * @param  schemaNS The namespace URI for the property. Has the same usage as in
+	 *         <code>getProperty()</code>.
+	 * @param  propName The name of the property.
+	 * 		   Has the same usage as in <code>getProperty()</code>.
+	 * @return Returns a <code>Double</code> value or <code>null</code> 
+	 * 		   if the property does not exist.
+	 * @throws XMPException Wraps all exceptions that may occur, 
+	 * 		   especially conversion errors.
+	 */
+	Double getPropertyDouble(String schemaNS, String propName) throws XMPException;
+	
+
+	/**
+	 * Convenience method to retrieve the literal value of a property.
+	 * 
+	 * @param  schemaNS The namespace URI for the property. Has the same usage as in
+	 *         <code>getProperty()</code>.
+	 * @param  propName The name of the property.
+	 * 		   Has the same usage as in <code>getProperty()</code>.
+	 * @return Returns a <code>XMPDateTime</code>-object or <code>null</code> 
+	 * 		   if the property does not exist.
+	 * @throws XMPException Wraps all exceptions that may occur, 
+	 * 		   especially conversion errors.
+	 */
+	XMPDateTime getPropertyDate(String schemaNS, String propName) throws XMPException;
+
+	
+	/**
+	 * Convenience method to retrieve the literal value of a property.
+	 * 
+	 * @param  schemaNS The namespace URI for the property. Has the same usage as in
+	 *         <code>getProperty()</code>.
+	 * @param  propName The name of the property.
+	 * 		   Has the same usage as in <code>getProperty()</code>.
+	 * @return Returns a Java <code>Calendar</code>-object or <code>null</code> 
+	 * 		   if the property does not exist.
+	 * @throws XMPException Wraps all exceptions that may occur, 
+	 * 		   especially conversion errors.
+	 */
+	Calendar getPropertyCalendar(String schemaNS, String propName) throws XMPException;
+
+	
+	/**
+	 * Convenience method to retrieve the literal value of a property.
+	 * 
+	 * @param  schemaNS The namespace URI for the property. Has the same usage as in
+	 *         <code>getProperty()</code>.
+	 * @param  propName The name of the property.
+	 * 		   Has the same usage as in <code>getProperty()</code>.
+	 * @return Returns a <code>byte[]</code>-array contained the decoded base64 value 
+	 * 		   or <code>null</code> if the property does not exist.
+	 * @throws XMPException Wraps all exceptions that may occur, 
+	 * 		   especially conversion errors.
+	 */
+	byte[] getPropertyBase64(String schemaNS, String propName) throws XMPException;
+
+	
+	/**
+	 * Convenience method to retrieve the literal value of a property.
+	 * <em>Note:</em> There is no <code>setPropertyString()</code>, 
+	 * because <code>setProperty()</code> sets a string value.
+	 * 
+	 * @param  schemaNS The namespace URI for the property. Has the same usage as in
+	 *         <code>getProperty()</code>.
+	 * @param  propName The name of the property.
+	 * 		   Has the same usage as in <code>getProperty()</code>.
+	 * @return Returns a <code>String</code> value or <code>null</code> 
+	 * 		   if the property does not exist.
+	 * @throws XMPException Wraps all exceptions that may occur, 
+	 * 		   especially conversion errors.
+	 */
+	String getPropertyString(String schemaNS, String propName) throws XMPException;
+	
+
+	/**
+	 * Convenience method to set a property to a literal <code>boolean</code> value.
+	 * 
+	 * @param  schemaNS The namespace URI for the property. Has the same usage as in
+	 *         <code>setProperty()</code>.
+	 * @param  propName The name of the property.
+	 * 		   Has the same usage as in <code>getProperty()</code>.
+	 * @param  propValue the literal property value as <code>boolean</code>.
+	 * @param  options options of the property to set (optional).
+	 * @throws XMPException Wraps all exceptions that may occur.
+	 */
+	void setPropertyBoolean(
+		String schemaNS,
+		String propName,
+		boolean propValue,
+		PropertyOptions options) throws XMPException;
+
+	
+	/**
+	 * @see XMPMeta#setPropertyBoolean(String, String, boolean, PropertyOptions)
+	 *  
+	 * @param  schemaNS The namespace URI for the property
+	 * @param  propName The name of the property
+	 * @param  propValue the literal property value as <code>boolean</code>
+	 * @throws XMPException Wraps all exceptions
+	 */
+	void setPropertyBoolean(
+			String schemaNS,
+			String propName,
+			boolean propValue) throws XMPException;
+	
+	
+	/**
+	 * Convenience method to set a property to a literal <code>int</code> value.
+	 * 
+	 * @param  schemaNS The namespace URI for the property. Has the same usage as in
+	 *         <code>setProperty()</code>.
+	 * @param  propName The name of the property.
+	 * 		   Has the same usage as in <code>getProperty()</code>.
+	 * @param  propValue the literal property value as <code>int</code>.
+	 * @param  options options of the property to set (optional).
+	 * @throws XMPException Wraps all exceptions that may occur.
+	 */
+	void setPropertyInteger(
+		String schemaNS,
+		String propName,
+		int propValue,
+		PropertyOptions options) throws XMPException;
+
+	
+	/**
+	 * @see XMPMeta#setPropertyInteger(String, String, int, PropertyOptions)
+	 *  
+	 * @param  schemaNS The namespace URI for the property
+	 * @param  propName The name of the property
+	 * @param  propValue the literal property value as <code>int</code>
+	 * @throws XMPException Wraps all exceptions
+	 */
+	void setPropertyInteger(
+			String schemaNS,
+			String propName,
+			int propValue) throws XMPException;
+	
+
+	/**
+	 * Convenience method to set a property to a literal <code>long</code> value.
+	 * 
+	 * @param  schemaNS The namespace URI for the property. Has the same usage as in
+	 *         <code>setProperty()</code>.
+	 * @param  propName The name of the property.
+	 * 		   Has the same usage as in <code>getProperty()</code>.
+	 * @param  propValue the literal property value as <code>long</code>.
+	 * @param  options options of the property to set (optional).
+	 * @throws XMPException Wraps all exceptions that may occur.
+	 */
+	void setPropertyLong(
+		String schemaNS,
+		String propName,
+		long propValue,
+		PropertyOptions options) throws XMPException;
+
+	
+	/**
+	 * @see XMPMeta#setPropertyLong(String, String, long, PropertyOptions)
+	 *  
+	 * @param  schemaNS The namespace URI for the property
+	 * @param  propName The name of the property
+	 * @param  propValue the literal property value as <code>long</code>
+	 * @throws XMPException Wraps all exceptions
+	 */
+	void setPropertyLong(
+			String schemaNS,
+			String propName,
+			long propValue) throws XMPException;
+	
+
+	/**
+	 * Convenience method to set a property to a literal <code>double</code> value.
+	 * 
+	 * @param  schemaNS The namespace URI for the property. Has the same usage as in
+	 *         <code>setProperty()</code>.
+	 * @param  propName The name of the property.
+	 * 		   Has the same usage as in <code>getProperty()</code>.
+	 * @param  propValue the literal property value as <code>double</code>.
+	 * @param  options options of the property to set (optional).
+	 * @throws XMPException Wraps all exceptions that may occur.
+	 */
+	void setPropertyDouble(
+		String schemaNS,
+		String propName,
+		double propValue,
+		PropertyOptions options) throws XMPException;
+
+	
+	/**
+	 * @see XMPMeta#setPropertyDouble(String, String, double, PropertyOptions)
+	 *  
+	 * @param  schemaNS The namespace URI for the property
+	 * @param  propName The name of the property
+	 * @param  propValue the literal property value as <code>double</code>
+	 * @throws XMPException Wraps all exceptions
+	 */
+	void setPropertyDouble(
+			String schemaNS,
+			String propName,
+			double propValue) throws XMPException;
+	
+
+	/**
+	 * Convenience method to set a property with an XMPDateTime-object, 
+	 * which is serialized to an ISO8601 date.
+	 * 
+	 * @param  schemaNS The namespace URI for the property. Has the same usage as in
+	 *         <code>setProperty()</code>.
+	 * @param  propName The name of the property.
+	 * 		   Has the same usage as in <code>getProperty()</code>.
+	 * @param  propValue the property value as <code>XMPDateTime</code>.
+	 * @param  options options of the property to set (optional).
+	 * @throws XMPException Wraps all exceptions that may occur.
+	 */
+	void setPropertyDate(
+		String schemaNS,
+		String propName,
+		XMPDateTime propValue,
+		PropertyOptions options) throws XMPException;
+
+	
+	/**
+	 * @see XMPMeta#setPropertyDate(String, String, XMPDateTime, PropertyOptions)
+	 *  
+	 * @param  schemaNS The namespace URI for the property
+	 * @param  propName The name of the property
+	 * @param  propValue the property value as <code>XMPDateTime</code>
+	 * @throws XMPException Wraps all exceptions
+	 */
+	void setPropertyDate(
+			String schemaNS,
+			String propName,
+			XMPDateTime propValue) throws XMPException;
+	
+	
+	/**
+	 * Convenience method to set a property with a Java Calendar-object, 
+	 * which is serialized to an ISO8601 date.
+	 * 
+	 * @param  schemaNS The namespace URI for the property. Has the same usage as in
+	 *         <code>setProperty()</code>.
+	 * @param  propName The name of the property.
+	 * 		   Has the same usage as in <code>getProperty()</code>.
+	 * @param  propValue the property value as Java <code>Calendar</code>.
+	 * @param  options options of the property to set (optional).
+	 * @throws XMPException Wraps all exceptions that may occur.
+	 */
+	void setPropertyCalendar(
+		String schemaNS,
+		String propName,
+		Calendar propValue,
+		PropertyOptions options) throws XMPException;
+
+	
+	/**
+	 * @see XMPMeta#setPropertyCalendar(String, String, Calendar, PropertyOptions)
+	 *  
+	 * @param  schemaNS The namespace URI for the property
+	 * @param  propName The name of the property
+	 * @param  propValue the property value as <code>Calendar</code>
+	 * @throws XMPException Wraps all exceptions
+	 */
+	void setPropertyCalendar(
+			String schemaNS,
+			String propName,
+			Calendar propValue) throws XMPException;
+	
+
+	/**
+	 * Convenience method to set a property from a binary <code>byte[]</code>-array, 
+	 * which is serialized as base64-string.
+	 * 
+	 * @param  schemaNS The namespace URI for the property. Has the same usage as in
+	 *         <code>setProperty()</code>.
+	 * @param  propName The name of the property.
+	 * 		   Has the same usage as in <code>getProperty()</code>.
+	 * @param  propValue the literal property value as byte array.
+	 * @param  options options of the property to set (optional).
+	 * @throws XMPException Wraps all exceptions that may occur.
+	 */
+	void setPropertyBase64(
+		String schemaNS,
+		String propName,
+		byte[] propValue,
+		PropertyOptions options) throws XMPException;
+
+	
+	/**
+	 * @see XMPMeta#setPropertyBase64(String, String, byte[], PropertyOptions)
+	 *  
+	 * @param  schemaNS The namespace URI for the property
+	 * @param  propName The name of the property
+	 * @param  propValue the literal property value as byte array
+	 * @throws XMPException Wraps all exceptions
+	 */
+	void setPropertyBase64(
+			String schemaNS,
+			String propName,
+			byte[] propValue) throws XMPException;
+	
+
+	/**
+	 * Constructs an iterator for the properties within this XMP object.
+	 * 
+	 * @return Returns an <code>XMPIterator</code>.
+	 * @see XMPMeta#iterator(String, String, IteratorOptions)
+	 * @throws XMPException Wraps all errors and exceptions that may occur.
+	 */
+	XMPIterator iterator() throws XMPException;
+
+
+	/**
+	 * Constructs an iterator for the properties within this XMP object using some options.
+	 * 
+	 * @param options Option flags to control the iteration.
+	 * @return Returns an <code>XMPIterator</code>.
+	 * @see XMPMeta#iterator(String, String, IteratorOptions)
+	 * @throws XMPException Wraps all errors and exceptions that may occur.
+	 */
+	XMPIterator iterator(IteratorOptions options) throws XMPException;
+
+
+	/**
+	 * Construct an iterator for the properties within an XMP object. The general operation of an
+	 * XMP object iterator was. According to the parameters it iterates the entire data tree,
+	 * properties within a specific schema, or a subtree rooted at a specific node.
+	 * 
+	 * @param schemaNS Optional schema namespace URI to restrict the iteration. Omitted (visit all
+	 *        schema) by passing <code>null</code> or empty String.
+	 * @param propName Optional property name to restrict the iteration. May be an arbitrary path
+	 *        expression. Omitted (visit all properties) by passing <code>null</code> or empty
+	 *        String. If no schema URI is given, it is ignored.
+	 * @param options Option flags to control the iteration. See {@link IteratorOptions} for
+	 *        details.
+	 * @return Returns an <code>XMPIterator</code> for this <code>XMPMeta</code>-object
+	 *         considering the given options.
+	 * @throws XMPException Wraps all errors and exceptions that may occur.
+	 */
+	XMPIterator iterator(
+		String schemaNS,
+		String propName,
+		IteratorOptions options) throws XMPException;
+
+
+	/**
+	 * This correlates to the about-attribute,
+	 * returns the empty String if no name is set.
+	 * 
+	 * @return Returns the name of the XMP object.
+	 */
+	String getObjectName();
+
+
+	/**
+	 * @param name Sets the name of the XMP object.
+	 */
+	void setObjectName(String name);
+
+	
+	/**
+	 * @return Returns the unparsed content of the &lt;?xpacket&gt; processing instruction.
+	 * 		This contains normally the attribute-like elements 'begin="&lt;BOM&gt;"
+	 *		id="W5M0MpCehiHzreSzNTczkc9d"' and possibly the deprecated elements 'bytes="1234"' or
+	 * 		'encoding="XXX"'. If the parsed packet has not been wrapped into an xpacket,
+	 * 		<code>null</code> is returned.   
+	 */
+	String getPacketHeader();
+	
+
+	/**
+	 * Clones the complete metadata tree.
+	 * 
+	 * @return Returns a deep copy of this instance.
+	 */
+	Object clone();
+
+	
+	/**
+	 * Sorts the complete datamodel according to the following rules:
+	 * <ul>
+	 * 		<li>Schema nodes are sorted by prefix. 
+	 * 		<li>Properties at top level and within structs are sorted by full name, that is 
+	 * 			prefix + local name.
+	 * 		<li>Array items are not sorted, even if they have no certain order such as bags.
+	 * 		<li>Qualifier are sorted, with the exception of "xml:lang" and/or "rdf:type" 
+	 * 			that stay at the top of the list in that order.  
+	 * </ul>
+	 */
+	void sort();
+	
+	
+	/**
+	 * Perform the normalization as a separate parsing step.
+	 * Normally it is done during parsing, unless the parsing option
+	 * {@link ParseOptions#OMIT_NORMALIZATION} is set to <code>true</code>.
+	 * <em>Note:</em> It does no harm to call this method to an already normalized xmp object. 
+	 * It was a PDF/A requirement to get hand on the unnormalized <code>XMPMeta</code> object.
+	 * 
+	 * @param options optional parsing options.
+ 	 * @throws XMPException Wraps all errors and exceptions that may occur.
+	 */
+	void normalize(ParseOptions options) throws XMPException;
+	
+	
+	/**
+	 * Renders this node and the tree unter this node in a human readable form.
+	 * @return Returns a multiline string containing the dump.
+	 */
+	String dumpObject();
+}
\ No newline at end of file
diff --git a/XMPCore/src/com/adobe/xmp/XMPMetaFactory.java b/XMPCore/src/com/adobe/xmp/XMPMetaFactory.java
new file mode 100644
index 0000000..afd437a
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/XMPMetaFactory.java
@@ -0,0 +1,320 @@
+//=================================================================================================
+//ADOBE SYSTEMS INCORPORATED
+//Copyright 2006-2007 Adobe Systems Incorporated
+//All Rights Reserved
+//
+//NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+//of the Adobe license agreement accompanying it.
+//=================================================================================================
+
+package com.adobe.xmp;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import com.adobe.xmp.impl.XMPMetaImpl;
+import com.adobe.xmp.impl.XMPMetaParser;
+import com.adobe.xmp.impl.XMPSchemaRegistryImpl;
+import com.adobe.xmp.impl.XMPSerializerHelper;
+import com.adobe.xmp.options.ParseOptions;
+import com.adobe.xmp.options.SerializeOptions;
+
+
+/**
+ * Creates <code>XMPMeta</code>-instances from an <code>InputStream</code>
+ * 
+ * @since 30.01.2006
+ */
+public final class XMPMetaFactory
+{
+	/** The singleton instance of the <code>XMPSchemaRegistry</code>. */ 
+	private static XMPSchemaRegistry schema = new XMPSchemaRegistryImpl();
+	/** cache for version info */
+	private static XMPVersionInfo versionInfo = null;
+	
+	/**
+	 * Hides public constructor
+	 */
+	private XMPMetaFactory()
+	{
+		// EMPTY
+	}
+
+
+	/**
+	 * @return Returns the singleton instance of the <code>XMPSchemaRegistry</code>.
+	 */
+	public static XMPSchemaRegistry getSchemaRegistry()
+	{
+		return schema;
+	}
+
+	
+	/**
+	 * @return Returns an empty <code>XMPMeta</code>-object.
+	 */
+	public static XMPMeta create()
+	{
+		return new XMPMetaImpl();
+	}
+
+	
+	/**
+	 * Parsing with default options.
+	 * @see XMPMetaFactory#parse(InputStream, ParseOptions)
+	 * 
+	 * @param in an <code>InputStream</code>
+	 * @return Returns the <code>XMPMeta</code>-object created from the input.
+	 * @throws XMPException If the file is not well-formed XML or if the parsing fails.
+	 */
+	public static XMPMeta parse(InputStream in) throws XMPException
+	{
+		return parse(in, null);
+	}
+	
+
+	/**
+	 * These functions support parsing serialized RDF into an XMP object, and serailizing an XMP
+	 * object into RDF. The input for parsing may be any valid Unicode
+	 * encoding. ISO Latin-1 is also recognized, but its use is strongly discouraged. Serialization
+	 * is always as UTF-8.
+	 * <p>
+	 * <code>parseFromBuffer()</code> parses RDF from an <code>InputStream</code>. The encoding
+	 * is recognized automatically.
+	 * 
+	 * @param in an <code>InputStream</code>
+	 * @param options Options controlling the parsing.<br>
+	 *        The available options are:
+	 *        <ul>
+	 *        <li> XMP_REQUIRE_XMPMETA - The &lt;x:xmpmeta&gt; XML element is required around
+	 *        <tt>&lt;rdf:RDF&gt;</tt>.
+	 *        <li> XMP_STRICT_ALIASING - Do not reconcile alias differences, throw an exception.
+	 *        </ul>
+	 *        <em>Note:</em>The XMP_STRICT_ALIASING option is not yet implemented.
+	 * @return Returns the <code>XMPMeta</code>-object created from the input.	
+	 * @throws XMPException If the file is not well-formed XML or if the parsing fails.
+	 */
+	public static XMPMeta parse(InputStream in, ParseOptions options)
+			throws XMPException
+	{
+		return XMPMetaParser.parse(in, options);
+	}
+
+	
+	/**
+	 * Parsing with default options.
+	 * @see XMPMetaFactory#parse(InputStream)
+	 * 
+	 * @param packet a String contain an XMP-file.
+	 * @return Returns the <code>XMPMeta</code>-object created from the input.
+	 * @throws XMPException If the file is not well-formed XML or if the parsing fails.
+	 */
+	public static XMPMeta parseFromString(String packet) throws XMPException
+	{
+		return parseFromString(packet, null);
+	}
+	
+
+	/**
+	 * Creates an <code>XMPMeta</code>-object from a string.
+	 * @see XMPMetaFactory#parseFromString(String, ParseOptions)
+	 * 
+	 * @param packet a String contain an XMP-file.
+	 * @param options Options controlling the parsing.
+	 * @return Returns the <code>XMPMeta</code>-object created from the input.
+	 * @throws XMPException If the file is not well-formed XML or if the parsing fails.
+	 */
+	public static XMPMeta parseFromString(String packet, ParseOptions options)
+			throws XMPException
+	{
+		return XMPMetaParser.parse(packet, options);
+	}
+
+
+	/**
+	 * Parsing with default options.
+	 * @see XMPMetaFactory#parseFromBuffer(byte[], ParseOptions)
+	 * 
+	 * @param buffer a String contain an XMP-file.
+	 * @return Returns the <code>XMPMeta</code>-object created from the input.
+	 * @throws XMPException If the file is not well-formed XML or if the parsing fails.
+	 */
+	public static XMPMeta parseFromBuffer(byte[] buffer) throws XMPException
+	{
+		return parseFromBuffer(buffer, null);
+	}
+	
+	
+	/**
+	 * Creates an <code>XMPMeta</code>-object from a byte-buffer.
+	 * @see XMPMetaFactory#parse(InputStream, ParseOptions)
+	 * 
+	 * @param buffer a String contain an XMP-file.
+	 * @param options Options controlling the parsing.
+	 * @return Returns the <code>XMPMeta</code>-object created from the input.
+	 * @throws XMPException If the file is not well-formed XML or if the parsing fails.
+	 */
+	public static XMPMeta parseFromBuffer(byte[] buffer, 
+		ParseOptions options) throws XMPException
+	{
+		return XMPMetaParser.parse(buffer, options);
+	}
+
+	
+	/**
+	 * Serializes an <code>XMPMeta</code>-object as RDF into an <code>OutputStream</code>
+	 * with default options.
+	 * 
+	 * @param xmp a metadata object 
+	 * @param out an <code>OutputStream</code> to write the serialized RDF to.
+	 * @throws XMPException on serializsation errors.
+	 */
+	public static void serialize(XMPMeta xmp, OutputStream out) throws XMPException
+	{
+		serialize(xmp, out, null);
+	}
+
+
+	/**
+	 * Serializes an <code>XMPMeta</code>-object as RDF into an <code>OutputStream</code>.
+	 * 
+	 * @param xmp a metadata object 
+	 * @param options Options to control the serialization (see {@link SerializeOptions}).
+	 * @param out an <code>OutputStream</code> to write the serialized RDF to.
+	 * @throws XMPException on serializsation errors.
+	 */
+	public static void serialize(XMPMeta xmp, OutputStream out, SerializeOptions options)
+			throws XMPException
+	{
+		assertImplementation(xmp);
+		XMPSerializerHelper.serialize((XMPMetaImpl) xmp, out, options);
+	}	
+	
+	
+	/**
+	 * Serializes an <code>XMPMeta</code>-object as RDF into a byte buffer.
+	 * 
+	 * @param xmp a metadata object 
+	 * @param options Options to control the serialization (see {@link SerializeOptions}).
+	 * @return Returns a byte buffer containing the serialized RDF.
+	 * @throws XMPException on serializsation errors.
+	 */
+	public static byte[] serializeToBuffer(XMPMeta xmp, SerializeOptions options)
+			throws XMPException
+	{
+		assertImplementation(xmp);
+		return XMPSerializerHelper.serializeToBuffer((XMPMetaImpl) xmp, options);
+	}
+
+
+	/**
+	 * Serializes an <code>XMPMeta</code>-object as RDF into a string. <em>Note:</em> Encoding
+	 * is ignored when serializing to a string.
+	 * 
+	 * @param xmp a metadata object 
+	 * @param options Options to control the serialization (see {@link SerializeOptions}).
+	 * @return Returns a string containing the serialized RDF.
+	 * @throws XMPException on serializsation errors.
+	 */
+	public static String serializeToString(XMPMeta xmp, SerializeOptions options)
+			throws XMPException
+	{
+		assertImplementation(xmp);
+		return XMPSerializerHelper.serializeToString((XMPMetaImpl) xmp, options);
+	}
+
+
+	/**
+	 * @param xmp Asserts that xmp is compatible to <code>XMPMetaImpl</code>.s
+	 */
+	private static void assertImplementation(XMPMeta xmp)
+	{
+		if (!(xmp instanceof XMPMetaImpl))
+		{
+			throw new UnsupportedOperationException("The serializing service works only" +
+				"with the XMPMeta implementation of this library");
+		}
+	}
+
+
+	/**
+	 * Resets the schema registry to its original state (creates a new one).
+	 * Be careful this might break all existing XMPMeta-objects and should be used
+	 * only for testing purpurses. 
+	 */
+	public static void reset()
+	{
+		schema = new XMPSchemaRegistryImpl();
+	}
+	
+	
+	/**
+	 * Obtain version information. The XMPVersionInfo singleton is created the first time
+	 * its requested.
+	 * 
+	 * @return Returns the version information.
+	 */
+	public static synchronized XMPVersionInfo getVersionInfo()
+	{
+		if (versionInfo == null)
+		{
+			try
+			{
+				final int major = 5;
+				final int minor = 1;
+				final int micro = 0;
+				final int engBuild = 3;
+				final boolean debug = false;
+				
+				// Adobe XMP Core 5.0-jc001 DEBUG-<branch>.<changelist>, 2009 Jan 28 15:22:38-CET
+				final String message = "Adobe XMP Core 5.1.0-jc003";
+					
+
+				versionInfo = new XMPVersionInfo()
+				{
+					public int getMajor()
+					{
+						return major;
+					}
+
+					public int getMinor()
+					{
+						return minor;
+					}
+
+					public int getMicro()
+					{
+						return micro;
+					}
+
+					public boolean isDebug()
+					{
+						return debug;
+					}
+
+					public int getBuild()
+					{
+						return engBuild;
+					}
+
+					public String getMessage()
+					{
+						return message;
+					}
+					
+					public String toString()
+					{
+						return message;
+					}
+				};
+				
+			}	
+			catch (Throwable e)
+			{
+				// EMTPY, severe error would be detected during the tests
+				System.out.println(e);
+			}
+		}
+		return versionInfo;
+	}
+}
diff --git a/XMPCore/src/com/adobe/xmp/XMPPathFactory.java b/XMPCore/src/com/adobe/xmp/XMPPathFactory.java
new file mode 100644
index 0000000..9d8f632
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/XMPPathFactory.java
@@ -0,0 +1,291 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp;
+
+import com.adobe.xmp.impl.Utils;
+import com.adobe.xmp.impl.xpath.XMPPath;
+import com.adobe.xmp.impl.xpath.XMPPathParser;
+
+/**
+ * Utility services for the metadata object. It has only public static functions, you cannot create
+ * an object. These are all functions that layer cleanly on top of the core XMP toolkit.
+ * <p>
+ * These functions provide support for composing path expressions to deeply nested properties. The
+ * functions <code>XMPMeta</code> such as <code>getProperty()</code>,
+ * <code>getArrayItem()</code> and <code>getStructField()</code> provide easy access to top
+ * level simple properties, items in top level arrays, and fields of top level structs. They do not
+ * provide convenient access to more complex things like fields several levels deep in a complex
+ * struct, or fields within an array of structs, or items of an array that is a field of a struct.
+ * These functions can also be used to compose paths to top level array items or struct fields so
+ * that you can use the binary accessors like <code>getPropertyAsInteger()</code>.
+ * <p>
+ * You can use these functions is to compose a complete path expression, or all but the last
+ * component. Suppose you have a property that is an array of integers within a struct. You can
+ * access one of the array items like this:
+ * <p>
+ * <blockquote>
+ * 
+ * <pre>
+ *      String path = XMPPathFactory.composeStructFieldPath (schemaNS, &quot;Struct&quot;, fieldNS,
+ *          &quot;Array&quot;);
+ *      String path += XMPPathFactory.composeArrayItemPath (schemaNS, &quot;Array&quot; index);
+ *      PropertyInteger result = xmpObj.getPropertyAsInteger(schemaNS, path);
+ * </pre>
+ * 
+ * </blockquote> You could also use this code if you want the string form of the integer:
+ * <blockquote>
+ * 
+ * <pre>
+ *      String path = XMPPathFactory.composeStructFieldPath (schemaNS, &quot;Struct&quot;, fieldNS,
+ *          &quot;Array&quot;);
+ *      PropertyText xmpObj.getArrayItem (schemaNS, path, index);
+ * </pre>
+ * 
+ * </blockquote>
+ * <p>
+ * <em>Note:</em> It might look confusing that the schemaNS is passed in all of the calls above.
+ * This is because the XMP toolkit keeps the top level &quot;schema&quot; namespace separate from
+ * the rest of the path expression.
+ * <em>Note:</em> These methods are much simpler than in the C++-API, they don't check the given
+ * path or array indices.
+ * 
+ * @since 25.01.2006
+ */
+public final class XMPPathFactory
+{
+	/** Private constructor */
+	private XMPPathFactory()
+	{
+		// EMPTY
+	}
+
+
+	/**
+	 * Compose the path expression for an item in an array.
+	 * 
+	 * @param arrayName The name of the array. May be a general path expression, must not be
+	 *        <code>null</code> or the empty string.
+	 * @param itemIndex The index of the desired item. Arrays in XMP are indexed from 1.
+	 * 		  0 and below means last array item and renders as <code>[last()]</code>.	
+	 * 					
+	 * @return Returns the composed path basing on fullPath. This will be of the form
+	 *         <tt>ns:arrayName[i]</tt>, where &quot;ns&quot; is the prefix for schemaNS and
+	 *         &quot;i&quot; is the decimal representation of itemIndex.
+	 * @throws XMPException Throws exeption if index zero is used.
+	 */
+	public static String composeArrayItemPath(String arrayName, int itemIndex) throws XMPException
+	{
+		if (itemIndex > 0)
+		{
+			return arrayName + '[' + itemIndex + ']';
+		}
+		else  if (itemIndex == XMPConst.ARRAY_LAST_ITEM)
+		{
+			return arrayName + "[last()]";
+		}
+		else
+		{
+			throw new XMPException("Array index must be larger than zero", XMPError.BADINDEX);
+		}
+	}
+
+	
+	/**
+	 * Compose the path expression for a field in a struct. The result can be added to the
+	 * path of 
+	 * 
+	 * 
+	 * @param fieldNS The namespace URI for the field. Must not be <code>null</code> or the empty
+	 *        string.
+	 * @param fieldName The name of the field. Must be a simple XML name, must not be
+	 *        <code>null</code> or the empty string.
+	 * @return Returns the composed path. This will be of the form
+	 *         <tt>ns:structName/fNS:fieldName</tt>, where &quot;ns&quot; is the prefix for
+	 *         schemaNS and &quot;fNS&quot; is the prefix for fieldNS.
+	 * @throws XMPException Thrown if the path to create is not valid.
+	 */
+	public static String composeStructFieldPath(String fieldNS,
+			String fieldName) throws XMPException
+	{
+		assertFieldNS(fieldNS);
+		assertFieldName(fieldName);
+		
+		XMPPath fieldPath = XMPPathParser.expandXPath(fieldNS, fieldName);
+		if (fieldPath.size() != 2) 
+		{
+			throw new XMPException("The field name must be simple", XMPError.BADXPATH);
+		}
+		
+		return '/' + fieldPath.getSegment(XMPPath.STEP_ROOT_PROP).getName(); 
+	}
+
+
+	/**
+	 * Compose the path expression for a qualifier.
+	 * 
+	 * @param qualNS The namespace URI for the qualifier. May be <code>null</code> or the empty
+	 *        string if the qualifier is in the XML empty namespace.
+	 * @param qualName The name of the qualifier. Must be a simple XML name, must not be
+	 *        <code>null</code> or the empty string.
+	 * @return Returns the composed path. This will be of the form
+	 *         <tt>ns:propName/?qNS:qualName</tt>, where &quot;ns&quot; is the prefix for
+	 *         schemaNS and &quot;qNS&quot; is the prefix for qualNS.
+	 * @throws XMPException Thrown if the path to create is not valid.
+	 */
+	public static String composeQualifierPath(
+			String qualNS,
+			String qualName) throws XMPException
+	{
+		assertQualNS(qualNS);
+		assertQualName(qualName);
+		
+		XMPPath qualPath = XMPPathParser.expandXPath(qualNS, qualName);
+		if (qualPath.size() != 2)
+		{
+			throw new XMPException("The qualifier name must be simple", XMPError.BADXPATH);
+		}
+
+		return "/?" + qualPath.getSegment(XMPPath.STEP_ROOT_PROP).getName();
+	}
+
+
+	/**
+	 * Compose the path expression to select an alternate item by language. The
+	 * path syntax allows two forms of &quot;content addressing&quot; that may
+	 * be used to select an item in an array of alternatives. The form used in
+	 * ComposeLangSelector lets you select an item in an alt-text array based on
+	 * the value of its <tt>xml:lang</tt> qualifier. The other form of content
+	 * addressing is shown in ComposeFieldSelector. \note ComposeLangSelector
+	 * does not supplant SetLocalizedText or GetLocalizedText. They should
+	 * generally be used, as they provide extra logic to choose the appropriate
+	 * language and maintain consistency with the 'x-default' value.
+	 * ComposeLangSelector gives you an path expression that is explicitly and
+	 * only for the language given in the langName parameter.
+	 * 
+	 * @param arrayName
+	 *            The name of the array. May be a general path expression, must
+	 *            not be <code>null</code> or the empty string.
+	 * @param langName
+	 *            The RFC 3066 code for the desired language.
+	 * @return Returns the composed path. This will be of the form
+	 *         <tt>ns:arrayName[@xml:lang='langName']</tt>, where
+	 *         &quot;ns&quot; is the prefix for schemaNS.
+	 */
+	public static String composeLangSelector(String arrayName,
+			String langName)
+	{
+		return arrayName + "[?xml:lang=\"" + Utils.normalizeLangValue(langName) + "\"]";
+	}
+
+
+	/**
+	 * Compose the path expression to select an alternate item by a field's value. The path syntax
+	 * allows two forms of &quot;content addressing&quot; that may be used to select an item in an
+	 * array of alternatives. The form used in ComposeFieldSelector lets you select an item in an
+	 * array of structs based on the value of one of the fields in the structs. The other form of
+	 * content addressing is shown in ComposeLangSelector. For example, consider a simple struct
+	 * that has two fields, the name of a city and the URI of an FTP site in that city. Use this to
+	 * create an array of download alternatives. You can show the user a popup built from the values
+	 * of the city fields. You can then get the corresponding URI as follows:
+	 * <p>
+	 * <blockquote>
+	 * 
+	 * <pre>
+	 *      String path = composeFieldSelector ( schemaNS, &quot;Downloads&quot;, fieldNS, 
+	 *          &quot;City&quot;, chosenCity ); 
+	 *      XMPProperty prop = xmpObj.getStructField ( schemaNS, path, fieldNS, &quot;URI&quot; );
+	 * </pre>
+	 * 
+	 * </blockquote>
+	 * 
+	 * @param arrayName The name of the array. May be a general path expression, must not be
+	 *        <code>null</code> or the empty string.
+	 * @param fieldNS The namespace URI for the field used as the selector. Must not be
+	 *        <code>null</code> or the empty string.
+	 * @param fieldName The name of the field used as the selector. Must be a simple XML name, must
+	 *        not be <code>null</code> or the empty string. It must be the name of a field that is
+	 *        itself simple.
+	 * @param fieldValue The desired value of the field.
+	 * @return Returns the composed path. This will be of the form
+	 *         <tt>ns:arrayName[fNS:fieldName='fieldValue']</tt>, where &quot;ns&quot; is the
+	 *         prefix for schemaNS and &quot;fNS&quot; is the prefix for fieldNS.
+	 * @throws XMPException Thrown if the path to create is not valid.
+	 */
+	public static String composeFieldSelector(String arrayName, String fieldNS,
+			String fieldName, String fieldValue) throws XMPException
+	{
+		XMPPath fieldPath = XMPPathParser.expandXPath(fieldNS, fieldName);
+		if (fieldPath.size() != 2) 
+		{
+			throw new XMPException("The fieldName name must be simple", XMPError.BADXPATH);
+		}
+		
+		return arrayName + '[' + fieldPath.getSegment(XMPPath.STEP_ROOT_PROP).getName() +
+			"=\"" + fieldValue + "\"]"; 
+	}
+	
+	
+	/**
+	 * ParameterAsserts that a qualifier namespace is set.
+	 * @param qualNS a qualifier namespace
+	 * @throws XMPException Qualifier schema is null or empty
+	 */
+	private static void assertQualNS(String qualNS) throws XMPException
+	{
+		if (qualNS == null  ||  qualNS.length() == 0)
+		{
+			throw new XMPException("Empty qualifier namespace URI", XMPError.BADSCHEMA);
+		}
+		
+	}
+	
+	
+	/**
+	 * ParameterAsserts that a qualifier name is set.
+	 * @param qualName a qualifier name or path
+	 * @throws XMPException Qualifier name is null or empty
+	 */
+	private static void assertQualName(String qualName) throws XMPException
+	{
+		if (qualName == null  ||  qualName.length() == 0)
+		{
+			throw new XMPException("Empty qualifier name", XMPError.BADXPATH);
+		}
+	}
+
+	
+	/**
+	 * ParameterAsserts that a struct field namespace is set.
+	 * @param fieldNS a struct field namespace
+	 * @throws XMPException Struct field schema is null or empty
+	 */
+	private static void assertFieldNS(String fieldNS) throws XMPException
+	{
+		if (fieldNS == null  ||  fieldNS.length() == 0)
+		{
+			throw new XMPException("Empty field namespace URI", XMPError.BADSCHEMA);
+		}
+		
+	}
+	
+	
+	/**
+	 * ParameterAsserts that a struct field name is set.
+	 * @param fieldName a struct field name or path
+	 * @throws XMPException Struct field name is null or empty
+	 */
+	private static void assertFieldName(String fieldName) throws XMPException
+	{
+		if (fieldName == null  ||  fieldName.length() == 0)
+		{
+			throw new XMPException("Empty f name", XMPError.BADXPATH);
+		}
+	}
+}
\ No newline at end of file
diff --git a/XMPCore/src/com/adobe/xmp/XMPSchemaRegistry.java b/XMPCore/src/com/adobe/xmp/XMPSchemaRegistry.java
new file mode 100644
index 0000000..4608a8e
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/XMPSchemaRegistry.java
@@ -0,0 +1,180 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp;
+
+import java.util.Map;
+
+import com.adobe.xmp.properties.XMPAliasInfo;
+
+/**
+ * The schema registry keeps track of all namespaces and aliases used in the XMP
+ * metadata. At initialisation time, the default namespaces and default aliases
+ * are automatically registered. <b>Namespaces</b> must be registered before
+ * used in namespace URI parameters or path expressions. Within the XMP Toolkit
+ * the registered namespace URIs and prefixes must be unique. Additional
+ * namespaces encountered when parsing RDF are automatically registered. The
+ * namespace URI should always end in an XML name separator such as '/' or '#'.
+ * This is because some forms of RDF shorthand catenate a namespace URI with an
+ * element name to form a new URI.
+ * <p>
+ * <b>Aliases</b> in XMP serve the same purpose as Windows file shortcuts,
+ * Macintosh file aliases, or UNIX file symbolic links. The aliases are simply
+ * multiple names for the same property. One distinction of XMP aliases is that
+ * they are ordered, there is an alias name pointing to an actual name. The
+ * primary significance of the actual name is that it is the preferred name for
+ * output, generally the most widely recognized name.
+ * <p>
+ * The names that can be aliased in XMP are restricted. The alias must be a top
+ * level property name, not a field within a structure or an element within an
+ * array. The actual may be a top level property name, the first element within
+ * a top level array, or the default element in an alt-text array. This does not
+ * mean the alias can only be a simple property. It is OK to alias a top level
+ * structure or array to an identical top level structure or array, or to the
+ * first item of an array of structures.
+ * 
+ * @since 27.01.2006
+ */
+public interface XMPSchemaRegistry
+{
+	// ---------------------------------------------------------------------------------------------
+	// Namespace Functions
+
+	/**
+	 * Register a namespace URI with a suggested prefix. It is not an error if
+	 * the URI is already registered, no matter what the prefix is. If the URI
+	 * is not registered but the suggested prefix is in use, a unique prefix is
+	 * created from the suggested one. The actual registeed prefix is always
+	 * returned. The function result tells if the registered prefix is the
+	 * suggested one.
+	 * <p>
+	 * Note: No checking is presently done on either the URI or the prefix.
+	 * 
+	 * @param namespaceURI
+	 *            The URI for the namespace. Must be a valid XML URI.
+	 * @param suggestedPrefix
+	 *            The suggested prefix to be used if the URI is not yet
+	 *            registered. Must be a valid XML name.
+	 * @return Returns the registered prefix for this URI, is equal to the
+	 *         suggestedPrefix if the namespace hasn't been registered before,
+	 *         otherwise the existing prefix.
+	 * @throws XMPException If the parameters are not accordingly set
+	 */
+	String registerNamespace(String namespaceURI, String suggestedPrefix) throws XMPException;
+
+	
+	/**
+	 * Obtain the prefix for a registered namespace URI.
+	 * <p>
+	 * It is not an error if the namespace URI is not registered. The output
+	 * namespacePrefix string is not modified if the namespace URI is not
+	 * registered.
+	 * 
+	 * @param namespaceURI
+	 *            The URI for the namespace. Must not be null or the empty
+	 *            string.
+	 * @return Returns true if the namespace URI is registered.
+	 */
+	String getNamespacePrefix(String namespaceURI);
+
+	
+	/**
+	 * Obtain the URI for a registered namespace prefix.
+	 * <p>
+	 * It is not an error if the namespace prefix is not registered. The output
+	 * namespaceURI string is not modified if the namespace prefix is not
+	 * registered.
+	 * 
+	 * @param namespacePrefix
+	 *            The prefix for the namespace. Must not be null or the empty
+	 *            string.
+	 * @return Returns the URI registered for this prefix.
+	 */
+	String getNamespaceURI(String namespacePrefix);
+
+	
+	/**
+	 * @return Returns the registered prefix/namespace-pairs as map, where the keys are the
+	 *         namespaces and the values are the prefixes.
+	 */
+	Map getNamespaces();
+
+	
+	/**
+	 * @return Returns the registered namespace/prefix-pairs as map, where the keys are the
+	 *         prefixes and the values are the namespaces.
+	 */
+	Map getPrefixes();
+	
+	
+	/**
+	 * Deletes a namespace from the registry.
+	 * <p>
+	 * Does nothing if the URI is not registered, or if the namespaceURI
+	 * parameter is null or the empty string.
+	 * <p>
+	 * Note: Not yet implemented.
+	 * 
+	 * @param namespaceURI
+	 *            The URI for the namespace.
+	 */
+	void deleteNamespace(String namespaceURI);
+
+	
+	
+	
+	
+	// ---------------------------------------------------------------------------------------------
+	// Alias Functions
+
+		
+	/**
+	 * Determines if a name is an alias, and what it is aliased to.
+	 * 
+	 * @param aliasNS
+	 *            The namespace URI of the alias. Must not be <code>null</code> or the empty
+	 *            string.
+	 * @param aliasProp
+	 *            The name of the alias. May be an arbitrary path expression
+	 *            path, must not be <code>null</code> or the empty string.
+	 * @return Returns the <code>XMPAliasInfo</code> for the given alias namespace and property or
+	 * 		<code>null</code> if there is no such alias.
+	 */
+	XMPAliasInfo resolveAlias(String aliasNS, String aliasProp);
+
+	
+	/**
+	 * Collects all aliases that are contained in the provided namespace.
+	 * If nothing is found, an empty array is returned. 
+	 * 
+	 * @param aliasNS a schema namespace URI
+	 * @return Returns all alias infos from aliases that are contained in the provided namespace. 
+	 */
+	XMPAliasInfo[] findAliases(String aliasNS);
+	
+	
+	/**
+	 * Searches for registered aliases.
+	 * 
+	 * @param qname
+	 *            an XML conform qname
+	 * @return Returns if an alias definition for the given qname to another
+	 *         schema and property is registered.
+	 */
+	XMPAliasInfo findAlias(String qname);
+	
+		
+	/**
+	 * @return Returns the registered aliases as map, where the key is the "qname" (prefix and name)
+	 * and the value an <code>XMPAliasInfo</code>-object.
+	 */
+	Map getAliases();
+
+
+}
\ No newline at end of file
diff --git a/XMPCore/src/com/adobe/xmp/XMPUtils.java b/XMPCore/src/com/adobe/xmp/XMPUtils.java
new file mode 100644
index 0000000..043d533
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/XMPUtils.java
@@ -0,0 +1,506 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp;
+
+import com.adobe.xmp.impl.Base64;
+import com.adobe.xmp.impl.ISO8601Converter;
+import com.adobe.xmp.impl.XMPUtilsImpl;
+import com.adobe.xmp.options.PropertyOptions;
+
+
+/**
+ * Utility methods for XMP. I included only those that are different from the
+ * Java default conversion utilities.
+ * 
+ * @since 21.02.2006
+ */
+public class XMPUtils
+{
+	/** Private constructor */
+	private XMPUtils()
+	{
+		// EMPTY
+	}
+
+
+	/**
+	 * Create a single edit string from an array of strings.
+	 * 
+	 * @param xmp
+	 *            The XMP object containing the array to be catenated.
+	 * @param schemaNS
+	 *            The schema namespace URI for the array. Must not be null or
+	 *            the empty string.
+	 * @param arrayName
+	 *            The name of the array. May be a general path expression, must
+	 *            not be null or the empty string. Each item in the array must
+	 *            be a simple string value.
+	 * @param separator
+	 *            The string to be used to separate the items in the catenated
+	 *            string. Defaults to &quot;; &quot;, ASCII semicolon and space
+	 *            (U+003B, U+0020).
+	 * @param quotes
+	 *            The characters to be used as quotes around array items that
+	 *            contain a separator. Defaults to &apos;&quot;&apos;
+	 * @param allowCommas
+	 *            Option flag to control the catenation.
+	 * @return Returns the string containing the catenated array items.
+	 * @throws XMPException Forwards the Exceptions from the metadata processing
+	 */
+	public static String catenateArrayItems(XMPMeta xmp, String schemaNS, String arrayName,
+			String separator, String quotes, boolean allowCommas) throws XMPException
+	{
+		return XMPUtilsImpl
+				.catenateArrayItems(xmp, schemaNS, arrayName, separator, quotes, allowCommas);
+	}
+
+
+	/**
+	 * Separate a single edit string into an array of strings.
+	 * 
+	 * @param xmp
+	 *            The XMP object containing the array to be updated.
+	 * @param schemaNS
+	 *            The schema namespace URI for the array. Must not be null or
+	 *            the empty string.
+	 * @param arrayName
+	 *            The name of the array. May be a general path expression, must
+	 *            not be null or the empty string. Each item in the array must
+	 *            be a simple string value.
+	 * @param catedStr
+	 *            The string to be separated into the array items.
+	 * @param arrayOptions Option flags to control the separation. 
+	 * @param preserveCommas Flag if commas shall be preserved
+	 * @throws XMPException Forwards the Exceptions from the metadata processing 
+	 */
+	public static void separateArrayItems(XMPMeta xmp, String schemaNS, String arrayName,
+			String catedStr, PropertyOptions arrayOptions, boolean preserveCommas) 
+				throws XMPException
+	{
+		XMPUtilsImpl.separateArrayItems(xmp, schemaNS, arrayName, catedStr, arrayOptions,
+				preserveCommas);
+	}
+
+
+	/**
+	 * Remove multiple properties from an XMP object.
+	 * 
+	 * RemoveProperties was created to support the File Info dialog's Delete
+	 * button, and has been been generalized somewhat from those specific needs.
+	 * It operates in one of three main modes depending on the schemaNS and
+	 * propName parameters:
+	 * 
+	 * <ul>
+	 * <li> Non-empty <code>schemaNS</code> and <code>propName</code> - The named property is
+	 * removed if it is an external property, or if the 
+	 * flag <code>doAllProperties</code> option is true. It does not matter whether the
+	 * named property is an actual property or an alias.
+	 * 
+	 * <li> Non-empty <code>schemaNS</code> and empty <code>propName</code> - The all external
+	 * properties in the named schema are removed. Internal properties are also
+	 * removed if the flag <code>doAllProperties</code> option is set. In addition,
+	 * aliases from the named schema will be removed if the flag <code>includeAliases</code> 
+	 * option is set.
+	 * 
+	 * <li> Empty <code>schemaNS</code> and empty <code>propName</code> - All external properties in
+	 * all schema are removed. Internal properties are also removed if the 
+	 * flag <code>doAllProperties</code> option is passed. Aliases are implicitly handled
+	 * because the associated actuals are internal if the alias is.
+	 * </ul>
+	 * 
+	 * It is an error to pass an empty <code>schemaNS</code> and non-empty <code>propName</code>.
+	 * 
+	 * @param xmp
+	 *            The XMP object containing the properties to be removed.
+	 * 
+	 * @param schemaNS
+	 *            Optional schema namespace URI for the properties to be
+	 *            removed.
+	 * 
+	 * @param propName
+	 *            Optional path expression for the property to be removed.
+	 * 
+	 * @param doAllProperties Option flag to control the deletion: do internal properties in
+	 *          addition to external properties.
+	 *            
+	 * @param includeAliases Option flag to control the deletion: 
+	 * 			Include aliases in the "named schema" case above.
+	 * 			<em>Note:</em> Currently not supported.
+	 * @throws XMPException Forwards the Exceptions from the metadata processing 
+	 */
+	public static void removeProperties(XMPMeta xmp, String schemaNS, String propName,
+			boolean doAllProperties, boolean includeAliases) throws XMPException
+	{
+		XMPUtilsImpl.removeProperties(xmp, schemaNS, propName, doAllProperties, includeAliases);
+	}
+
+	
+
+	/**
+	 * Alias without the new option <code>deleteEmptyValues</code>.
+	 * @param source The source XMP object.
+	 * @param dest The destination XMP object.
+	 * @param doAllProperties Do internal properties in addition to external properties.
+	 * @param replaceOldValues Replace the values of existing properties.
+	 * @throws XMPException Forwards the Exceptions from the metadata processing 
+	 */
+	public static void appendProperties(XMPMeta source, XMPMeta dest, boolean doAllProperties,
+			boolean replaceOldValues) throws XMPException
+	{
+		appendProperties(source, dest, doAllProperties, replaceOldValues, false);
+	}
+	
+
+	/**
+	 * <p>Append properties from one XMP object to another.
+	 * 
+	 * <p>XMPUtils#appendProperties was created to support the File Info dialog's Append button, and
+	 * has been been generalized somewhat from those specific needs. It appends information from one
+	 * XMP object (source) to another (dest). The default operation is to append only external
+	 * properties that do not already exist in the destination. The flag 
+	 * <code>doAllProperties</code> can be used to operate on all properties, external and internal.
+	 * The flag <code>replaceOldValues</code> option can be used to replace the values 
+	 * of existing properties. The notion of external
+	 * versus internal applies only to top level properties. The keep-or-replace-old notion applies
+	 * within structs and arrays as described below.
+	 * <ul>
+	 * <li>If <code>replaceOldValues</code> is true then the processing is restricted to the top 
+	 * level properties. The processed properties from the source (according to 
+	 * <code>doAllProperties</code>) are propagated to the destination, 
+	 * replacing any existing values.Properties in the destination that are not in the source 
+	 * are left alone.
+	 *
+	 * <li>If <code>replaceOldValues</code> is not passed then the processing is more complicated. 
+	 * Top level properties are added to the destination if they do not already exist. 
+	 * If they do exist but differ in form (simple/struct/array) then the destination is left alone.
+	 * If the forms match, simple properties are left unchanged while structs and arrays are merged.
+	 * 
+	 * <li>If <code>deleteEmptyValues</code> is passed then an empty value in the source XMP causes
+	 * the corresponding destination XMP property to be deleted. The default is to treat empty 
+	 * values the same as non-empty values. An empty value is any of a simple empty string, an array
+	 * with no items, or a struct with no fields. Qualifiers are ignored.
+	 * </ul>
+	 * 
+	 * <p>The detailed behavior is defined by the following pseudo-code:
+	 * <blockquote>
+	 * <pre>
+     *    appendProperties ( sourceXMP, destXMP, doAllProperties, 
+     *    			replaceOldValues, deleteEmptyValues ):
+     *       for all source schema (top level namespaces):
+     *          for all top level properties in sourceSchema:
+     *             if doAllProperties or prop is external:
+     *                appendSubtree ( sourceNode, destSchema, replaceOldValues, deleteEmptyValues )
+     * 
+     *    appendSubtree ( sourceNode, destParent, replaceOldValues, deleteEmptyValues ):
+     *        if deleteEmptyValues and source value is empty:
+     *            delete the corresponding child from destParent
+     *        else if sourceNode not in destParent (by name):
+     *           copy sourceNode's subtree to destParent
+     *        else if replaceOld:
+     *            delete subtree from destParent
+     *            copy sourceNode's subtree to destParent
+     *        else:
+     *            // Already exists in dest and not replacing, merge structs and arrays
+     *            if sourceNode and destNode forms differ:
+     *                return, leave the destNode alone
+     *            else if form is a struct:
+     *                for each field in sourceNode:
+     *                    AppendSubtree ( sourceNode.field, destNode, replaceOldValues )
+     *            else if form is an alt-text array:
+     *                copy new items by "xml:lang" value into the destination
+     *            else if form is an array:
+     *                copy new items by value into the destination, ignoring order and duplicates
+     * </pre>
+	 * </blockquote>
+	 *
+	 * <p><em>Note:</em> appendProperties can be expensive if replaceOldValues is not passed and 
+	 * the XMP contains large arrays. The array item checking described above is n-squared. 
+	 * Each source item is checked to see if it already exists in the destination, 
+	 * without regard to order or duplicates.
+	 * <p>Simple items are compared by value and "xml:lang" qualifier, other qualifiers are ignored.
+	 * Structs are recursively compared by field names, without regard to field order. Arrays are
+	 * compared by recursively comparing all items.
+	 *
+	 * @param source The source XMP object.
+	 * @param dest The destination XMP object.
+	 * @param doAllProperties Do internal properties in addition to external properties.
+	 * @param replaceOldValues Replace the values of existing properties.
+	 * @param deleteEmptyValues Delete destination values if source property is empty.
+	 * @throws XMPException Forwards the Exceptions from the metadata processing 
+	 */
+	public static void appendProperties(XMPMeta source, XMPMeta dest, boolean doAllProperties,
+			boolean replaceOldValues, boolean deleteEmptyValues) throws XMPException
+	{
+		XMPUtilsImpl.appendProperties(source, dest, doAllProperties, replaceOldValues, 
+			deleteEmptyValues);
+	}
+
+
+	/**
+	 * Convert from string to Boolean.
+	 * 
+	 * @param value
+	 *            The string representation of the Boolean.
+	 * @return The appropriate boolean value for the string. The checked values
+	 *         for <code>true</code> and <code>false</code> are:
+	 *         <ul>
+	 *    	    	<li>{@link XMPConst#TRUESTR} and {@link XMPConst#FALSESTR}
+	 *    		    <li>&quot;t&quot; and &quot;f&quot;
+	 *    		    <li>&quot;on&quot; and &quot;off&quot;
+	 *    		    <li>&quot;yes&quot; and &quot;no&quot;
+	 *   		  	<li>&quot;value <> 0&quot; and &quot;value == 0&quot;
+	 *         </ul>
+	 * @throws XMPException If an empty string is passed.
+	 */
+	public static boolean convertToBoolean(String value) throws XMPException
+	{
+		if (value == null  ||  value.length() == 0)
+		{
+			throw new XMPException("Empty convert-string", XMPError.BADVALUE);
+		}
+		value = value.toLowerCase();
+		
+		try
+		{
+			// First try interpretation as Integer (anything not 0 is true)
+			return Integer.parseInt(value) != 0;
+		}
+		catch (NumberFormatException e)
+		{
+			return
+				"true".equals(value)  ||
+				"t".equals(value)  ||
+				"on".equals(value)  ||
+				"yes".equals(value);
+		}	
+	}
+
+	
+	/**
+	 * Convert from boolean to string.
+	 * 
+	 * @param value
+	 *            a boolean value
+	 * @return The XMP string representation of the boolean. The values used are
+	 *         given by the constnts {@link XMPConst#TRUESTR} and
+	 *         {@link XMPConst#FALSESTR}.
+	 */
+	public static String convertFromBoolean(boolean value)
+	{
+		return value ? XMPConst.TRUESTR : XMPConst.FALSESTR;
+	}
+
+
+	/**
+	 * Converts a string value to an <code>int</code>.
+	 * 
+	 * @param rawValue
+	 *            the string value
+	 * @return Returns an int.
+	 * @throws XMPException
+	 *             If the <code>rawValue</code> is <code>null</code> or empty or the
+	 *             conversion fails.
+	 */
+	public static int convertToInteger(String rawValue) throws XMPException
+	{
+		try
+		{
+			if (rawValue == null  ||  rawValue.length() == 0)
+			{
+				throw new XMPException("Empty convert-string", XMPError.BADVALUE);
+			}
+			if (rawValue.startsWith("0x"))
+			{
+				return Integer.parseInt(rawValue.substring(2), 16);
+			}	
+			else
+			{
+				return Integer.parseInt(rawValue);
+			}
+		}
+		catch (NumberFormatException e)
+		{
+			throw new XMPException("Invalid integer string", XMPError.BADVALUE);
+		}
+	}
+
+
+	/**
+	 * Convert from int to string.
+	 * 
+	 * @param value
+	 *            an int value
+	 * @return The string representation of the int.
+	 */
+	public static String convertFromInteger(int value)
+	{
+		return String.valueOf(value);
+	}
+	
+
+	/**
+	 * Converts a string value to a <code>long</code>.
+	 * 
+	 * @param rawValue
+	 *            the string value
+	 * @return Returns a long.
+	 * @throws XMPException
+	 *             If the <code>rawValue</code> is <code>null</code> or empty or the
+	 *             conversion fails.
+	 */
+	public static long convertToLong(String rawValue) throws XMPException
+	{
+		try
+		{
+			if (rawValue == null  ||  rawValue.length() == 0)
+			{
+				throw new XMPException("Empty convert-string", XMPError.BADVALUE);
+			}
+			if (rawValue.startsWith("0x"))
+			{	
+				return Long.parseLong(rawValue.substring(2), 16);
+			}	
+			else
+			{
+				return Long.parseLong(rawValue);
+			}
+		}
+		catch (NumberFormatException e)
+		{
+			throw new XMPException("Invalid long string", XMPError.BADVALUE);
+		}
+	}
+
+
+	/**
+	 * Convert from long to string.
+	 * 
+	 * @param value
+	 *            a long value
+	 * @return The string representation of the long.
+	 */
+	public static String convertFromLong(long value)
+	{
+		return String.valueOf(value);
+	}
+
+	
+	/**
+	 * Converts a string value to a <code>double</code>.
+	 * 
+	 * @param rawValue
+	 *            the string value
+	 * @return Returns a double.
+	 * @throws XMPException
+	 *             If the <code>rawValue</code> is <code>null</code> or empty or the
+	 *             conversion fails.
+	 */
+	public static double convertToDouble(String rawValue) throws XMPException
+	{
+		try
+		{
+			if (rawValue == null  ||  rawValue.length() == 0)
+			{
+				throw new XMPException("Empty convert-string", XMPError.BADVALUE);
+			}
+			else
+			{
+				return Double.parseDouble(rawValue);
+			}
+		}
+		catch (NumberFormatException e)
+		{
+			throw new XMPException("Invalid double string", XMPError.BADVALUE);
+		}
+	}
+
+	
+	/**
+	 * Convert from long to string.
+	 * 
+	 * @param value
+	 *            a long value
+	 * @return The string representation of the long.
+	 */
+	public static String convertFromDouble(double value)
+	{
+		return String.valueOf(value);
+	}
+	
+
+	/**
+	 * Converts a string value to an <code>XMPDateTime</code>.
+	 * 
+	 * @param rawValue
+	 *            the string value
+	 * @return Returns an <code>XMPDateTime</code>-object.
+	 * @throws XMPException
+	 *             If the <code>rawValue</code> is <code>null</code> or empty or the
+	 *             conversion fails.
+	 */
+	public static XMPDateTime convertToDate(String rawValue) throws XMPException
+	{
+		if (rawValue == null  ||  rawValue.length() == 0)
+		{
+			throw new XMPException("Empty convert-string", XMPError.BADVALUE);
+		}
+		else
+		{	
+			return ISO8601Converter.parse(rawValue);
+		}	
+	}
+	
+	
+	/**
+	 * Convert from <code>XMPDateTime</code> to string.
+	 * 
+	 * @param value
+	 *            an <code>XMPDateTime</code>
+	 * @return The string representation of the long.
+	 */
+	public static String convertFromDate(XMPDateTime value)
+	{
+		return ISO8601Converter.render(value);
+	}
+	
+	
+	 /**
+	  * Convert from a byte array to a base64 encoded string.
+	  * 
+	  * @param buffer
+	  *            the byte array to be converted
+	  * @return Returns the base64 string.
+	  */
+	public static String encodeBase64(byte[] buffer)
+	{
+		return new String(Base64.encode(buffer));
+	}
+
+
+	/**
+	 * Decode from Base64 encoded string to raw data.
+	 * 
+	 * @param base64String
+	 *            a base64 encoded string
+	 * @return Returns a byte array containg the decoded string.
+	 * @throws XMPException Thrown if the given string is not property base64 encoded
+	 */
+	public static byte[] decodeBase64(String base64String) throws XMPException
+	{
+		try
+		{
+			return Base64.decode(base64String.getBytes());
+		}
+		catch (Throwable e)
+		{
+			throw new XMPException("Invalid base64 string", XMPError.BADVALUE, e);
+		}
+	}			
+}
\ No newline at end of file
diff --git a/XMPCore/src/com/adobe/xmp/XMPVersionInfo.java b/XMPCore/src/com/adobe/xmp/XMPVersionInfo.java
new file mode 100644
index 0000000..24b7c20
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/XMPVersionInfo.java
@@ -0,0 +1,45 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp;
+
+/**
+ * XMP Toolkit Version Information.
+ * <p>
+ * Version information for the XMP toolkit is stored in the jar-library and available through a
+ * runtime call, {@link XMPMetaFactory#getVersionInfo()},  addition static version numbers are
+ * defined in "version.properties". 
+ * 
+ * @since 23.01.2006
+ */
+public interface XMPVersionInfo
+{
+	/** @return Returns the primary release number, the "1" in version "1.2.3". */
+	int getMajor();
+
+
+	/** @return Returns the secondary release number, the "2" in version "1.2.3". */
+	int getMinor();
+
+
+	/** @return Returns the tertiary release number, the "3" in version "1.2.3". */
+	int getMicro();
+
+
+	/** @return Returns a rolling build number, monotonically increasing in a release. */
+	int getBuild();
+
+
+	/** @return Returns true if this is a debug build. */
+	boolean isDebug();
+	
+	
+	/** @return Returns a comprehensive version information string. */
+	String getMessage();
+}
\ No newline at end of file
diff --git a/XMPCore/src/com/adobe/xmp/impl/Base64.java b/XMPCore/src/com/adobe/xmp/impl/Base64.java
new file mode 100644
index 0000000..a327876
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/impl/Base64.java
@@ -0,0 +1,251 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2001 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp.impl;
+
+
+
+
+/**
+ * A utility class to perform base64 encoding and decoding as specified
+ * in RFC-1521. See also RFC 1421.
+ *
+ * @version     $Revision: 1.4 $
+ */
+public class  Base64
+{
+	/** marker for invalid bytes */
+	private static final byte INVALID = -1; 
+	/** marker for accepted whitespace bytes */
+	private static final byte WHITESPACE = -2;
+	/** marker for an equal symbol */
+	private static final byte EQUAL = -3;
+	
+	/** */
+    private static byte[] base64 = {
+        (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D',   //  0 to  3
+        (byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H',   //  4 to  7
+        (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L',   //  8 to 11
+        (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P',   // 11 to 15
+        (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T',   // 16 to 19
+        (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X',   // 20 to 23
+        (byte) 'Y', (byte) 'Z', (byte) 'a', (byte) 'b',   // 24 to 27
+        (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f',   // 28 to 31
+        (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j',   // 32 to 35
+        (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n',   // 36 to 39
+        (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r',   // 40 to 43
+        (byte) 's', (byte) 't', (byte) 'u', (byte) 'v',   // 44 to 47
+        (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z',   // 48 to 51
+        (byte) '0', (byte) '1', (byte) '2', (byte) '3',   // 52 to 55
+        (byte) '4', (byte) '5', (byte) '6', (byte) '7',   // 56 to 59
+        (byte) '8', (byte) '9', (byte) '+', (byte) '/'    // 60 to 63
+    };
+    /** */
+    private static byte[] ascii = new byte[255];
+    /** */
+    static {
+    	// not valid bytes
+        for (int idx = 0; idx < 255; idx++)
+		{
+			ascii[idx] = INVALID;
+		}
+    	// valid bytes
+		for (int idx = 0; idx < base64.length; idx++)
+		{
+			ascii[base64[idx]] = (byte) idx;
+		}
+		// whitespaces
+		ascii[0x09] = WHITESPACE;
+		ascii[0x0A] = WHITESPACE;
+		ascii[0x0D] = WHITESPACE;
+		ascii[0x20] = WHITESPACE;
+		
+		// trailing equals
+		ascii[0x3d] = EQUAL;
+    }
+
+    
+    /**
+	 * Encode the given byte[].
+	 * 
+	 * @param src the source string.
+	 * @return the base64-encoded data.
+	 */
+    public static final byte[] encode(byte[] src)
+	{
+    	return encode(src, 0);
+	}
+    	
+
+    /**
+	 * Encode the given byte[].
+	 * 
+	 * @param src the source string.
+	 * @param lineFeed a linefeed is added after <code>linefeed</code> characters;
+	 *            must be dividable by four; 0 means no linefeeds
+	 * @return the base64-encoded data.
+	 */
+    public static final byte[] encode(byte[] src, int lineFeed)
+	{
+    	// linefeed must be dividable by 4
+    	lineFeed = lineFeed / 4 * 4;
+    	if (lineFeed < 0)
+    	{
+    		lineFeed = 0;
+    	}
+    	
+		// determine code length
+    	int codeLength = ((src.length + 2) / 3) * 4;
+		if (lineFeed > 0)
+		{
+			codeLength += (codeLength - 1) / lineFeed;
+		}
+		
+		byte[] dst = new byte[codeLength];
+		int bits24;
+		int bits6;
+		//
+		// Do 3-byte to 4-byte conversion + 0-63 to ascii printable conversion
+		//
+		int didx = 0;
+		int sidx = 0;
+		int lf = 0;
+		while (sidx + 3 <= src.length)
+		{
+            bits24  = (src[sidx++] & 0xFF) << 16; 
+            bits24 |= (src[sidx++] & 0xFF) <<  8; 
+            bits24 |= (src[sidx++] & 0xFF) <<  0;
+            bits6   = (bits24 & 0x00FC0000) >> 18; 
+            dst[didx++] = base64[bits6];
+            bits6   = (bits24 & 0x0003F000) >> 12; 
+            dst[didx++] = base64[bits6];
+            bits6   = (bits24 & 0x00000FC0) >>  6; 
+            dst[didx++] = base64[bits6];
+            bits6   = (bits24 & 0x0000003F);
+            dst[didx++] = base64[bits6];
+            
+            lf += 4;
+            if (didx < codeLength  &&  lineFeed > 0  &&  lf % lineFeed == 0)
+            {
+            	dst[didx++] = 0x0A;
+            }
+        }
+        if (src.length - sidx == 2)
+		{
+            bits24  = (src[sidx    ] & 0xFF) << 16; 
+            bits24 |= (src[sidx + 1] & 0xFF) <<  8;
+            bits6 = (bits24 & 0x00FC0000) >> 18;
+            dst[didx++] = base64[bits6]; 
+            bits6 = (bits24 & 0x0003F000) >> 12; 
+            dst[didx++] = base64[bits6]; 
+            bits6 = (bits24 & 0x00000FC0) >>  6; 
+            dst[didx++] = base64[bits6];
+            dst[didx++] = (byte) '='; 
+        }
+        else if (src.length - sidx == 1)
+		{
+            bits24 = (src[sidx] & 0xFF) << 16;
+            bits6  = (bits24 & 0x00FC0000) >> 18;
+            dst[didx++] = base64[bits6];
+            bits6  = (bits24 & 0x0003F000) >> 12; 
+            dst[didx++] = base64[bits6];
+            dst[didx++] = (byte) '='; 
+            dst[didx++] = (byte) '='; 
+        }
+        return dst;
+    }
+
+
+    /**
+     * Encode the given string.
+     * @param src the source string.
+     * @return the base64-encoded string.
+     */
+    public static final String encode(String src)
+	{
+		return new String(encode(src.getBytes()));
+	}
+
+
+    /**
+	 * Decode the given byte[].
+	 * 
+	 * @param src
+	 *            the base64-encoded data.
+	 * @return the decoded data.
+     * @throws IllegalArgumentException Thrown if the base 64 strings contains non-valid characters,
+     * 		beside the bas64 chars, LF, CR, tab and space are accepted.
+	 */
+    public static final byte[] decode(byte[] src) throws IllegalArgumentException
+	{
+        //
+        // Do ascii printable to 0-63 conversion.
+        //
+        int sidx;
+        int srcLen = 0;
+        for (sidx = 0; sidx < src.length; sidx++)
+        {
+        	byte val = ascii[src[sidx]];
+        	if (val >= 0)
+        	{	
+        		src[srcLen++] = val;
+        	}
+        	else if (val == INVALID)
+        	{
+        		throw new IllegalArgumentException("Invalid base 64 string");
+        	}
+        }
+    	
+		//
+		// Trim any padding.
+		//
+		while (srcLen > 0  &&  src[srcLen - 1] == EQUAL)
+		{
+			srcLen--;
+		}
+		byte[] dst = new byte[srcLen * 3 / 4];
+
+		//
+        // Do 4-byte to 3-byte conversion.
+        //
+        int didx;
+        for (sidx = 0, didx = 0; didx < dst.length - 2; sidx += 4, didx += 3)
+        {
+            dst[didx    ] = (byte) (((src[sidx    ] << 2) & 0xFF)
+                            | ((src[sidx + 1] >>> 4) & 0x03));
+            dst[didx + 1] = (byte) (((src[sidx + 1] << 4) & 0xFF)
+                            | ((src[sidx + 2] >>> 2) & 0x0F));
+            dst[didx + 2] = (byte) (((src[sidx + 2] << 6) & 0xFF)
+                            | ((src[sidx + 3]) & 0x3F));
+        }
+        if (didx < dst.length)
+        {
+            dst[didx]     = (byte) (((src[sidx    ] << 2) & 0xFF)
+                            | ((src[sidx + 1] >>> 4) & 0x03));
+        }
+        if (++didx < dst.length)
+        {
+            dst[didx]     = (byte) (((src[sidx + 1] << 4) & 0xFF)
+                            | ((src[sidx + 2] >>> 2) & 0x0F));
+        }
+        return dst;
+    }
+
+
+    /**
+	 * Decode the given string.
+	 * 
+	 * @param src the base64-encoded string.
+	 * @return the decoded string.
+	 */
+	public static final String decode(String src)
+	{
+		return new String(decode(src.getBytes()));
+	}
+}
diff --git a/XMPCore/src/com/adobe/xmp/impl/ByteBuffer.java b/XMPCore/src/com/adobe/xmp/impl/ByteBuffer.java
new file mode 100644
index 0000000..22cbc4b
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/impl/ByteBuffer.java
@@ -0,0 +1,326 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+
+
+package com.adobe.xmp.impl;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+/**
+ * Byte buffer container including length of valid data.
+ * 
+ * @since   11.10.2006
+ */
+public class ByteBuffer
+{
+	/** */
+	private byte[] buffer;
+	/** */
+	private int length;
+	/** */
+	private String encoding = null;
+	
+
+	/**
+	 * @param initialCapacity the initial capacity for this buffer
+	 */
+	public ByteBuffer(int initialCapacity)
+	{
+		this.buffer = new byte[initialCapacity];
+		this.length = 0;
+	}
+
+	
+	/**
+	 * @param buffer a byte array that will be wrapped with <code>ByteBuffer</code>.
+	 */
+	public ByteBuffer(byte[] buffer)
+	{
+		this.buffer = buffer;
+		this.length = buffer.length;
+	}
+	
+	
+	/**
+	 * @param buffer a byte array that will be wrapped with <code>ByteBuffer</code>.
+	 * @param length the length of valid bytes in the array
+	 */
+	public ByteBuffer(byte[] buffer, int length)
+	{
+		if (length > buffer.length)
+		{
+			throw new ArrayIndexOutOfBoundsException("Valid length exceeds the buffer length.");
+		}
+		this.buffer = buffer;
+		this.length = length;
+	}
+
+	
+	/**
+	 * Loads the stream into a buffer. 
+	 * 
+	 * @param in an InputStream
+	 * @throws IOException If the stream cannot be read.
+	 */
+	public ByteBuffer(InputStream in) throws IOException
+	{
+		// load stream into buffer
+		int chunk = 16384;
+		this.length = 0;
+		this.buffer = new byte[chunk];
+		
+		int read;
+		while ((read = in.read(this.buffer, this.length, chunk)) > 0)
+		{
+			this.length += read;
+			if (read == chunk)
+			{
+				ensureCapacity(length + chunk);
+			}
+			else
+			{
+				break;
+			}
+		}
+	}	
+	
+	
+	/**
+	 * @param buffer a byte array that will be wrapped with <code>ByteBuffer</code>.
+	 * @param offset the offset of the provided buffer.
+	 * @param length the length of valid bytes in the array
+	 */
+	public ByteBuffer(byte[] buffer, int offset, int length)
+	{
+		if (length > buffer.length - offset)
+		{
+			throw new ArrayIndexOutOfBoundsException("Valid length exceeds the buffer length.");
+		}
+		this.buffer = new byte[length];
+		System.arraycopy(buffer, offset, this.buffer, 0, length);
+		this.length = length;
+	}
+	
+	
+	/**
+	 * @return Returns a byte stream that is limited to the valid amount of bytes.
+	 */
+	public InputStream getByteStream()
+	{
+		return new ByteArrayInputStream(buffer, 0, length);
+	}
+	
+	
+	/**
+	 * @return Returns the length, that means the number of valid bytes, of the buffer;
+	 * the inner byte array might be bigger than that.
+	 */
+	public int length()
+	{
+		return length;
+	}
+
+
+//	/**
+//	 * <em>Note:</em> Only the byte up to length are valid!
+//	 * @return Returns the inner byte buffer.
+//	 */
+//	public byte[] getBuffer()
+//	{
+//		return buffer;
+//	}
+
+	
+	/**
+	 * @param index the index to retrieve the byte from
+	 * @return Returns a byte from the buffer
+	 */
+	public byte byteAt(int index)
+	{
+		if (index < length)
+		{	
+			return buffer[index];
+		}
+		else
+		{
+			throw new IndexOutOfBoundsException("The index exceeds the valid buffer area");
+		}
+	}
+
+	
+	/**
+	 * @param index the index to retrieve a byte as int or char.
+	 * @return Returns a byte from the buffer
+	 */
+	public int charAt(int index)
+	{
+		if (index < length)
+		{	
+			return buffer[index] & 0xFF;
+		}
+		else
+		{
+			throw new IndexOutOfBoundsException("The index exceeds the valid buffer area");
+		}
+	}
+	
+	
+	/**
+	 * Appends a byte to the buffer.
+	 * @param b a byte
+	 */
+	public void append(byte b)
+	{
+		ensureCapacity(length + 1);
+		buffer[length++] = b;
+	}
+	
+	
+	/**
+	 * Appends a byte array or part of to the buffer.
+	 * 
+	 * @param bytes a byte array
+	 * @param offset an offset with
+	 * @param len
+	 */
+	public void append(byte[] bytes, int offset, int len)
+	{
+		ensureCapacity(length + len);
+		System.arraycopy(bytes, offset, buffer, length, len);
+		length += len;
+	}
+	
+	
+	/**
+	 * Append a byte array to the buffer 
+	 * @param bytes a byte array
+	 */
+	public void append(byte[] bytes)
+	{
+		append(bytes, 0, bytes.length);
+	}
+	
+	
+	/**
+	 * Append another buffer to this buffer. 
+	 * @param anotherBuffer another <code>ByteBuffer</code>
+	 */
+	public void append(ByteBuffer anotherBuffer)
+	{
+		append(anotherBuffer.buffer, 0, anotherBuffer.length); 
+	}
+	
+	
+	/**
+	 * Detects the encoding of the byte buffer, stores and returns it. 
+	 * Only UTF-8, UTF-16LE/BE and UTF-32LE/BE are recognized.
+	 * <em>Note:</em> UTF-32 flavors are not supported by Java, the XML-parser will complain.
+	 *
+	 * @return Returns the encoding string.
+	 */
+	public String getEncoding()
+	{
+		if (encoding == null)
+		{	
+			// needs four byte at maximum to determine encoding
+			if (length < 2)
+			{
+				// only one byte length must be UTF-8
+				encoding = "UTF-8";
+			}
+			else if (buffer[0] == 0)
+			{
+				// These cases are:
+				//   00 nn -- -- - Big endian UTF-16
+				//   00 00 00 nn - Big endian UTF-32
+				//   00 00 FE FF - Big endian UTF 32
+		
+				if (length < 4  ||  buffer[1] != 0)
+				{
+					encoding =  "UTF-16BE";
+				}
+				else if ((buffer[2] & 0xFF) == 0xFE  &&  (buffer[3] & 0xFF) == 0xFF)
+				{
+					encoding = "UTF-32BE";
+				}
+				else
+				{
+					encoding = "UTF-32";
+				}
+			} 
+			else if ((buffer[0] & 0xFF) < 0x80)
+			{
+				// These cases are:
+				//   nn mm -- -- - UTF-8, includes EF BB BF case
+				//   nn 00 -- -- - Little endian UTF-16
+		
+				if (buffer[1] != 0)
+				{
+					encoding = "UTF-8";
+				}
+				else if (length < 4  ||  buffer[2] != 0) 
+				{
+					encoding = "UTF-16LE";
+				}
+				else
+				{
+					encoding = "UTF-32LE";
+				}
+			}
+			else
+			{
+				// These cases are:
+				//   EF BB BF -- - UTF-8
+				//   FE FF -- -- - Big endian UTF-16
+				//   FF FE 00 00 - Little endian UTF-32
+				//   FF FE -- -- - Little endian UTF-16
+		
+				if ((buffer[0] & 0xFF) == 0xEF)
+				{
+					encoding = "UTF-8";
+				}
+				else if ((buffer[0] & 0xFF) == 0xFE)
+				{
+					encoding = "UTF-16"; // in fact BE 
+				}
+				else if (length < 4  ||  buffer[2] != 0)
+				{
+					encoding = "UTF-16"; // in fact LE
+				}
+				else
+				{
+					encoding = "UTF-32"; // in fact LE
+				}
+			}
+		}
+		
+		return encoding;
+	}
+	
+	
+	/**
+	 * Ensures the requested capacity by increasing the buffer size when the
+	 * current length is exceeded.
+	 * 
+	 * @param requestedLength requested new buffer length
+	 */
+	private void ensureCapacity(int requestedLength)
+	{
+		if (requestedLength > buffer.length)
+		{
+			byte[] oldBuf = buffer;
+			buffer = new byte[oldBuf.length * 2];
+			System.arraycopy(oldBuf, 0, buffer, 0, oldBuf.length);
+		}
+	}
+}
\ No newline at end of file
diff --git a/XMPCore/src/com/adobe/xmp/impl/CountOutputStream.java b/XMPCore/src/com/adobe/xmp/impl/CountOutputStream.java
new file mode 100644
index 0000000..ded1296
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/impl/CountOutputStream.java
@@ -0,0 +1,79 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp.impl;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+
+/**
+ * An <code>OutputStream</code> that counts the written bytes.
+ * 
+ * @since   08.11.2006
+ */
+public final class CountOutputStream extends OutputStream
+{
+	/** the decorated output stream */
+	private final OutputStream out;
+	/** the byte counter */
+	private int bytesWritten = 0;
+
+
+	/**
+	 * Constructor with providing the output stream to decorate.
+	 * @param out an <code>OutputStream</code>
+	 */
+	CountOutputStream(OutputStream out)
+	{
+		this.out = out;
+	}
+
+
+	/**
+	 * Counts the written bytes.
+	 * @see java.io.OutputStream#write(byte[], int, int)
+	 */
+	public void write(byte[] buf, int off, int len) throws IOException
+	{
+		out.write(buf, off, len);
+		bytesWritten += len;
+	}
+
+
+	/**
+	 * Counts the written bytes.
+	 * @see java.io.OutputStream#write(byte[])
+	 */
+	public void write(byte[] buf) throws IOException
+	{
+		out.write(buf);
+		bytesWritten += buf.length;
+	}
+
+
+	/**
+	 * Counts the written bytes.
+	 * @see java.io.OutputStream#write(int)
+	 */
+	public void write(int b) throws IOException
+	{
+		out.write(b);
+		bytesWritten++;
+	}
+
+
+	/**
+	 * @return the bytesWritten
+	 */
+	public int getBytesWritten()
+	{
+		return bytesWritten;
+	}
+}
\ No newline at end of file
diff --git a/XMPCore/src/com/adobe/xmp/impl/FixASCIIControlsReader.java b/XMPCore/src/com/adobe/xmp/impl/FixASCIIControlsReader.java
new file mode 100644
index 0000000..c6ac7d4
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/impl/FixASCIIControlsReader.java
@@ -0,0 +1,214 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp.impl;
+
+import java.io.IOException;
+import java.io.PushbackReader;
+import java.io.Reader;
+
+
+/**
+ * @since   22.08.2006
+ */
+public class FixASCIIControlsReader extends PushbackReader
+{
+	/** */
+	private static final int STATE_START = 0;
+	/** */
+	private static final int STATE_AMP = 1;
+	/** */
+	private static final int STATE_HASH = 2;
+	/** */
+	private static final int STATE_HEX = 3;
+	/** */
+	private static final int STATE_DIG1 = 4;
+	/** */
+	private static final int STATE_ERROR = 5;
+	/** */
+	private static final int BUFFER_SIZE = 8;
+	/** the state of the automaton */
+	private int state = STATE_START;
+	/** the result of the escaping sequence */
+	private int control = 0;
+	/** count the digits of the sequence */
+	private int digits = 0; 
+	
+	/**
+	 * The look-ahead size is 6 at maximum (&amp;#xAB;)
+	 * @see PushbackReader#PushbackReader(Reader, int)
+	 * @param in a Reader
+	 */
+	public FixASCIIControlsReader(Reader in)
+	{
+		super(in, BUFFER_SIZE);
+	}
+
+	
+	/**
+	 * @see Reader#read(char[], int, int)
+	 */
+	public int read(char[] cbuf, int off, int len) throws IOException
+	{
+		int readAhead = 0;
+		int read = 0;
+		int pos = off;
+		char[] readAheadBuffer = new char[BUFFER_SIZE];
+		
+		boolean available = true;
+		while (available  &&  read < len)
+		{
+			available = super.read(readAheadBuffer, readAhead, 1) == 1;
+			if (available)
+			{
+				char c = processChar(readAheadBuffer[readAhead]);
+				if (state == STATE_START)
+				{
+					// replace control chars with space
+					if (Utils.isControlChar(c))
+					{	
+						c = ' ';
+					}	
+					cbuf[pos++] = c;
+					readAhead = 0;
+					read++;
+				}
+				else if (state == STATE_ERROR)
+				{
+					unread(readAheadBuffer, 0, readAhead + 1);
+					readAhead = 0;
+				}
+				else
+				{
+					readAhead++;
+				}
+			}
+			else if (readAhead > 0)
+			{
+				// handles case when file ends within excaped sequence
+				unread(readAheadBuffer, 0, readAhead);
+				state = STATE_ERROR;
+				readAhead = 0;
+				available = true;
+			}
+		}
+		
+		
+		return read > 0  ||  available ? read : -1; 
+	}
+	
+	
+	/**
+	 * Processes numeric escaped chars to find out if they are a control character.
+	 * @param ch a char
+	 * @return Returns the char directly or as replacement for the escaped sequence.
+	 */
+	private char processChar(char ch)
+	{
+		switch (state)
+		{
+			case STATE_START:
+				if (ch == '&')
+				{
+					state = STATE_AMP;
+				}
+				return ch;
+				
+			case STATE_AMP:
+				if (ch == '#')
+				{
+					state = STATE_HASH;
+				}
+				else
+				{
+					state = STATE_ERROR;
+				}	
+				return ch;
+				
+			case STATE_HASH:
+				if (ch == 'x')
+				{
+					control = 0;
+					digits = 0;
+					state = STATE_HEX;
+				}
+				else if ('0' <= ch  &&  ch <= '9')
+				{	
+					control = Character.digit(ch, 10);
+					digits = 1;
+					state = STATE_DIG1;
+				}
+				else
+				{
+					state = STATE_ERROR;
+				}
+				return ch;
+				
+			case STATE_DIG1:
+				if ('0' <= ch  &&  ch <= '9')
+				{	
+					control = control * 10 + Character.digit(ch, 10);
+					digits++;
+					if (digits <= 5)
+					{	
+						state = STATE_DIG1;
+					}
+					else
+					{
+						state = STATE_ERROR; // sequence too long
+					}
+				}
+				else if (ch == ';'  &&  Utils.isControlChar((char) control))
+				{
+					state = STATE_START;
+					return (char) control;
+				}
+				else
+				{
+					state = STATE_ERROR;
+				}	
+				return ch;
+				
+			case STATE_HEX:
+				if (('0' <= ch  &&  ch <= '9')  ||
+					('a' <= ch  &&  ch <= 'f')  ||
+					('A' <= ch  &&  ch <= 'F'))
+				{	
+					control = control * 16 + Character.digit(ch, 16);
+					digits++;
+					if (digits <= 4)
+					{	
+						state = STATE_HEX;
+					}
+					else
+					{
+						state = STATE_ERROR; // sequence too long
+					}
+				}
+				else if (ch == ';'  &&   Utils.isControlChar((char) control))
+				{
+					state = STATE_START;
+					return (char) control;
+				}
+				else
+				{
+					state = STATE_ERROR;
+				}	
+				return ch;
+
+			case STATE_ERROR:
+				state = STATE_START;
+				return ch;
+				
+			default:
+				// not reachable
+				return ch;
+		}
+	}
+}
diff --git a/XMPCore/src/com/adobe/xmp/impl/ISO8601Converter.java b/XMPCore/src/com/adobe/xmp/impl/ISO8601Converter.java
new file mode 100644
index 0000000..b38bb16
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/impl/ISO8601Converter.java
@@ -0,0 +1,505 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp.impl;
+
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.util.Locale;
+import java.util.SimpleTimeZone;
+
+import com.adobe.xmp.XMPDateTime;
+import com.adobe.xmp.XMPError;
+import com.adobe.xmp.XMPException;
+
+
+/**
+ * Converts between ISO 8601 Strings and <code>Calendar</code> with millisecond resolution.
+ *
+ * @since   16.02.2006
+ */
+public final class ISO8601Converter
+{
+	/** Hides public constructor */
+	private ISO8601Converter()
+	{
+		// EMPTY
+	}
+
+	
+	/**
+	 * Converts an ISO 8601 string to an <code>XMPDateTime</code>.
+	 * 
+	 * Parse a date according to ISO 8601 and
+	 * http://www.w3.org/TR/NOTE-datetime:
+	 * <ul>
+	 * <li>YYYY
+	 * <li>YYYY-MM
+	 * <li>YYYY-MM-DD
+	 * <li>YYYY-MM-DDThh:mmTZD
+	 * <li>YYYY-MM-DDThh:mm:ssTZD
+	 * <li>YYYY-MM-DDThh:mm:ss.sTZD
+	 * </ul>
+	 * 
+	 * Data fields:
+	 * <ul>
+	 * <li>YYYY = four-digit year
+	 * <li>MM = two-digit month (01=January, etc.)
+	 * <li>DD = two-digit day of month (01 through 31)
+	 * <li>hh = two digits of hour (00 through 23)
+	 * <li>mm = two digits of minute (00 through 59)
+	 * <li>ss = two digits of second (00 through 59)
+	 * <li>s = one or more digits representing a decimal fraction of a second
+	 * <li>TZD = time zone designator (Z or +hh:mm or -hh:mm)
+	 * </ul>
+	 * 
+	 * Note that ISO 8601 does not seem to allow years less than 1000 or greater
+	 * than 9999. We allow any year, even negative ones. The year is formatted
+	 * as "%.4d".
+	 * <p>
+	 * <em>Note:</em> Tolerate missing TZD, assume is UTC. Photoshop 8 writes
+	 * dates like this for exif:GPSTimeStamp.<br>
+	 * <em>Note:</em> Tolerate missing date portion, in case someone foolishly
+	 * writes a time-only value that way.
+	 * 
+	 * @param iso8601String a date string that is ISO 8601 conform.
+	 * @return Returns a <code>Calendar</code>.
+	 * @throws XMPException Is thrown when the string is non-conform.
+	 */
+	public static XMPDateTime parse(String iso8601String) throws XMPException
+	{
+		return parse(iso8601String, new XMPDateTimeImpl());
+	}
+	
+
+	/**
+	 * @param iso8601String a date string that is ISO 8601 conform.
+	 * @param binValue an existing XMPDateTime to set with the parsed date
+	 * @return Returns an XMPDateTime-object containing the ISO8601-date.
+	 * @throws XMPException Is thrown when the string is non-conform.
+	 */
+	public static XMPDateTime parse(String iso8601String, XMPDateTime binValue) throws XMPException
+	{
+		ParameterAsserts.assertNotNull(iso8601String);
+		
+		ParseState input = new ParseState(iso8601String);
+		int value;
+		
+		boolean timeOnly =  
+			 input.ch(0) == 'T'  ||
+			(input.length() >= 2  &&  input.ch(1) == ':'  ||
+			(input.length() >= 3  &&  input.ch(2) == ':'));
+		
+		if (!timeOnly)
+		{
+			if (input.ch(0) == '-')
+			{
+				input.skip();
+			}
+
+			
+			// Extract the year.
+			value = input.gatherInt("Invalid year in date string", 9999);
+			if (input.hasNext()  &&  input.ch() != '-')
+			{
+				throw new XMPException("Invalid date string, after year", XMPError.BADVALUE);
+			}
+
+			if (input.ch(0) == '-')
+			{
+				value = -value;
+			}
+			binValue.setYear(value);
+			if (!input.hasNext())
+			{
+				return binValue;
+			}
+			input.skip();
+			
+			
+			// Extract the month.
+			value = input.gatherInt("Invalid month in date string", 12);
+			if (input.hasNext()  &&  input.ch() != '-')
+			{
+				throw new XMPException("Invalid date string, after month", XMPError.BADVALUE);
+			}
+			binValue.setMonth(value);
+			if (!input.hasNext())
+			{
+				return binValue;
+			}
+			input.skip();
+
+			
+			// Extract the day.
+			value = input.gatherInt("Invalid day in date string", 31);
+			if (input.hasNext()  &&  input.ch() != 'T')
+			{
+				throw new XMPException("Invalid date string, after day", XMPError.BADVALUE);
+			}
+			binValue.setDay(value);
+			if (!input.hasNext())
+			{
+				return binValue;
+			}
+		}
+		else
+		{
+			// set default day and month in the year 0000
+			binValue.setMonth(1);
+			binValue.setDay(1);
+		}
+			
+		if (input.ch() == 'T')
+		{
+			input.skip();
+		}
+		else if (!timeOnly)
+		{
+			throw new XMPException("Invalid date string, missing 'T' after date",
+					XMPError.BADVALUE);
+		}
+
+		
+		// Extract the hour.
+		value = input.gatherInt("Invalid hour in date string", 23);
+		if (input.ch() != ':')
+		{
+			throw new XMPException("Invalid date string, after hour", XMPError.BADVALUE);
+		}
+		binValue.setHour(value);
+		
+		// Don't check for done, we have to work up to the time zone.
+		input.skip();
+		
+		
+		// Extract the minute.
+		value = input.gatherInt("Invalid minute in date string", 59);
+		if (input.hasNext()  &&
+			input.ch() != ':' && input.ch() != 'Z' && input.ch() != '+' && input.ch() != '-')
+		{
+			throw new XMPException("Invalid date string, after minute", XMPError.BADVALUE);
+		}
+		binValue.setMinute(value);
+		
+		if (input.ch() == ':')
+		{
+			input.skip();
+			value = input.gatherInt("Invalid whole seconds in date string", 59);
+			if (input.hasNext()  &&  input.ch() != '.'  &&  input.ch() != 'Z'  && 
+				input.ch() != '+' && input.ch() != '-')
+			{
+				throw new XMPException("Invalid date string, after whole seconds",
+						XMPError.BADVALUE);
+			}
+			binValue.setSecond(value);
+			if (input.ch() == '.')
+			{
+				input.skip();
+				int digits = input.pos();
+				value = input.gatherInt("Invalid fractional seconds in date string", 999999999);
+				if (input.ch() != 'Z'  &&  input.ch() != '+'  &&  input.ch() != '-')
+				{
+					throw new XMPException("Invalid date string, after fractional second",
+							XMPError.BADVALUE);
+				}
+				digits = input.pos() - digits;
+				for (; digits > 9; --digits)
+				{	
+					value = value / 10;
+				}	
+				for (; digits < 9; ++digits)
+				{	
+					value = value * 10;
+				}	
+				binValue.setNanoSecond(value);
+			}
+		}
+		
+		int tzSign = 0;
+		int tzHour = 0;
+		int tzMinute = 0;
+		if (input.ch() == 'Z')
+		{
+			input.skip();
+		}
+		else if (input.hasNext())
+		{
+			if (input.ch() == '+')
+			{
+				tzSign = 1;
+			}
+			else if (input.ch() == '-')
+			{
+				tzSign = -1;
+			}
+			else
+			{
+				throw new XMPException("Time zone must begin with 'Z', '+', or '-'",
+						XMPError.BADVALUE);
+			}
+
+			input.skip();
+			// Extract the time zone hour.
+			tzHour = input.gatherInt("Invalid time zone hour in date string", 23);
+			if (input.ch() != ':')
+			{
+				throw new XMPException("Invalid date string, after time zone hour",
+						XMPError.BADVALUE);
+			}
+			input.skip();
+
+			// Extract the time zone minute.
+			tzMinute = input.gatherInt("Invalid time zone minute in date string", 59);
+		}
+		
+		// create a corresponding TZ and set it time zone
+		int offset = (tzHour * 3600 * 1000 + tzMinute * 60 * 1000) * tzSign;   
+		binValue.setTimeZone(new SimpleTimeZone(offset, ""));
+		
+
+		if (input.hasNext())
+		{
+			throw new XMPException(
+				"Invalid date string, extra chars at end", XMPError.BADVALUE);
+		}
+		
+		return binValue;
+	}
+
+	
+	/**
+	 * Converts a <code>Calendar</code> into an ISO 8601 string.
+	 * Format a date according to ISO 8601 and http://www.w3.org/TR/NOTE-datetime:
+	 * <ul>
+	 * <li>YYYY
+	 * <li>YYYY-MM
+	 * <li>YYYY-MM-DD
+	 * <li>YYYY-MM-DDThh:mmTZD
+	 * <li>YYYY-MM-DDThh:mm:ssTZD
+	 * <li>YYYY-MM-DDThh:mm:ss.sTZD
+	 * </ul>
+	 * 
+	 * Data fields:
+	 * <ul>
+	 * <li>YYYY = four-digit year
+	 * <li>MM	 = two-digit month (01=January, etc.)
+	 * <li>DD	 = two-digit day of month (01 through 31)
+	 * <li>hh	 = two digits of hour (00 through 23)
+	 * <li>mm	 = two digits of minute (00 through 59)
+	 * <li>ss	 = two digits of second (00 through 59)
+	 * <li>s	 = one or more digits representing a decimal fraction of a second
+	 * <li>TZD	 = time zone designator (Z or +hh:mm or -hh:mm)
+	 * </ul>
+	 * <p>
+	 * <em>Note:</em> ISO 8601 does not seem to allow years less than 1000 or greater than 9999. 
+	 * We allow any year, even negative ones. The year is formatted as "%.4d".<p>
+	 * <em>Note:</em> Fix for bug 1269463 (silently fix out of range values) included in parsing.
+	 * The quasi-bogus "time only" values from Photoshop CS are not supported.
+	 * 
+	 * @param dateTime an XMPDateTime-object.
+	 * @return Returns an ISO 8601 string.
+	 */
+	public static String render(XMPDateTime dateTime)
+	{
+		StringBuffer buffer = new StringBuffer();
+
+		// year is rendered in any case, even 0000
+		DecimalFormat df = new DecimalFormat("0000", new DecimalFormatSymbols(Locale.ENGLISH));
+		buffer.append(df.format(dateTime.getYear()));
+		if (dateTime.getMonth() == 0)
+		{
+			return buffer.toString();
+		}
+
+		// month
+		df.applyPattern("'-'00");
+		buffer.append(df.format(dateTime.getMonth()));
+		if (dateTime.getDay() == 0)
+		{
+			return buffer.toString();
+		}
+
+		// day
+		buffer.append(df.format(dateTime.getDay()));
+		
+		// time, rendered if any time field is not zero
+		if (dateTime.getHour() != 0  ||
+			dateTime.getMinute() != 0  ||
+			dateTime.getSecond() != 0  ||
+			dateTime.getNanoSecond() != 0  ||
+			(dateTime.getTimeZone() != null  &&  dateTime.getTimeZone().getRawOffset() != 0))
+		{
+			// hours and minutes
+			buffer.append('T');
+			df.applyPattern("00");
+			buffer.append(df.format(dateTime.getHour()));
+			buffer.append(':');
+			buffer.append(df.format(dateTime.getMinute()));
+			
+			// seconds and nanoseconds
+			if (dateTime.getSecond() != 0 || dateTime.getNanoSecond() != 0)
+			{
+				double seconds = dateTime.getSecond() + dateTime.getNanoSecond() / 1e9d;
+
+				df.applyPattern(":00.#########");
+				buffer.append(df.format(seconds));
+			}
+			
+			// time zone
+			if (dateTime.getTimeZone() != null)
+			{
+				// used to calculate the time zone offset incl. Daylight Savings
+				long timeInMillis = dateTime.getCalendar().getTimeInMillis();
+				int offset = dateTime.getTimeZone().getOffset(timeInMillis);				
+				if (offset == 0)
+				{
+					// UTC
+					buffer.append('Z');
+				}
+				else
+				{
+					int thours = offset / 3600000;
+					int tminutes = Math.abs(offset % 3600000 / 60000);
+					df.applyPattern("+00;-00");
+					buffer.append(df.format(thours));
+					df.applyPattern(":00");
+					buffer.append(df.format(tminutes));
+				}	
+			}
+		}	
+		return buffer.toString();
+	}
+	
+	
+}
+
+
+/**
+ * @since   22.08.2006
+ */
+class ParseState
+{
+	/** */
+	private String str;
+	/** */
+	private int pos = 0;
+
+	
+	/**
+	 * @param str initializes the parser container
+	 */
+	public ParseState(String str)
+	{
+		this.str = str;
+	}
+	
+	
+	/**
+	 * @return Returns the length of the input.
+	 */
+	public int length()
+	{
+		return str.length();
+	}
+
+
+	/**
+	 * @return Returns whether there are more chars to come.
+	 */
+	public boolean hasNext()
+	{
+		return pos < str.length();
+	}
+
+	
+	/**
+	 * @param index index of char
+	 * @return Returns char at a certain index.
+	 */
+	public char ch(int index)
+	{
+		return index < str.length() ? 
+			str.charAt(index) :
+			0x0000;
+	}
+
+	
+	/**
+	 * @return Returns the current char or 0x0000 if there are no more chars.
+	 */
+	public char ch()
+	{
+		return pos < str.length() ? 
+			str.charAt(pos) :
+			0x0000;
+	}
+	
+	
+	/**
+	 * Skips the next char.
+	 */
+	public void skip()
+	{
+		pos++;
+	}
+
+	
+	/**
+	 * @return Returns the current position.
+	 */
+	public int pos()
+	{
+		return pos;
+	}
+	
+	
+	/**
+	 * Parses a integer from the source and sets the pointer after it.
+	 * @param errorMsg Error message to put in the exception if no number can be found
+	 * @param maxValue the max value of the number to return 
+	 * @return Returns the parsed integer.
+	 * @throws XMPException Thrown if no integer can be found.
+	 */
+	public int gatherInt(String errorMsg, int maxValue) throws XMPException
+	{
+		int value = 0;
+		boolean success = false;
+		char ch = ch(pos);
+		while ('0' <= ch  &&  ch <= '9')
+		{
+			value = (value * 10) + (ch - '0');
+			success = true;
+			pos++;
+			ch = ch(pos);
+		}
+		
+		if (success)
+		{
+			if (value > maxValue)
+			{
+				return maxValue;
+			}
+			else if (value < 0)
+			{
+				return 0;
+			}
+			else
+			{	
+				return value;
+			}	
+		}
+		else
+		{
+			throw new XMPException(errorMsg, XMPError.BADVALUE);
+		}
+	}
+}	
+
+
diff --git a/XMPCore/src/com/adobe/xmp/impl/Latin1Converter.java b/XMPCore/src/com/adobe/xmp/impl/Latin1Converter.java
new file mode 100644
index 0000000..118d77d
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/impl/Latin1Converter.java
@@ -0,0 +1,197 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+
+
+package com.adobe.xmp.impl;
+
+import java.io.UnsupportedEncodingException;
+
+
+/**
+ * @since   12.10.2006
+ */
+public class Latin1Converter
+{
+	/** */
+	private static final int STATE_START = 0;
+	/** */
+	private static final int STATE_UTF8CHAR = 11;
+
+	
+	/**
+	 * Private constructor
+	 */
+	private Latin1Converter()
+	{
+		// EMPTY
+	}
+	
+	
+	/**
+	 * A converter that processes a byte buffer containing a mix of UTF8 and Latin-1/Cp1252 chars.
+	 * The result is a buffer where those chars have been converted to UTF-8; 
+	 * that means it contains only valid UTF-8 chars.
+	 * <p>
+	 * <em>Explanation of the processing:</em> First the encoding of the buffer is detected looking 
+	 * at the first four bytes (that works only if the buffer starts with an ASCII-char, 
+	 * like xmls &apos;&lt;&apos;). UTF-16/32 flavours do not require further proccessing.
+	 * <p> 
+	 * In the case, UTF-8 is detected, it assumes wrong UTF8 chars to be a sequence of 
+	 * Latin-1/Cp1252 encoded bytes and converts the chars to their corresponding UTF-8 byte 
+	 * sequence.
+	 * <p> 
+	 * The 0x80..0x9F range is undefined in Latin-1, but is defined in Windows code
+	 * page 1252. The bytes 0x81, 0x8D, 0x8F, 0x90, and 0x9D are formally undefined
+	 * by Windows 1252. These are in XML's RestrictedChar set, so we map them to a
+	 * space. 
+	 * <p>
+	 * The official Latin-1 characters in the range 0xA0..0xFF are converted into
+	 * the Unicode Latin Supplement range U+00A0 - U+00FF.
+	 * <p>
+	 * <em>Example:</em> If an Euro-symbol (€) appears in the byte buffer (0xE2, 0x82, 0xAC), 
+	 * it will be left as is. But if only the first two bytes are appearing, 
+	 * followed by an ASCII char a (0xE2 - 0x82 - 0x41), it will be converted to 
+	 * 0xC3, 0xA2 (â) - 0xE2, 0x80, 0x9A (‚) - 0x41 (a).
+	 *  
+	 * @param buffer a byte buffer contain
+	 * @return Returns a new buffer containing valid UTF-8
+	 */
+	public static ByteBuffer convert(ByteBuffer buffer)
+	{
+		if ("UTF-8".equals(buffer.getEncoding()))
+		{
+			// the buffer containing one UTF-8 char (up to 8 bytes) 
+			byte[] readAheadBuffer = new byte[8];
+			// the number of bytes read ahead.
+			int readAhead  = 0;
+			// expected UTF8 bytesto come
+			int expectedBytes = 0;
+			// output buffer with estimated length
+			ByteBuffer out = new ByteBuffer(buffer.length() * 4 / 3);
+			
+			int state = STATE_START;
+			for (int i = 0; i < buffer.length(); i++)
+			{
+				int b = buffer.charAt(i);
+			
+				switch (state)
+				{
+					default:
+					case STATE_START:
+						if (b < 0x7F)
+						{
+							out.append((byte) b);
+						}
+						else if (b >= 0xC0)
+						{
+							// start of UTF8 sequence
+							expectedBytes = -1;
+							int test = b;
+							for (; expectedBytes < 8  &&  (test & 0x80) == 0x80; test = test << 1)
+							{
+								expectedBytes++;
+							}
+							readAheadBuffer[readAhead++] = (byte) b;
+							state = STATE_UTF8CHAR;
+						}
+						else //  implicitly:  b >= 0x80  &&  b < 0xC0
+						{
+							// invalid UTF8 start char, assume to be Latin-1
+							byte[] utf8 = convertToUTF8((byte) b);
+							out.append(utf8);
+						}
+						break;
+						
+					case STATE_UTF8CHAR:
+						if (expectedBytes > 0  &&  (b & 0xC0) == 0x80)
+						{
+							// valid UTF8 char, add to readAheadBuffer
+							readAheadBuffer[readAhead++] = (byte) b;
+							expectedBytes--;
+							
+							if (expectedBytes == 0)
+							{
+								out.append(readAheadBuffer, 0, readAhead);
+								readAhead = 0;
+								
+								state = STATE_START;
+							}
+						}
+						else
+						{
+							// invalid UTF8 char: 
+							// 1. convert first of seq to UTF8 
+							byte[] utf8 = convertToUTF8(readAheadBuffer[0]);
+							out.append(utf8);
+
+							// 2. continue processing at second byte of sequence
+							i = i - readAhead;
+							readAhead = 0;
+							
+							state = STATE_START;
+						}
+						break;
+				}		
+			}
+			
+			// loop ends with "half" Utf8 char --> assume that the bytes are Latin-1
+			if (state == STATE_UTF8CHAR)
+			{
+				for (int j = 0; j < readAhead; j++)
+				{
+					byte b = readAheadBuffer[j];
+					byte[] utf8 = convertToUTF8(b);
+					out.append(utf8);
+				}
+			}
+			
+			return out;
+		}
+		else
+		{
+			// Latin-1 fixing applies only to UTF-8
+			return buffer;
+		}	
+	}
+		
+		
+	/**
+	 * Converts a Cp1252 char (contains all Latin-1 chars above 0x80) into a
+	 * UTF-8 byte sequence. The bytes 0x81, 0x8D, 0x8F, 0x90, and 0x9D are
+	 * formally undefined by Windows 1252 and therefore replaced by a space
+	 * (0x20).
+	 * 
+	 * @param ch
+	 *            an Cp1252 / Latin-1 byte
+	 * @return Returns a byte array containing a UTF-8 byte sequence.
+	 */
+	private static byte[] convertToUTF8(byte ch)
+	{
+		int c = ch & 0xFF; 
+		try
+		{
+			if (c >= 0x80)
+			{
+				if (c == 0x81  ||  c == 0x8D  ||  c == 0x8F  ||  c == 0x90  ||  c == 0x9D)
+				{
+					return new byte[] { 0x20 }; // space for undefined 
+				}
+				
+				// interpret byte as Windows Cp1252 char
+				return new String(new byte[] { ch }, "cp1252").getBytes("UTF-8");
+			}
+		}	
+		catch (UnsupportedEncodingException e)
+		{
+			// EMPTY
+		}
+		return new byte[] { ch };
+	}
+}
diff --git a/XMPCore/src/com/adobe/xmp/impl/ParameterAsserts.java b/XMPCore/src/com/adobe/xmp/impl/ParameterAsserts.java
new file mode 100644
index 0000000..78ae5d6
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/impl/ParameterAsserts.java
@@ -0,0 +1,153 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp.impl;
+
+import com.adobe.xmp.XMPConst;
+import com.adobe.xmp.XMPError;
+import com.adobe.xmp.XMPException;
+import com.adobe.xmp.XMPMeta;
+
+
+/**
+ * @since   11.08.2006
+ */
+class ParameterAsserts implements XMPConst
+{
+	/**
+	 * private constructor
+	 */
+	private ParameterAsserts()
+	{
+		// EMPTY
+	}
+
+	
+	/**
+	 * Asserts that an array name is set.
+	 * @param arrayName an array name
+	 * @throws XMPException Array name is null or empty
+	 */
+	public static void assertArrayName(String arrayName) throws XMPException
+	{
+		if (arrayName == null  ||  arrayName.length() == 0)
+		{
+			throw new XMPException("Empty array name", XMPError.BADPARAM);
+		}
+	}
+
+	
+	/**
+	 * Asserts that a property name is set.
+	 * @param propName a property name or path
+	 * @throws XMPException Property name is null or empty
+	 */
+	public static void assertPropName(String propName) throws XMPException
+	{
+		if (propName == null  ||  propName.length() == 0)
+		{
+			throw new XMPException("Empty property name", XMPError.BADPARAM);
+		}
+	}
+
+	
+	/**
+	 * Asserts that a schema namespace is set.
+	 * @param schemaNS a schema namespace
+	 * @throws XMPException Schema is null or empty
+	 */
+	public static void assertSchemaNS(String schemaNS) throws XMPException
+	{
+		if (schemaNS == null  ||  schemaNS.length() == 0)
+		{
+			throw new XMPException("Empty schema namespace URI", XMPError.BADPARAM);
+		}
+	}
+
+	
+	/**
+	 * Asserts that a prefix is set.
+	 * @param prefix a prefix
+	 * @throws XMPException Prefix is null or empty
+	 */
+	public static void assertPrefix(String prefix) throws XMPException
+	{
+		if (prefix == null  ||  prefix.length() == 0)
+		{
+			throw new XMPException("Empty prefix", XMPError.BADPARAM);
+		}
+	}
+	
+	
+	/**
+	 * Asserts that a specific language is set.
+	 * @param specificLang a specific lang
+	 * @throws XMPException Specific language is null or empty
+	 */
+	public static void assertSpecificLang(String specificLang) throws XMPException
+	{
+		if (specificLang == null  ||  specificLang.length() == 0)
+		{
+			throw new XMPException("Empty specific language", XMPError.BADPARAM);
+		}
+	}
+
+	
+	/**
+	 * Asserts that a struct name is set.
+	 * @param structName a struct name
+	 * @throws XMPException Struct name is null or empty
+	 */
+	public static void assertStructName(String structName) throws XMPException
+	{
+		if (structName == null  ||  structName.length() == 0)
+		{
+			throw new XMPException("Empty array name", XMPError.BADPARAM);
+		}
+	}
+
+
+	/**
+	 * Asserts that any string parameter is set.
+	 * @param param any string parameter
+	 * @throws XMPException Thrown if the parameter is null or has length 0.
+	 */
+	public static void assertNotNull(Object param) throws XMPException
+	{
+		if (param == null)
+		{
+			throw new XMPException("Parameter must not be null", XMPError.BADPARAM);
+		}
+		else if ((param instanceof String)  &&  ((String) param).length() == 0)
+		{
+			throw new XMPException("Parameter must not be null or empty", XMPError.BADPARAM);
+		}
+	}
+
+
+	/**
+	 * Asserts that the xmp object is of this implemention
+	 * ({@link XMPMetaImpl}). 
+	 * @param xmp the XMP object
+	 * @throws XMPException A wrong implentaion is used.
+	 */
+	public static void assertImplementation(XMPMeta xmp) throws XMPException
+	{
+		if (xmp == null)
+		{
+			throw new XMPException("Parameter must not be null",
+					XMPError.BADPARAM);
+		}
+		else if (!(xmp instanceof XMPMetaImpl))
+		{
+			throw new XMPException("The XMPMeta-object is not compatible with this implementation",
+					XMPError.BADPARAM);
+		}
+	}
+}
\ No newline at end of file
diff --git a/XMPCore/src/com/adobe/xmp/impl/ParseRDF.java b/XMPCore/src/com/adobe/xmp/impl/ParseRDF.java
new file mode 100644
index 0000000..2b96550
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/impl/ParseRDF.java
@@ -0,0 +1,1323 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp.impl;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+
+import com.adobe.xmp.XMPConst;
+import com.adobe.xmp.XMPError;
+import com.adobe.xmp.XMPException;
+import com.adobe.xmp.XMPMetaFactory;
+import com.adobe.xmp.XMPSchemaRegistry;
+import com.adobe.xmp.options.PropertyOptions;
+
+
+/**
+ * Parser for "normal" XML serialisation of RDF.  
+ * 
+ * @since   14.07.2006
+ */
+public class ParseRDF implements XMPError, XMPConst
+{
+	/** */
+	public static final int RDFTERM_OTHER = 0;
+	/** Start of coreSyntaxTerms. */
+	public static final int RDFTERM_RDF = 1;
+	/** */
+	public static final int RDFTERM_ID = 2;
+	/** */
+	public static final int RDFTERM_ABOUT = 3;
+	/** */
+	public static final int RDFTERM_PARSE_TYPE = 4;
+	/** */
+	public static final int RDFTERM_RESOURCE = 5;
+	/** */
+	public static final int RDFTERM_NODE_ID = 6;
+	/** End of coreSyntaxTerms */
+	public static final int RDFTERM_DATATYPE = 7;
+	/** Start of additions for syntax Terms. */
+	public static final int RDFTERM_DESCRIPTION = 8; 
+	/** End of of additions for syntaxTerms. */
+	public static final int RDFTERM_LI = 9;
+	/** Start of oldTerms. */
+	public static final int RDFTERM_ABOUT_EACH = 10; 
+	/** */
+	public static final int RDFTERM_ABOUT_EACH_PREFIX = 11;
+	/** End of oldTerms. */
+	public static final int RDFTERM_BAG_ID = 12;
+	/** */
+	public static final int RDFTERM_FIRST_CORE = RDFTERM_RDF;
+	/** */
+	public static final int RDFTERM_LAST_CORE = RDFTERM_DATATYPE;
+	/** ! Yes, the syntax terms include the core terms. */
+	public static final int RDFTERM_FIRST_SYNTAX = RDFTERM_FIRST_CORE;
+	/** */
+	public static final int RDFTERM_LAST_SYNTAX = RDFTERM_LI;
+	/** */
+	public static final int RDFTERM_FIRST_OLD = RDFTERM_ABOUT_EACH;
+	/** */
+	public static final int RDFTERM_LAST_OLD = RDFTERM_BAG_ID;
+
+	/** this prefix is used for default namespaces */
+	public static final String DEFAULT_PREFIX = "_dflt";
+	
+	
+	
+	/**
+	 * The main parsing method. The XML tree is walked through from the root node and and XMP tree
+	 * is created. This is a raw parse, the normalisation of the XMP tree happens outside.
+	 * 
+	 * @param xmlRoot the XML root node
+	 * @return Returns an XMP metadata object (not normalized)
+	 * @throws XMPException Occurs if the parsing fails for any reason.
+	 */
+	static XMPMetaImpl parse(Node xmlRoot) throws XMPException
+	{
+		XMPMetaImpl xmp = new XMPMetaImpl();
+		rdf_RDF(xmp, xmlRoot);
+		return xmp;
+	}
+	
+	
+	/**
+	 * Each of these parsing methods is responsible for recognizing an RDF
+	 * syntax production and adding the appropriate structure to the XMP tree.
+	 * They simply return for success, failures will throw an exception.
+	 * 
+	 * @param xmp the xmp metadata object that is generated
+	 * @param rdfRdfNode the top-level xml node
+	 * @throws XMPException thown on parsing errors
+	 */
+	static void rdf_RDF(XMPMetaImpl xmp, Node rdfRdfNode) throws XMPException
+	{
+		if (rdfRdfNode.hasAttributes())
+		{
+			rdf_NodeElementList (xmp, xmp.getRoot(), rdfRdfNode);
+		}	
+		else
+		{	
+			throw new XMPException("Invalid attributes of rdf:RDF element", BADRDF);
+		}
+	}
+	
+	
+	/**
+	 * 7.2.10 nodeElementList<br>
+	 * ws* ( nodeElement ws* )*
+	 * 
+	 * Note: this method is only called from the rdf:RDF-node (top level)
+	 * @param xmp the xmp metadata object that is generated
+	 * @param xmpParent the parent xmp node
+	 * @param rdfRdfNode the top-level xml node
+	 * @throws XMPException thown on parsing errors
+	 */
+	private static void rdf_NodeElementList(XMPMetaImpl xmp, XMPNode xmpParent, Node rdfRdfNode) 
+		throws XMPException
+	{
+		for (int i = 0; i < rdfRdfNode.getChildNodes().getLength(); i++)
+		{
+			Node child = rdfRdfNode.getChildNodes().item(i);
+			// filter whitespaces (and all text nodes)
+			if (!isWhitespaceNode(child))
+			{	
+				rdf_NodeElement  (xmp, xmpParent, child, true);
+			}	
+		} 
+	}
+
+
+	/**
+ 	 * 7.2.5 nodeElementURIs
+	 * 		anyURI - ( coreSyntaxTerms | rdf:li | oldTerms )
+	 *
+ 	 * 7.2.11 nodeElement
+	 * 		start-element ( URI == nodeElementURIs,
+	 * 		attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) )
+	 * 		propertyEltList
+	 * 		end-element()
+	 * 
+	 * A node element URI is rdf:Description or anything else that is not an RDF
+	 * term.
+	 * 
+	 * @param xmp the xmp metadata object that is generated
+	 * @param xmpParent the parent xmp node
+	 * @param xmlNode the currently processed XML node
+	 * @param isTopLevel Flag if the node is a top-level node
+	 * @throws XMPException thown on parsing errors
+	 */
+	private static void rdf_NodeElement(XMPMetaImpl xmp, XMPNode xmpParent, Node xmlNode,
+			boolean isTopLevel) throws XMPException
+	{
+		int nodeTerm = getRDFTermKind (xmlNode);
+		if (nodeTerm != RDFTERM_DESCRIPTION  &&  nodeTerm != RDFTERM_OTHER)
+		{
+			throw new XMPException("Node element must be rdf:Description or typed node",
+				BADRDF);
+		}
+		else if (isTopLevel  &&  nodeTerm == RDFTERM_OTHER)
+		{
+			throw new XMPException("Top level typed node not allowed", BADXMP);
+		}
+		else
+		{
+			rdf_NodeElementAttrs (xmp, xmpParent, xmlNode, isTopLevel);
+			rdf_PropertyElementList (xmp, xmpParent, xmlNode, isTopLevel);
+		}
+		
+	}
+
+
+	/**
+	 * 
+	 * 7.2.7 propertyAttributeURIs
+	 * 		anyURI - ( coreSyntaxTerms | rdf:Description | rdf:li | oldTerms )
+	 *
+	 * 7.2.11 nodeElement
+	 * start-element ( URI == nodeElementURIs,
+	 * 					attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) )
+	 * 					propertyEltList
+	 * 					end-element()
+	 * 
+	 * Process the attribute list for an RDF node element. A property attribute URI is 
+	 * anything other than an RDF term. The rdf:ID and rdf:nodeID attributes are simply ignored, 
+	 * as are rdf:about attributes on inner nodes.
+	 * 
+	 * @param xmp the xmp metadata object that is generated
+	 * @param xmpParent the parent xmp node
+	 * @param xmlNode the currently processed XML node
+	 * @param isTopLevel Flag if the node is a top-level node
+	 * @throws XMPException thown on parsing errors
+	 */
+	private static void rdf_NodeElementAttrs(XMPMetaImpl xmp, XMPNode xmpParent, Node xmlNode,
+			boolean isTopLevel) throws XMPException
+	{
+		// Used to detect attributes that are mutually exclusive.
+		int exclusiveAttrs = 0;	
+	
+		for (int i = 0; i < xmlNode.getAttributes().getLength(); i++)
+		{
+			Node attribute = xmlNode.getAttributes().item(i);
+			
+			// quick hack, ns declarations do not appear in C++
+			// ignore "ID" without namespace
+			if ("xmlns".equals(attribute.getPrefix())  ||
+				(attribute.getPrefix() == null  &&  "xmlns".equals(attribute.getNodeName())))
+			{
+				continue;
+			}
+			
+			int attrTerm = getRDFTermKind(attribute);
+
+			switch (attrTerm)
+			{
+				case RDFTERM_ID:
+				case RDFTERM_NODE_ID:
+				case RDFTERM_ABOUT:
+					if (exclusiveAttrs > 0)
+					{
+						throw new XMPException("Mutally exclusive about, ID, nodeID attributes",
+								BADRDF);
+					}
+					
+					exclusiveAttrs++;
+	
+					if (isTopLevel && (attrTerm == RDFTERM_ABOUT))
+					{
+						// This is the rdf:about attribute on a top level node. Set
+						// the XMP tree name if
+						// it doesn't have a name yet. Make sure this name matches
+						// the XMP tree name.
+						if (xmpParent.getName() != null && xmpParent.getName().length() > 0)
+						{
+							if (!xmpParent.getName().equals(attribute.getNodeValue()))
+							{
+								throw new XMPException("Mismatched top level rdf:about values",
+										BADXMP);
+							}
+						}
+						else
+						{
+							xmpParent.setName(attribute.getNodeValue());
+						}
+					}
+					break;
+	
+				case RDFTERM_OTHER:
+					addChildNode(xmp, xmpParent, attribute, attribute.getNodeValue(), isTopLevel);
+					break;
+	
+				default:
+					throw new XMPException("Invalid nodeElement attribute", BADRDF);
+			}
+
+		}		
+	}
+
+
+	/**
+	 * 7.2.13 propertyEltList
+	 * ws* ( propertyElt ws* )*
+	 * 
+	 * @param xmp the xmp metadata object that is generated
+	 * @param xmpParent the parent xmp node
+	 * @param xmlParent the currently processed XML node
+	 * @param isTopLevel Flag if the node is a top-level node
+	 * @throws XMPException thown on parsing errors
+	 */
+	private static void rdf_PropertyElementList(XMPMetaImpl xmp, XMPNode xmpParent, Node xmlParent,
+			boolean isTopLevel) throws XMPException
+	{
+		for (int i = 0; i < xmlParent.getChildNodes().getLength(); i++)
+		{
+			Node currChild = xmlParent.getChildNodes().item(i);
+			if (isWhitespaceNode(currChild))
+			{
+				continue;
+			}	
+			else if (currChild.getNodeType() != Node.ELEMENT_NODE)
+			{
+				throw new XMPException("Expected property element node not found", BADRDF);
+			}
+			else
+			{	
+				rdf_PropertyElement(xmp, xmpParent, currChild, isTopLevel);
+			}	
+		}
+	}
+
+
+	/**
+	 * 7.2.14 propertyElt
+	 * 
+	 *		resourcePropertyElt | literalPropertyElt | parseTypeLiteralPropertyElt |
+	 *		parseTypeResourcePropertyElt | parseTypeCollectionPropertyElt | 
+	 *		parseTypeOtherPropertyElt | emptyPropertyElt
+	 *
+	 * 7.2.15 resourcePropertyElt
+	 *		start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) )
+	 *		ws* nodeElement ws*
+	 *		end-element()
+	 *
+	 * 7.2.16 literalPropertyElt
+	 *		start-element (
+	 *			URI == propertyElementURIs, attributes == set ( idAttr?, datatypeAttr?) )
+	 *		text()
+	 *		end-element()
+	 *
+	 * 7.2.17 parseTypeLiteralPropertyElt
+	 *		start-element (
+	 *			URI == propertyElementURIs, attributes == set ( idAttr?, parseLiteral ) )
+	 *		literal
+	 *		end-element()
+	 *
+	 * 7.2.18 parseTypeResourcePropertyElt
+	 *		start-element (
+	 *			 URI == propertyElementURIs, attributes == set ( idAttr?, parseResource ) )
+	 *		propertyEltList
+	 *		end-element()
+	 *
+	 * 7.2.19 parseTypeCollectionPropertyElt
+	 *		start-element (
+	 *			URI == propertyElementURIs, attributes == set ( idAttr?, parseCollection ) )
+	 *		nodeElementList
+	 *		end-element()
+	 *
+	 * 7.2.20 parseTypeOtherPropertyElt
+	 *		start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseOther ) )
+	 *		propertyEltList
+	 *		end-element()
+	 *
+	 * 7.2.21 emptyPropertyElt
+	 *		start-element ( URI == propertyElementURIs,
+	 *			attributes == set ( idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) )
+	 *		end-element()
+	 *
+	 * The various property element forms are not distinguished by the XML element name, 
+	 * but by their attributes for the most part. The exceptions are resourcePropertyElt and 
+	 * literalPropertyElt. They are distinguished by their XML element content.
+	 *
+	 * NOTE: The RDF syntax does not explicitly include the xml:lang attribute although it can 
+	 * appear in many of these. We have to allow for it in the attibute counts below.	 
+	 *  
+	 * @param xmp the xmp metadata object that is generated
+	 * @param xmpParent the parent xmp node
+	 * @param xmlNode the currently processed XML node
+	 * @param isTopLevel Flag if the node is a top-level node
+	 * @throws XMPException thown on parsing errors
+	 */
+	private static void rdf_PropertyElement(XMPMetaImpl xmp, XMPNode xmpParent, Node xmlNode,
+			boolean isTopLevel) throws XMPException
+	{
+		int nodeTerm = getRDFTermKind (xmlNode);
+		if (!isPropertyElementName(nodeTerm)) 
+		{
+			throw new XMPException("Invalid property element name", BADRDF);
+		}
+		
+		// remove the namespace-definitions from the list
+		NamedNodeMap attributes = xmlNode.getAttributes();
+		List nsAttrs = null;
+		for (int i = 0; i < attributes.getLength(); i++)
+		{
+			Node attribute = attributes.item(i);
+			if ("xmlns".equals(attribute.getPrefix())  ||
+				(attribute.getPrefix() == null  &&  "xmlns".equals(attribute.getNodeName())))
+			{
+				if (nsAttrs == null)
+				{
+					nsAttrs = new ArrayList();
+				}
+				nsAttrs.add(attribute.getNodeName());
+			}
+		}
+		if (nsAttrs != null)
+		{
+			for (Iterator it = nsAttrs.iterator(); it.hasNext();)
+			{
+				String ns = (String) it.next();
+				attributes.removeNamedItem(ns);
+			}
+		}
+		
+		
+		if (attributes.getLength() > 3)
+		{
+			// Only an emptyPropertyElt can have more than 3 attributes.
+			rdf_EmptyPropertyElement(xmp, xmpParent, xmlNode, isTopLevel);
+		} 
+		else 
+		{
+			// Look through the attributes for one that isn't rdf:ID or xml:lang, 
+			// it will usually tell what we should be dealing with. 
+			// The called routines must verify their specific syntax!
+	
+			for (int i = 0; i < attributes.getLength(); i++)
+			{
+				Node attribute = attributes.item(i);
+				String attrLocal = attribute.getLocalName();
+				String attrNS = attribute.getNamespaceURI();
+				String attrValue = attribute.getNodeValue();
+				if (!(XML_LANG.equals(attribute.getNodeName())  &&
+					!("ID".equals(attrLocal)  &&  NS_RDF.equals(attrNS))))  
+				{
+					if ("datatype".equals(attrLocal)  &&  NS_RDF.equals(attrNS))
+					{
+						rdf_LiteralPropertyElement (xmp, xmpParent, xmlNode, isTopLevel);
+					}
+					else if (!("parseType".equals(attrLocal)  &&  NS_RDF.equals(attrNS)))
+					{
+						rdf_EmptyPropertyElement (xmp, xmpParent, xmlNode, isTopLevel);
+					}
+					else if ("Literal".equals(attrValue))
+					{
+						rdf_ParseTypeLiteralPropertyElement();
+					}
+					else if ("Resource".equals(attrValue))
+					{
+						rdf_ParseTypeResourcePropertyElement(xmp, xmpParent, xmlNode, isTopLevel);
+					}
+					else if ("Collection".equals(attrValue))
+					{
+						rdf_ParseTypeCollectionPropertyElement();
+					}
+					else
+					{
+						rdf_ParseTypeOtherPropertyElement();
+					}
+		
+					return;
+				}
+			}
+			
+			// Only rdf:ID and xml:lang, could be a resourcePropertyElt, a literalPropertyElt, 
+			// or an emptyPropertyElt. Look at the child XML nodes to decide which.
+
+			if (xmlNode.hasChildNodes())
+			{
+				for (int i = 0; i < xmlNode.getChildNodes().getLength(); i++)
+				{
+					Node currChild = xmlNode.getChildNodes().item(i);
+					if (currChild.getNodeType() != Node.TEXT_NODE)
+					{
+						rdf_ResourcePropertyElement (xmp, xmpParent, xmlNode, isTopLevel);
+						return;
+					}
+				}
+				
+				rdf_LiteralPropertyElement (xmp, xmpParent, xmlNode, isTopLevel);
+			}
+			else
+			{
+				rdf_EmptyPropertyElement (xmp, xmpParent, xmlNode, isTopLevel);
+			}
+		}		
+	}
+
+	
+	/**
+	 * 7.2.15 resourcePropertyElt
+	 *		start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) )
+	 *		ws* nodeElement ws*
+	 *		end-element()
+	 *
+	 * This handles structs using an rdf:Description node, 
+	 * arrays using rdf:Bag/Seq/Alt, and typedNodes. It also catches and cleans up qualified 
+	 * properties written with rdf:Description and rdf:value.
+	 * 
+	 * @param xmp the xmp metadata object that is generated
+	 * @param xmpParent the parent xmp node
+	 * @param xmlNode the currently processed XML node
+	 * @param isTopLevel Flag if the node is a top-level node
+	 * @throws XMPException thown on parsing errors
+	 */
+	private static void rdf_ResourcePropertyElement(XMPMetaImpl xmp, XMPNode xmpParent,
+			Node xmlNode, boolean isTopLevel) throws XMPException
+	{
+		if (isTopLevel  &&  "iX:changes".equals(xmlNode.getNodeName()))
+		{
+			// Strip old "punchcard" chaff which has on the prefix "iX:".
+			return;	
+		}
+		
+		XMPNode newCompound = addChildNode(xmp, xmpParent, xmlNode, "", isTopLevel);
+		
+		// walk through the attributes
+		for (int i = 0; i < xmlNode.getAttributes().getLength(); i++)
+		{
+			Node attribute = xmlNode.getAttributes().item(i);
+			if ("xmlns".equals(attribute.getPrefix())  ||
+					(attribute.getPrefix() == null  &&  "xmlns".equals(attribute.getNodeName())))
+			{
+				continue;
+			}
+			
+			String attrLocal = attribute.getLocalName();
+			String attrNS = attribute.getNamespaceURI();
+			if (XML_LANG.equals(attribute.getNodeName()))
+			{
+				addQualifierNode (newCompound, XML_LANG, attribute.getNodeValue());
+			} 
+			else if ("ID".equals(attrLocal)  &&  NS_RDF.equals(attrNS))
+			{
+				continue;	// Ignore all rdf:ID attributes.
+			}
+			else
+			{
+				throw new XMPException(
+					"Invalid attribute for resource property element", BADRDF);
+			}
+		}
+
+		// walk through the children
+		
+		Node currChild = null;
+		boolean found = false;
+		int i;
+		for (i = 0; i < xmlNode.getChildNodes().getLength(); i++)
+		{
+			currChild = xmlNode.getChildNodes().item(i);
+			if (!isWhitespaceNode(currChild))
+			{
+				if (currChild.getNodeType() == Node.ELEMENT_NODE  &&  !found)
+				{
+					boolean isRDF = NS_RDF.equals(currChild.getNamespaceURI());
+					String childLocal = currChild.getLocalName();
+					
+					if (isRDF  &&  "Bag".equals(childLocal))
+					{
+						newCompound.getOptions().setArray(true);
+					}
+					else if (isRDF  &&  "Seq".equals(childLocal))
+					{
+						newCompound.getOptions().setArray(true).setArrayOrdered(true);
+					}
+					else if (isRDF  &&  "Alt".equals(childLocal))
+					{
+						newCompound.getOptions().setArray(true).setArrayOrdered(true)
+								.setArrayAlternate(true);
+					}
+					else
+					{
+						newCompound.getOptions().setStruct(true);
+						if (!isRDF  &&  !"Description".equals(childLocal))
+						{
+							String typeName = currChild.getNamespaceURI();
+							if (typeName == null)
+							{
+								throw new XMPException(
+										"All XML elements must be in a namespace", BADXMP);
+							}
+							typeName += ':' + childLocal;
+							addQualifierNode (newCompound, "rdf:type", typeName);
+						}
+					}
+	
+					rdf_NodeElement (xmp, newCompound, currChild, false);
+					
+					if (newCompound.getHasValueChild())
+					{
+						fixupQualifiedNode (newCompound);
+					} 
+					else if (newCompound.getOptions().isArrayAlternate())
+					{
+						XMPNodeUtils.detectAltText(newCompound);
+					}				
+					
+					found = true;
+				}
+				else if (found)
+				{
+					// found second child element
+					throw new XMPException(
+						"Invalid child of resource property element", BADRDF);
+				}
+				else
+				{
+					throw new XMPException(
+						"Children of resource property element must be XML elements", BADRDF);
+				}
+			}
+		}
+		
+		if (!found)
+		{
+			// didn't found any child elements
+			throw new XMPException("Missing child of resource property element", BADRDF);
+		}
+	}	
+
+	
+	/**
+	 * 7.2.16 literalPropertyElt
+	 *		start-element ( URI == propertyElementURIs, 
+	 *				attributes == set ( idAttr?, datatypeAttr?) )
+	 *		text()
+	 *		end-element()
+	 *
+	 * Add a leaf node with the text value and qualifiers for the attributes.
+	 * @param xmp the xmp metadata object that is generated
+	 * @param xmpParent the parent xmp node
+	 * @param xmlNode the currently processed XML node
+	 * @param isTopLevel Flag if the node is a top-level node
+	 * @throws XMPException thown on parsing errors
+	 */	
+	private static void rdf_LiteralPropertyElement(XMPMetaImpl xmp, XMPNode xmpParent,
+			Node xmlNode, boolean isTopLevel) throws XMPException
+	{
+		XMPNode newChild = addChildNode (xmp, xmpParent, xmlNode, null, isTopLevel);
+		
+		for (int i = 0; i < xmlNode.getAttributes().getLength(); i++)
+		{
+			Node attribute = xmlNode.getAttributes().item(i);
+			if ("xmlns".equals(attribute.getPrefix())  ||
+					(attribute.getPrefix() == null  &&  "xmlns".equals(attribute.getNodeName())))
+			{
+				continue;
+			}
+			
+			String attrNS = attribute.getNamespaceURI();
+			String attrLocal = attribute.getLocalName();
+			if (XML_LANG.equals(attribute.getNodeName()))
+			{
+				addQualifierNode(newChild, XML_LANG, attribute.getNodeValue());
+			} 
+			else if (NS_RDF.equals(attrNS)  &&
+					 ("ID".equals(attrLocal)  ||  "datatype".equals(attrLocal)))
+			{
+				continue;	// Ignore all rdf:ID and rdf:datatype attributes.
+			}
+			else
+			{
+				throw new XMPException(
+					"Invalid attribute for literal property element", BADRDF);
+			}
+		}
+		String textValue = "";
+		for (int i = 0; i < xmlNode.getChildNodes().getLength(); i++)
+		{
+			Node child = xmlNode.getChildNodes().item(i);
+			if (child.getNodeType() == Node.TEXT_NODE)
+			{
+				textValue += child.getNodeValue();
+			}
+			else
+			{
+				throw new XMPException("Invalid child of literal property element", BADRDF);
+			}
+		}
+		newChild.setValue(textValue);
+	}
+	
+	
+	/**
+	 * 7.2.17 parseTypeLiteralPropertyElt
+	 *		start-element ( URI == propertyElementURIs,
+	 *			attributes == set ( idAttr?, parseLiteral ) )
+	 *		literal
+	 *		end-element()
+	 * 
+	 * @throws XMPException thown on parsing errors
+	 */
+	private static void rdf_ParseTypeLiteralPropertyElement() throws XMPException
+	{
+		throw new XMPException("ParseTypeLiteral property element not allowed", BADXMP);
+	}
+
+	
+	/**
+	 * 7.2.18 parseTypeResourcePropertyElt
+	 *		start-element ( URI == propertyElementURIs, 
+	 *			attributes == set ( idAttr?, parseResource ) )
+	 *		propertyEltList
+	 *		end-element()
+	 *
+	 * Add a new struct node with a qualifier for the possible rdf:ID attribute. 
+	 * Then process the XML child nodes to get the struct fields.
+	 * 
+	 * @param xmp the xmp metadata object that is generated
+	 * @param xmpParent the parent xmp node
+	 * @param xmlNode the currently processed XML node
+	 * @param isTopLevel Flag if the node is a top-level node
+	 * @throws XMPException thown on parsing errors
+	 */
+	private static void rdf_ParseTypeResourcePropertyElement(XMPMetaImpl xmp, XMPNode xmpParent,
+			Node xmlNode, boolean isTopLevel) throws XMPException
+	{
+		XMPNode newStruct = addChildNode (xmp, xmpParent, xmlNode, "", isTopLevel);
+		
+		newStruct.getOptions().setStruct(true);
+
+		for (int i = 0; i < xmlNode.getAttributes().getLength(); i++)
+		{
+			Node attribute = xmlNode.getAttributes().item(i);
+			if ("xmlns".equals(attribute.getPrefix())  ||
+					(attribute.getPrefix() == null  &&  "xmlns".equals(attribute.getNodeName())))
+			{
+				continue;
+			}
+			
+			String attrLocal = attribute.getLocalName();
+			String attrNS = attribute.getNamespaceURI();
+			if (XML_LANG.equals(attribute.getNodeName()))
+			{
+				addQualifierNode (newStruct, XML_LANG, attribute.getNodeValue());
+			}
+			else if (NS_RDF.equals(attrNS)  &&
+					 ("ID".equals(attrLocal)  ||  "parseType".equals(attrLocal)))
+			{
+				continue;	// The caller ensured the value is "Resource".
+							// Ignore all rdf:ID attributes.
+			} 
+			else
+			{
+				throw new XMPException("Invalid attribute for ParseTypeResource property element",
+						BADRDF);
+			}
+		}
+
+		rdf_PropertyElementList (xmp, newStruct, xmlNode, false);
+
+		if (newStruct.getHasValueChild())
+		{
+			fixupQualifiedNode (newStruct);
+		}
+	}
+
+	
+	/**
+	 * 7.2.19 parseTypeCollectionPropertyElt
+	 *		start-element ( URI == propertyElementURIs, 
+	 *			attributes == set ( idAttr?, parseCollection ) )
+	 *		nodeElementList
+	 *		end-element()
+	 *
+	 * @throws XMPException thown on parsing errors
+	 */
+	private static void rdf_ParseTypeCollectionPropertyElement() throws XMPException
+	{
+		throw new XMPException("ParseTypeCollection property element not allowed", BADXMP);
+	}
+
+
+	/**
+	 * 7.2.20 parseTypeOtherPropertyElt
+	 *		start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseOther ) )
+	 *		propertyEltList
+	 *		end-element()
+	 * 
+	 * @throws XMPException thown on parsing errors
+	 */
+	private static void rdf_ParseTypeOtherPropertyElement() throws XMPException
+	{
+		throw new XMPException("ParseTypeOther property element not allowed", BADXMP);
+	}
+
+
+	/**
+	 * 7.2.21 emptyPropertyElt
+	 *		start-element ( URI == propertyElementURIs,
+	 *						attributes == set (
+	 *							idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) )
+	 *		end-element()
+	 *
+	 *	<ns:Prop1/>  <!-- a simple property with an empty value --> 
+	 *	<ns:Prop2 rdf:resource="http: *www.adobe.com/"/> <!-- a URI value --> 
+	 *	<ns:Prop3 rdf:value="..." ns:Qual="..."/> <!-- a simple qualified property --> 
+	 *	<ns:Prop4 ns:Field1="..." ns:Field2="..."/> <!-- a struct with simple fields -->
+	 *
+	 * An emptyPropertyElt is an element with no contained content, just a possibly empty set of
+	 * attributes. An emptyPropertyElt can represent three special cases of simple XMP properties: a
+	 * simple property with an empty value (ns:Prop1), a simple property whose value is a URI
+	 * (ns:Prop2), or a simple property with simple qualifiers (ns:Prop3). 
+	 * An emptyPropertyElt can also represent an XMP struct whose fields are all simple and 
+	 * unqualified (ns:Prop4).
+	 *
+	 * It is an error to use both rdf:value and rdf:resource - that can lead to invalid  RDF in the
+	 * verbose form written using a literalPropertyElt.
+	 *
+	 * The XMP mapping for an emptyPropertyElt is a bit different from generic RDF, partly for 
+	 * design reasons and partly for historical reasons. The XMP mapping rules are:
+	 * <ol> 
+	 *		<li> If there is an rdf:value attribute then this is a simple property
+	 *				 with a text value.
+	 *		All other attributes are qualifiers.
+	 *		<li> If there is an rdf:resource attribute then this is a simple property 
+	 *			with a URI value. 
+	 *		All other attributes are qualifiers.
+	 *		<li> If there are no attributes other than xml:lang, rdf:ID, or rdf:nodeID
+	 *				then this is a simple 
+	 *		property with an empty value. 
+	 *		<li> Otherwise this is a struct, the attributes other than xml:lang, rdf:ID, 
+	 *				or rdf:nodeID are fields. 
+	 * </ol>
+	 * 
+	 * @param xmp the xmp metadata object that is generated
+	 * @param xmpParent the parent xmp node
+	 * @param xmlNode the currently processed XML node
+	 * @param isTopLevel Flag if the node is a top-level node
+	 * @throws XMPException thown on parsing errors
+	 */
+	private static void rdf_EmptyPropertyElement(XMPMetaImpl xmp, XMPNode xmpParent, Node xmlNode,
+			boolean isTopLevel) throws XMPException
+	{
+		boolean hasPropertyAttrs = false;
+		boolean hasResourceAttr = false;
+		boolean hasNodeIDAttr = false;
+		boolean hasValueAttr = false;
+		
+		Node valueNode = null;	// ! Can come from rdf:value or rdf:resource.
+		
+		if (xmlNode.hasChildNodes())
+		{
+			throw new XMPException(
+					"Nested content not allowed with rdf:resource or property attributes",
+					BADRDF);
+		}
+		
+		// First figure out what XMP this maps to and remember the XML node for a simple value.
+		for (int i = 0; i < xmlNode.getAttributes().getLength(); i++)
+		{
+			Node attribute = xmlNode.getAttributes().item(i);
+			if ("xmlns".equals(attribute.getPrefix())  ||
+					(attribute.getPrefix() == null  &&  "xmlns".equals(attribute.getNodeName())))
+			{
+				continue;
+			}
+			
+			int attrTerm = getRDFTermKind (attribute);
+
+			switch (attrTerm)
+			{
+				case RDFTERM_ID :
+					// Nothing to do.
+					break;
+
+				case RDFTERM_RESOURCE :
+					if (hasNodeIDAttr)
+					{
+						throw new XMPException(
+							"Empty property element can't have both rdf:resource and rdf:nodeID",
+							BADRDF);
+					}
+					else if (hasValueAttr)
+					{
+						throw new XMPException(
+								"Empty property element can't have both rdf:value and rdf:resource",
+								BADXMP);
+					}
+
+					hasResourceAttr = true;
+					if (!hasValueAttr) 
+					{
+						valueNode = attribute;
+					}	
+					break;
+
+				case RDFTERM_NODE_ID:
+				if (hasResourceAttr)
+				{
+					throw new XMPException(
+							"Empty property element can't have both rdf:resource and rdf:nodeID",
+							BADRDF);
+				}
+				hasNodeIDAttr = true;
+				break;
+
+			case RDFTERM_OTHER:
+				if ("value".equals(attribute.getLocalName())
+						&& NS_RDF.equals(attribute.getNamespaceURI()))
+				{
+					if (hasResourceAttr)
+					{
+						throw new XMPException(
+								"Empty property element can't have both rdf:value and rdf:resource",
+								BADXMP);
+					}
+					hasValueAttr = true;
+					valueNode = attribute;
+				}
+				else if (!XML_LANG.equals(attribute.getNodeName()))
+				{
+					hasPropertyAttrs = true;
+				}
+				break;
+
+			default:
+				throw new XMPException("Unrecognized attribute of empty property element",
+						BADRDF);
+			}
+		}
+		
+		// Create the right kind of child node and visit the attributes again 
+		// to add the fields or qualifiers.
+		// ! Because of implementation vagaries, 
+		//   the xmpParent is the tree root for top level properties.
+		// ! The schema is found, created if necessary, by addChildNode.
+		
+		XMPNode childNode = addChildNode(xmp, xmpParent, xmlNode, "", isTopLevel);
+		boolean childIsStruct = false;
+		
+		if (hasValueAttr || hasResourceAttr)
+		{
+			childNode.setValue(valueNode != null ? valueNode.getNodeValue() : "");
+			if (!hasValueAttr)
+			{
+				// ! Might have both rdf:value and rdf:resource.
+				childNode.getOptions().setURI(true);	
+			}
+		}
+		else if (hasPropertyAttrs)
+		{
+			childNode.getOptions().setStruct(true);
+			childIsStruct = true;
+		}
+		
+		for (int i = 0; i < xmlNode.getAttributes().getLength(); i++)
+		{
+			Node attribute = xmlNode.getAttributes().item(i);
+			if (attribute == valueNode  ||
+				"xmlns".equals(attribute.getPrefix())  ||
+				(attribute.getPrefix() == null  &&  "xmlns".equals(attribute.getNodeName())))
+			{
+				continue;	// Skip the rdf:value or rdf:resource attribute holding the value.
+			}
+			
+			int attrTerm = getRDFTermKind (attribute);
+
+			switch (attrTerm)
+			{
+				case RDFTERM_ID :
+				case RDFTERM_NODE_ID :
+					break;	// Ignore all rdf:ID and rdf:nodeID attributes.
+					
+				case RDFTERM_RESOURCE :
+					addQualifierNode(childNode, "rdf:resource", attribute.getNodeValue());
+					break;
+
+				case RDFTERM_OTHER :
+					if (!childIsStruct)
+					{
+						addQualifierNode(
+							childNode, attribute.getNodeName(), attribute.getNodeValue());
+					}
+					else if (XML_LANG.equals(attribute.getNodeName()))
+					{
+						addQualifierNode (childNode, XML_LANG, attribute.getNodeValue());
+					}
+					else
+					{
+						addChildNode (xmp, childNode, attribute, attribute.getNodeValue(), false);
+					}
+					break;
+
+				default :
+					throw new XMPException("Unrecognized attribute of empty property element",
+						BADRDF);
+			}
+
+		}		
+	}
+
+
+	/**
+	 * Adds a child node.
+	 *  
+	 * @param xmp the xmp metadata object that is generated
+	 * @param xmpParent the parent xmp node
+	 * @param xmlNode the currently processed XML node
+	 * @param value Node value	
+	 * @param isTopLevel Flag if the node is a top-level node
+	 * @return Returns the newly created child node.
+	 * @throws XMPException thown on parsing errors
+	 */
+	private static XMPNode addChildNode(XMPMetaImpl xmp, XMPNode xmpParent, Node xmlNode,
+			String value, boolean isTopLevel) throws XMPException
+	{
+		XMPSchemaRegistry registry = XMPMetaFactory.getSchemaRegistry();
+		String namespace = xmlNode.getNamespaceURI();
+		String childName;
+		if (namespace != null)
+		{
+			if (NS_DC_DEPRECATED.equals(namespace))
+			{
+				// Fix a legacy DC namespace
+				namespace = NS_DC;
+			}
+			
+			String prefix = registry.getNamespacePrefix(namespace);
+			if (prefix == null)
+			{
+				prefix = xmlNode.getPrefix() != null ? xmlNode.getPrefix() : DEFAULT_PREFIX;
+				prefix = registry.registerNamespace(namespace, prefix);
+			}
+			childName = prefix + xmlNode.getLocalName();
+		}
+		else
+		{
+			throw new XMPException(
+				"XML namespace required for all elements and attributes", BADRDF);
+		}
+
+		
+		// create schema node if not already there
+		PropertyOptions childOptions = new PropertyOptions();
+		boolean isAlias = false;
+		if (isTopLevel)
+		{
+			// Lookup the schema node, adjust the XMP parent pointer.
+			// Incoming parent must be the tree root.
+			XMPNode schemaNode = XMPNodeUtils.findSchemaNode(xmp.getRoot(), namespace,
+				DEFAULT_PREFIX, true);
+			schemaNode.setImplicit(false);	// Clear the implicit node bit.
+			// need runtime check for proper 32 bit code.
+			xmpParent = schemaNode;
+			
+			// If this is an alias set the alias flag in the node 
+			// and the hasAliases flag in the tree.
+			if (registry.findAlias(childName) != null)
+			{
+				isAlias = true;
+				xmp.getRoot().setHasAliases(true);
+				schemaNode.setHasAliases(true);
+			}
+		}
+
+		
+		// Make sure that this is not a duplicate of a named node.
+		boolean isArrayItem  = "rdf:li".equals(childName);
+		boolean isValueNode  = "rdf:value".equals(childName);
+
+		// Create XMP node and so some checks
+		XMPNode newChild = new XMPNode(
+			childName, value, childOptions);
+		newChild.setAlias(isAlias);
+		
+		// Add the new child to the XMP parent node, a value node first.
+		if (!isValueNode)
+		{
+			xmpParent.addChild(newChild);
+		}
+		else
+		{
+			xmpParent.addChild(1, newChild);
+		}
+		
+		
+		if (isValueNode)
+		{
+			if (isTopLevel  ||  !xmpParent.getOptions().isStruct())
+			{
+				throw new XMPException("Misplaced rdf:value element", BADRDF);
+			}	
+			xmpParent.setHasValueChild(true);
+		}
+		
+		if (isArrayItem)
+		{
+			if (!xmpParent.getOptions().isArray()) 
+			{
+				throw new XMPException("Misplaced rdf:li element", BADRDF);
+			}	
+			newChild.setName(ARRAY_ITEM_NAME);
+		}
+		
+		return newChild;
+	}
+
+	
+	/**
+	 * Adds a qualifier node.
+	 * 
+	 * @param xmpParent the parent xmp node
+	 * @param name the name of the qualifier which has to be 
+	 * 		QName including the <b>default prefix</b>
+	 * @param value the value of the qualifier
+	 * @return Returns the newly created child node.
+	 * @throws XMPException thown on parsing errors
+	 */
+	private static XMPNode addQualifierNode(XMPNode xmpParent, String name, String value)
+			throws XMPException
+	{
+		boolean isLang = XML_LANG.equals(name);
+	
+		XMPNode newQual = null;
+
+		// normalize value of language qualifiers
+		newQual = new XMPNode(name, isLang ? Utils.normalizeLangValue(value) : value, null);
+		xmpParent.addQualifier(newQual);
+		
+		return newQual;
+	}
+	
+
+	/**
+	 * The parent is an RDF pseudo-struct containing an rdf:value field. Fix the
+	 * XMP data model. The rdf:value node must be the first child, the other
+	 * children are qualifiers. The form, value, and children of the rdf:value
+	 * node are the real ones. The rdf:value node's qualifiers must be added to
+	 * the others.
+	 * 
+	 * @param xmpParent the parent xmp node
+	 * @throws XMPException thown on parsing errors
+	 */
+	private static void fixupQualifiedNode(XMPNode xmpParent) throws XMPException
+	{
+		assert xmpParent.getOptions().isStruct()  &&  xmpParent.hasChildren();
+
+		XMPNode valueNode = xmpParent.getChild(1);
+		assert "rdf:value".equals(valueNode.getName());
+
+		// Move the qualifiers on the value node to the parent. 
+		// Make sure an xml:lang qualifier stays at the front.
+		// Check for duplicate names between the value node's qualifiers and the parent's children. 
+		// The parent's children are about to become qualifiers. Check here, between the groups. 
+		// Intra-group duplicates are caught by XMPNode#addChild(...).
+		if (valueNode.getOptions().getHasLanguage())
+		{
+			if (xmpParent.getOptions().getHasLanguage())
+			{
+				throw new XMPException("Redundant xml:lang for rdf:value element", 
+					BADXMP);
+			}
+			XMPNode langQual = valueNode.getQualifier(1);
+			valueNode.removeQualifier(langQual);
+			xmpParent.addQualifier(langQual);
+		}
+		
+		// Start the remaining copy after the xml:lang qualifier.		
+		for (int i = 1; i <= valueNode.getQualifierLength(); i++)
+		{
+			XMPNode qualifier = valueNode.getQualifier(i);
+			xmpParent.addQualifier(qualifier);
+		}
+		
+		
+		// Change the parent's other children into qualifiers. 
+		// This loop starts at 1, child 0 is the rdf:value node.
+		for (int i = 2; i <= xmpParent.getChildrenLength(); i++)
+		{
+			XMPNode qualifier = xmpParent.getChild(i);
+			xmpParent.addQualifier(qualifier);
+		}
+		
+		// Move the options and value last, other checks need the parent's original options. 
+		// Move the value node's children to be the parent's children.
+		assert xmpParent.getOptions().isStruct()  ||  xmpParent.getHasValueChild();
+		
+		xmpParent.setHasValueChild(false);
+		xmpParent.getOptions().setStruct(false);
+		xmpParent.getOptions().mergeWith(valueNode.getOptions());
+		xmpParent.setValue(valueNode.getValue());
+		
+		xmpParent.removeChildren();
+		for (Iterator it = valueNode.iterateChildren(); it.hasNext();)
+		{
+			XMPNode child = (XMPNode) it.next();
+			xmpParent.addChild(child);
+		}
+	}		
+
+	
+	/**
+	 * Checks if the node is a white space.
+	 * @param node an XML-node
+	 * @return Returns whether the node is a whitespace node, 
+	 * 		i.e. a text node that contains only whitespaces.
+	 */
+	private static boolean isWhitespaceNode(Node node)
+	{
+		if (node.getNodeType() != Node.TEXT_NODE)
+		{
+			return false;
+		}
+		
+		String value = node.getNodeValue();
+		for (int i = 0; i < value.length(); i++)
+		{
+			if (!Character.isWhitespace(value.charAt(i)))
+			{
+				return false;
+			}
+		}
+		
+		return true;
+	}
+	
+	
+	/**
+	 * 7.2.6 propertyElementURIs
+	 *			anyURI - ( coreSyntaxTerms | rdf:Description | oldTerms )
+	 * 
+	 * @param term the term id
+	 * @return Return true if the term is a property element name.
+	 */
+	private static boolean isPropertyElementName(int term)
+	{
+		if (term == RDFTERM_DESCRIPTION  ||  isOldTerm(term))
+		{
+			return false;
+		}
+		else
+		{	
+			return (!isCoreSyntaxTerm(term));
+		}	
+	}
+
+	
+	/**
+	 * 7.2.4 oldTerms<br>
+	 * rdf:aboutEach | rdf:aboutEachPrefix | rdf:bagID
+	 * 
+	 * @param term the term id
+	 * @return Returns true if the term is an old term.
+	 */
+	private static boolean isOldTerm(int term)
+	{
+		return  RDFTERM_FIRST_OLD <= term  &&  term <= RDFTERM_LAST_OLD;
+	}
+
+
+	/**
+	 * 7.2.2 coreSyntaxTerms<br>
+	 * rdf:RDF | rdf:ID | rdf:about | rdf:parseType | rdf:resource | rdf:nodeID |
+	 * rdf:datatype
+	 * 
+	 * @param term the term id
+	 * @return Return true if the term is a core syntax term
+	 */
+	private static boolean isCoreSyntaxTerm(int term)
+	{
+		return  RDFTERM_FIRST_CORE <= term  &&  term <= RDFTERM_LAST_CORE;
+	}
+
+
+	/**
+	 * Determines the ID for a certain RDF Term.
+	 * Arranged to hopefully minimize the parse time for large XMP.
+	 * 
+	 * @param node an XML node 
+	 * @return Returns the term ID.
+	 */
+	private static int getRDFTermKind(Node node)
+	{
+		String localName = node.getLocalName();
+		String namespace = node.getNamespaceURI();
+		
+		if (
+				namespace == null  && 
+				("about".equals(localName) || "ID".equals(localName))  &&
+				(node instanceof Attr)  &&
+				NS_RDF.equals(((Attr) node).getOwnerElement().getNamespaceURI())
+		   )
+		{
+			namespace = NS_RDF; 
+		}
+		
+		if (NS_RDF.equals(namespace))
+		{
+			if ("li".equals(localName))
+			{
+				return RDFTERM_LI;
+			}
+			else if ("parseType".equals(localName))
+			{
+				return RDFTERM_PARSE_TYPE;
+			}
+			else if ("Description".equals(localName))
+			{
+				return RDFTERM_DESCRIPTION;
+			}
+			else if ("about".equals(localName))
+			{
+				return RDFTERM_ABOUT;
+			}
+			else if ("resource".equals(localName))
+			{
+				return RDFTERM_RESOURCE;
+			}
+			else if ("RDF".equals(localName))
+			{
+				return RDFTERM_RDF;
+			}
+			else if ("ID".equals(localName))
+			{
+				return RDFTERM_ID;
+			}
+			else if ("nodeID".equals(localName))
+			{
+				return RDFTERM_NODE_ID;
+			}
+			else if ("datatype".equals(localName))
+			{
+				return RDFTERM_DATATYPE;
+			}
+			else if ("aboutEach".equals(localName))
+			{
+				return RDFTERM_ABOUT_EACH;
+			}
+			else if ("aboutEachPrefix".equals(localName))
+			{
+				return RDFTERM_ABOUT_EACH_PREFIX;
+			}
+			else if ("bagID".equals(localName))
+			{
+				return RDFTERM_BAG_ID;
+			}
+		}
+		
+		return RDFTERM_OTHER;
+	}
+}
\ No newline at end of file
diff --git a/XMPCore/src/com/adobe/xmp/impl/QName.java b/XMPCore/src/com/adobe/xmp/impl/QName.java
new file mode 100644
index 0000000..783f792
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/impl/QName.java
@@ -0,0 +1,80 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp.impl;
+
+/**
+ * @since   09.11.2006
+ */
+public class QName
+{
+	/** XML namespace prefix */
+	private String prefix;
+	/** XML localname */
+	private String localName;
+
+	
+	/**
+	 * Splits a qname into prefix and localname.
+	 * @param qname a QName
+	 */
+	public QName(String qname)
+	{
+		int colon = qname.indexOf(':');
+		
+		if (colon >= 0)
+		{
+			prefix = qname.substring(0, colon);
+			localName = qname.substring(colon + 1);
+		}
+		else
+		{
+			prefix = "";
+			localName = qname;
+		}
+	}
+	
+	
+	/** Constructor that initializes the fields
+	 * @param prefix the prefix  
+	 * @param localName the name
+	 */
+	public QName(String prefix, String localName)
+	{
+		this.prefix = prefix;
+		this.localName = localName;
+	}
+
+	
+	/**
+	 * @return Returns whether the QName has a prefix.
+	 */
+	public boolean hasPrefix()
+	{
+		return prefix != null  &&  prefix.length() > 0;
+	}
+	
+
+	/**
+	 * @return the localName
+	 */
+	public String getLocalName()
+	{
+		return localName;
+	}
+
+
+	/**
+	 * @return the prefix
+	 */
+	public String getPrefix()
+	{
+		return prefix;
+	}
+}
\ No newline at end of file
diff --git a/XMPCore/src/com/adobe/xmp/impl/Utils.java b/XMPCore/src/com/adobe/xmp/impl/Utils.java
new file mode 100644
index 0000000..7ca3b27
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/impl/Utils.java
@@ -0,0 +1,511 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp.impl;
+
+
+import com.adobe.xmp.XMPConst;
+
+
+/**
+ * Utility functions for the XMPToolkit implementation.
+ * 
+ * @since 06.06.2006
+ */
+public class Utils implements XMPConst
+{
+	/** segments of a UUID */
+	public static final int UUID_SEGMENT_COUNT = 4;
+	/** length of a UUID */
+	public static final int UUID_LENGTH = 32 + UUID_SEGMENT_COUNT;
+	/** table of XML name start chars (<= 0xFF) */
+	private  static boolean[] xmlNameStartChars;
+	/** table of XML name chars (<= 0xFF) */
+	private static boolean[] xmlNameChars;
+	/** init char tables */
+	static
+	{
+		initCharTables();
+	}
+	
+	
+	/**
+	 * Private constructor
+	 */
+	private Utils()
+	{
+		// EMPTY
+	}
+
+	
+	/**
+	 * Normalize an xml:lang value so that comparisons are effectively case
+	 * insensitive as required by RFC 3066 (which superceeds RFC 1766). The
+	 * normalization rules:
+	 * <ul>
+	 * <li> The primary subtag is lower case, the suggested practice of ISO 639.
+	 * <li> All 2 letter secondary subtags are upper case, the suggested
+	 * practice of ISO 3166.
+	 * <li> All other subtags are lower case.
+	 * </ul>
+	 * 
+	 * @param value
+	 *            raw value
+	 * @return Returns the normalized value.
+	 */
+	public static String normalizeLangValue(String value)
+	{
+		// don't normalize x-default
+		if (XMPConst.X_DEFAULT.equals(value))
+		{
+			return value;
+		}	
+		
+		int subTag = 1;
+		StringBuffer buffer = new StringBuffer();
+
+		for (int i = 0; i < value.length(); i++)
+		{
+			switch (value.charAt(i))
+			{
+			case '-':
+			case '_':
+				// move to next subtag and convert underscore to hyphen
+				buffer.append('-');
+				subTag++;
+				break;
+			case ' ':
+				// remove spaces
+				break;
+			default:
+				// convert second subtag to uppercase, all other to lowercase
+				if (subTag != 2)
+				{
+					buffer.append(Character.toLowerCase(value.charAt(i)));
+				}
+				else
+				{
+					buffer.append(Character.toUpperCase(value.charAt(i)));
+				}
+			}
+
+		}
+		return buffer.toString();
+	}
+
+
+	/**
+	 * Split the name and value parts for field and qualifier selectors:
+	 * <ul>
+	 * <li>[qualName="value"] - An element in an array of structs, chosen by a
+	 * field value.
+	 * <li>[?qualName="value"] - An element in an array, chosen by a qualifier
+	 * value.
+	 * </ul>
+	 * The value portion is a string quoted by ''' or '"'. The value may contain
+	 * any character including a doubled quoting character. The value may be
+	 * empty. <em>Note:</em> It is assumed that the expression is formal
+	 * correct
+	 * 
+	 * @param selector
+	 *            the selector
+	 * @return Returns an array where the first entry contains the name and the
+	 *         second the value.
+	 */
+	static String[] splitNameAndValue(String selector)
+	{
+		// get the name
+		int eq = selector.indexOf('=');
+		int pos = 1;
+		if (selector.charAt(pos) == '?')
+		{
+			pos++;
+		}
+		String name = selector.substring(pos, eq);
+
+		// get the value
+		pos = eq + 1;
+		char quote = selector.charAt(pos);
+		pos++;
+		int end = selector.length() - 2; // quote and ]
+		StringBuffer value = new StringBuffer(end - eq);
+		while (pos < end)
+		{
+			value.append(selector.charAt(pos));
+			pos++;
+			if (selector.charAt(pos) == quote)
+			{
+				// skip one quote in value
+				pos++;
+			}
+		}
+		return new String[] { name, value.toString() };
+	}
+	
+
+	/**
+	 * 
+	 * @param schema
+	 *            a schema namespace
+	 * @param prop
+	 *            an XMP Property
+	 * @return Returns true if the property is defined as &quot;Internal
+	 *         Property&quot;, see XMP Specification.
+	 */
+	static boolean isInternalProperty(String schema, String prop)
+	{
+		boolean isInternal = false;
+
+		if (NS_DC.equals(schema))
+		{
+			if ("dc:format".equals(prop) || "dc:language".equals(prop))
+			{
+				isInternal = true;
+			}
+		}
+		else if (NS_XMP.equals(schema))
+		{
+			if ("xmp:BaseURL".equals(prop) || "xmp:CreatorTool".equals(prop)
+					|| "xmp:Format".equals(prop) || "xmp:Locale".equals(prop)
+					|| "xmp:MetadataDate".equals(prop) || "xmp:ModifyDate".equals(prop))
+			{
+				isInternal = true;
+			}
+		}
+		else if (NS_PDF.equals(schema))
+		{
+			if ("pdf:BaseURL".equals(prop) || "pdf:Creator".equals(prop)
+					|| "pdf:ModDate".equals(prop) || "pdf:PDFVersion".equals(prop)
+					|| "pdf:Producer".equals(prop))
+			{
+				isInternal = true;
+			}
+		}
+		else if (NS_TIFF.equals(schema))
+		{
+			isInternal = true;
+			if ("tiff:ImageDescription".equals(prop) || "tiff:Artist".equals(prop)
+					|| "tiff:Copyright".equals(prop))
+			{
+				isInternal = false;
+			}
+		}
+		else if (NS_EXIF.equals(schema))
+		{
+			isInternal = true;
+			if ("exif:UserComment".equals(prop))
+			{
+				isInternal = false;
+			}
+		}
+		else if (NS_EXIF_AUX.equals(schema))
+		{
+			isInternal = true;
+		}
+		else if (NS_PHOTOSHOP.equals(schema))
+		{
+			if ("photoshop:ICCProfile".equals(prop))
+			{
+				isInternal = true;
+			}
+		}
+		else if (NS_CAMERARAW.equals(schema))
+		{
+			if ("crs:Version".equals(prop) || "crs:RawFileName".equals(prop)
+					|| "crs:ToneCurveName".equals(prop))
+			{
+				isInternal = true;
+			}
+		}
+		else if (NS_ADOBESTOCKPHOTO.equals(schema))
+		{
+			isInternal = true;
+		}
+		else if (NS_XMP_MM.equals(schema))
+		{
+			isInternal = true;
+		}
+		else if (TYPE_TEXT.equals(schema))
+		{
+			isInternal = true;
+		}
+		else if (TYPE_PAGEDFILE.equals(schema))
+		{
+			isInternal = true;
+		}
+		else if (TYPE_GRAPHICS.equals(schema))
+		{
+			isInternal = true;
+		}
+		else if (TYPE_IMAGE.equals(schema))
+		{
+			isInternal = true;
+		}
+		else if (TYPE_FONT.equals(schema))
+		{
+			isInternal = true;
+		}
+
+		return isInternal;
+	}
+
+
+	/**
+	 * Check some requirements for an UUID:
+	 * <ul>
+	 * <li>Length of the UUID is 32</li>
+	 * <li>The Delimiter count is 4 and all the 4 delimiter are on their right
+	 * position (8,13,18,23)</li>
+	 * </ul>
+	 * 
+	 * 
+	 * @param uuid uuid to test
+	 * @return true - this is a well formed UUID, false - UUID has not the expected format
+	 */
+
+	static boolean checkUUIDFormat(String uuid)
+	{
+		boolean result = true;
+		int delimCnt = 0;
+		int delimPos = 0;
+
+		if (uuid == null)
+		{
+			return false;
+		}
+		
+		for (delimPos = 0; delimPos < uuid.length(); delimPos++)
+		{
+			if (uuid.charAt(delimPos) == '-')
+			{
+				delimCnt++;
+				result = result  &&  
+					(delimPos == 8 || delimPos == 13 || delimPos == 18 || delimPos == 23);
+			}
+		}
+
+		return result && UUID_SEGMENT_COUNT == delimCnt && UUID_LENGTH == delimPos;
+	}
+
+	
+	/**
+	 * Simple check for valid XMLNames. Within ASCII range<br>
+	 * ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6]<br>
+	 * are accepted, above all characters (which is not entirely 
+	 * correct according to the XML Spec.
+	 *  
+	 * @param name an XML Name
+	 * @return Return <code>true</code> if the name is correct.
+	 */
+	public static boolean isXMLName(String name)
+	{
+		if (name.length() > 0  &&  !isNameStartChar(name.charAt(0)))
+		{
+			return false;
+		}
+		for (int i = 1; i < name.length(); i++)
+		{
+			if (!isNameChar(name.charAt(i)))
+			{	
+				return false;
+			}
+		}
+		return true;
+	}
+
+	
+	/**
+	 * Checks if the value is a legal "unqualified" XML name, as
+	 * defined in the XML Namespaces proposed recommendation.
+	 * These are XML names, except that they must not contain a colon. 
+	 * @param name the value to check
+	 * @return Returns true if the name is a valid "unqualified" XML name.
+	 */
+	public static boolean isXMLNameNS(String name)
+	{
+		if (name.length() > 0  &&  (!isNameStartChar(name.charAt(0))  ||  name.charAt(0) == ':'))
+		{
+			return false;
+		}
+		for (int i = 1; i < name.length(); i++)
+		{
+			if (!isNameChar(name.charAt(i))  ||  name.charAt(i) == ':')
+			{	
+				return false;
+			}
+		}
+		return true;
+	}
+	
+
+	/**
+	 * @param c  a char
+	 * @return Returns true if the char is an ASCII control char.
+	 */
+	static boolean isControlChar(char c)
+	{
+		return (c <= 0x1F  ||  c == 0x7F)  &&
+				c != 0x09  &&  c != 0x0A  &&  c != 0x0D;
+	}
+
+	
+	/**
+	 * Serializes the node value in XML encoding. Its used for tag bodies and
+	 * attributes.<br>
+	 * <em>Note:</em> The attribute is always limited by quotes,
+	 * thats why <code>&amp;apos;</code> is never serialized.<br> 
+	 * <em>Note:</em> Control chars are written unescaped, but if the user uses others than tab, LF
+	 * and CR the resulting XML will become invalid.
+	 * @param value a string
+	 * @param forAttribute flag if string is attribute value (need to additional escape quotes)
+	 * @param escapeWhitespaces Decides if LF, CR and TAB are escaped.
+	 * @return Returns the value ready for XML output.
+	 */
+	public static String escapeXML(String value, boolean forAttribute, boolean escapeWhitespaces)
+	{
+		// quick check if character are contained that need special treatment
+		boolean needsEscaping = false;
+		for (int i = 0; i < value.length (); i++)
+        {
+            char c = value.charAt (i);
+			if (
+				 c == '<'  ||  c == '>'  ||  c == '&'  ||							    // XML chars
+				(escapeWhitespaces  &&  (c == '\t'  ||  c == '\n'  ||  c == '\r'))  || 
+				(forAttribute  &&  c == '"'))
+			{
+				needsEscaping = true;
+				break;
+			}
+        }
+		
+		if (!needsEscaping)
+		{
+			// fast path
+			return value;
+		}
+		else
+		{
+			// slow path with escaping
+			StringBuffer buffer = new StringBuffer(value.length() * 4 / 3);
+	        for (int i = 0; i < value.length (); i++)
+	        {
+	            char c = value.charAt (i);
+	            if (!(escapeWhitespaces  &&  (c == '\t'  ||  c == '\n'  ||  c == '\r')))
+	            {
+	            	switch (c)
+		            {	
+	            		// we do what "Canonical XML" expects
+	            		// AUDIT: &apos; not serialized as only outer qoutes are used
+		              	case '<':	buffer.append("&lt;"); continue;
+		              	case '>':	buffer.append("&gt;"); continue;
+		              	case '&':	buffer.append("&amp;"); continue;
+		              	case '"': 	buffer.append(forAttribute ? "&quot;" : "\""); continue;
+		              	default:	buffer.append(c); continue;
+		            }
+		        }
+	            else
+	            {
+	            	// write control chars escaped,
+	            	// if there are others than tab, LF and CR the xml will become invalid.
+	            	buffer.append("&#x");
+	            	buffer.append(Integer.toHexString(c).toUpperCase());
+	            	buffer.append(';');
+	            }
+	        }
+	        return buffer.toString();
+		}
+	}	
+	
+	
+	/**
+	 * Replaces the ASCII control chars with a space.
+	 * 
+	 * @param value
+	 *            a node value
+	 * @return Returns the cleaned up value
+	 */
+	static String removeControlChars(String value)
+	{
+		StringBuffer buffer = new StringBuffer(value);
+		for (int i = 0; i < buffer.length(); i++)
+		{
+			if (isControlChar(buffer.charAt(i)))
+			{
+				buffer.setCharAt(i, ' ');
+			}
+		}
+		return buffer.toString();
+	}
+
+	
+	/** 
+	 * Simple check if a character is a valid XML start name char.
+	 * Within ASCII range<br>
+	 * ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6]<br>
+	 * are accepted, above all characters (which is not entirely 
+	 * correct according to the XML Spec)
+	 *  
+	 * @param ch a character
+	 * @return Returns true if the character is a valid first char of an XML name.
+	 */
+	private static boolean isNameStartChar(char ch)
+	{
+		return ch > 0xFF  ||  xmlNameStartChars[ch];
+	}
+
+	
+	/** 
+	 * Simple check if a character is a valid XML name char
+	 * (every char except the first one).
+	 * Within ASCII range<br>
+	 * ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6]<br>
+	 * are accepted, above all characters (which is not entirely 
+	 * correct according to the XML Spec)
+	 * 
+	 * @param ch a character
+	 * @return Returns true if the character is a valid char of an XML name.
+	 */
+	private static boolean isNameChar(char ch)
+	{
+		return ch > 0xFF  ||  xmlNameChars[ch];
+	}
+
+	
+	/**
+	 * Initializes the char tables for later use.
+	 */
+	private static void initCharTables()
+	{
+		xmlNameChars = new boolean[0x0100];
+		xmlNameStartChars = new boolean[0x0100];
+		
+		for (char ch = 0; ch < xmlNameChars.length; ch++)
+		{
+			xmlNameStartChars[ch] = 
+				('a' <= ch  &&  ch <= 'z')  ||
+				('A' <= ch  &&  ch <= 'Z')  ||
+				ch == ':'  ||
+				ch == '_'  ||
+				(0xC0 <= ch  &&  ch <= 0xD6)  ||
+				(0xD8 <= ch  &&  ch <= 0xF6);
+			
+			xmlNameChars[ch] =
+				('a' <= ch  &&  ch <= 'z')  ||
+				('A' <= ch  &&  ch <= 'Z')  ||
+				('0' <= ch  &&  ch <= '9')  ||
+				ch == ':'  ||
+				ch == '_'  ||
+				ch == '-'  ||
+				ch == '.'  ||
+				ch == 0xB7  ||
+				(0xC0 <= ch  &&  ch <= 0xD6)  ||
+				(0xD8 <= ch  &&  ch <= 0xF6);
+		}
+	}	
+}
\ No newline at end of file
diff --git a/XMPCore/src/com/adobe/xmp/impl/XMPDateTimeImpl.java b/XMPCore/src/com/adobe/xmp/impl/XMPDateTimeImpl.java
new file mode 100644
index 0000000..f3a17e7
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/impl/XMPDateTimeImpl.java
@@ -0,0 +1,346 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp.impl;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import com.adobe.xmp.XMPDateTime;
+import com.adobe.xmp.XMPException;
+
+
+/**
+ * The implementation of <code>XMPDateTime</code>. Internally a <code>calendar</code> is used
+ * plus an additional nano seconds field, because <code>Calendar</code> supports only milli
+ * seconds. The <code>nanoSeconds</code> convers only the resolution beyond a milli second.
+ * 
+ * @since 16.02.2006
+ */
+public class XMPDateTimeImpl implements XMPDateTime 
+{
+	/** */
+	private int year = 0;
+	/** */
+	private int month = 0;
+	/** */
+	private int day = 0;
+	/** */
+	private int hour = 0;
+	/** */
+	private int minute = 0;
+	/** */
+	private int second = 0;
+	/** Use the unversal time as default */
+	private TimeZone timeZone = TimeZone.getTimeZone("UTC");
+	/**
+	 * The nano seconds take micro and nano seconds, while the milli seconds are in the calendar.
+	 */
+	private int nanoSeconds;
+
+
+	/**
+	 * Creates an <code>XMPDateTime</code>-instance with the current time in the default time
+	 * zone.
+	 */
+	public XMPDateTimeImpl()
+	{
+		// EMPTY
+	}
+
+	
+	/**
+	 * Creates an <code>XMPDateTime</code>-instance from a calendar.
+	 * 
+	 * @param calendar a <code>Calendar</code>
+	 */
+	public XMPDateTimeImpl(Calendar calendar)
+	{
+		// extract the date and timezone from the calendar provided
+        Date date = calendar.getTime();
+        TimeZone zone = calendar.getTimeZone();
+
+        // put that date into a calendar the pretty much represents ISO8601
+        // I use US because it is close to the "locale" for the ISO8601 spec
+        GregorianCalendar intCalendar = 
+        	(GregorianCalendar) Calendar.getInstance(Locale.US);
+        intCalendar.setGregorianChange(new Date(Long.MIN_VALUE));       
+        intCalendar.setTimeZone(zone);           
+        intCalendar.setTime(date); 		
+		
+		this.year = intCalendar.get(Calendar.YEAR);
+		this.month = intCalendar.get(Calendar.MONTH) + 1; // cal is from 0..12
+		this.day = intCalendar.get(Calendar.DAY_OF_MONTH);
+		this.hour = intCalendar.get(Calendar.HOUR_OF_DAY);
+		this.minute = intCalendar.get(Calendar.MINUTE);
+		this.second = intCalendar.get(Calendar.SECOND);
+		this.nanoSeconds = intCalendar.get(Calendar.MILLISECOND) * 1000000;
+		this.timeZone = intCalendar.getTimeZone();
+	}
+
+	
+	/**
+	 * Creates an <code>XMPDateTime</code>-instance from 
+	 * a <code>Date</code> and a <code>TimeZone</code>.
+	 * 
+	 * @param date a date describing an absolute point in time
+	 * @param timeZone a TimeZone how to interpret the date
+	 */
+	public XMPDateTimeImpl(Date date, TimeZone timeZone)
+	{
+		GregorianCalendar calendar = new GregorianCalendar(timeZone);
+		calendar.setTime(date);
+		this.year = calendar.get(Calendar.YEAR);
+		this.month = calendar.get(Calendar.MONTH) + 1; // cal is from 0..12
+		this.day = calendar.get(Calendar.DAY_OF_MONTH);
+		this.hour = calendar.get(Calendar.HOUR_OF_DAY);
+		this.minute = calendar.get(Calendar.MINUTE);
+		this.second = calendar.get(Calendar.SECOND);
+		this.nanoSeconds = calendar.get(Calendar.MILLISECOND) * 1000000;
+		this.timeZone = timeZone;
+	}
+
+	
+	/**
+	 * Creates an <code>XMPDateTime</code>-instance from an ISO 8601 string.
+	 * 
+	 * @param strValue an ISO 8601 string
+	 * @throws XMPException If the string is a non-conform ISO 8601 string, an exception is thrown
+	 */
+	public XMPDateTimeImpl(String strValue) throws XMPException
+	{
+		ISO8601Converter.parse(strValue, this);
+	}
+
+
+	/**
+	 * @see XMPDateTime#getYear()
+	 */
+	public int getYear()
+	{
+		return year;
+	}
+
+	
+	/**
+	 * @see XMPDateTime#setYear(int)
+	 */
+	public void setYear(int year)
+	{
+		this.year = Math.min(Math.abs(year), 9999);
+	}	
+	
+
+	/**
+	 * @see XMPDateTime#getMonth()
+	 */
+	public int getMonth()
+	{
+		return month;
+	}
+
+
+	/**
+	 * @see XMPDateTime#setMonth(int)
+	 */
+	public void setMonth(int month)
+	{
+		if (month < 1)
+		{	
+			this.month = 1;
+		}
+		else if (month > 12)
+		{
+			this.month = 12;
+		}
+		else
+		{
+			this.month = month;
+		}
+	}
+
+	
+	/**
+	 * @see XMPDateTime#getDay()
+	 */
+	public int getDay()
+	{
+		return day;
+	}
+
+
+	/**
+	 * @see XMPDateTime#setDay(int)
+	 */
+	public void setDay(int day)
+	{
+		if (day < 1)
+		{	
+			this.day = 1;
+		}
+		else if (day > 31)
+		{
+			this.day = 31;
+		}
+		else
+		{
+			this.day = day;
+		}
+	}
+	
+	
+	/**
+	 * @see XMPDateTime#getHour()
+	 */
+	public int getHour()
+	{
+		return hour;
+	}
+
+	
+	/**
+	 * @see XMPDateTime#setHour(int)
+	 */
+	public void setHour(int hour)
+	{
+		this.hour = Math.min(Math.abs(hour), 23);
+	}
+
+
+	/**
+	 * @see XMPDateTime#getMinute()
+	 */
+	public int getMinute()
+	{
+		return minute;
+	}
+
+	
+	/**
+	 * @see XMPDateTime#setMinute(int)
+	 */
+	public void setMinute(int minute)
+	{
+		this.minute = Math.min(Math.abs(minute), 59);
+	}
+
+	
+	/**
+	 * @see XMPDateTime#getSecond()
+	 */
+	public int getSecond()
+	{
+		return second;
+	}
+
+	
+	/**
+	 * @see XMPDateTime#setSecond(int)
+	 */
+	public void setSecond(int second)
+	{
+		this.second = Math.min(Math.abs(second), 59);
+	}
+
+
+	/**
+	 * @see XMPDateTime#getNanoSecond()
+	 */
+	public int getNanoSecond()
+	{
+		return nanoSeconds;
+	}
+
+	
+	/**
+	 * @see XMPDateTime#setNanoSecond(int)
+	 */
+	public void setNanoSecond(int nanoSecond)
+	{
+		this.nanoSeconds = nanoSecond;
+	}
+	
+
+	/**
+	 * @see Comparable#compareTo(Object)
+	 */
+	public int compareTo(Object dt)
+	{
+		long d = getCalendar().getTimeInMillis()
+				- ((XMPDateTime) dt).getCalendar().getTimeInMillis();
+		if (d != 0)
+		{
+			return (int) (d % 2);
+		}
+		else
+		{
+			// if millis are equal, compare nanoseconds
+			d = nanoSeconds - ((XMPDateTime) dt).getNanoSecond();
+			return (int) (d % 2);
+		}
+	}
+
+
+	/**
+	 * @see XMPDateTime#getTimeZone()
+	 */
+	public TimeZone getTimeZone()
+	{
+		return timeZone;
+	}
+
+
+	/**
+	 * @see XMPDateTime#setTimeZone(TimeZone)
+	 */
+	public void setTimeZone(TimeZone timeZone)
+	{
+		this.timeZone = timeZone;
+	}
+
+
+	/**
+	 * @see XMPDateTime#getCalendar()
+	 */
+	public Calendar getCalendar()
+	{
+		GregorianCalendar calendar = (GregorianCalendar) Calendar.getInstance(Locale.US);
+		calendar.setGregorianChange(new Date(Long.MIN_VALUE));
+		calendar.setTimeZone(timeZone);
+		calendar.set(Calendar.YEAR, year);
+		calendar.set(Calendar.MONTH, month - 1);
+		calendar.set(Calendar.DAY_OF_MONTH, day);
+		calendar.set(Calendar.HOUR_OF_DAY, hour);
+		calendar.set(Calendar.MINUTE, minute);
+		calendar.set(Calendar.SECOND, second);
+		calendar.set(Calendar.MILLISECOND, nanoSeconds / 1000000);
+		return calendar;
+	}
+
+
+	/**
+	 * @see XMPDateTime#getISO8601String()
+	 */
+	public String getISO8601String()
+	{
+		return ISO8601Converter.render(this);
+	}
+
+	
+	/**
+	 * @return Returns the ISO string representation.
+	 */
+	public String toString()
+	{
+		return getISO8601String();
+	}
+}
\ No newline at end of file
diff --git a/XMPCore/src/com/adobe/xmp/impl/XMPIteratorImpl.java b/XMPCore/src/com/adobe/xmp/impl/XMPIteratorImpl.java
new file mode 100644
index 0000000..22ddebb
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/impl/XMPIteratorImpl.java
@@ -0,0 +1,598 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp.impl;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import com.adobe.xmp.XMPError;
+import com.adobe.xmp.XMPException;
+import com.adobe.xmp.XMPIterator;
+import com.adobe.xmp.impl.xpath.XMPPath;
+import com.adobe.xmp.impl.xpath.XMPPathParser;
+import com.adobe.xmp.options.IteratorOptions;
+import com.adobe.xmp.options.PropertyOptions;
+import com.adobe.xmp.properties.XMPPropertyInfo;
+
+
+/**
+ * The <code>XMPIterator</code> implementation.
+ * Iterates the XMP Tree according to a set of options.
+ * During the iteration the XMPMeta-object must not be changed.
+ * Calls to <code>skipSubtree()</code> / <code>skipSiblings()</code> will affect the iteration.
+ *  
+ * @since   29.06.2006
+ */
+public class XMPIteratorImpl implements XMPIterator
+{
+	/** stores the iterator options */
+	private IteratorOptions options;
+	/** the base namespace of the property path, will be changed during the iteration */ 
+	private String baseNS = null;
+	/** flag to indicate that skipSiblings() has been called. */
+	protected boolean skipSiblings = false;
+	/** flag to indicate that skipSiblings() has been called. */
+	protected boolean skipSubtree = false;
+	/** the node iterator doing the work */
+	private Iterator nodeIterator = null;
+	
+	
+	/**
+	 * Constructor with optionsl initial values. If <code>propName</code> is provided, 
+	 * <code>schemaNS</code> has also be provided.
+	 * @param xmp the iterated metadata object.
+	 * @param schemaNS the iteration is reduced to this schema (optional) 
+	 * @param propPath the iteration is redurce to this property within the <code>schemaNS</code>
+	 * @param options advanced iteration options, see {@link IteratorOptions}
+	 * @throws XMPException If the node defined by the paramters is not existing. 
+	 */
+	public XMPIteratorImpl(XMPMetaImpl xmp, String schemaNS, String propPath,
+			IteratorOptions options) throws XMPException
+	{
+		// make sure that options is defined at least with defaults
+		this.options = options != null ? options : new IteratorOptions();
+		
+		// the start node of the iteration depending on the schema and property filter
+		XMPNode startNode = null;
+		String initialPath = null;
+		boolean baseSchema = schemaNS != null  &&  schemaNS.length() > 0; 
+		boolean baseProperty = propPath != null  &&  propPath.length() > 0; 
+		
+		if (!baseSchema  &&  !baseProperty)
+		{
+			// complete tree will be iterated
+			startNode = xmp.getRoot();
+		}
+		else if (baseSchema  &&  baseProperty)
+		{
+			// Schema and property node provided
+			XMPPath path = XMPPathParser.expandXPath(schemaNS, propPath);
+			
+			// base path is the prop path without the property leaf
+			XMPPath basePath = new XMPPath();
+			for (int i = 0; i < path.size() - 1; i++)
+			{
+				basePath.add(path.getSegment(i));
+			}
+			
+			startNode = XMPNodeUtils.findNode(xmp.getRoot(), path, false, null);
+			baseNS = schemaNS;
+			initialPath = basePath.toString();
+		}
+		else if (baseSchema  &&  !baseProperty)
+		{
+			// Only Schema provided
+			startNode = XMPNodeUtils.findSchemaNode(xmp.getRoot(), schemaNS, false);
+		}
+		else // !baseSchema  &&  baseProperty
+		{
+			// No schema but property provided -> error
+			throw new XMPException("Schema namespace URI is required", XMPError.BADSCHEMA);
+		}			
+
+		
+		// create iterator
+		if (startNode != null)
+		{
+			if (!this.options.isJustChildren())
+			{	
+				nodeIterator = new NodeIterator(startNode, initialPath, 1);
+			}
+			else
+			{
+				nodeIterator = new NodeIteratorChildren(startNode, initialPath);
+			}
+		}
+		else
+		{
+			// create null iterator
+			nodeIterator = Collections.EMPTY_LIST.iterator();
+		}
+	}
+
+
+	/**
+	 * @see XMPIterator#skipSubtree()
+	 */
+	public void skipSubtree()
+	{
+		this.skipSubtree = true;
+	}
+
+	
+	/**
+	 * @see XMPIterator#skipSiblings()
+	 */
+	public void skipSiblings()
+	{
+		skipSubtree();
+		this.skipSiblings = true;
+	}
+	
+
+	/**
+	 * @see java.util.Iterator#hasNext()
+	 */
+	public boolean hasNext()
+	{
+		return nodeIterator.hasNext();
+	}
+
+	
+	/**
+	 * @see java.util.Iterator#next()
+	 */
+	public Object next()
+	{
+		return nodeIterator.next();
+	}
+
+	
+	/**
+	 * @see java.util.Iterator#remove()
+	 */
+	public void remove()
+	{
+		throw new UnsupportedOperationException("The XMPIterator does not support remove().");
+	}
+	
+	
+	/**
+	 * @return Exposes the options for inner class.
+	 */
+	protected IteratorOptions getOptions()
+	{
+		return options;
+	}
+
+	
+	/**
+	 * @return Exposes the options for inner class.
+	 */
+	protected String getBaseNS()
+	{
+		return baseNS;
+	}
+
+	
+	/**
+	 * @param baseNS sets the baseNS from the inner class.
+	 */
+	protected void setBaseNS(String baseNS)
+	{
+		this.baseNS = baseNS;
+	}
+	
+	
+	
+	
+	
+	
+	/**
+	 * The <code>XMPIterator</code> implementation.
+	 * It first returns the node itself, then recursivly the children and qualifier of the node.
+	 * 
+	 * @since   29.06.2006
+	 */
+	private class NodeIterator implements Iterator
+	{
+		/** iteration state */
+		protected static final int ITERATE_NODE = 0;
+		/** iteration state */
+		protected static final int ITERATE_CHILDREN = 1;
+		/** iteration state */
+		protected static final int ITERATE_QUALIFIER = 2;
+		
+		/** the state of the iteration */
+		private int state = ITERATE_NODE; 
+		/** the currently visited node */
+		private XMPNode visitedNode;
+		/** the recursively accumulated path */
+		private String path;
+		/** the iterator that goes through the children and qualifier list */
+		private Iterator childrenIterator = null;
+		/** index of node with parent, only interesting for arrays */
+		private int index = 0;
+		/** the iterator for each child */
+		private Iterator subIterator = Collections.EMPTY_LIST.iterator();
+		/** the cached <code>PropertyInfo</code> to return */
+		private XMPPropertyInfo returnProperty = null;
+
+		
+		/**
+		 * Default constructor
+		 */
+		public NodeIterator()
+		{
+			// EMPTY
+		}
+		
+		
+		/**
+		 * Constructor for the node iterator.
+		 * @param visitedNode the currently visited node
+		 * @param parentPath the accumulated path of the node
+		 * @param index the index within the parent node (only for arrays)
+		 */
+		public NodeIterator(XMPNode visitedNode, String parentPath, int index)
+		{
+			this.visitedNode = visitedNode;
+			this.state = NodeIterator.ITERATE_NODE;
+			if (visitedNode.getOptions().isSchemaNode())
+			{	
+				setBaseNS(visitedNode.getName());
+			}
+
+			// for all but the root node and schema nodes
+			path = accumulatePath(visitedNode, parentPath, index);
+		}
+
+		
+		/**
+		 * Prepares the next node to return if not already done. 
+		 * 
+		 * @see Iterator#hasNext()
+		 */
+		public boolean hasNext()
+		{
+			if (returnProperty != null)
+			{
+				// hasNext has been called before
+				return true;
+			}
+			
+			// find next node
+			if (state == ITERATE_NODE)
+			{
+				return reportNode();
+			}
+			else if (state == ITERATE_CHILDREN)
+			{
+				if (childrenIterator == null)
+				{
+					childrenIterator = visitedNode.iterateChildren();
+				}
+				
+				boolean hasNext = iterateChildren(childrenIterator);
+				
+				if (!hasNext  &&  visitedNode.hasQualifier()  &&  !getOptions().isOmitQualifiers()) 
+				{
+					state = ITERATE_QUALIFIER;
+					childrenIterator = null;
+					hasNext = hasNext();
+				}
+				return hasNext;
+			}
+			else
+			{
+				if (childrenIterator == null)
+				{
+					childrenIterator = visitedNode.iterateQualifier();
+				}
+				
+				return iterateChildren(childrenIterator);
+			}
+		}
+
+
+		/**
+		 * Sets the returnProperty as next item or recurses into <code>hasNext()</code>.
+		 * @return Returns if there is a next item to return. 
+		 */
+		protected boolean reportNode()
+		{
+			state = ITERATE_CHILDREN;
+			if (visitedNode.getParent() != null  &&
+				(!getOptions().isJustLeafnodes()  ||  !visitedNode.hasChildren()))
+			{	
+				returnProperty = createPropertyInfo(visitedNode, getBaseNS(), path);
+				return true;
+			}
+			else
+			{
+				return hasNext();
+			}
+		}
+
+
+		/**
+		 * Handles the iteration of the children or qualfier
+		 * @param iterator an iterator
+		 * @return Returns if there are more elements available.
+		 */
+		private boolean iterateChildren(Iterator iterator)
+		{
+			if (skipSiblings)
+			{
+				// setSkipSiblings(false);
+				skipSiblings = false;
+				subIterator = Collections.EMPTY_LIST.iterator();
+			}
+			
+			// create sub iterator for every child,
+			// if its the first child visited or the former child is finished 
+			if ((!subIterator.hasNext())  &&  iterator.hasNext())
+			{
+				XMPNode child = (XMPNode) iterator.next();
+				index++;
+				subIterator = new NodeIterator(child, path, index);
+			}
+			
+			if (subIterator.hasNext())
+			{
+				returnProperty = (XMPPropertyInfo) subIterator.next();
+				return true;
+			}
+			else
+			{
+				return false;
+			}
+		}
+	
+		
+		/**
+		 * Calls hasNext() and returnes the prepared node. Afterwards its set to null.
+		 * The existance of returnProperty indicates if there is a next node, otherwise
+		 * an exceptio is thrown.
+		 * 
+		 * @see Iterator#next()
+		 */
+		public Object next()
+		{
+			if (hasNext())
+			{
+				XMPPropertyInfo result = returnProperty; 
+				returnProperty = null;
+				return result;
+			}
+			else
+			{
+				throw new NoSuchElementException("There are no more nodes to return");
+			}
+		}
+	
+		
+		/**
+		 * Not supported.
+		 * @see Iterator#remove()
+		 */
+		public void remove()
+		{
+			throw new UnsupportedOperationException();
+		}
+		
+		
+		/**
+		 * @param currNode the node that will be added to the path.
+		 * @param parentPath the path up to this node.
+		 * @param currentIndex the current array index if an arrey is traversed
+		 * @return Returns the updated path.
+		 */
+		protected String accumulatePath(XMPNode currNode, String parentPath, int currentIndex)
+		{
+			String separator;
+			String segmentName;
+			if (currNode.getParent() == null  ||  currNode.getOptions().isSchemaNode())
+			{
+				return null;
+			}
+			else if (currNode.getParent().getOptions().isArray())
+			{
+				separator = "";
+				segmentName = "[" + String.valueOf(currentIndex) + "]";
+			}
+			else
+			{	
+				separator = "/";
+				segmentName = currNode.getName();
+			}
+			
+			
+			if (parentPath == null  ||  parentPath.length() == 0)
+			{
+				return segmentName;
+			}
+			else if (getOptions().isJustLeafname())
+			{
+				return !segmentName.startsWith("?") ? 
+					segmentName :
+					segmentName.substring(1); // qualifier
+			}
+			else 
+			{
+				return parentPath + separator + segmentName;
+			}
+		}
+			
+		
+		/**
+		 * Creates a property info object from an <code>XMPNode</code>.
+		 * @param node an <code>XMPNode</code>
+		 * @param baseNS the base namespace to report
+		 * @param path the full property path
+		 * @return Returns a <code>XMPProperty</code>-object that serves representation of the node.
+		 */
+		protected XMPPropertyInfo createPropertyInfo(final XMPNode node, final String baseNS,
+				final String path)
+		{
+			final Object value = node.getOptions().isSchemaNode() ? null : node.getValue();
+			
+			return new XMPPropertyInfo()
+			{
+				public String getNamespace()
+				{
+					return baseNS;
+				}
+	
+				public String getPath()
+				{
+					return path;
+				}
+				
+				public Object getValue()
+				{
+					return value;
+				}
+	
+				public PropertyOptions getOptions()
+				{
+					return node.getOptions();
+				}
+
+				public String getLanguage()
+				{
+					// the language is not reported
+					return null;
+				}
+			};
+		}
+
+
+		/**
+		 * @return the childrenIterator
+		 */
+		protected  Iterator getChildrenIterator()
+		{
+			return childrenIterator;
+		}
+
+
+		/**
+		 * @param childrenIterator the childrenIterator to set
+		 */
+		protected void setChildrenIterator(Iterator childrenIterator)
+		{
+			this.childrenIterator = childrenIterator;
+		}
+
+		
+		/**
+		 * @return Returns the returnProperty.
+		 */
+		protected XMPPropertyInfo getReturnProperty()
+		{
+			return returnProperty;
+		}
+		
+
+		/**
+		 * @param returnProperty the returnProperty to set
+		 */
+		protected  void setReturnProperty(XMPPropertyInfo returnProperty)
+		{
+			this.returnProperty = returnProperty;
+		}
+	}
+	
+	
+	/**
+	 * This iterator is derived from the default <code>NodeIterator</code>,
+	 * and is only used for the option {@link IteratorOptions#JUST_CHILDREN}.
+	 * 
+	 * @since 02.10.2006
+	 */
+	private class NodeIteratorChildren extends NodeIterator
+	{
+		/** */
+		private String parentPath;
+		/** */
+		private Iterator childrenIterator;
+		/** */
+		private int index = 0;
+		
+		
+		/**
+		 * Constructor 
+		 * @param parentNode the node which children shall be iterated. 
+		 * @param parentPath the full path of the former node without the leaf node.
+		 */
+		public NodeIteratorChildren(XMPNode parentNode, String parentPath)
+		{
+			if (parentNode.getOptions().isSchemaNode())
+			{	
+				setBaseNS(parentNode.getName());
+			}
+			this.parentPath = accumulatePath(parentNode, parentPath, 1);
+
+			childrenIterator = parentNode.iterateChildren();
+		}
+		
+		
+		/**
+		 * Prepares the next node to return if not already done. 
+		 * 
+		 * @see Iterator#hasNext()
+		 */
+		public boolean hasNext()
+		{
+			if (getReturnProperty() != null)
+			{
+				// hasNext has been called before
+				return true;
+			}
+			else if (skipSiblings)
+			{
+				return false;
+			}
+			else if (childrenIterator.hasNext())
+			{
+				XMPNode child = (XMPNode) childrenIterator.next();
+				index++;
+				
+				String path = null;
+				if (child.getOptions().isSchemaNode())
+				{	
+					setBaseNS(child.getName());
+				}
+				else if (child.getParent() != null)
+				{
+					// for all but the root node and schema nodes
+					path = accumulatePath(child, parentPath, index);
+				}
+
+				// report next property, skip not-leaf nodes in case options is set
+				if (!getOptions().isJustLeafnodes()  ||  !child.hasChildren())
+				{	
+					setReturnProperty(createPropertyInfo(child, getBaseNS(), path));
+					return true;
+				}
+				else
+				{
+					return hasNext();
+				}
+			}
+			else
+			{
+				return false;
+			}
+		}		
+	}
+}
\ No newline at end of file
diff --git a/XMPCore/src/com/adobe/xmp/impl/XMPMetaImpl.java b/XMPCore/src/com/adobe/xmp/impl/XMPMetaImpl.java
new file mode 100644
index 0000000..a97de99
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/impl/XMPMetaImpl.java
@@ -0,0 +1,1424 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp.impl;
+
+import java.util.Calendar;
+import java.util.Iterator;
+
+import com.adobe.xmp.XMPConst;
+import com.adobe.xmp.XMPDateTime;
+import com.adobe.xmp.XMPError;
+import com.adobe.xmp.XMPException;
+import com.adobe.xmp.XMPIterator;
+import com.adobe.xmp.XMPMeta;
+import com.adobe.xmp.XMPPathFactory;
+import com.adobe.xmp.XMPUtils;
+import com.adobe.xmp.impl.xpath.XMPPath;
+import com.adobe.xmp.impl.xpath.XMPPathParser;
+import com.adobe.xmp.options.IteratorOptions;
+import com.adobe.xmp.options.ParseOptions;
+import com.adobe.xmp.options.PropertyOptions;
+import com.adobe.xmp.properties.XMPProperty;
+
+
+/**
+ * Implementation for {@link XMPMeta}.
+ * 
+ * @since 17.02.2006
+ */
+public class XMPMetaImpl implements XMPMeta, XMPConst
+{
+	/** Property values are Strings by default */
+	private static final int VALUE_STRING = 0;
+	/** */
+	private static final int VALUE_BOOLEAN = 1;
+	/** */
+	private static final int VALUE_INTEGER = 2;
+	/** */
+	private static final int VALUE_LONG = 3;
+	/** */
+	private static final int VALUE_DOUBLE = 4;
+	/** */
+	private static final int VALUE_DATE = 5;
+	/** */
+	private static final int VALUE_CALENDAR = 6;
+	/** */
+	private static final int VALUE_BASE64 = 7;
+
+	/** root of the metadata tree */
+	private XMPNode tree;
+	/** the xpacket processing instructions content */ 
+	private String packetHeader = null;
+	
+
+	/**
+	 * Constructor for an empty metadata object.
+	 */
+	public XMPMetaImpl()
+	{
+		// create root node
+		tree = new XMPNode(null, null, null);
+	}
+
+
+	/**
+	 * Constructor for a cloned metadata tree.
+	 * 
+	 * @param tree
+	 *            an prefilled metadata tree which fulfills all
+	 *            <code>XMPNode</code> contracts.
+	 */
+	public XMPMetaImpl(XMPNode tree)
+	{
+		this.tree = tree;
+	}
+
+
+	/**
+	 * @see XMPMeta#appendArrayItem(String, String, PropertyOptions, String,
+	 *      PropertyOptions)
+	 */
+	public void appendArrayItem(String schemaNS, String arrayName, PropertyOptions arrayOptions,
+			String itemValue, PropertyOptions itemOptions) throws XMPException
+	{
+		ParameterAsserts.assertSchemaNS(schemaNS);
+		ParameterAsserts.assertArrayName(arrayName);
+
+		if (arrayOptions == null)
+		{
+			arrayOptions = new PropertyOptions();
+		}
+		if (!arrayOptions.isOnlyArrayOptions())
+		{
+			throw new XMPException("Only array form flags allowed for arrayOptions",
+					XMPError.BADOPTIONS);
+		}
+
+		// Check if array options are set correctly.
+		arrayOptions = XMPNodeUtils.verifySetOptions(arrayOptions, null);
+
+
+		// Locate or create the array. If it already exists, make sure the array
+		// form from the options
+		// parameter is compatible with the current state.
+		XMPPath arrayPath = XMPPathParser.expandXPath(schemaNS, arrayName);
+
+
+		// Just lookup, don't try to create.
+		XMPNode arrayNode = XMPNodeUtils.findNode(tree, arrayPath, false, null);
+
+		if (arrayNode != null)
+		{
+			// The array exists, make sure the form is compatible. Zero
+			// arrayForm means take what exists.
+			if (!arrayNode.getOptions().isArray())
+			{
+				throw new XMPException("The named property is not an array", XMPError.BADXPATH);
+			}
+			// if (arrayOptions != null && !arrayOptions.equalArrayTypes(arrayNode.getOptions()))
+			// {
+			// throw new XMPException("Mismatch of existing and specified array form", BADOPTIONS);
+			// }
+		}
+		else
+		{
+			// The array does not exist, try to create it.
+			if (arrayOptions.isArray())
+			{
+				arrayNode = XMPNodeUtils.findNode(tree, arrayPath, true, arrayOptions);
+				if (arrayNode == null)
+				{
+					throw new XMPException("Failure creating array node", XMPError.BADXPATH);
+				}
+			}
+			else
+			{
+				// array options missing
+				throw new XMPException("Explicit arrayOptions required to create new array",
+						XMPError.BADOPTIONS);
+			}
+		}
+
+		doSetArrayItem(arrayNode, ARRAY_LAST_ITEM, itemValue, itemOptions, true);
+	}
+
+	
+	/**
+	 * @see XMPMeta#appendArrayItem(String, String, String)
+	 */
+	public void appendArrayItem(String schemaNS, String arrayName, String itemValue)
+			throws XMPException
+	{
+		appendArrayItem(schemaNS, arrayName, null, itemValue, null);
+	}
+
+
+	/**
+	 * @throws XMPException
+	 * @see XMPMeta#countArrayItems(String, String)
+	 */
+	public int countArrayItems(String schemaNS, String arrayName) throws XMPException
+	{
+		ParameterAsserts.assertSchemaNS(schemaNS);
+		ParameterAsserts.assertArrayName(arrayName);
+
+		XMPPath arrayPath = XMPPathParser.expandXPath(schemaNS, arrayName);
+		XMPNode arrayNode = XMPNodeUtils.findNode(tree, arrayPath, false, null);
+
+		if (arrayNode == null)
+		{
+			return 0;
+		}
+
+		if (arrayNode.getOptions().isArray())
+		{
+			return arrayNode.getChildrenLength();
+		}
+		else
+		{
+			throw new XMPException("The named property is not an array", XMPError.BADXPATH);
+		}
+	}
+
+
+	/**
+	 * @see XMPMeta#deleteArrayItem(String, String, int)
+	 */
+	public void deleteArrayItem(String schemaNS, String arrayName, int itemIndex)
+	{
+		try
+		{
+			ParameterAsserts.assertSchemaNS(schemaNS);
+			ParameterAsserts.assertArrayName(arrayName);
+
+			String itemPath = XMPPathFactory.composeArrayItemPath(arrayName, itemIndex);
+			deleteProperty(schemaNS, itemPath);
+		}
+		catch (XMPException e)
+		{
+			// EMPTY, exceptions are ignored within delete
+		}
+	}
+
+
+	/**
+	 * @see XMPMeta#deleteProperty(String, String)
+	 */
+	public void deleteProperty(String schemaNS, String propName)
+	{
+		try
+		{
+			ParameterAsserts.assertSchemaNS(schemaNS);
+			ParameterAsserts.assertPropName(propName);
+
+			XMPPath expPath = XMPPathParser.expandXPath(schemaNS, propName);
+
+			XMPNode propNode = XMPNodeUtils.findNode(tree, expPath, false, null);
+			if (propNode != null)
+			{
+				XMPNodeUtils.deleteNode(propNode);
+			}
+		}
+		catch (XMPException e)
+		{
+			// EMPTY, exceptions are ignored within delete
+		}
+	}
+
+
+	/**
+	 * @see XMPMeta#deleteQualifier(String, String, String, String)
+	 */
+	public void deleteQualifier(String schemaNS, String propName, String qualNS, String qualName)
+	{
+		try
+		{
+			// Note: qualNS and qualName are checked inside composeQualfierPath
+			ParameterAsserts.assertSchemaNS(schemaNS);
+			ParameterAsserts.assertPropName(propName);
+
+			String qualPath = propName + XMPPathFactory.composeQualifierPath(qualNS, qualName);
+			deleteProperty(schemaNS, qualPath);
+		}
+		catch (XMPException e)
+		{
+			// EMPTY, exceptions within delete are ignored
+		}
+	}
+
+
+	/**
+	 * @see XMPMeta#deleteStructField(String, String, String, String)
+	 */
+	public void deleteStructField(String schemaNS, String structName, String fieldNS,
+			String fieldName)
+	{
+		try
+		{
+			// fieldNS and fieldName are checked inside composeStructFieldPath
+			ParameterAsserts.assertSchemaNS(schemaNS);
+			ParameterAsserts.assertStructName(structName);
+
+			String fieldPath = structName
+					+ XMPPathFactory.composeStructFieldPath(fieldNS, fieldName);
+			deleteProperty(schemaNS, fieldPath);
+		}
+		catch (XMPException e)
+		{
+			// EMPTY, exceptions within delete are ignored
+		}
+	}
+
+
+	/**
+	 * @see XMPMeta#doesPropertyExist(String, String)
+	 */
+	public boolean doesPropertyExist(String schemaNS, String propName)
+	{
+		try
+		{
+			ParameterAsserts.assertSchemaNS(schemaNS);
+			ParameterAsserts.assertPropName(propName);
+
+			XMPPath expPath = XMPPathParser.expandXPath(schemaNS, propName);
+			final XMPNode propNode = XMPNodeUtils.findNode(tree, expPath, false, null);
+			return propNode != null;
+		}
+		catch (XMPException e)
+		{
+			return false;
+		}
+	}
+
+
+	/**
+	 * @see XMPMeta#doesArrayItemExist(String, String, int)
+	 */
+	public boolean doesArrayItemExist(String schemaNS, String arrayName, int itemIndex)
+	{
+		try
+		{
+			ParameterAsserts.assertSchemaNS(schemaNS);
+			ParameterAsserts.assertArrayName(arrayName);
+
+			String path = XMPPathFactory.composeArrayItemPath(arrayName, itemIndex);
+			return doesPropertyExist(schemaNS, path);
+		}
+		catch (XMPException e)
+		{
+			return false;
+		}
+	}
+
+
+	/**
+	 * @see XMPMeta#doesStructFieldExist(String, String, String, String)
+	 */
+	public boolean doesStructFieldExist(String schemaNS, String structName, String fieldNS,
+			String fieldName)
+	{
+		try
+		{
+			// fieldNS and fieldName are checked inside composeStructFieldPath()
+			ParameterAsserts.assertSchemaNS(schemaNS);
+			ParameterAsserts.assertStructName(structName);
+
+			String path = XMPPathFactory.composeStructFieldPath(fieldNS, fieldName);
+			return doesPropertyExist(schemaNS, structName + path);
+		}
+		catch (XMPException e)
+		{
+			return false;
+		}
+	}
+
+
+	/**
+	 * @see XMPMeta#doesQualifierExist(String, String, String, String)
+	 */
+	public boolean doesQualifierExist(String schemaNS, String propName, String qualNS,
+			String qualName)
+	{
+		try
+		{
+			// qualNS and qualName are checked inside composeQualifierPath()
+			ParameterAsserts.assertSchemaNS(schemaNS);
+			ParameterAsserts.assertPropName(propName);
+
+			String path = XMPPathFactory.composeQualifierPath(qualNS, qualName);
+			return doesPropertyExist(schemaNS, propName + path);
+		}
+		catch (XMPException e)
+		{
+			return false;
+		}
+	}
+
+
+	/**
+	 * @see XMPMeta#getArrayItem(String, String, int)
+	 */
+	public XMPProperty getArrayItem(String schemaNS, String arrayName, int itemIndex)
+			throws XMPException
+	{
+		ParameterAsserts.assertSchemaNS(schemaNS);
+		ParameterAsserts.assertArrayName(arrayName);
+
+		String itemPath = XMPPathFactory.composeArrayItemPath(arrayName, itemIndex);
+		return getProperty(schemaNS, itemPath);
+	}
+
+
+	/**
+	 * @throws XMPException
+	 * @see XMPMeta#getLocalizedText(String, String, String, String)
+	 */
+	public XMPProperty getLocalizedText(String schemaNS, String altTextName, String genericLang,
+			String specificLang) throws XMPException
+	{
+		ParameterAsserts.assertSchemaNS(schemaNS);
+		ParameterAsserts.assertArrayName(altTextName);
+		ParameterAsserts.assertSpecificLang(specificLang);
+
+		genericLang = genericLang != null ? Utils.normalizeLangValue(genericLang) : null;
+		specificLang = Utils.normalizeLangValue(specificLang);
+
+		XMPPath arrayPath = XMPPathParser.expandXPath(schemaNS, altTextName);
+		XMPNode arrayNode = XMPNodeUtils.findNode(tree, arrayPath, false, null);
+		if (arrayNode == null)
+		{
+			return null;
+		}
+
+		Object[] result = XMPNodeUtils.chooseLocalizedText(arrayNode, genericLang, specificLang);
+		int match = ((Integer) result[0]).intValue();
+		final XMPNode itemNode = (XMPNode) result[1];
+
+		if (match != XMPNodeUtils.CLT_NO_VALUES)
+		{
+			return new XMPProperty()
+			{
+				public Object getValue()
+				{
+					return itemNode.getValue();
+				}
+
+
+				public PropertyOptions getOptions()
+				{
+					return itemNode.getOptions();
+				}
+
+
+				public String getLanguage()
+				{
+					return itemNode.getQualifier(1).getValue();
+				}
+
+
+				public String toString()
+				{
+					return itemNode.getValue().toString();
+				}
+			};
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+
+	/**
+	 * @see XMPMeta#setLocalizedText(String, String, String, String, String,
+	 *      PropertyOptions)
+	 */
+	public void setLocalizedText(String schemaNS, String altTextName, String genericLang,
+			String specificLang, String itemValue, PropertyOptions options) throws XMPException
+	{
+		ParameterAsserts.assertSchemaNS(schemaNS);
+		ParameterAsserts.assertArrayName(altTextName);
+		ParameterAsserts.assertSpecificLang(specificLang);
+
+		genericLang = genericLang != null ? Utils.normalizeLangValue(genericLang) : null;
+		specificLang = Utils.normalizeLangValue(specificLang);
+
+		XMPPath arrayPath = XMPPathParser.expandXPath(schemaNS, altTextName);
+
+		// Find the array node and set the options if it was just created.
+		XMPNode arrayNode = XMPNodeUtils.findNode(tree, arrayPath, true, new PropertyOptions(
+				PropertyOptions.ARRAY | PropertyOptions.ARRAY_ORDERED
+						| PropertyOptions.ARRAY_ALTERNATE | PropertyOptions.ARRAY_ALT_TEXT));
+
+		if (arrayNode == null)
+		{
+			throw new XMPException("Failed to find or create array node", XMPError.BADXPATH);
+		}
+		else if (!arrayNode.getOptions().isArrayAltText())
+		{
+			if (!arrayNode.hasChildren() && arrayNode.getOptions().isArrayAlternate())
+			{
+				arrayNode.getOptions().setArrayAltText(true);
+			}
+			else
+			{
+				throw new XMPException(
+					"Specified property is no alt-text array", XMPError.BADXPATH);
+			}
+		}
+
+		// Make sure the x-default item, if any, is first.
+		boolean haveXDefault = false;
+		XMPNode xdItem = null;
+
+		for (Iterator it = arrayNode.iterateChildren(); it.hasNext();)
+		{
+			XMPNode currItem = (XMPNode) it.next();
+			if (!currItem.hasQualifier()
+					|| !XMPConst.XML_LANG.equals(currItem.getQualifier(1).getName()))
+			{
+				throw new XMPException("Language qualifier must be first", XMPError.BADXPATH);
+			}
+			else if (XMPConst.X_DEFAULT.equals(currItem.getQualifier(1).getValue()))
+			{
+				xdItem = currItem;
+				haveXDefault = true;
+				break;
+			}
+		}
+
+		// Moves x-default to the beginning of the array
+		if (xdItem != null  &&  arrayNode.getChildrenLength() > 1)
+		{
+			arrayNode.removeChild(xdItem);
+			arrayNode.addChild(1, xdItem);
+		}
+
+		// Find the appropriate item.
+		// chooseLocalizedText will make sure the array is a language
+		// alternative.
+		Object[] result = XMPNodeUtils.chooseLocalizedText(arrayNode, genericLang, specificLang);
+		int match = ((Integer) result[0]).intValue();
+		XMPNode itemNode = (XMPNode) result[1];
+
+		boolean specificXDefault = XMPConst.X_DEFAULT.equals(specificLang);
+
+		switch (match)
+		{
+		case XMPNodeUtils.CLT_NO_VALUES:
+
+			// Create the array items for the specificLang and x-default, with
+			// x-default first.
+			XMPNodeUtils.appendLangItem(arrayNode, XMPConst.X_DEFAULT, itemValue);
+			haveXDefault = true;
+			if (!specificXDefault)
+			{
+				XMPNodeUtils.appendLangItem(arrayNode, specificLang, itemValue);
+			}
+			break;
+
+		case XMPNodeUtils.CLT_SPECIFIC_MATCH:
+
+			if (!specificXDefault)
+			{
+				// Update the specific item, update x-default if it matches the
+				// old value.
+				if (haveXDefault && xdItem != itemNode && xdItem != null
+						&& xdItem.getValue().equals(itemNode.getValue()))
+				{
+					xdItem.setValue(itemValue);
+				}
+				// ! Do this after the x-default check!
+				itemNode.setValue(itemValue);
+			}
+			else
+			{
+				// Update all items whose values match the old x-default value.
+				assert  haveXDefault  &&  xdItem == itemNode;
+				for (Iterator it = arrayNode.iterateChildren(); it.hasNext();)
+				{
+					XMPNode currItem = (XMPNode) it.next();
+					if (currItem == xdItem
+							|| !currItem.getValue().equals(
+									xdItem != null ? xdItem.getValue() : null))
+					{
+						continue;
+					}
+					currItem.setValue(itemValue);
+				}
+				// And finally do the x-default item.
+				if (xdItem != null)
+				{	
+					xdItem.setValue(itemValue);
+				}	
+			}
+			break;
+
+		case XMPNodeUtils.CLT_SINGLE_GENERIC:
+
+			// Update the generic item, update x-default if it matches the old
+			// value.
+			if (haveXDefault && xdItem != itemNode && xdItem != null
+					&& xdItem.getValue().equals(itemNode.getValue()))
+			{
+				xdItem.setValue(itemValue);
+			}
+			itemNode.setValue(itemValue); // ! Do this after
+			// the x-default
+			// check!
+			break;
+
+		case XMPNodeUtils.CLT_MULTIPLE_GENERIC:
+
+			// Create the specific language, ignore x-default.
+			XMPNodeUtils.appendLangItem(arrayNode, specificLang, itemValue);
+			if (specificXDefault)
+			{
+				haveXDefault = true;
+			}
+			break;
+
+		case XMPNodeUtils.CLT_XDEFAULT:
+
+			// Create the specific language, update x-default if it was the only
+			// item.
+			if (xdItem != null  &&  arrayNode.getChildrenLength() == 1)
+			{
+				xdItem.setValue(itemValue);
+			}
+			XMPNodeUtils.appendLangItem(arrayNode, specificLang, itemValue);
+			break;
+
+		case XMPNodeUtils.CLT_FIRST_ITEM:
+
+			// Create the specific language, don't add an x-default item.
+			XMPNodeUtils.appendLangItem(arrayNode, specificLang, itemValue);
+			if (specificXDefault)
+			{
+				haveXDefault = true;
+			}
+			break;
+
+		default:
+			// does not happen under normal circumstances
+			throw new XMPException("Unexpected result from ChooseLocalizedText",
+					XMPError.INTERNALFAILURE);
+
+		}
+
+		// Add an x-default at the front if needed.
+		if (!haveXDefault && arrayNode.getChildrenLength() == 1)
+		{
+			XMPNodeUtils.appendLangItem(arrayNode, XMPConst.X_DEFAULT, itemValue);
+		}
+	}
+
+	
+	/**
+	 * @see XMPMeta#setLocalizedText(String, String, String, String, String)
+	 */
+	public void setLocalizedText(String schemaNS, String altTextName, String genericLang,
+			String specificLang, String itemValue) throws XMPException
+	{
+		setLocalizedText(schemaNS, altTextName, genericLang, specificLang, itemValue, null);
+	}
+	
+
+	/**
+	 * @throws XMPException
+	 * @see XMPMeta#getProperty(String, String)
+	 */
+	public XMPProperty getProperty(String schemaNS, String propName) throws XMPException
+	{
+		return getProperty(schemaNS, propName, VALUE_STRING);
+	}
+
+
+	/**
+	 * Returns a property, but the result value can be requested. It can be one
+	 * of {@link XMPMetaImpl#VALUE_STRING}, {@link XMPMetaImpl#VALUE_BOOLEAN},
+	 * {@link XMPMetaImpl#VALUE_INTEGER}, {@link XMPMetaImpl#VALUE_LONG},
+	 * {@link XMPMetaImpl#VALUE_DOUBLE}, {@link XMPMetaImpl#VALUE_DATE},
+	 * {@link XMPMetaImpl#VALUE_CALENDAR}, {@link XMPMetaImpl#VALUE_BASE64}.
+	 * 
+	 * @see XMPMeta#getProperty(String, String)
+	 * @param schemaNS
+	 *            a schema namespace
+	 * @param propName
+	 *            a property name or path
+	 * @param valueType
+	 *            the type of the value, see VALUE_...
+	 * @return Returns an <code>XMPProperty</code>
+	 * @throws XMPException
+	 *             Collects any exception that occurs.
+	 */
+	protected XMPProperty getProperty(String schemaNS, String propName, int valueType)
+			throws XMPException
+	{
+		ParameterAsserts.assertSchemaNS(schemaNS);
+		ParameterAsserts.assertPropName(propName);
+
+		final XMPPath expPath = XMPPathParser.expandXPath(schemaNS, propName);
+		final XMPNode propNode = XMPNodeUtils.findNode(tree, expPath, false, null);
+
+		if (propNode != null)
+		{
+			if (valueType != VALUE_STRING && propNode.getOptions().isCompositeProperty())
+			{
+				throw new XMPException("Property must be simple when a value type is requested",
+						XMPError.BADXPATH);
+			}
+
+			final Object value = evaluateNodeValue(valueType, propNode);
+
+			return new XMPProperty()
+			{
+				public Object getValue()
+				{
+					return value;
+				}
+
+
+				public PropertyOptions getOptions()
+				{
+					return propNode.getOptions();
+				}
+
+
+				public String getLanguage()
+				{
+					return null;
+				}
+
+
+				public String toString()
+				{
+					return value.toString();
+				}
+			};
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+
+	/**
+	 * Returns a property, but the result value can be requested.
+	 * 
+	 * @see XMPMeta#getProperty(String, String)
+	 * @param schemaNS
+	 *            a schema namespace
+	 * @param propName
+	 *            a property name or path
+	 * @param valueType
+	 *            the type of the value, see VALUE_...
+	 * @return Returns the node value as an object according to the
+	 *         <code>valueType</code>.
+	 * @throws XMPException
+	 *             Collects any exception that occurs.
+	 */
+	protected Object getPropertyObject(String schemaNS, String propName, int valueType)
+			throws XMPException
+	{
+		ParameterAsserts.assertSchemaNS(schemaNS);
+		ParameterAsserts.assertPropName(propName);
+
+		final XMPPath expPath = XMPPathParser.expandXPath(schemaNS, propName);
+		final XMPNode propNode = XMPNodeUtils.findNode(tree, expPath, false, null);
+
+		if (propNode != null)
+		{
+			if (valueType != VALUE_STRING && propNode.getOptions().isCompositeProperty())
+			{
+				throw new XMPException("Property must be simple when a value type is requested",
+						XMPError.BADXPATH);
+			}
+
+			return evaluateNodeValue(valueType, propNode);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+
+	/**
+	 * @see XMPMeta#getPropertyBoolean(String, String)
+	 */
+	public Boolean getPropertyBoolean(String schemaNS, String propName) throws XMPException
+	{
+		return (Boolean) getPropertyObject(schemaNS, propName, VALUE_BOOLEAN);
+	}
+
+
+	/**
+	 * @throws XMPException
+	 * @see XMPMeta#setPropertyBoolean(String, String, boolean, PropertyOptions)
+	 */
+	public void setPropertyBoolean(String schemaNS, String propName, boolean propValue,
+			PropertyOptions options) throws XMPException
+	{
+		setProperty(schemaNS, propName, propValue ? TRUESTR : FALSESTR, options);
+	}
+
+	
+	/**
+	 * @see XMPMeta#setPropertyBoolean(String, String, boolean)
+	 */
+	public void setPropertyBoolean(String schemaNS, String propName, boolean propValue)
+			throws XMPException
+	{
+		setProperty(schemaNS, propName, propValue ? TRUESTR : FALSESTR, null);
+	}
+	
+
+	/**
+	 * @see XMPMeta#getPropertyInteger(String, String)
+	 */
+	public Integer getPropertyInteger(String schemaNS, String propName) throws XMPException
+	{
+		return (Integer) getPropertyObject(schemaNS, propName, VALUE_INTEGER);
+	}
+
+
+	/**
+	 * @see XMPMeta#setPropertyInteger(String, String, int, PropertyOptions)
+	 */
+	public void setPropertyInteger(String schemaNS, String propName, int propValue,
+			PropertyOptions options) throws XMPException
+	{
+		setProperty(schemaNS, propName, new Integer(propValue), options);
+	}
+
+	
+	/**
+	 * @see XMPMeta#setPropertyInteger(String, String, int)
+	 */
+	public void setPropertyInteger(String schemaNS, String propName, int propValue)
+			throws XMPException
+	{
+		setProperty(schemaNS, propName, new Integer(propValue), null);
+	}
+	
+
+	/**
+	 * @see XMPMeta#getPropertyLong(String, String)
+	 */
+	public Long getPropertyLong(String schemaNS, String propName) throws XMPException
+	{
+		return (Long) getPropertyObject(schemaNS, propName, VALUE_LONG);
+	}
+
+
+	/**
+	 * @see XMPMeta#setPropertyLong(String, String, long, PropertyOptions)
+	 */
+	public void setPropertyLong(String schemaNS, String propName, long propValue,
+			PropertyOptions options) throws XMPException
+	{
+		setProperty(schemaNS, propName, new Long(propValue), options);
+	}
+
+
+	/**
+	 * @see XMPMeta#setPropertyLong(String, String, long)
+	 */
+	public void setPropertyLong(String schemaNS, String propName, long propValue)
+			throws XMPException
+	{
+		setProperty(schemaNS, propName, new Long(propValue), null);
+	}
+	
+
+	/**
+	 * @see XMPMeta#getPropertyDouble(String, String)
+	 */
+	public Double getPropertyDouble(String schemaNS, String propName) throws XMPException
+	{
+		return (Double) getPropertyObject(schemaNS, propName, VALUE_DOUBLE);
+	}
+
+
+	/**
+	 * @see XMPMeta#setPropertyDouble(String, String, double, PropertyOptions)
+	 */
+	public void setPropertyDouble(String schemaNS, String propName, double propValue,
+			PropertyOptions options) throws XMPException
+	{
+		setProperty(schemaNS, propName, new Double(propValue), options);
+	}
+
+	
+	/**
+	 * @see XMPMeta#setPropertyDouble(String, String, double)
+	 */
+	public void setPropertyDouble(String schemaNS, String propName, double propValue)
+			throws XMPException
+	{
+		setProperty(schemaNS, propName, new Double(propValue), null);
+	}
+	
+
+	/**
+	 * @see XMPMeta#getPropertyDate(String, String)
+	 */
+	public XMPDateTime getPropertyDate(String schemaNS, String propName) throws XMPException
+	{
+		return (XMPDateTime) getPropertyObject(schemaNS, propName, VALUE_DATE);
+	}
+
+
+	/**
+	 * @see XMPMeta#setPropertyDate(String, String, XMPDateTime,
+	 *      PropertyOptions)
+	 */
+	public void setPropertyDate(String schemaNS, String propName, XMPDateTime propValue,
+			PropertyOptions options) throws XMPException
+	{
+		setProperty(schemaNS, propName, propValue, options);
+	}
+
+	
+	/**
+	 * @see XMPMeta#setPropertyDate(String, String, XMPDateTime)
+	 */
+	public void setPropertyDate(String schemaNS, String propName, XMPDateTime propValue)
+			throws XMPException
+	{
+		setProperty(schemaNS, propName, propValue, null);
+	}
+	
+
+	/**
+	 * @see XMPMeta#getPropertyCalendar(String, String)
+	 */
+	public Calendar getPropertyCalendar(String schemaNS, String propName) throws XMPException
+	{
+		return (Calendar) getPropertyObject(schemaNS, propName, VALUE_CALENDAR);
+	}
+
+
+	/**
+	 * @see XMPMeta#setPropertyCalendar(String, String, Calendar,
+	 *      PropertyOptions)
+	 */
+	public void setPropertyCalendar(String schemaNS, String propName, Calendar propValue,
+			PropertyOptions options) throws XMPException
+	{
+		setProperty(schemaNS, propName, propValue, options);
+	}
+
+	
+	/**
+	 * @see XMPMeta#setPropertyCalendar(String, String, Calendar)
+	 */
+	public void setPropertyCalendar(String schemaNS, String propName, Calendar propValue)
+			throws XMPException
+	{
+		setProperty(schemaNS, propName, propValue, null);
+	}
+	
+
+	/**
+	 * @see XMPMeta#getPropertyBase64(String, String)
+	 */
+	public byte[] getPropertyBase64(String schemaNS, String propName) throws XMPException
+	{
+		return (byte[]) getPropertyObject(schemaNS, propName, VALUE_BASE64);
+	}
+
+
+	/**
+	 * @see XMPMeta#getPropertyString(String, String)
+	 */
+	public String getPropertyString(String schemaNS, String propName) throws XMPException
+	{
+		return (String) getPropertyObject(schemaNS, propName, VALUE_STRING);
+	}
+
+
+	/**
+	 * @see XMPMeta#setPropertyBase64(String, String, byte[], PropertyOptions)
+	 */
+	public void setPropertyBase64(String schemaNS, String propName, byte[] propValue,
+			PropertyOptions options) throws XMPException
+	{
+		setProperty(schemaNS, propName, propValue, options);
+	}
+
+	
+	/**
+	 * @see XMPMeta#setPropertyBase64(String, String, byte[])
+	 */
+	public void setPropertyBase64(String schemaNS, String propName, byte[] propValue)
+			throws XMPException
+	{
+		setProperty(schemaNS, propName, propValue, null);
+	}
+	
+
+	/**
+	 * @throws XMPException
+	 * @see XMPMeta#getQualifier(String, String, String, String)
+	 */
+	public XMPProperty getQualifier(String schemaNS, String propName, String qualNS, 
+		String qualName) throws XMPException
+	{
+		// qualNS and qualName are checked inside composeQualfierPath
+		ParameterAsserts.assertSchemaNS(schemaNS);
+		ParameterAsserts.assertPropName(propName);
+
+		String qualPath = propName + XMPPathFactory.composeQualifierPath(qualNS, qualName);
+		return getProperty(schemaNS, qualPath);
+	}
+
+
+	/**
+	 * @see XMPMeta#getStructField(String, String, String, String)
+	 */
+	public XMPProperty getStructField(String schemaNS, String structName, String fieldNS,
+			String fieldName) throws XMPException
+	{
+		// fieldNS and fieldName are checked inside composeStructFieldPath
+		ParameterAsserts.assertSchemaNS(schemaNS);
+		ParameterAsserts.assertStructName(structName);
+
+		String fieldPath = structName + XMPPathFactory.composeStructFieldPath(fieldNS, fieldName);
+		return getProperty(schemaNS, fieldPath);
+	}
+
+
+	/**
+	 * @throws XMPException
+	 * @see XMPMeta#iterator()
+	 */
+	public XMPIterator iterator() throws XMPException
+	{
+		return iterator(null, null, null);
+	}
+
+
+	/**
+	 * @see XMPMeta#iterator(IteratorOptions)
+	 */
+	public XMPIterator iterator(IteratorOptions options) throws XMPException
+	{
+		return iterator(null, null, options);
+	}
+
+
+	/**
+	 * @see XMPMeta#iterator(String, String, IteratorOptions)
+	 */
+	public XMPIterator iterator(String schemaNS, String propName, IteratorOptions options)
+			throws XMPException
+	{
+		return new XMPIteratorImpl(this, schemaNS, propName, options);
+	}
+
+
+	/**
+	 * @throws XMPException
+	 * @see XMPMeta#setArrayItem(String, String, int, String, PropertyOptions)
+	 */
+	public void setArrayItem(String schemaNS, String arrayName, int itemIndex, String itemValue,
+			PropertyOptions options) throws XMPException
+	{
+		ParameterAsserts.assertSchemaNS(schemaNS);
+		ParameterAsserts.assertArrayName(arrayName);
+
+		// Just lookup, don't try to create.
+		XMPPath arrayPath = XMPPathParser.expandXPath(schemaNS, arrayName);
+		XMPNode arrayNode = XMPNodeUtils.findNode(tree, arrayPath, false, null);
+
+		if (arrayNode != null)
+		{
+			doSetArrayItem(arrayNode, itemIndex, itemValue, options, false);
+		}
+		else
+		{
+			throw new XMPException("Specified array does not exist", XMPError.BADXPATH);
+		}
+	}
+
+	
+	/**
+	 * @see XMPMeta#setArrayItem(String, String, int, String)
+	 */
+	public void setArrayItem(String schemaNS, String arrayName, int itemIndex, String itemValue)
+			throws XMPException
+	{
+		setArrayItem(schemaNS, arrayName, itemIndex, itemValue, null);
+	}
+	
+
+	/**
+	 * @throws XMPException
+	 * @see XMPMeta#insertArrayItem(String, String, int, String,
+	 *      PropertyOptions)
+	 */
+	public void insertArrayItem(String schemaNS, String arrayName, int itemIndex, String itemValue,
+			PropertyOptions options) throws XMPException
+	{
+		ParameterAsserts.assertSchemaNS(schemaNS);
+		ParameterAsserts.assertArrayName(arrayName);
+
+		// Just lookup, don't try to create.
+		XMPPath arrayPath = XMPPathParser.expandXPath(schemaNS, arrayName);
+		XMPNode arrayNode = XMPNodeUtils.findNode(tree, arrayPath, false, null);
+
+		if (arrayNode != null)
+		{
+			doSetArrayItem(arrayNode, itemIndex, itemValue, options, true);
+		}
+		else
+		{
+			throw new XMPException("Specified array does not exist", XMPError.BADXPATH);
+		}
+	}
+
+	
+	/**
+	 * @see XMPMeta#insertArrayItem(String, String, int, String)
+	 */
+	public void insertArrayItem(String schemaNS, String arrayName, int itemIndex, String itemValue)
+			throws XMPException
+	{
+		insertArrayItem(schemaNS, arrayName, itemIndex, itemValue, null);
+	}
+	
+
+	/**
+	 * @throws XMPException
+	 * @see XMPMeta#setProperty(String, String, Object, PropertyOptions)
+	 */
+	public void setProperty(String schemaNS, String propName, Object propValue,
+			PropertyOptions options) throws XMPException
+	{
+		ParameterAsserts.assertSchemaNS(schemaNS);
+		ParameterAsserts.assertPropName(propName);
+
+		options = XMPNodeUtils.verifySetOptions(options, propValue);
+
+		XMPPath expPath = XMPPathParser.expandXPath(schemaNS, propName);
+
+		XMPNode propNode = XMPNodeUtils.findNode(tree, expPath, true, options);
+		if (propNode != null)
+		{
+			setNode(propNode, propValue, options, false);
+		}
+		else
+		{
+			throw new XMPException("Specified property does not exist", XMPError.BADXPATH);
+		}
+	}
+
+	
+	/**
+	 * @see XMPMeta#setProperty(String, String, Object)
+	 */
+	public void setProperty(String schemaNS, String propName, Object propValue) throws XMPException
+	{
+		setProperty(schemaNS, propName, propValue, null);
+	}
+	
+
+	/**
+	 * @throws XMPException
+	 * @see XMPMeta#setQualifier(String, String, String, String, String,
+	 *      PropertyOptions)
+	 */
+	public void setQualifier(String schemaNS, String propName, String qualNS, String qualName,
+			String qualValue, PropertyOptions options) throws XMPException
+	{
+		ParameterAsserts.assertSchemaNS(schemaNS);
+		ParameterAsserts.assertPropName(propName);
+
+		if (!doesPropertyExist(schemaNS, propName))
+		{
+			throw new XMPException("Specified property does not exist!", XMPError.BADXPATH);
+		}
+
+		String qualPath = propName + XMPPathFactory.composeQualifierPath(qualNS, qualName);
+		setProperty(schemaNS, qualPath, qualValue, options);
+	}
+
+	
+	/**
+	 * @see XMPMeta#setQualifier(String, String, String, String, String)
+	 */
+	public void setQualifier(String schemaNS, String propName, String qualNS, String qualName,
+			String qualValue) throws XMPException
+	{
+		setQualifier(schemaNS, propName, qualNS, qualName, qualValue, null);
+		
+	}
+	
+
+	/**
+	 * @see XMPMeta#setStructField(String, String, String, String, String,
+	 *      PropertyOptions)
+	 */
+	public void setStructField(String schemaNS, String structName, String fieldNS,
+			String fieldName, String fieldValue, PropertyOptions options) throws XMPException
+	{
+		ParameterAsserts.assertSchemaNS(schemaNS);
+		ParameterAsserts.assertStructName(structName);
+
+		String fieldPath = structName + XMPPathFactory.composeStructFieldPath(fieldNS, fieldName);
+		setProperty(schemaNS, fieldPath, fieldValue, options);
+	}
+
+	
+	/**
+	 * @see XMPMeta#setStructField(String, String, String, String, String)
+	 */
+	public void setStructField(String schemaNS, String structName, String fieldNS,
+			String fieldName, String fieldValue) throws XMPException
+	{
+		setStructField(schemaNS, structName, fieldNS, fieldName, fieldValue, null);
+	}
+	
+
+	/**
+	 * @see XMPMeta#getObjectName()
+	 */
+	public String getObjectName()
+	{
+		return tree.getName() != null ? tree.getName() : "";
+	}
+
+	
+	/**
+	 * @see XMPMeta#setObjectName(String)
+	 */
+	public void setObjectName(String name)
+	{
+		tree.setName(name);
+	}
+
+	
+	/**
+	 * @see XMPMeta#getPacketHeader()
+	 */
+	public String getPacketHeader()
+	{
+		return packetHeader;
+	}
+
+	
+	/**
+	 * Sets the packetHeader attributes, only used by the parser.
+	 * @param packetHeader the processing instruction content
+	 */
+	public void setPacketHeader(String packetHeader)
+	{
+		this.packetHeader = packetHeader;
+	}
+	
+	
+	/**
+	 * Performs a deep clone of the XMPMeta-object
+	 * 
+	 * @see java.lang.Object#clone()
+	 */
+	public Object clone()
+	{
+		XMPNode clonedTree = (XMPNode) tree.clone();
+		return new XMPMetaImpl(clonedTree);
+	}
+
+
+	/**
+	 * @see XMPMeta#dumpObject()
+	 */
+	public String dumpObject()
+	{
+		// renders tree recursively
+		return getRoot().dumpNode(true);
+	}
+
+	
+	/**
+	 * @see XMPMeta#sort()
+	 */
+	public void sort()
+	{
+		this.tree.sort();
+	}
+
+
+	/**
+	 * @see XMPMeta#normalize(ParseOptions)
+	 */
+	public void normalize(ParseOptions options) throws XMPException
+	{
+		if (options == null)
+		{
+			options = new ParseOptions();
+		}
+		XMPNormalizer.process(this, options);		
+	}
+
+	
+	/**
+	 * @return Returns the root node of the XMP tree.
+	 */
+	public XMPNode getRoot()
+	{
+		return tree;
+	}
+
+
+
+	// -------------------------------------------------------------------------------------
+	// private
+
+
+	/**
+	 * Locate or create the item node and set the value. Note the index
+	 * parameter is one-based! The index can be in the range [1..size + 1] or
+	 * "last()", normalize it and check the insert flags. The order of the
+	 * normalization checks is important. If the array is empty we end up with
+	 * an index and location to set item size + 1.
+	 * 
+	 * @param arrayNode an array node
+	 * @param itemIndex the index where to insert the item
+	 * @param itemValue the item value
+	 * @param itemOptions the options for the new item
+	 * @param insert insert oder overwrite at index position?
+	 * @throws XMPException
+	 */
+	private void doSetArrayItem(XMPNode arrayNode, int itemIndex, String itemValue,
+			PropertyOptions itemOptions, boolean insert) throws XMPException
+	{
+		XMPNode itemNode = new XMPNode(ARRAY_ITEM_NAME, null);
+		itemOptions = XMPNodeUtils.verifySetOptions(itemOptions, itemValue);
+
+		// in insert mode the index after the last is allowed,
+		// even ARRAY_LAST_ITEM points to the index *after* the last.
+		int maxIndex = insert ? arrayNode.getChildrenLength() + 1 : arrayNode.getChildrenLength();
+		if (itemIndex == ARRAY_LAST_ITEM)
+		{
+			itemIndex = maxIndex;
+		}
+
+		if (1 <= itemIndex && itemIndex <= maxIndex)
+		{
+			if (!insert)
+			{
+				arrayNode.removeChild(itemIndex);
+			}
+			arrayNode.addChild(itemIndex, itemNode);
+			setNode(itemNode, itemValue, itemOptions, false);
+		}
+		else
+		{
+			throw new XMPException("Array index out of bounds", XMPError.BADINDEX);
+		}
+	}
+
+
+	/**
+	 * The internals for setProperty() and related calls, used after the node is
+	 * found or created.
+	 * 
+	 * @param node
+	 *            the newly created node
+	 * @param value
+	 *            the node value, can be <code>null</code>
+	 * @param newOptions
+	 *            options for the new node, must not be <code>null</code>.
+	 * @param deleteExisting flag if the existing value is to be overwritten 
+	 * @throws XMPException thrown if options and value do not correspond
+	 */
+	void setNode(XMPNode node, Object value, PropertyOptions newOptions, boolean deleteExisting)
+			throws XMPException
+	{
+		if (deleteExisting)
+		{
+			node.clear();
+		}
+
+		// its checked by setOptions(), if the merged result is a valid options set
+		node.getOptions().mergeWith(newOptions);
+
+		if (!node.getOptions().isCompositeProperty())
+		{
+			// This is setting the value of a leaf node.
+			XMPNodeUtils.setNodeValue(node, value);
+		}
+		else
+		{
+			if (value != null && value.toString().length() > 0)
+			{
+				throw new XMPException("Composite nodes can't have values", XMPError.BADXPATH);
+			}
+
+			node.removeChildren();
+		}
+
+	}
+
+
+	/**
+	 * Evaluates a raw node value to the given value type, apply special
+	 * conversions for defined types in XMP.
+	 * 
+	 * @param valueType
+	 *            an int indicating the value type
+	 * @param propNode
+	 *            the node containing the value
+	 * @return Returns a literal value for the node.
+	 * @throws XMPException
+	 */
+	private Object evaluateNodeValue(int valueType, final XMPNode propNode) throws XMPException
+	{
+		final Object value;
+		String rawValue = propNode.getValue();
+		switch (valueType)
+		{
+		case VALUE_BOOLEAN:
+			value = new Boolean(XMPUtils.convertToBoolean(rawValue));
+			break;
+		case VALUE_INTEGER:
+			value = new Integer(XMPUtils.convertToInteger(rawValue));
+			break;
+		case VALUE_LONG:
+			value = new Long(XMPUtils.convertToLong(rawValue));
+			break;
+		case VALUE_DOUBLE:
+			value = new Double(XMPUtils.convertToDouble(rawValue));
+			break;
+		case VALUE_DATE:
+			value = XMPUtils.convertToDate(rawValue);
+			break;
+		case VALUE_CALENDAR:
+			XMPDateTime dt = XMPUtils.convertToDate(rawValue);
+			value = dt.getCalendar();
+			break;
+		case VALUE_BASE64:
+			value = XMPUtils.decodeBase64(rawValue);
+			break;
+		case VALUE_STRING:
+		default:
+			// leaf values return empty string instead of null
+			// for the other cases the converter methods provides a "null"
+			// value.
+			// a default value can only occur if this method is made public.
+			value = rawValue != null || propNode.getOptions().isCompositeProperty() ? rawValue : "";
+			break;
+		}
+		return value;
+	}
+}
diff --git a/XMPCore/src/com/adobe/xmp/impl/XMPMetaParser.java b/XMPCore/src/com/adobe/xmp/impl/XMPMetaParser.java
new file mode 100644
index 0000000..40c0ce2
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/impl/XMPMetaParser.java
@@ -0,0 +1,411 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.UnsupportedEncodingException;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.ProcessingInstruction;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import com.adobe.xmp.XMPConst;
+import com.adobe.xmp.XMPError;
+import com.adobe.xmp.XMPException;
+import com.adobe.xmp.XMPMeta;
+import com.adobe.xmp.options.ParseOptions;
+
+
+/**
+ * This class replaces the <code>ExpatAdapter.cpp</code> and does the
+ * XML-parsing and fixes the prefix. After the parsing several normalisations
+ * are applied to the XMPTree.
+ * 
+ * @since 01.02.2006
+ */
+public class XMPMetaParser
+{
+	/**  */
+	private static final Object XMP_RDF = new Object();
+	/** the DOM Parser Factory, options are set */ 
+	private static DocumentBuilderFactory factory = createDocumentBuilderFactory();
+
+	/**
+	 * Hidden constructor, initialises the SAX parser handler.
+	 */
+	private XMPMetaParser()
+	{
+		// EMPTY
+	}
+
+	
+
+	/**
+	 * Parses the input source into an XMP metadata object, including
+	 * de-aliasing and normalisation.
+	 * 
+	 * @param input the input can be an <code>InputStream</code>, a <code>String</code> or 
+	 * 			a byte buffer containing the XMP packet.
+	 * @param options the parse options
+	 * @return Returns the resulting XMP metadata object
+	 * @throws XMPException Thrown if parsing or normalisation fails.
+	 */
+	public static XMPMeta parse(Object input, ParseOptions options) throws XMPException
+	{
+		ParameterAsserts.assertNotNull(input);
+		options = options != null ? options : new ParseOptions();
+
+		Document document = parseXml(input, options);
+
+		boolean xmpmetaRequired = options.getRequireXMPMeta();
+		Object[] result = new Object[3];
+		result = findRootNode(document, xmpmetaRequired, result);
+		
+		if (result != null  &&  result[1] == XMP_RDF)
+		{
+			XMPMetaImpl xmp = ParseRDF.parse((Node) result[0]);
+			xmp.setPacketHeader((String) result[2]);
+			
+			// Check if the XMP object shall be normalized
+			if (!options.getOmitNormalization())
+			{
+				return XMPNormalizer.process(xmp, options);
+			}
+			else
+			{
+				return xmp;
+			}
+		}
+		else
+		{
+			// no appropriate root node found, return empty metadata object
+			return new XMPMetaImpl();
+		}
+	}
+
+	
+	/**
+	 * Parses the raw XML metadata packet considering the parsing options.
+	 * Latin-1/ISO-8859-1 can be accepted when the input is a byte stream 
+	 * (some old toolkits versions such packets). The stream is 
+	 * then wrapped in another stream that converts Latin-1 to UTF-8.
+	 * <p>
+	 * If control characters shall be fixed, a reader is used that fixes the chars to spaces
+	 * (if the input is a byte stream is has to be read as character stream).
+	 * <p>   
+	 * Both options reduce the performance of the parser.
+	 *  
+	 * @param input the input can be an <code>InputStream</code>, a <code>String</code> or 
+	 * 			a byte buffer containing the XMP packet.
+	 * @param options the parsing options
+	 * @return Returns the parsed XML document or an exception.
+	 * @throws XMPException Thrown if the parsing fails for different reasons
+	 */
+	private static Document parseXml(Object input, ParseOptions options)
+			throws XMPException
+	{
+		if (input instanceof InputStream)
+		{
+			return parseXmlFromInputStream((InputStream) input, options);
+		}
+		else if (input instanceof byte[])
+		{
+			return parseXmlFromBytebuffer(new ByteBuffer((byte[]) input), options);
+		} 
+		else
+		{
+			return parseXmlFromString((String) input, options);
+		}
+	}
+	
+
+	/**
+	 * Parses XML from an {@link InputStream},
+	 * fixing the encoding (Latin-1 to UTF-8) and illegal control character optionally.
+	 *  
+	 * @param stream an <code>InputStream</code>
+	 * @param options the parsing options
+	 * @return Returns an XML DOM-Document.
+	 * @throws XMPException Thrown when the parsing fails.
+	 */
+	private static Document parseXmlFromInputStream(InputStream stream, ParseOptions options)
+			throws XMPException
+	{
+		if (!options.getAcceptLatin1()  &&  !options.getFixControlChars())
+		{
+			return parseInputSource(new InputSource(stream));
+		}
+		else
+		{
+			// load stream into bytebuffer
+			try
+			{
+				ByteBuffer buffer = new ByteBuffer(stream);
+				return parseXmlFromBytebuffer(buffer, options);
+			}
+			catch (IOException e)
+			{
+				throw new XMPException("Error reading the XML-file",
+						XMPError.BADSTREAM, e);
+			}
+		}
+	}
+
+	
+	/**
+	 * Parses XML from a byte buffer, 
+	 * fixing the encoding (Latin-1 to UTF-8) and illegal control character optionally.
+	 * 
+	 * @param buffer a byte buffer containing the XMP packet
+	 * @param options the parsing options
+	 * @return Returns an XML DOM-Document.
+	 * @throws XMPException Thrown when the parsing fails.
+	 */
+	private static Document parseXmlFromBytebuffer(ByteBuffer buffer, ParseOptions options)
+		throws XMPException
+	{
+		InputSource source = new InputSource(buffer.getByteStream());
+		try
+		{
+			return parseInputSource(source);
+		}
+		catch (XMPException e)
+		{
+			if (e.getErrorCode() == XMPError.BADXML  ||
+				e.getErrorCode() == XMPError.BADSTREAM)
+			{
+				if (options.getAcceptLatin1())
+				{
+					buffer = Latin1Converter.convert(buffer);
+				}
+				
+				if (options.getFixControlChars())
+				{
+					try
+					{
+						String encoding = buffer.getEncoding();
+						Reader fixReader = new FixASCIIControlsReader(
+							new InputStreamReader(
+								buffer.getByteStream(), encoding));
+						return parseInputSource(new InputSource(fixReader));
+					}
+					catch (UnsupportedEncodingException e1)
+					{
+						// can normally not happen as the encoding is provided by a util function
+						throw new XMPException("Unsupported Encoding",
+								XMPError.INTERNALFAILURE, e);
+					}
+				}
+				source = new InputSource(buffer.getByteStream());
+				return parseInputSource(source);
+			}
+			else
+			{
+				throw e;
+			}	
+		}
+	}
+
+
+	/**
+	 * Parses XML from a {@link String}, 
+	 * fixing the illegal control character optionally.
+	 *  
+	 * @param input a <code>String</code> containing the XMP packet
+	 * @param options the parsing options
+	 * @return Returns an XML DOM-Document.
+	 * @throws XMPException Thrown when the parsing fails.
+	 */
+	private static Document parseXmlFromString(String input, ParseOptions options)
+			throws XMPException
+	{
+		InputSource source = new InputSource(new StringReader(input));
+		try
+		{
+			return parseInputSource(source);
+		}
+		catch (XMPException e)
+		{
+			if (e.getErrorCode() == XMPError.BADXML  &&  options.getFixControlChars())
+			{
+				source = new InputSource(new FixASCIIControlsReader(new StringReader(input)));
+				return parseInputSource(source);
+			}
+			else
+			{
+				throw e;
+			}	
+		}
+	}
+
+	
+	/**
+	 * Runs the XML-Parser. 
+	 * @param source an <code>InputSource</code>
+	 * @return Returns an XML DOM-Document.
+	 * @throws XMPException Wraps parsing and I/O-exceptions into an XMPException.
+	 */
+	private static Document parseInputSource(InputSource source) throws XMPException
+	{
+		try
+		{
+			DocumentBuilder builder = factory.newDocumentBuilder();
+			builder.setErrorHandler(null);
+			return builder.parse(source);
+		}
+		catch (SAXException e)
+		{
+			throw new XMPException("XML parsing failure", XMPError.BADXML, e);
+		}
+		catch (ParserConfigurationException e)
+		{
+			throw new XMPException("XML Parser not correctly configured",
+					XMPError.UNKNOWN, e);
+		}
+		catch (IOException e)
+		{
+			throw new XMPException("Error reading the XML-file", XMPError.BADSTREAM, e);
+		}
+	}
+	
+
+	/**
+	 * Find the XML node that is the root of the XMP data tree. Generally this
+	 * will be an outer node, but it could be anywhere if a general XML document
+	 * is parsed (e.g. SVG). The XML parser counted all rdf:RDF and
+	 * pxmp:XMP_Packet nodes, and kept a pointer to the last one. If there is
+	 * more than one possible root use PickBestRoot to choose among them.
+	 * <p>
+	 * If there is a root node, try to extract the version of the previous XMP
+	 * toolkit.
+	 * <p>
+	 * Pick the first x:xmpmeta among multiple root candidates. If there aren't
+	 * any, pick the first bare rdf:RDF if that is allowed. The returned root is
+	 * the rdf:RDF child if an x:xmpmeta element was chosen. The search is
+	 * breadth first, so a higher level candiate is chosen over a lower level
+	 * one that was textually earlier in the serialized XML.
+	 * 
+	 * @param root the root of the xml document
+	 * @param xmpmetaRequired flag if the xmpmeta-tag is still required, might be set 
+	 * 		initially to <code>true</code>, if the parse option "REQUIRE_XMP_META" is set
+	 * @param result The result array that is filled during the recursive process.
+	 * @return Returns an array that contains the result or <code>null</code>. 
+	 * 		   The array contains:
+	 * <ol>
+	 * 		<li>the rdf:RDF-node
+	 * 		<li>an object that is either XMP_RDF or XMP_PLAIN
+	 * 		<li>a flag that is true if a <?xpacket..> processing instruction has been found
+	 * 		<li>the body text of the xpacket-instruction.
+	 * </ol>
+	 * 
+	 */
+	private static Object[] findRootNode(Node root, boolean xmpmetaRequired, Object[] result)
+	{
+		// Look among this parent's content for x:xapmeta or x:xmpmeta.
+		// The recursion for x:xmpmeta is broader than the strictly defined choice, 
+		// but gives us smaller code.
+		NodeList children = root.getChildNodes();
+		for (int i = 0; i < children.getLength(); i++)
+		{
+			root = children.item(i);
+			if (Node.PROCESSING_INSTRUCTION_NODE == root.getNodeType()  &&
+				((ProcessingInstruction) root).getTarget() == XMPConst.XMP_PI)
+			{
+				// Store the processing instructions content
+				if (result != null)
+				{	
+					result[2] = ((ProcessingInstruction) root).getData();
+				}	
+			}
+			else if (Node.TEXT_NODE != root.getNodeType()  &&  
+				Node.PROCESSING_INSTRUCTION_NODE != root.getNodeType())
+			{	
+				String rootNS = root.getNamespaceURI();
+				String rootLocal = root.getLocalName();
+				if (
+						(
+							XMPConst.TAG_XMPMETA.equals(rootLocal)  ||  
+							XMPConst.TAG_XAPMETA.equals(rootLocal)
+						)  &&
+						XMPConst.NS_X.equals(rootNS)
+				   )
+				{
+					// by not passing the RequireXMPMeta-option, the rdf-Node will be valid
+					return findRootNode(root, false, result);
+				}
+				else if (!xmpmetaRequired  &&
+						"RDF".equals(rootLocal)  &&
+						 XMPConst.NS_RDF.equals(rootNS))
+				{	
+					if (result != null)
+					{	
+						result[0] = root;
+						result[1] = XMP_RDF;
+					}	
+					return result;
+				}
+				else
+				{
+					// continue searching
+					Object[] newResult = findRootNode(root, xmpmetaRequired, result);
+					if (newResult != null)
+					{
+						return newResult;
+					}
+					else
+					{
+						continue;
+					}	
+				}
+			}	
+		}
+
+		// no appropriate node has been found
+		return null;
+		//     is extracted here in the C++ Toolkit		
+	}
+
+	
+	/**
+	 * @return Creates, configures and returnes the document builder factory for
+	 *         the Metadata Parser.
+	 */
+	private static DocumentBuilderFactory createDocumentBuilderFactory()
+	{
+		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+		factory.setNamespaceAware(true);
+		factory.setIgnoringComments(true);
+		
+		try
+		{
+			// honor System parsing limits, e.g.
+			// System.setProperty("entityExpansionLimit", "10");
+			factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+		}
+		catch (Exception e)
+		{
+			// Ignore IllegalArgumentException and ParserConfigurationException
+			// in case the configured XML-Parser does not implement the feature.
+		}		
+		return factory;
+	}
+}
\ No newline at end of file
diff --git a/XMPCore/src/com/adobe/xmp/impl/XMPNode.java b/XMPCore/src/com/adobe/xmp/impl/XMPNode.java
new file mode 100644
index 0000000..9170efe
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/impl/XMPNode.java
@@ -0,0 +1,921 @@
+//=================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp.impl;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+import com.adobe.xmp.XMPConst;
+import com.adobe.xmp.XMPError;
+import com.adobe.xmp.XMPException;
+import com.adobe.xmp.options.PropertyOptions;
+
+
+/**
+ * A node in the internally XMP tree, which can be a schema node, a property node, an array node,
+ * an array item, a struct node or a qualifier node (without '?').
+ * 
+ * Possible improvements:
+ * 
+ * 1. The kind Node of node might be better represented by a class-hierarchy of different nodes.
+ * 2. The array type should be an enum
+ * 3. isImplicitNode should be removed completely and replaced by return values of fi.
+ * 4. hasLanguage, hasType should be automatically maintained by XMPNode
+ * 
+ * @since 21.02.2006
+ */
+class XMPNode implements Comparable
+{
+	/** name of the node, contains different information depending of the node kind */
+	private String name;
+	/** value of the node, contains different information depending of the node kind */
+	private String value;
+	/** link to the parent node */
+	private XMPNode parent;
+	/** list of child nodes, lazy initialized */
+	private List children = null; 
+	/** list of qualifier of the node, lazy initialized */
+	private List qualifier = null;
+	/** options describing the kind of the node */
+	private PropertyOptions options = null;
+	
+	// internal processing options
+	
+	/** flag if the node is implicitly created */
+	private boolean implicit;
+	/** flag if the node has aliases */
+	private boolean hasAliases;
+	/** flag if the node is an alias */
+	private boolean alias;
+	/** flag if the node has an "rdf:value" child node. */
+	private boolean hasValueChild;
+	
+	
+	
+	/**
+	 * Creates an <code>XMPNode</code> with initial values.
+	 * 
+	 * @param name the name of the node
+	 * @param value the value of the node
+	 * @param options the options of the node
+	 */
+	public XMPNode(String name, String value, PropertyOptions options)
+	{
+		this.name = name;
+		this.value = value;
+		this.options = options;
+	}
+
+	
+	/**
+	 * Constructor for the node without value.
+	 * 
+	 * @param name the name of the node
+	 * @param options the options of the node
+	 */
+	public XMPNode(String name, PropertyOptions options)
+	{
+		this(name, null, options);
+	}
+	
+
+	/**
+	 * Resets the node.
+	 */
+	public void clear()
+	{
+		options = null;
+		name = null;
+		value = null;
+		children = null;
+		qualifier = null;
+	}
+
+	
+	/**
+	 * @return Returns the parent node.
+	 */
+	public XMPNode getParent()
+	{
+		return parent;
+	}
+
+	
+	/**
+	 * @param index an index [1..size]
+	 * @return Returns the child with the requested index.
+	 */
+	public XMPNode getChild(int index)
+	{
+		return (XMPNode) getChildren().get(index - 1);
+	}
+	
+	
+	/**
+	 * Adds a node as child to this node.
+	 * @param node an XMPNode
+	 * @throws XMPException 
+	 */
+	public void addChild(XMPNode node) throws XMPException
+	{
+		// check for duplicate properties
+		assertChildNotExisting(node.getName());
+		node.setParent(this);
+		getChildren().add(node);
+	}
+
+	
+	/**
+	 * Adds a node as child to this node.
+	 * @param index the index of the node <em>before</em> which the new one is inserted.
+	 * <em>Note:</em> The node children are indexed from [1..size]! 
+	 * An index of size + 1 appends a node.   
+	 * @param node an XMPNode
+	 * @throws XMPException 
+	 */
+	public void addChild(int index, XMPNode node) throws XMPException
+	{
+		assertChildNotExisting(node.getName());
+		node.setParent(this);
+		getChildren().add(index - 1, node);
+	}
+
+	
+	/**
+	 * Replaces a node with another one.
+	 * @param index the index of the node that will be replaced.
+	 * <em>Note:</em> The node children are indexed from [1..size]! 
+	 * @param node the replacement XMPNode
+	 */
+	public void replaceChild(int index, XMPNode node)
+	{
+		node.setParent(this);
+		getChildren().set(index - 1, node);
+	}
+	
+	
+	/**
+	 * Removes a child at the requested index.
+	 * @param itemIndex the index to remove [1..size] 
+	 */
+	public void removeChild(int itemIndex)
+	{
+		getChildren().remove(itemIndex - 1);
+		cleanupChildren();
+	}
+	
+	
+	/**
+	 * Removes a child node.
+	 * If its a schema node and doesn't have any children anymore, its deleted.
+	 * 
+	 * @param node the child node to delete.
+	 */
+	public void removeChild(XMPNode node)
+	{
+		getChildren().remove(node);
+		cleanupChildren();
+	}
+
+
+	/**
+	 * Removes the children list if this node has no children anymore;
+	 * checks if the provided node is a schema node and doesn't have any children anymore, 
+	 * its deleted.
+	 */
+	protected void cleanupChildren()
+	{
+		if (children.isEmpty())
+		{
+			children = null;
+		}
+	}
+
+	
+	/**
+	 * Removes all children from the node. 
+	 */ 
+	public void removeChildren()
+	{
+		children = null;
+	}
+
+	
+	/**
+	 * @return Returns the number of children without neccessarily creating a list.
+	 */
+	public int getChildrenLength()
+	{
+		return children != null ?
+			children.size() :	
+			0;
+	}
+
+	
+	/**
+	 * @param expr child node name to look for
+	 * @return Returns an <code>XMPNode</code> if node has been found, <code>null</code> otherwise. 
+	 */
+	public XMPNode findChildByName(String expr)
+	{
+		return find(getChildren(), expr);
+	}
+
+	
+	/**
+	 * @param index an index [1..size]
+	 * @return Returns the qualifier with the requested index.
+	 */
+	public XMPNode getQualifier(int index)
+	{
+		return (XMPNode) getQualifier().get(index - 1);
+	}
+	
+	
+	/**
+	 * @return Returns the number of qualifier without neccessarily creating a list.
+	 */
+	public int getQualifierLength()
+	{
+		return qualifier != null ?
+			qualifier.size() :	
+			0;
+	}
+	
+	
+	/**
+	 * Appends a qualifier to the qualifier list and sets respective options.
+	 * @param qualNode a qualifier node.
+	 * @throws XMPException 
+	 */
+	public void addQualifier(XMPNode qualNode) throws XMPException
+	{
+		assertQualifierNotExisting(qualNode.getName());
+		qualNode.setParent(this);
+		qualNode.getOptions().setQualifier(true);
+		getOptions().setHasQualifiers(true);
+		
+		// contraints
+		if (qualNode.isLanguageNode())
+		{
+			// "xml:lang" is always first and the option "hasLanguage" is set
+			options.setHasLanguage(true);
+			getQualifier().add(0, qualNode);
+		}
+		else if (qualNode.isTypeNode())
+		{
+			// "rdf:type" must be first or second after "xml:lang" and the option "hasType" is set
+			options.setHasType(true);
+			getQualifier().add(
+				!options.getHasLanguage() ? 0 : 1,	
+				qualNode);
+		}
+		else
+		{
+			// other qualifiers are appended
+			getQualifier().add(qualNode);
+		}	
+	}
+
+	
+	/**
+	 * Removes one qualifier node and fixes the options.
+	 * @param qualNode qualifier to remove
+	 */
+	public void removeQualifier(XMPNode qualNode)
+	{
+		PropertyOptions opts = getOptions();
+		if (qualNode.isLanguageNode())
+		{
+			// if "xml:lang" is removed, remove hasLanguage-flag too
+			opts.setHasLanguage(false);
+		}
+		else if (qualNode.isTypeNode())
+		{
+			// if "rdf:type" is removed, remove hasType-flag too
+			opts.setHasType(false);
+		}
+		
+		getQualifier().remove(qualNode);
+		if (qualifier.isEmpty())
+		{
+			opts.setHasQualifiers(false);
+			qualifier = null;
+		}
+		
+	}
+
+	
+	/**
+	 * Removes all qualifiers from the node and sets the options appropriate. 
+	 */ 
+	public void removeQualifiers()
+	{
+		PropertyOptions opts = getOptions();
+		// clear qualifier related options
+		opts.setHasQualifiers(false);
+		opts.setHasLanguage(false);
+		opts.setHasType(false);
+		qualifier = null;
+	}
+
+
+	/**
+	 * @param expr qualifier node name to look for
+	 * @return Returns a qualifier <code>XMPNode</code> if node has been found, 
+	 * <code>null</code> otherwise. 
+	 */
+	public XMPNode findQualifierByName(String expr)
+	{
+		return find(qualifier, expr);
+	}
+	
+
+	/**
+	 * @return Returns whether the node has children.
+	 */
+	public boolean hasChildren()
+	{
+		return children != null  &&  children.size() > 0;
+	}	
+	
+
+	/**
+	 * @return Returns an iterator for the children.
+	 * <em>Note:</em> take care to use it.remove(), as the flag are not adjusted in that case.
+	 */
+	public Iterator iterateChildren()
+	{
+		if (children != null)
+		{
+			return getChildren().iterator();
+		}
+		else
+		{
+			return Collections.EMPTY_LIST.listIterator();
+		}
+	}
+	
+	
+	/**
+	 * @return Returns whether the node has qualifier attached.
+	 */
+	public boolean hasQualifier()
+	{
+		return qualifier != null  &&  qualifier.size() > 0;
+	}
+	
+	
+	/**
+	 * @return Returns an iterator for the qualifier.
+	 * <em>Note:</em> take care to use it.remove(), as the flag are not adjusted in that case.
+	 */
+	public Iterator iterateQualifier()
+	{
+		if (qualifier != null)
+		{
+			final Iterator it = getQualifier().iterator();
+			
+			return new Iterator()
+			{
+				public boolean hasNext()
+				{
+					return it.hasNext();
+				}
+
+				public Object next()
+				{
+					return it.next();
+				}
+
+				public void remove()
+				{
+					throw new UnsupportedOperationException(
+							"remove() is not allowed due to the internal contraints");
+				}
+				
+			};
+		}
+		else
+		{
+			return Collections.EMPTY_LIST.iterator();
+		}
+	}
+	
+	
+	/**
+	 * Performs a <b>deep clone</b> of the node and the complete subtree.
+	 * 
+	 * @see java.lang.Object#clone()
+	 */
+	public Object clone()
+	{
+		PropertyOptions newOptions;
+		try
+		{
+			newOptions = new PropertyOptions(getOptions().getOptions());
+		}
+		catch (XMPException e)
+		{
+			// cannot happen
+			newOptions = new PropertyOptions();
+		}
+		
+		XMPNode newNode = new XMPNode(name, value, newOptions);
+		cloneSubtree(newNode);
+		
+		return newNode;
+	}
+	
+	
+	/**
+	 * Performs a <b>deep clone</b> of the complete subtree (children and
+	 * qualifier )into and add it to the destination node.
+	 * 
+	 * @param destination the node to add the cloned subtree
+	 */
+	public void cloneSubtree(XMPNode destination)
+	{
+		try
+		{
+			for (Iterator it = iterateChildren(); it.hasNext();)
+			{
+				XMPNode child = (XMPNode) it.next();
+				destination.addChild((XMPNode) child.clone());
+			}
+			
+			for (Iterator it = iterateQualifier(); it.hasNext();)
+			{
+				XMPNode qualifier = (XMPNode) it.next();
+				destination.addQualifier((XMPNode) qualifier.clone());
+			}
+		}
+		catch (XMPException e)
+		{
+			// cannot happen (duplicate childs/quals do not exist in this node)
+			assert false;
+		}
+		
+	}	
+	
+	
+	/** 
+	 * Renders this node and the tree unter this node in a human readable form.
+	 * @param recursive Flag is qualifier and child nodes shall be rendered too
+	 * @return Returns a multiline string containing the dump.
+	 */
+	public String dumpNode(boolean recursive)
+	{
+		StringBuffer result = new StringBuffer(512);
+		this.dumpNode(result, recursive, 0, 0);
+		return result.toString();
+	}
+	
+	
+	/**
+	 * @see Comparable#compareTo(Object) 
+	 */
+	public int compareTo(Object xmpNode)
+	{
+		if (getOptions().isSchemaNode())
+		{
+			return this.value.compareTo(((XMPNode) xmpNode).getValue());
+		}
+		else
+		{
+			return this.name.compareTo(((XMPNode) xmpNode).getName());
+		}	
+	}
+	
+	
+	/**
+	 * @return Returns the name.
+	 */
+	public String getName()
+	{
+		return name;
+	}
+
+
+	/**
+	 * @param name The name to set.
+	 */
+	public void setName(String name)
+	{
+		this.name = name;
+	}
+
+
+	/**
+	 * @return Returns the value.
+	 */
+	public String getValue()
+	{
+		return value;
+	}
+
+
+	/**
+	 * @param value The value to set.
+	 */
+	public void setValue(String value)
+	{
+		this.value = value;
+	}	
+
+	
+	/**
+	 * @return Returns the options.
+	 */
+	public PropertyOptions getOptions()
+	{
+		if (options == null)
+		{
+			options = new PropertyOptions();
+		}
+		return options;
+	}
+
+	
+	/**
+	 * Updates the options of the node.
+	 * @param options the options to set.
+	 */
+	public void setOptions(PropertyOptions options)
+	{
+		this.options = options;
+	}
+
+	
+	/**
+	 * @return Returns the implicit flag
+	 */
+	public boolean isImplicit()
+	{
+		return implicit;
+	}
+
+
+	/**
+	 * @param implicit Sets the implicit node flag
+	 */
+	public void setImplicit(boolean implicit)
+	{
+		this.implicit = implicit;
+	}
+	
+	
+	/**
+	 * @return Returns if the node contains aliases (applies only to schema nodes)
+	 */
+	public boolean getHasAliases()
+	{
+		return hasAliases;
+	}
+
+
+	/**
+	 * @param hasAliases sets the flag that the node contains aliases
+	 */
+	public void setHasAliases(boolean hasAliases)
+	{
+		this.hasAliases = hasAliases;
+	}	
+	
+	
+	/**
+	 * @return Returns if the node contains aliases (applies only to schema nodes)
+	 */
+	public boolean isAlias()
+	{
+		return alias;
+	}
+
+
+	/**
+	 * @param alias sets the flag that the node is an alias
+	 */
+	public void setAlias(boolean alias)
+	{
+		this.alias = alias;
+	}	
+	
+	
+	/**
+	 * @return the hasValueChild
+	 */
+	public boolean getHasValueChild()
+	{
+		return hasValueChild;
+	}
+
+
+	/**
+	 * @param hasValueChild the hasValueChild to set
+	 */
+	public void setHasValueChild(boolean hasValueChild)
+	{
+		this.hasValueChild = hasValueChild;
+	}
+	
+	
+	
+	/**
+	 * Sorts the complete datamodel according to the following rules:
+	 * <ul>
+	 * 		<li>Nodes at one level are sorted by name, that is prefix + local name
+	 * 		<li>Starting at the root node the children and qualifier are sorted recursively, 
+	 * 			which the following exceptions.
+	 * 		<li>Sorting will not be used for arrays.
+	 * 		<li>Within qualifier "xml:lang" and/or "rdf:type" stay at the top in that order, 
+	 * 			all others are sorted.  
+	 * </ul>
+	 */
+	public void sort()
+	{
+		// sort qualifier
+		if (hasQualifier())
+		{
+			XMPNode[] quals = (XMPNode[]) getQualifier()
+				.toArray(new XMPNode[getQualifierLength()]);
+			int sortFrom = 0;
+			while (
+					quals.length > sortFrom  &&
+					(XMPConst.XML_LANG.equals(quals[sortFrom].getName())  ||
+					 "rdf:type".equals(quals[sortFrom].getName()))
+				  )		 
+			{
+				quals[sortFrom].sort();
+				sortFrom++;
+			}
+
+			Arrays.sort(quals, sortFrom, quals.length);
+			ListIterator it = qualifier.listIterator();
+			for (int j = 0; j < quals.length; j++)
+			{
+				it.next();
+				it.set(quals[j]);
+				quals[j].sort();
+			}
+		}
+		
+		// sort children
+		if (hasChildren())
+		{	
+			if (!getOptions().isArray())
+			{
+				Collections.sort(children);
+			}
+			for (Iterator it = iterateChildren(); it.hasNext();)
+			{
+				((XMPNode) it.next()).sort();
+				
+			}
+		}
+	}	
+	
+	
+	
+	//------------------------------------------------------------------------------ private methods
+
+	
+	/**
+	 * Dumps this node and its qualifier and children recursively.
+	 * <em>Note:</em> It creats empty options on every node.
+	 *  
+	 * @param result the buffer to append the dump.
+	 * @param recursive Flag is qualifier and child nodes shall be rendered too
+	 * @param indent the current indent level.
+	 * @param index the index within the parent node (important for arrays) 
+	 */
+	private void dumpNode(StringBuffer result, boolean recursive, int indent, int index)
+	{
+		// write indent
+		for (int i = 0; i < indent; i++)
+		{
+			result.append('\t');
+		}
+		
+		// render Node
+		if (parent != null)
+		{
+			if (getOptions().isQualifier())
+			{
+				result.append('?');
+				result.append(name);
+			}
+			else if (getParent().getOptions().isArray())
+			{
+				result.append('[');
+				result.append(index);
+				result.append(']');
+			}
+			else
+			{
+				result.append(name);
+			}
+		}
+		else
+		{
+			// applies only to the root node
+			result.append("ROOT NODE");
+			if (name != null  &&  name.length() > 0)
+			{
+				// the "about" attribute
+				result.append(" (");
+				result.append(name);
+				result.append(')');
+			}	
+		}
+		
+		if (value != null  &&  value.length() > 0)
+		{
+			result.append(" = \"");
+			result.append(value);
+			result.append('"');
+		}
+		
+		// render options if at least one is set
+		if (getOptions().containsOneOf(0xffffffff))
+		{
+			result.append("\t(");
+			result.append(getOptions().toString());
+			result.append(" : ");
+			result.append(getOptions().getOptionsString());
+			result.append(')');
+		}
+		
+		result.append('\n');
+		
+		// render qualifier
+		if (recursive  &&  hasQualifier())
+		{
+			XMPNode[] quals = (XMPNode[]) getQualifier()
+				.toArray(new XMPNode[getQualifierLength()]);
+			int i = 0;
+			while (quals.length > i  &&
+					(XMPConst.XML_LANG.equals(quals[i].getName())  ||
+					 "rdf:type".equals(quals[i].getName()))
+				  )		 
+			{
+				i++;
+			}
+			Arrays.sort(quals, i, quals.length);
+			for (i = 0; i < quals.length; i++)
+			{
+				XMPNode qualifier = quals[i];
+				qualifier.dumpNode(result, recursive, indent + 2, i + 1);
+			}
+		}
+		
+		// render children
+		if (recursive  &&  hasChildren())
+		{	
+			XMPNode[] children = (XMPNode[]) getChildren()
+				.toArray(new XMPNode[getChildrenLength()]);
+			if (!getOptions().isArray())
+			{	
+				Arrays.sort(children);
+			}
+			for (int i = 0; i < children.length; i++)
+			{
+				XMPNode child = children[i];
+				child.dumpNode(result, recursive, indent + 1, i + 1);
+			}
+		}
+	}
+	
+	
+	/**
+	 * @return Returns whether this node is a language qualifier. 
+	 */
+	private boolean isLanguageNode()
+	{
+		return XMPConst.XML_LANG.equals(name);
+	}
+
+	
+	/**
+	 * @return Returns whether this node is a type qualifier. 
+	 */
+	private boolean isTypeNode()
+	{
+		return "rdf:type".equals(name);
+	}
+	
+
+	/**
+	 * <em>Note:</em> This method should always be called when accessing 'children' to be sure
+	 * that its initialized.
+	 * @return Returns list of children that is lazy initialized.
+	 */
+	private List getChildren()
+	{
+		if (children == null)
+		{
+			children = new ArrayList(0);
+		}
+		return children;
+	}
+
+	
+	/**
+	 * @return Returns a read-only copy of child nodes list.
+	 */
+	public List getUnmodifiableChildren()
+	{
+		return Collections.unmodifiableList(new ArrayList(getChildren()));
+	}
+	
+	
+	/**
+	 * @return Returns list of qualifier that is lazy initialized.
+	 */
+	private List getQualifier()
+	{
+		if (qualifier == null)
+		{
+			qualifier = new ArrayList(0);
+		}
+		return qualifier;
+	}
+	
+	
+	/**
+	 * Sets the parent node, this is solely done by <code>addChild(...)</code>
+	 * and <code>addQualifier()</code>.
+	 * 
+	 * @param parent
+	 *            Sets the parent node.
+	 */
+	protected void setParent(XMPNode parent)
+	{
+		this.parent = parent;
+	}
+
+	
+	/**
+	 * Internal find.
+	 * @param list the list to search in
+	 * @param expr the search expression
+	 * @return Returns the found node or <code>nulls</code>.
+	 */
+	private XMPNode find(List list, String expr)
+	{
+		
+		if (list != null)
+		{	
+			for (Iterator it = list.iterator(); it.hasNext();)
+			{
+				XMPNode child = (XMPNode) it.next();
+				if (child.getName().equals(expr))
+				{
+					return child;
+				}
+			}
+		}	
+		return null;
+	}
+	
+	
+	/**
+	 * Checks that a node name is not existing on the same level, except for array items.
+	 * @param childName the node name to check
+	 * @throws XMPException Thrown if a node with the same name is existing.
+	 */
+	private void assertChildNotExisting(String childName) throws XMPException
+	{
+		if (!XMPConst.ARRAY_ITEM_NAME.equals(childName)  &&
+			findChildByName(childName) != null)
+		{
+			throw new XMPException("Duplicate property or field node '" + childName + "'",
+					XMPError.BADXMP);
+		}
+	}
+	
+	
+	/**
+	 * Checks that a qualifier name is not existing on the same level.
+	 * @param qualifierName the new qualifier name
+	 * @throws XMPException Thrown if a node with the same name is existing.
+	 */
+	private void assertQualifierNotExisting(String qualifierName) throws XMPException
+	{
+		if (!XMPConst.ARRAY_ITEM_NAME.equals(qualifierName)  &&
+			findQualifierByName(qualifierName) != null)
+		{
+			throw new XMPException("Duplicate '" + qualifierName + "' qualifier", XMPError.BADXMP);
+		}
+	}
+}
\ No newline at end of file
diff --git a/XMPCore/src/com/adobe/xmp/impl/XMPNodeUtils.java b/XMPCore/src/com/adobe/xmp/impl/XMPNodeUtils.java
new file mode 100644
index 0000000..8537469
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/impl/XMPNodeUtils.java
@@ -0,0 +1,924 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp.impl;
+
+import java.util.GregorianCalendar;
+import java.util.Iterator;
+
+import com.adobe.xmp.XMPConst;
+import com.adobe.xmp.XMPDateTime;
+import com.adobe.xmp.XMPDateTimeFactory;
+import com.adobe.xmp.XMPError;
+import com.adobe.xmp.XMPException;
+import com.adobe.xmp.XMPMetaFactory;
+import com.adobe.xmp.XMPUtils;
+import com.adobe.xmp.impl.xpath.XMPPath;
+import com.adobe.xmp.impl.xpath.XMPPathSegment;
+import com.adobe.xmp.options.AliasOptions;
+import com.adobe.xmp.options.PropertyOptions;
+
+
+/**
+ * Utilities for <code>XMPNode</code>.
+ * 
+ * @since   Aug 28, 2006
+ */
+public class XMPNodeUtils implements XMPConst
+{
+	/** */
+	static final int CLT_NO_VALUES = 0;
+	/** */
+	static final int CLT_SPECIFIC_MATCH = 1;
+	/** */
+	static final int CLT_SINGLE_GENERIC = 2;
+	/** */
+	static final int CLT_MULTIPLE_GENERIC = 3;
+	/** */
+	static final int CLT_XDEFAULT = 4;
+	/** */
+	static final int CLT_FIRST_ITEM = 5;
+
+
+	/**
+	 * Private Constructor
+	 */
+	private XMPNodeUtils()
+	{
+		// EMPTY
+	}
+
+	
+	/**
+	 * Find or create a schema node if <code>createNodes</code> is false and
+	 *  
+	 * @param tree the root of the xmp tree. 
+	 * @param namespaceURI a namespace
+	 * @param createNodes a flag indicating if the node shall be created if not found.
+	 * 		  <em>Note:</em> The namespace must be registered prior to this call.
+	 * 
+	 * @return Returns the schema node if found, <code>null</code> otherwise.
+	 * 		   Note: If <code>createNodes</code> is <code>true</code>, it is <b>always</b>
+	 * 		   returned a valid node. 
+	 * @throws XMPException An exception is only thrown if an error occurred, not if a
+	 *         		node was not found.
+	 */
+	static XMPNode findSchemaNode(XMPNode tree, String namespaceURI,
+			boolean createNodes)
+			throws XMPException
+	{
+		return findSchemaNode(tree, namespaceURI, null, createNodes);
+	}
+	
+	
+	/**
+	 * Find or create a schema node if <code>createNodes</code> is true.
+	 *  
+	 * @param tree the root of the xmp tree. 
+	 * @param namespaceURI a namespace
+	 * @param suggestedPrefix If a prefix is suggested, the namespace is allowed to be registered.
+	 * @param createNodes a flag indicating if the node shall be created if not found.
+	 * 		  <em>Note:</em> The namespace must be registered prior to this call.
+	 * 
+	 * @return Returns the schema node if found, <code>null</code> otherwise.
+	 * 		   Note: If <code>createNodes</code> is <code>true</code>, it is <b>always</b>
+	 * 		   returned a valid node. 
+	 * @throws XMPException An exception is only thrown if an error occurred, not if a
+	 *         		node was not found.
+	 */
+	static XMPNode findSchemaNode(XMPNode tree, String namespaceURI, String suggestedPrefix,
+			boolean createNodes)
+			throws XMPException
+	{
+		assert tree.getParent() == null; // make sure that its the root
+		XMPNode schemaNode = tree.findChildByName(namespaceURI);
+		
+		if (schemaNode == null  &&  createNodes)
+		{
+			schemaNode = new XMPNode(namespaceURI, 
+				new PropertyOptions()
+					.setSchemaNode(true));
+			schemaNode.setImplicit(true);
+			
+			// only previously registered schema namespaces are allowed in the XMP tree.
+			String prefix = XMPMetaFactory.getSchemaRegistry().getNamespacePrefix(namespaceURI);
+			if (prefix == null)
+			{	
+				if (suggestedPrefix != null  &&  suggestedPrefix.length() != 0)
+				{
+					prefix = XMPMetaFactory.getSchemaRegistry().registerNamespace(namespaceURI,
+							suggestedPrefix);
+				}
+				else
+				{
+					throw new XMPException("Unregistered schema namespace URI",
+							XMPError.BADSCHEMA);
+				}	
+			}	
+				
+			schemaNode.setValue(prefix);
+	
+			tree.addChild(schemaNode);
+		}
+		
+		return schemaNode;
+	}
+
+	
+	/**
+	 * Find or create a child node under a given parent node. If the parent node is no 
+	 * Returns the found or created child node.
+	 * 
+	 * @param parent
+	 *            the parent node
+	 * @param childName
+	 *            the node name to find
+	 * @param createNodes
+	 *            flag, if new nodes shall be created.
+	 * @return Returns the found or created node or <code>null</code>.
+	 * @throws XMPException Thrown if 
+	 */
+	static XMPNode findChildNode(XMPNode parent, String childName, boolean createNodes)
+			throws XMPException
+	{
+		if (!parent.getOptions().isSchemaNode() && !parent.getOptions().isStruct())
+		{
+			if (!parent.isImplicit())
+			{
+				throw new XMPException("Named children only allowed for schemas and structs",
+						XMPError.BADXPATH);
+			}	
+			else if (parent.getOptions().isArray())
+			{
+				throw new XMPException("Named children not allowed for arrays",
+						XMPError.BADXPATH);
+			}
+			else if (createNodes)
+			{	
+				parent.getOptions().setStruct(true);
+			}	
+		}
+			
+		XMPNode childNode = parent.findChildByName(childName); 
+		
+		if (childNode == null  &&  createNodes)
+		{
+			PropertyOptions options = new PropertyOptions();
+			childNode = new XMPNode(childName, options);
+			childNode.setImplicit(true);
+			parent.addChild(childNode);
+		}
+		
+		assert childNode != null ||  !createNodes;
+	
+		return childNode;
+	}
+
+
+	/**
+	 * Follow an expanded path expression to find or create a node.
+	 * 
+	 * @param xmpTree the node to begin the search. 
+	 * @param xpath the complete xpath
+	 * @param createNodes flag if nodes shall be created 
+	 * 			(when called by <code>setProperty()</code>)
+	 * @param leafOptions the options for the created leaf nodes (only when
+	 *			<code>createNodes == true</code>).
+	 * @return Returns the node if found or created or <code>null</code>.
+	 * @throws XMPException An exception is only thrown if an error occurred, 
+	 * 			not if a node was not found.
+	 */
+	static XMPNode findNode(XMPNode xmpTree, XMPPath xpath, boolean createNodes,
+		PropertyOptions leafOptions) throws XMPException
+	{
+		// check if xpath is set.
+		if (xpath == null  ||  xpath.size() == 0)
+		{
+			throw new XMPException("Empty XMPPath", XMPError.BADXPATH);
+		}
+
+		// Root of implicitly created subtree to possible delete it later. 
+		// Valid only if leaf is new.
+		XMPNode rootImplicitNode = null; 
+		XMPNode currNode = null;
+		
+		// resolve schema step
+		currNode = findSchemaNode(xmpTree,
+			xpath.getSegment(XMPPath.STEP_SCHEMA).getName(), createNodes);
+		if (currNode == null) 
+		{
+			return null;
+		}
+		else if (currNode.isImplicit())
+		{
+			currNode.setImplicit(false);	// Clear the implicit node bit.
+			rootImplicitNode = currNode;	// Save the top most implicit node.
+		}
+
+
+		// Now follow the remaining steps of the original XMPPath.
+		try
+		{
+			for (int i = 1; i < xpath.size(); i++)
+			{
+				currNode = followXPathStep(currNode, xpath.getSegment(i), createNodes);
+				if (currNode == null) 
+				{
+					if (createNodes)
+					{	
+						// delete implicitly created nodes
+						deleteNode(rootImplicitNode);
+					}	
+					return null;
+				}
+				else if (currNode.isImplicit())
+				{
+					// clear the implicit node flag
+					currNode.setImplicit(false);
+
+					// if node is an ALIAS (can be only in root step, auto-create array 
+					// when the path has been resolved from a not simple alias type
+					if (i == 1  &&  
+						xpath.getSegment(i).isAlias()  &&
+						xpath.getSegment(i).getAliasForm() != 0)
+					{
+						currNode.getOptions().setOption(xpath.getSegment(i).getAliasForm(), true);
+					}
+					// "CheckImplicitStruct" in C++
+					else if (i < xpath.size() - 1  &&
+						xpath.getSegment(i).getKind() == XMPPath.STRUCT_FIELD_STEP  &&	
+						!currNode.getOptions().isCompositeProperty())
+					{
+						currNode.getOptions().setStruct(true);
+					}					
+					
+					if (rootImplicitNode == null)
+					{
+						rootImplicitNode = currNode;	// Save the top most implicit node.
+					}
+				}
+			}
+		}
+		catch (XMPException e)
+		{
+			// if new notes have been created prior to the error, delete them
+			if (rootImplicitNode != null)
+			{
+				deleteNode(rootImplicitNode);
+			}
+			throw e;
+		}
+		
+		
+		if (rootImplicitNode != null)
+		{
+			// set options only if a node has been successful created
+			currNode.getOptions().mergeWith(leafOptions);
+			currNode.setOptions(currNode.getOptions());
+		}
+		
+		return currNode;
+	}
+
+
+	/**
+	 * Deletes the the given node and its children from its parent.
+	 * Takes care about adjusting the flags.
+	 * @param node the top-most node to delete.
+	 */
+	static void deleteNode(XMPNode node)
+	{
+		XMPNode parent = node.getParent();
+		
+		if (node.getOptions().isQualifier())
+		{
+			// root is qualifier
+			parent.removeQualifier(node);
+		}
+		else
+		{
+			// root is NO qualifier
+			parent.removeChild(node);
+		}
+		
+		// delete empty Schema nodes
+		if (!parent.hasChildren()  &&  parent.getOptions().isSchemaNode())
+		{
+			parent.getParent().removeChild(parent);
+		}
+	}
+
+
+	/**
+	 * This is setting the value of a leaf node.
+	 * 
+	 * @param node an XMPNode
+	 * @param value a value
+	 */
+	static void setNodeValue(XMPNode node, Object value)
+	{
+		String strValue = serializeNodeValue(value);
+		if (!(node.getOptions().isQualifier()  &&  XML_LANG.equals(node.getName()))) 
+		{	
+			node.setValue(strValue);
+		}
+		else
+		{
+			node.setValue(Utils.normalizeLangValue(strValue));
+		}
+	}
+	
+	
+	/**
+	 * Verifies the PropertyOptions for consistancy and updates them as needed. 
+	 * If options are <code>null</code> they are created with default values.
+	 *  
+	 * @param options the <code>PropertyOptions</code>
+	 * @param itemValue the node value to set
+	 * @return Returns the updated options.
+	 * @throws XMPException If the options are not consistant. 
+	 */
+	static PropertyOptions verifySetOptions(PropertyOptions options, Object itemValue)
+			throws XMPException
+	{
+		// create empty and fix existing options
+		if (options == null)
+		{
+			// set default options
+			options = new PropertyOptions();
+		}
+		
+		if (options.isArrayAltText())
+		{
+			options.setArrayAlternate(true);
+		}
+	
+		if (options.isArrayAlternate())
+		{
+			options.setArrayOrdered(true);
+		}
+	
+		if (options.isArrayOrdered())
+		{
+			options.setArray(true);
+		}
+	
+		if (options.isCompositeProperty() && itemValue != null && itemValue.toString().length() > 0)
+		{
+			throw new XMPException("Structs and arrays can't have values",
+				XMPError.BADOPTIONS);
+		}
+	
+		options.assertConsistency(options.getOptions());
+		
+		return options;
+	}
+
+
+	/**
+	 * Converts the node value to String, apply special conversions for defined
+	 * types in XMP.
+	 * 
+	 * @param value
+	 *            the node value to set
+	 * @return Returns the String representation of the node value.
+	 */
+	static String serializeNodeValue(Object value)
+	{
+		String strValue;
+		if (value == null)
+		{
+			strValue = null;
+		}
+		else if (value instanceof Boolean)
+		{
+			strValue = XMPUtils.convertFromBoolean(((Boolean) value).booleanValue());
+		}
+		else if (value instanceof Integer)
+		{
+			strValue = XMPUtils.convertFromInteger(((Integer) value).intValue());
+		}
+		else if (value instanceof Long)
+		{
+			strValue = XMPUtils.convertFromLong(((Long) value).longValue());
+		}
+		else if (value instanceof Double)
+		{
+			strValue = XMPUtils.convertFromDouble(((Double) value).doubleValue());
+		}
+		else if (value instanceof XMPDateTime)
+		{
+			strValue = XMPUtils.convertFromDate((XMPDateTime) value);
+		}
+		else if (value instanceof GregorianCalendar)
+		{
+			XMPDateTime dt = XMPDateTimeFactory.createFromCalendar((GregorianCalendar) value);
+			strValue = XMPUtils.convertFromDate(dt);
+		}
+		else if (value instanceof byte[])
+		{
+			strValue = XMPUtils.encodeBase64((byte[]) value);
+		}
+		else
+		{
+			strValue = value.toString();
+		}
+	
+		return strValue != null ? Utils.removeControlChars(strValue) : null;
+	}
+	
+	
+	/** 
+	 * After processing by ExpandXPath, a step can be of these forms:
+	 * <ul>
+	 * 	<li>qualName - A top level property or struct field.
+	 * <li>[index] - An element of an array.
+	 * <li>[last()] - The last element of an array.
+	 * <li>[qualName="value"] - An element in an array of structs, chosen by a field value.
+	 * <li>[?qualName="value"] - An element in an array, chosen by a qualifier value.
+	 * <li>?qualName - A general qualifier.
+	 * </ul>
+	 * Find the appropriate child node, resolving aliases, and optionally creating nodes.
+	 * 
+	 * @param parentNode the node to start to start from 
+	 * @param nextStep the xpath segment 
+	 * @param createNodes 
+	 * @return returns the found or created XMPPath node 
+	 * @throws XMPException 
+	 */
+	private static XMPNode followXPathStep(
+				XMPNode parentNode, 
+				XMPPathSegment nextStep,
+				boolean createNodes) throws XMPException
+	{
+		XMPNode nextNode = null;
+		int index = 0;
+		int stepKind = nextStep.getKind();
+		
+		if (stepKind == XMPPath.STRUCT_FIELD_STEP)
+		{
+			nextNode = findChildNode(parentNode, nextStep.getName(), createNodes);
+		}
+		else if (stepKind == XMPPath.QUALIFIER_STEP)
+		{
+			nextNode = findQualifierNode(
+				parentNode, nextStep.getName().substring(1), createNodes);
+		}
+		else
+		{
+			// This is an array indexing step. First get the index, then get the node.
+
+			if (!parentNode.getOptions().isArray())
+			{
+				throw new XMPException("Indexing applied to non-array", XMPError.BADXPATH);
+			}
+
+			if (stepKind == XMPPath.ARRAY_INDEX_STEP)
+			{
+				index = findIndexedItem(parentNode, nextStep.getName(), createNodes);
+			}
+			else if (stepKind == XMPPath.ARRAY_LAST_STEP)
+			{
+				index = parentNode.getChildrenLength();
+			}
+			else if (stepKind == XMPPath.FIELD_SELECTOR_STEP)
+			{
+				String[] result = Utils.splitNameAndValue(nextStep.getName());
+				String fieldName = result[0];
+				String fieldValue = result[1];
+				index = lookupFieldSelector(parentNode, fieldName, fieldValue);
+			}
+			else if (stepKind == XMPPath.QUAL_SELECTOR_STEP)
+			{
+				String[] result = Utils.splitNameAndValue(nextStep.getName());
+				String qualName = result[0];
+				String qualValue = result[1];
+				index = lookupQualSelector(
+					parentNode, qualName, qualValue, nextStep.getAliasForm());
+			}
+			else
+			{
+				throw new XMPException("Unknown array indexing step in FollowXPathStep",
+						XMPError.INTERNALFAILURE);
+			}
+
+			if (1 <= index  &&  index <=  parentNode.getChildrenLength())
+			{
+				nextNode = parentNode.getChild(index);
+			}
+		}
+	
+		return nextNode;		
+	}
+
+
+	/**
+	 * Find or create a qualifier node under a given parent node. Returns a pointer to the 
+	 * qualifier node, and optionally an iterator for the node's position in 
+	 * the parent's vector of qualifiers. The iterator is unchanged if no qualifier node (null) 
+	 * is returned.
+	 * <em>Note:</em> On entry, the qualName parameter must not have the leading '?' from the 
+	 * XMPPath step.
+	 * 
+	 * @param parent the parent XMPNode 
+	 * @param qualName the qualifier name
+	 * @param createNodes flag if nodes shall be created
+	 * @return Returns the qualifier node if found or created, <code>null</code> otherwise.
+	 * @throws XMPException 
+	 */
+	private static XMPNode findQualifierNode(XMPNode parent, String qualName, boolean createNodes)
+			throws XMPException
+	{
+		assert !qualName.startsWith("?");
+		
+		XMPNode qualNode = parent.findQualifierByName(qualName);
+		
+		if (qualNode == null  &&  createNodes)
+		{
+			qualNode = new XMPNode(qualName, null);
+			qualNode.setImplicit(true);
+	
+			parent.addQualifier(qualNode);				
+		}
+		
+		return qualNode;
+	}
+	
+	
+	/**
+	 * @param arrayNode an array node
+	 * @param segment the segment containing the array index
+	 * @param createNodes flag if new nodes are allowed to be created.
+	 * @return Returns the index or index = -1 if not found
+	 * @throws XMPException Throws Exceptions
+	 */
+	private static int findIndexedItem(XMPNode arrayNode, String segment, boolean createNodes)
+			throws XMPException
+	{
+		int index = 0;
+	
+		try
+		{
+			segment = segment.substring(1, segment.length() - 1);
+			index = Integer.parseInt(segment);
+			if (index < 1)
+			{
+				throw new XMPException("Array index must be larger than zero",
+						XMPError.BADXPATH);
+			}
+		}
+		catch (NumberFormatException e)
+		{
+			throw new XMPException("Array index not digits.", XMPError.BADXPATH);
+		}
+	
+		if (createNodes  &&  index == arrayNode.getChildrenLength() + 1)
+		{
+			// Append a new last + 1 node.
+			XMPNode newItem = new XMPNode(ARRAY_ITEM_NAME, null);
+			newItem.setImplicit(true);
+			arrayNode.addChild(newItem);
+		}
+
+		return index;
+	}
+	
+	
+	/**
+	 * Searches for a field selector in a node:
+	 * [fieldName="value] - an element in an array of structs, chosen by a field value.
+	 * No implicit nodes are created by field selectors. 
+	 * 
+	 * @param arrayNode
+	 * @param fieldName
+	 * @param fieldValue
+	 * @return Returns the index of the field if found, otherwise -1.
+	 * @throws XMPException 
+	 */
+	private static int lookupFieldSelector(XMPNode arrayNode, String fieldName, String fieldValue)
+		throws XMPException
+	{
+		int result = -1;
+		
+		for (int index = 1; index <= arrayNode.getChildrenLength()  &&  result < 0; index++)
+		{
+			XMPNode currItem = arrayNode.getChild(index);
+	
+			if (!currItem.getOptions().isStruct())
+			{
+				throw new XMPException("Field selector must be used on array of struct",
+						XMPError.BADXPATH);
+			}
+	
+			for (int f = 1; f <= currItem.getChildrenLength(); f++)
+			{
+				XMPNode currField = currItem.getChild(f);
+				if (!fieldName.equals(currField.getName()))
+				{
+					continue;
+				}
+				if (fieldValue.equals(currField.getValue())) 
+				{
+					result = index;
+					break;
+				}
+			}
+		}
+		
+		return result;
+	}
+	
+	
+	/**
+	 * Searches for a qualifier selector in a node:
+	 * [?qualName="value"] - an element in an array, chosen by a qualifier value.
+	 * No implicit nodes are created for qualifier selectors, 
+	 * except for an alias to an x-default item.
+	 * 
+	 * @param arrayNode an array node 
+	 * @param qualName the qualifier name
+	 * @param qualValue the qualifier value
+	 * @param aliasForm in case the qual selector results from an alias,
+	 * 		  an x-default node is created if there has not been one. 
+	 * @return Returns the index of th
+	 * @throws XMPException 
+	 */
+	private static int lookupQualSelector(XMPNode arrayNode, String qualName, 
+		String qualValue, int aliasForm) throws XMPException
+	{
+		if (XML_LANG.equals(qualName))
+		{
+			qualValue = Utils.normalizeLangValue(qualValue);
+			int index = XMPNodeUtils.lookupLanguageItem(arrayNode, qualValue);
+			if (index < 0  &&  (aliasForm & AliasOptions.PROP_ARRAY_ALT_TEXT) > 0)
+			{	
+				XMPNode langNode = new XMPNode(ARRAY_ITEM_NAME, null);
+				XMPNode xdefault = new XMPNode(XML_LANG, X_DEFAULT, null);
+				langNode.addQualifier(xdefault);
+				arrayNode.addChild(1, langNode);
+				return 1;
+			}
+			else
+			{
+				return index;
+			}
+		}
+		else
+		{
+			for (int index = 1; index < arrayNode.getChildrenLength(); index++)
+			{
+				XMPNode currItem = arrayNode.getChild(index);
+		
+				for (Iterator it = currItem.iterateQualifier(); it.hasNext();)
+				{
+					XMPNode qualifier = (XMPNode) it.next();
+					if (qualName.equals(qualifier.getName())  &&
+						qualValue.equals(qualifier.getValue()))
+					{
+						return index;
+					}
+				}
+			}
+			return -1;
+		}	
+	}
+
+
+	/**
+	 * Make sure the x-default item is first. Touch up &quot;single value&quot;
+	 * arrays that have a default plus one real language. This case should have
+	 * the same value for both items. Older Adobe apps were hardwired to only
+	 * use the &quot;x-default&quot; item, so we copy that value to the other
+	 * item.
+	 * 
+	 * @param arrayNode
+	 *            an alt text array node
+	 */
+	static void normalizeLangArray(XMPNode arrayNode)
+	{
+		if (!arrayNode.getOptions().isArrayAltText())
+		{
+			return;
+		}
+	
+		// check if node with x-default qual is first place
+		for (int i = 2; i <= arrayNode.getChildrenLength(); i++)
+		{
+			XMPNode child = arrayNode.getChild(i);
+			if (child.hasQualifier() && X_DEFAULT.equals(child.getQualifier(1).getValue()))
+			{
+				// move node to first place
+				try
+				{
+					arrayNode.removeChild(i);
+					arrayNode.addChild(1, child);
+				}
+				catch (XMPException e)
+				{
+					// cannot occur, because same child is removed before
+					assert false;
+				}
+				
+				if (i == 2)
+				{
+					arrayNode.getChild(2).setValue(child.getValue());
+				}
+				break;
+			}
+		}
+	}
+
+
+	/**
+	 * See if an array is an alt-text array. If so, make sure the x-default item
+	 * is first.
+	 * 
+	 * @param arrayNode
+	 *            the array node to check if its an alt-text array
+	 */
+	static void detectAltText(XMPNode arrayNode)
+	{
+		if (arrayNode.getOptions().isArrayAlternate() && arrayNode.hasChildren())
+		{
+			boolean isAltText = false;
+			for (Iterator it = arrayNode.iterateChildren(); it.hasNext();)
+			{
+				XMPNode child = (XMPNode) it.next();
+				if (child.getOptions().getHasLanguage())
+				{
+					isAltText = true;
+					break;
+				}
+			}
+	
+			if (isAltText)
+			{
+				arrayNode.getOptions().setArrayAltText(true);
+				normalizeLangArray(arrayNode);
+			}
+		}
+	}
+
+
+	/**
+	 * Appends a language item to an alt text array.
+	 * 
+	 * @param arrayNode the language array
+	 * @param itemLang the language of the item
+	 * @param itemValue the content of the item
+	 * @throws XMPException Thrown if a duplicate property is added
+	 */
+	static void appendLangItem(XMPNode arrayNode, String itemLang, String itemValue)
+			throws XMPException
+	{
+		XMPNode newItem = new XMPNode(ARRAY_ITEM_NAME, itemValue, null);
+		XMPNode langQual = new XMPNode(XML_LANG, itemLang, null);
+		newItem.addQualifier(langQual);
+	
+		if (!X_DEFAULT.equals(langQual.getValue()))
+		{
+			arrayNode.addChild(newItem);
+		}
+		else
+		{
+			arrayNode.addChild(1, newItem);
+		}
+	}
+
+
+	/**
+	 * <ol>
+	 * <li>Look for an exact match with the specific language.
+	 * <li>If a generic language is given, look for partial matches.
+	 * <li>Look for an "x-default"-item.
+	 * <li>Choose the first item.
+	 * </ol>
+	 * 
+	 * @param arrayNode
+	 *            the alt text array node
+	 * @param genericLang
+	 *            the generic language
+	 * @param specificLang
+	 *            the specific language
+	 * @return Returns the kind of match as an Integer and the found node in an
+	 *         array.
+	 * 
+	 * @throws XMPException
+	 */
+	static Object[] chooseLocalizedText(XMPNode arrayNode, String genericLang, String specificLang)
+			throws XMPException
+	{
+		// See if the array has the right form. Allow empty alt arrays,
+		// that is what parsing returns.
+		if (!arrayNode.getOptions().isArrayAltText())
+		{
+			throw new XMPException("Localized text array is not alt-text", XMPError.BADXPATH);
+		}
+		else if (!arrayNode.hasChildren())
+		{
+			return new Object[] { new Integer(XMPNodeUtils.CLT_NO_VALUES), null };
+		}
+	
+		int foundGenericMatches = 0;
+		XMPNode resultNode = null;
+		XMPNode xDefault = null;
+	
+		// Look for the first partial match with the generic language.
+		for (Iterator it = arrayNode.iterateChildren(); it.hasNext();)
+		{
+			XMPNode currItem = (XMPNode) it.next();
+	
+			// perform some checks on the current item
+			if (currItem.getOptions().isCompositeProperty())
+			{
+				throw new XMPException("Alt-text array item is not simple", XMPError.BADXPATH);
+			}
+			else if (!currItem.hasQualifier()
+					|| !XML_LANG.equals(currItem.getQualifier(1).getName()))
+			{
+				throw new XMPException("Alt-text array item has no language qualifier",
+						XMPError.BADXPATH);
+			}
+	
+			String currLang = currItem.getQualifier(1).getValue();
+	
+			// Look for an exact match with the specific language.
+			if (specificLang.equals(currLang))
+			{
+				return new Object[] { new Integer(XMPNodeUtils.CLT_SPECIFIC_MATCH), currItem };
+			}
+			else if (genericLang != null && currLang.startsWith(genericLang))
+			{
+				if (resultNode == null)
+				{
+					resultNode = currItem;
+				}
+				// ! Don't return/break, need to look for other matches.
+				foundGenericMatches++;
+			}
+			else if (X_DEFAULT.equals(currLang))
+			{
+				xDefault = currItem;
+			}
+		}
+	
+		// evaluate loop
+		if (foundGenericMatches == 1)
+		{
+			return new Object[] { new Integer(XMPNodeUtils.CLT_SINGLE_GENERIC), resultNode };
+		}
+		else if (foundGenericMatches > 1)
+		{
+			return new Object[] { new Integer(XMPNodeUtils.CLT_MULTIPLE_GENERIC), resultNode };
+		}
+		else if (xDefault != null)
+		{
+			return new Object[] { new Integer(XMPNodeUtils.CLT_XDEFAULT), xDefault };
+		}
+		else
+		{
+			// Everything failed, choose the first item.
+			return new Object[] { new Integer(XMPNodeUtils.CLT_FIRST_ITEM), arrayNode.getChild(1) };
+		}
+	}
+
+
+	/**
+	 * Looks for the appropriate language item in a text alternative array.item
+	 * 
+	 * @param arrayNode
+	 *            an array node
+	 * @param language
+	 *            the requested language
+	 * @return Returns the index if the language has been found, -1 otherwise.
+	 * @throws XMPException
+	 */
+	static int lookupLanguageItem(XMPNode arrayNode, String language) throws XMPException
+	{
+		if (!arrayNode.getOptions().isArray())
+		{
+			throw new XMPException("Language item must be used on array", XMPError.BADXPATH);
+		}
+	
+		for (int index = 1; index <= arrayNode.getChildrenLength(); index++)
+		{
+			XMPNode child = arrayNode.getChild(index);
+			if (!child.hasQualifier() || !XML_LANG.equals(child.getQualifier(1).getName()))
+			{
+				continue;
+			}
+			else if (language.equals(child.getQualifier(1).getValue()))
+			{
+				return index;
+			}
+		}
+	
+		return -1;
+	}
+}
diff --git a/XMPCore/src/com/adobe/xmp/impl/XMPNormalizer.java b/XMPCore/src/com/adobe/xmp/impl/XMPNormalizer.java
new file mode 100644
index 0000000..d51d7de
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/impl/XMPNormalizer.java
@@ -0,0 +1,695 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp.impl;
+
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import com.adobe.xmp.XMPConst;
+import com.adobe.xmp.XMPDateTime;
+import com.adobe.xmp.XMPError;
+import com.adobe.xmp.XMPException;
+import com.adobe.xmp.XMPMeta;
+import com.adobe.xmp.XMPMetaFactory;
+import com.adobe.xmp.XMPUtils;
+import com.adobe.xmp.impl.xpath.XMPPath;
+import com.adobe.xmp.impl.xpath.XMPPathParser;
+import com.adobe.xmp.options.ParseOptions;
+import com.adobe.xmp.options.PropertyOptions;
+import com.adobe.xmp.properties.XMPAliasInfo;
+
+/**
+ * @since   Aug 18, 2006
+ */
+public class XMPNormalizer
+{
+	/** caches the correct dc-property array forms */
+	private static Map dcArrayForms;
+	/** init char tables */
+	static
+	{
+		initDCArrays();
+	}
+	
+	
+	/**
+	 * Hidden constructor
+	 */
+	private XMPNormalizer()
+	{
+		// EMPTY
+	}
+
+	
+	/**
+	 * Normalizes a raw parsed XMPMeta-Object
+	 * @param xmp the raw metadata object
+	 * @param options the parsing options
+	 * @return Returns the normalized metadata object
+	 * @throws XMPException Collects all severe processing errors. 
+	 */
+	static XMPMeta process(XMPMetaImpl xmp, ParseOptions options) throws XMPException 
+	{
+		XMPNode tree = xmp.getRoot();
+
+		touchUpDataModel(xmp);
+		moveExplicitAliases(tree, options);
+		
+		tweakOldXMP(tree);
+		
+		deleteEmptySchemas(tree);
+		
+		return xmp;
+	}
+	
+	
+	/**
+	 * Tweak old XMP: Move an instance ID from rdf:about to the
+	 * <em>xmpMM:InstanceID</em> property. An old instance ID usually looks
+	 * like &quot;uuid:bac965c4-9d87-11d9-9a30-000d936b79c4&quot;, plus InDesign
+	 * 3.0 wrote them like &quot;bac965c4-9d87-11d9-9a30-000d936b79c4&quot;. If
+	 * the name looks like a UUID simply move it to <em>xmpMM:InstanceID</em>,
+	 * don't worry about any existing <em>xmpMM:InstanceID</em>. Both will
+	 * only be present when a newer file with the <em>xmpMM:InstanceID</em>
+	 * property is updated by an old app that uses <em>rdf:about</em>.
+	 * 
+	 * @param tree the root of the metadata tree
+	 * @throws XMPException Thrown if tweaking fails. 
+	 */
+	private static void tweakOldXMP(XMPNode tree) throws XMPException
+	{
+		if (tree.getName() != null  &&  tree.getName().length() >= Utils.UUID_LENGTH)
+		{
+			String nameStr = tree.getName().toLowerCase();
+			if (nameStr.startsWith("uuid:"))
+			{
+				nameStr = nameStr.substring(5);
+			}
+			
+			if (Utils.checkUUIDFormat(nameStr))
+			{	
+				// move UUID to xmpMM:InstanceID and remove it from the root node
+				XMPPath path = XMPPathParser.expandXPath(XMPConst.NS_XMP_MM, "InstanceID");
+				XMPNode idNode = XMPNodeUtils.findNode (tree, path, true, null);
+				if (idNode != null)
+				{
+					idNode.setOptions(null);	// Clobber any existing xmpMM:InstanceID.
+					idNode.setValue("uuid:" + nameStr);
+					idNode.removeChildren();
+					idNode.removeQualifiers();		
+					tree.setName(null);
+				}
+				else
+				{	
+					throw new XMPException("Failure creating xmpMM:InstanceID",
+							XMPError.INTERNALFAILURE);
+				}	
+			}
+		}		
+	}
+
+	
+	/**
+	 * Visit all schemas to do general fixes and handle special cases.
+	 * 
+	 * @param xmp the metadata object implementation
+	 * @throws XMPException Thrown if the normalisation fails.
+	 */
+	private static void touchUpDataModel(XMPMetaImpl xmp) throws XMPException
+	{
+		// make sure the DC schema is existing, because it might be needed within the normalization
+		// if not touched it will be removed by removeEmptySchemas
+		XMPNodeUtils.findSchemaNode(xmp.getRoot(), XMPConst.NS_DC, true);
+		
+		// Do the special case fixes within each schema.
+		for (Iterator it = xmp.getRoot().iterateChildren(); it.hasNext();)
+		{
+			XMPNode currSchema = (XMPNode) it.next();
+			if (XMPConst.NS_DC.equals(currSchema.getName()))
+			{
+				normalizeDCArrays(currSchema);
+			}
+			else if (XMPConst.NS_EXIF.equals(currSchema.getName()))
+			{
+				// Do a special case fix for exif:GPSTimeStamp.
+				fixGPSTimeStamp(currSchema);
+				XMPNode arrayNode = XMPNodeUtils.findChildNode(currSchema, "exif:UserComment",
+						false);
+				if (arrayNode != null)
+				{
+					repairAltText(arrayNode);
+				}
+			}
+			else if (XMPConst.NS_DM.equals(currSchema.getName()))
+			{
+				// Do a special case migration of xmpDM:copyright to
+				// dc:rights['x-default'].
+				XMPNode dmCopyright = XMPNodeUtils.findChildNode(currSchema, "xmpDM:copyright",
+						false);
+				if (dmCopyright != null)
+				{
+					migrateAudioCopyright(xmp, dmCopyright);
+				}
+			}
+			else if (XMPConst.NS_XMP_RIGHTS.equals(currSchema.getName()))
+			{
+				XMPNode arrayNode = XMPNodeUtils.findChildNode(currSchema, "xmpRights:UsageTerms",
+						false);			
+				if (arrayNode != null)
+				{	
+					repairAltText(arrayNode);
+				}	
+			}
+		}
+	}
+	
+
+	/**
+	 * Undo the denormalization performed by the XMP used in Acrobat 5.<br> 
+	 * If a Dublin Core array had only one item, it was serialized as a simple
+	 * property. <br>
+	 * The <code>xml:lang</code> attribute was dropped from an
+	 * <code>alt-text</code> item if the language was <code>x-default</code>.
+	 * 
+	 * @param dcSchema the DC schema node
+	 * @throws XMPException Thrown if normalization fails
+	 */
+	private static void normalizeDCArrays(XMPNode dcSchema) throws XMPException
+	{
+		for (int i = 1; i <= dcSchema.getChildrenLength(); i++)
+		{
+			XMPNode currProp = dcSchema.getChild(i);
+			
+			PropertyOptions arrayForm = (PropertyOptions) dcArrayForms.get(currProp.getName());
+			if (arrayForm == null)
+			{
+				continue;
+			}
+			else if (currProp.getOptions().isSimple())
+			{	
+				// create a new array and add the current property as child, 
+				// if it was formerly simple 
+				XMPNode newArray = new XMPNode(currProp.getName(), arrayForm);
+				currProp.setName(XMPConst.ARRAY_ITEM_NAME);
+				newArray.addChild(currProp);
+				dcSchema.replaceChild(i, newArray);
+	
+				// fix language alternatives
+				if (arrayForm.isArrayAltText()  &&  !currProp.getOptions().getHasLanguage())
+				{
+					XMPNode newLang = new XMPNode(XMPConst.XML_LANG, XMPConst.X_DEFAULT, null);
+					currProp.addQualifier(newLang);
+				}
+			}
+			else
+			{
+				// clear array options and add corrected array form if it has been an array before
+				currProp.getOptions().setOption(
+					PropertyOptions.ARRAY  |
+					PropertyOptions.ARRAY_ORDERED  |
+					PropertyOptions.ARRAY_ALTERNATE  |
+					PropertyOptions.ARRAY_ALT_TEXT,  
+					false);
+				currProp.getOptions().mergeWith(arrayForm);
+				
+				if (arrayForm.isArrayAltText())
+				{
+					// applying for "dc:description", "dc:rights", "dc:title"
+					repairAltText(currProp);
+				}
+			}
+		
+		}
+	}
+
+	
+	/**
+	 * Make sure that the array is well-formed AltText. Each item must be simple
+	 * and have an "xml:lang" qualifier. If repairs are needed, keep simple
+	 * non-empty items by adding the "xml:lang" with value "x-repair".
+	 * @param arrayNode the property node of the array to repair.
+	 * @throws XMPException Forwards unexpected exceptions.
+	 */
+	private static void repairAltText(XMPNode arrayNode) throws XMPException
+	{
+		if (arrayNode == null  ||  
+			!arrayNode.getOptions().isArray())
+		{
+			// Already OK or not even an array.
+			return;	
+		}
+
+		// fix options
+		arrayNode.getOptions().setArrayOrdered(true).setArrayAlternate(true).setArrayAltText(true);
+		
+		for (Iterator it = arrayNode.iterateChildren(); it.hasNext();)
+		{
+			XMPNode currChild = (XMPNode) it.next();
+			if (currChild.getOptions().isCompositeProperty())
+			{
+				// Delete non-simple children.
+				it.remove();
+			} 
+			else if (!currChild.getOptions().getHasLanguage())
+			{
+				String childValue = currChild.getValue();
+				if (childValue == null  ||  childValue.length() == 0)
+				{
+					// Delete empty valued children that have no xml:lang.
+					it.remove();
+				}
+				else
+				{
+					// Add an xml:lang qualifier with the value "x-repair".
+					XMPNode repairLang = new XMPNode(XMPConst.XML_LANG, "x-repair", null);
+					currChild.addQualifier(repairLang);
+				}
+			}
+		}		
+	}
+	
+
+	/**
+	 * Visit all of the top level nodes looking for aliases. If there is
+	 * no base, transplant the alias subtree. If there is a base and strict
+	 * aliasing is on, make sure the alias and base subtrees match.
+	 * 
+	 * @param tree the root of the metadata tree
+	 * @param options th parsing options
+	 * @throws XMPException Forwards XMP errors
+	 */
+	private static void moveExplicitAliases(XMPNode tree, ParseOptions options)
+			throws XMPException
+	{
+		if (!tree.getHasAliases())
+		{
+			return;
+		}
+		tree.setHasAliases(false);
+		
+		boolean strictAliasing = options.getStrictAliasing();
+
+		for (Iterator schemaIt = tree.getUnmodifiableChildren().iterator(); schemaIt.hasNext();)
+		{
+			XMPNode currSchema = (XMPNode) schemaIt.next();
+			if (!currSchema.getHasAliases())
+			{
+				continue;
+			}
+			
+			for (Iterator propertyIt = currSchema.iterateChildren(); propertyIt.hasNext();)
+			{
+				XMPNode currProp = (XMPNode) propertyIt.next();
+				
+				if (!currProp.isAlias())
+				{
+					continue;
+				}
+				
+				currProp.setAlias(false);
+	
+				// Find the base path, look for the base schema and root node.
+				XMPAliasInfo info = XMPMetaFactory.getSchemaRegistry()
+						.findAlias(currProp.getName());
+				if (info != null)
+				{	
+					// find or create schema
+					XMPNode baseSchema = XMPNodeUtils.findSchemaNode(tree, info
+							.getNamespace(), null, true);
+					baseSchema.setImplicit(false);
+					
+					XMPNode baseNode = XMPNodeUtils
+							.findChildNode(baseSchema, 
+								info.getPrefix() + info.getPropName(), false);
+					if (baseNode == null)
+					{
+						if (info.getAliasForm().isSimple())
+						{
+							// A top-to-top alias, transplant the property.
+							// change the alias property name to the base name
+							String qname = info.getPrefix() + info.getPropName();
+							currProp.setName(qname);
+							baseSchema.addChild(currProp);
+							// remove the alias property
+							propertyIt.remove();
+						}
+						else
+						{
+							// An alias to an array item, 
+							// create the array and transplant the property.
+							baseNode = new XMPNode(info.getPrefix() + info.getPropName(), info
+									.getAliasForm().toPropertyOptions());
+							baseSchema.addChild(baseNode);
+							transplantArrayItemAlias (propertyIt, currProp, baseNode);
+						}
+					
+					} 
+					else if (info.getAliasForm().isSimple())
+					{
+						// The base node does exist and this is a top-to-top alias.
+						// Check for conflicts if strict aliasing is on. 
+						// Remove and delete the alias subtree.
+						if (strictAliasing)
+						{
+							compareAliasedSubtrees (currProp, baseNode, true);
+						}
+						
+						propertyIt.remove();
+					}
+					else
+					{
+						// This is an alias to an array item and the array exists.
+						// Look for the aliased item.
+						// Then transplant or check & delete as appropriate.
+						
+						XMPNode  itemNode = null;
+						if (info.getAliasForm().isArrayAltText())
+						{
+							int xdIndex = XMPNodeUtils.lookupLanguageItem(baseNode,
+									XMPConst.X_DEFAULT); 
+							if (xdIndex != -1)
+							{
+								itemNode = baseNode.getChild(xdIndex);
+							}
+						}
+						else if (baseNode.hasChildren())
+						{
+							itemNode = baseNode.getChild(1);
+						}
+						
+						if (itemNode == null)
+						{
+							transplantArrayItemAlias (propertyIt, currProp, baseNode);
+						}
+						else
+						{
+							if (strictAliasing)
+							{
+								compareAliasedSubtrees (currProp, itemNode, true);
+							}
+							
+							propertyIt.remove();
+						}
+					}
+				}
+			}
+			currSchema.setHasAliases(false);
+		}
+	}
+
+	
+	/**
+	 * Moves an alias node of array form to another schema into an array
+	 * @param propertyIt the property iterator of the old schema (used to delete the property)
+	 * @param childNode the node to be moved
+	 * @param baseArray the base array for the array item 
+	 * @throws XMPException Forwards XMP errors
+	 */
+	private static void transplantArrayItemAlias(Iterator propertyIt, XMPNode childNode,
+			XMPNode baseArray) throws XMPException
+	{
+		if (baseArray.getOptions().isArrayAltText())
+		{
+			if (childNode.getOptions().getHasLanguage())
+			{
+				throw new XMPException("Alias to x-default already has a language qualifier",
+						XMPError.BADXMP);
+			}
+			
+			XMPNode langQual = new XMPNode(XMPConst.XML_LANG, XMPConst.X_DEFAULT, null);
+			childNode.addQualifier(langQual);
+		}
+	
+		propertyIt.remove();
+		childNode.setName(XMPConst.ARRAY_ITEM_NAME);
+		baseArray.addChild(childNode);
+	}
+	
+	
+	/**
+	 * Fixes the GPS Timestamp in EXIF.
+	 * @param exifSchema the EXIF schema node
+	 * @throws XMPException Thrown if the date conversion fails.
+	 */
+	private static void fixGPSTimeStamp(XMPNode exifSchema)
+			throws XMPException
+	{
+		// Note: if dates are not found the convert-methods throws an exceptions,
+		// 		 and this methods returns.
+		XMPNode gpsDateTime = XMPNodeUtils.findChildNode(exifSchema, "exif:GPSTimeStamp", false);
+		if (gpsDateTime == null)
+		{
+			return;
+		}
+		
+		try
+		{
+			XMPDateTime binGPSStamp;
+			XMPDateTime binOtherDate;
+			
+			binGPSStamp = XMPUtils.convertToDate(gpsDateTime.getValue());
+			if (binGPSStamp.getYear() != 0  || 
+				binGPSStamp.getMonth() != 0  ||
+				binGPSStamp.getDay() != 0)
+			{
+				return;
+			}
+			
+			XMPNode otherDate = XMPNodeUtils.findChildNode(exifSchema, "exif:DateTimeOriginal",
+					false);
+			if (otherDate == null)
+			{
+				otherDate = XMPNodeUtils.findChildNode(exifSchema, "exif:DateTimeDigitized", false);
+			}
+	
+			binOtherDate = XMPUtils.convertToDate(otherDate.getValue());
+			Calendar cal = binGPSStamp.getCalendar();
+			cal.set(Calendar.YEAR, binOtherDate.getYear());
+			cal.set(Calendar.MONTH, binOtherDate.getMonth());
+			cal.set(Calendar.DAY_OF_MONTH, binOtherDate.getDay());
+			binGPSStamp = new XMPDateTimeImpl(cal);
+			gpsDateTime.setValue(XMPUtils.convertFromDate (binGPSStamp));
+		}
+		catch (XMPException e)
+		{
+			// Don't let a missing or bad date stop other things.
+			return;
+		}
+	}
+
+
+
+	/**
+	 * Remove all empty schemas from the metadata tree that were generated during the rdf parsing.
+	 * @param tree the root of the metadata tree
+	 */
+	private static void deleteEmptySchemas(XMPNode tree)
+	{
+		// Delete empty schema nodes. Do this last, other cleanup can make empty
+		// schema.
+	
+		for (Iterator it = tree.iterateChildren(); it.hasNext();)
+		{
+			XMPNode schema = (XMPNode) it.next();
+			if (!schema.hasChildren())
+			{
+				it.remove();
+			}
+		}
+	}
+
+	
+	/**
+	 * The outermost call is special. The names almost certainly differ. The
+	 * qualifiers (and hence options) will differ for an alias to the x-default
+	 * item of a langAlt array.
+	 * 
+	 * @param aliasNode the alias node
+	 * @param baseNode the base node of the alias
+	 * @param outerCall marks the outer call of the recursion
+	 * @throws XMPException Forwards XMP errors 
+	 */
+	private static void compareAliasedSubtrees(XMPNode aliasNode, XMPNode baseNode,
+			boolean outerCall) throws XMPException 
+	{
+		if (!aliasNode.getValue().equals(baseNode.getValue())  || 
+			aliasNode.getChildrenLength() != baseNode.getChildrenLength())
+		{
+			throw new XMPException("Mismatch between alias and base nodes", XMPError.BADXMP);
+		}
+		
+		if (
+				!outerCall  &&
+				(!aliasNode.getName().equals(baseNode.getName())  ||
+				 !aliasNode.getOptions().equals(baseNode.getOptions())  ||
+				 aliasNode.getQualifierLength() != baseNode.getQualifierLength())
+		   ) 
+	    {
+			throw new XMPException("Mismatch between alias and base nodes", 
+				XMPError.BADXMP);
+		}
+		
+		for (Iterator an = aliasNode.iterateChildren(), 
+					  bn = baseNode.iterateChildren();
+			 an.hasNext() && bn.hasNext();)
+		{
+			XMPNode aliasChild = (XMPNode) an.next();
+			XMPNode baseChild =  (XMPNode) bn.next();
+			compareAliasedSubtrees (aliasChild, baseChild, false);
+		}
+	
+		
+		for (Iterator an = aliasNode.iterateQualifier(), 
+					  bn = baseNode.iterateQualifier();
+			 an.hasNext() && bn.hasNext();)
+		{
+			XMPNode aliasQual = (XMPNode) an.next();
+			XMPNode baseQual =  (XMPNode) bn.next();
+			compareAliasedSubtrees (aliasQual, baseQual, false);
+		}
+	}
+
+	
+	/**
+	 * The initial support for WAV files mapped a legacy ID3 audio copyright
+	 * into a new xmpDM:copyright property. This is special case code to migrate
+	 * that into dc:rights['x-default']. The rules:
+	 * 
+	 * <pre>
+	 * 1. If there is no dc:rights array, or an empty array -
+	 *    Create one with dc:rights['x-default'] set from double linefeed and xmpDM:copyright.
+	 * 
+	 * 2. If there is a dc:rights array but it has no x-default item -
+	 *    Create an x-default item as a copy of the first item then apply rule #3.
+	 * 
+	 * 3. If there is a dc:rights array with an x-default item, 
+	 *    Look for a double linefeed in the value.
+	 *     A. If no double linefeed, compare the x-default value to the xmpDM:copyright value.
+	 *         A1. If they match then leave the x-default value alone.
+	 *         A2. Otherwise, append a double linefeed and 
+	 *             the xmpDM:copyright value to the x-default value.
+	 *     B. If there is a double linefeed, compare the trailing text to the xmpDM:copyright value.
+	 *         B1. If they match then leave the x-default value alone.
+	 *         B2. Otherwise, replace the trailing x-default text with the xmpDM:copyright value.
+	 * 
+	 * 4. In all cases, delete the xmpDM:copyright property.
+	 * </pre>
+	 * 
+	 * @param xmp the metadata object 
+	 * @param dmCopyright the "dm:copyright"-property
+	 */
+	private static void	migrateAudioCopyright (XMPMeta xmp, XMPNode dmCopyright)
+	{
+		try 
+		{
+			XMPNode dcSchema = XMPNodeUtils.findSchemaNode(
+				((XMPMetaImpl) xmp).getRoot(), XMPConst.NS_DC, true);
+			
+			String dmValue = dmCopyright.getValue();
+			String doubleLF = "\n\n";
+			
+			XMPNode dcRightsArray = XMPNodeUtils.findChildNode (dcSchema, "dc:rights", false);
+			
+			if (dcRightsArray == null  ||  !dcRightsArray.hasChildren()) 
+			{
+				// 1. No dc:rights array, create from double linefeed and xmpDM:copyright.
+				dmValue = doubleLF + dmValue;
+				xmp.setLocalizedText(XMPConst.NS_DC, "rights", "", XMPConst.X_DEFAULT, dmValue,
+						null);
+			}
+			else
+			{
+				int xdIndex = XMPNodeUtils.lookupLanguageItem(dcRightsArray, XMPConst.X_DEFAULT);
+				
+				if (xdIndex < 0)
+				{
+					// 2. No x-default item, create from the first item.
+					String firstValue = dcRightsArray.getChild(1).getValue();
+					xmp.setLocalizedText (XMPConst.NS_DC, "rights", "", XMPConst.X_DEFAULT, 
+						firstValue, null);
+					xdIndex = XMPNodeUtils.lookupLanguageItem(dcRightsArray, XMPConst.X_DEFAULT);
+				}
+							
+				// 3. Look for a double linefeed in the x-default value.
+				XMPNode defaultNode = dcRightsArray.getChild(xdIndex);
+				String defaultValue = defaultNode.getValue();
+				int lfPos = defaultValue.indexOf(doubleLF);
+				
+				if (lfPos < 0)
+				{
+					// 3A. No double LF, compare whole values.
+					if (!dmValue.equals(defaultValue))
+					{
+						// 3A2. Append the xmpDM:copyright to the x-default
+						// item.
+						defaultNode.setValue(defaultValue + doubleLF + dmValue);
+					}
+				}
+				else
+				{
+					// 3B. Has double LF, compare the tail.
+					if (!defaultValue.substring(lfPos + 2).equals(dmValue))
+					{
+						// 3B2. Replace the x-default tail.
+						defaultNode.setValue(defaultValue.substring(0, lfPos + 2) + dmValue);
+					}
+				}
+
+			}
+			
+			// 4. Get rid of the xmpDM:copyright.
+			dmCopyright.getParent().removeChild(dmCopyright);
+		}
+		catch (XMPException e)
+		{
+			// Don't let failures (like a bad dc:rights form) stop other
+			// cleanup.
+		}
+	}
+	
+	
+	/**
+	 * Initializes the map that contains the known arrays, that are fixed by 
+	 * {@link XMPNormalizer#normalizeDCArrays(XMPNode)}. 
+	 */
+	private static void initDCArrays()
+	{
+		dcArrayForms = new HashMap(); 
+		
+		// Properties supposed to be a "Bag".
+		PropertyOptions bagForm = new PropertyOptions();
+		bagForm.setArray(true);
+		dcArrayForms.put("dc:contributor", bagForm);
+		dcArrayForms.put("dc:language", bagForm);
+		dcArrayForms.put("dc:publisher", bagForm);
+		dcArrayForms.put("dc:relation", bagForm);
+		dcArrayForms.put("dc:subject", bagForm);
+		dcArrayForms.put("dc:type", bagForm);
+
+		// Properties supposed to be a "Seq".
+		PropertyOptions seqForm = new PropertyOptions();
+		seqForm.setArray(true);
+		seqForm.setArrayOrdered(true);
+		dcArrayForms.put("dc:creator", seqForm);
+		dcArrayForms.put("dc:date", seqForm);
+		
+		// Properties supposed to be an "Alt" in alternative-text form.
+		PropertyOptions altTextForm = new PropertyOptions();
+		altTextForm.setArray(true);
+		altTextForm.setArrayOrdered(true);
+		altTextForm.setArrayAlternate(true);
+		altTextForm.setArrayAltText(true);
+		dcArrayForms.put("dc:description", altTextForm);
+		dcArrayForms.put("dc:rights", altTextForm);
+		dcArrayForms.put("dc:title", altTextForm);
+	}
+}
diff --git a/XMPCore/src/com/adobe/xmp/impl/XMPSchemaRegistryImpl.java b/XMPCore/src/com/adobe/xmp/impl/XMPSchemaRegistryImpl.java
new file mode 100644
index 0000000..1b8430d
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/impl/XMPSchemaRegistryImpl.java
@@ -0,0 +1,490 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp.impl;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.regex.Pattern;
+
+import com.adobe.xmp.XMPConst;
+import com.adobe.xmp.XMPError;
+import com.adobe.xmp.XMPException;
+import com.adobe.xmp.XMPSchemaRegistry;
+import com.adobe.xmp.options.AliasOptions;
+import com.adobe.xmp.properties.XMPAliasInfo;
+
+
+/**
+ * The schema registry handles the namespaces, aliases and global options for the XMP Toolkit. There
+ * is only one single instance used by the toolkit.
+ * 
+ * @since 27.01.2006
+ */
+public final class XMPSchemaRegistryImpl implements XMPSchemaRegistry, XMPConst
+{
+	/** a map from a namespace URI to its registered prefix */
+	private Map namespaceToPrefixMap = new HashMap();
+
+	/** a map from a prefix to the associated namespace URI */
+	private Map prefixToNamespaceMap = new HashMap();
+
+	/** a map of all registered aliases. 
+	 *  The map is a relationship from a qname to an <code>XMPAliasInfo</code>-object. */
+	private Map aliasMap = new HashMap();
+	/** The pattern that must not be contained in simple properties */
+	private Pattern p = Pattern.compile("[/*?\\[\\]]");
+
+	
+	/**
+	 * Performs the initialisation of the registry with the default namespaces, aliases and global
+	 * options.
+	 */
+	public XMPSchemaRegistryImpl()
+	{
+		try
+		{
+			registerStandardNamespaces();
+			registerStandardAliases();
+		}
+		catch (XMPException e)
+		{
+			throw new RuntimeException("The XMPSchemaRegistry cannot be initialized!");
+		}
+	}
+
+	
+	// ---------------------------------------------------------------------------------------------
+	// Namespace Functions
+	
+
+	/**
+	 * @see XMPSchemaRegistry#registerNamespace(String, String)
+	 */
+	public synchronized String registerNamespace(String namespaceURI, String suggestedPrefix)
+			throws XMPException
+	{
+		ParameterAsserts.assertSchemaNS(namespaceURI);
+		ParameterAsserts.assertPrefix(suggestedPrefix);
+		
+		if (suggestedPrefix.charAt(suggestedPrefix.length() - 1) != ':')
+		{
+			suggestedPrefix += ':';
+		}
+		
+		if (!Utils.isXMLNameNS(suggestedPrefix.substring(0,
+				suggestedPrefix.length() - 1)))
+		{
+			throw new XMPException("The prefix is a bad XML name", XMPError.BADXML);
+		}
+		
+		String registeredPrefix = (String) namespaceToPrefixMap.get(namespaceURI);
+		String registeredNS = (String) prefixToNamespaceMap.get(suggestedPrefix);
+		if (registeredPrefix != null)
+		{
+			// Return the actual prefix
+			return registeredPrefix;
+		}
+		else
+		{
+			if (registeredNS != null) 
+			{
+				// the namespace is new, but the prefix is already engaged,
+				// we generate a new prefix out of the suggested
+				String generatedPrefix = suggestedPrefix;
+				for (int i = 1; prefixToNamespaceMap.containsKey(generatedPrefix); i++)
+				{
+					generatedPrefix = suggestedPrefix
+							.substring(0, suggestedPrefix.length() - 1)
+							+ "_" + i + "_:";
+				}
+				suggestedPrefix = generatedPrefix;
+			}			
+			prefixToNamespaceMap.put(suggestedPrefix, namespaceURI);
+			namespaceToPrefixMap.put(namespaceURI, suggestedPrefix);
+			
+			// Return the suggested prefix
+			return suggestedPrefix;
+		}
+	}
+
+
+	/**
+	 * @see XMPSchemaRegistry#deleteNamespace(String)
+	 */
+	public synchronized void deleteNamespace(String namespaceURI)
+	{
+		String prefixToDelete = getNamespacePrefix(namespaceURI);
+		if (prefixToDelete != null)
+		{	
+			namespaceToPrefixMap.remove(namespaceURI);
+			prefixToNamespaceMap.remove(prefixToDelete);
+		}	
+	}
+
+
+	/**
+	 * @see XMPSchemaRegistry#getNamespacePrefix(String)
+	 */
+	public synchronized String getNamespacePrefix(String namespaceURI)
+	{
+		return (String) namespaceToPrefixMap.get(namespaceURI);
+	}
+
+
+	/**
+	 * @see XMPSchemaRegistry#getNamespaceURI(String)
+	 */
+	public synchronized String getNamespaceURI(String namespacePrefix)
+	{
+		if (namespacePrefix != null  &&  !namespacePrefix.endsWith(":"))
+		{
+			namespacePrefix += ":";
+		}
+		return (String) prefixToNamespaceMap.get(namespacePrefix);
+	}
+
+
+	/**
+	 * @see XMPSchemaRegistry#getNamespaces()
+	 */
+	public synchronized Map getNamespaces()
+	{
+		return Collections.unmodifiableMap(new TreeMap(namespaceToPrefixMap));
+	}
+	
+	
+	/**
+	 * @see XMPSchemaRegistry#getPrefixes()
+	 */
+	public synchronized Map getPrefixes()
+	{
+		return Collections.unmodifiableMap(new TreeMap(prefixToNamespaceMap));
+	}
+	
+	
+	/**
+	 * Register the standard namespaces of schemas and types that are included in the XMP
+	 * Specification and some other Adobe private namespaces.
+	 * Note: This method is not lock because only called by the constructor.
+	 * 
+	 * @throws XMPException Forwards processing exceptions
+	 */
+	private void registerStandardNamespaces() throws XMPException
+	{
+		// register standard namespaces
+		registerNamespace(NS_XML, "xml");
+		registerNamespace(NS_RDF, "rdf");
+		registerNamespace(NS_DC, "dc");
+		registerNamespace(NS_IPTCCORE, "Iptc4xmpCore");
+
+		// register Adobe standard namespaces
+		registerNamespace(NS_X, "x");
+		registerNamespace(NS_IX, "iX");
+
+		registerNamespace(NS_XMP, "xmp");
+		registerNamespace(NS_XMP_RIGHTS, "xmpRights");
+		registerNamespace(NS_XMP_MM, "xmpMM");
+		registerNamespace(NS_XMP_BJ, "xmpBJ");
+		registerNamespace(NS_XMP_NOTE, "xmpNote");
+		
+		registerNamespace(NS_PDF, "pdf");
+		registerNamespace(NS_PDFX, "pdfx");
+		registerNamespace(NS_PDFX_ID, "pdfxid");
+		registerNamespace(NS_PDFA_SCHEMA, "pdfaSchema");
+		registerNamespace(NS_PDFA_PROPERTY, "pdfaProperty");
+		registerNamespace(NS_PDFA_TYPE, "pdfaType");
+		registerNamespace(NS_PDFA_FIELD, "pdfaField");
+		registerNamespace(NS_PDFA_ID, "pdfaid");
+		registerNamespace(NS_PDFA_EXTENSION, "pdfaExtension");
+		registerNamespace(NS_PHOTOSHOP, "photoshop");
+		registerNamespace(NS_PSALBUM, "album");
+		registerNamespace(NS_EXIF, "exif");
+		registerNamespace(NS_EXIF_AUX, "aux");
+		registerNamespace(NS_TIFF, "tiff");
+		registerNamespace(NS_PNG, "png");
+		registerNamespace(NS_JPEG, "jpeg");
+		registerNamespace(NS_JP2K, "jp2k");
+		registerNamespace(NS_CAMERARAW, "crs");
+		registerNamespace(NS_ADOBESTOCKPHOTO, "bmsp");
+		registerNamespace(NS_CREATOR_ATOM, "creatorAtom");
+		registerNamespace(NS_ASF, "asf");
+		registerNamespace(NS_WAV, "wav");
+		
+		// register Adobe private namespaces
+		registerNamespace(NS_DM, "xmpDM");
+		registerNamespace(NS_TRANSIENT, "xmpx");
+		
+		// register Adobe standard type namespaces
+		registerNamespace(TYPE_TEXT, "xmpT");
+		registerNamespace(TYPE_PAGEDFILE, "xmpTPg");
+		registerNamespace(TYPE_GRAPHICS, "xmpG");
+		registerNamespace(TYPE_IMAGE, "xmpGImg");
+		registerNamespace(TYPE_FONT, "stFNT");
+		registerNamespace(TYPE_DIMENSIONS, "stDim");
+		registerNamespace(TYPE_RESOURCEEVENT, "stEvt");
+		registerNamespace(TYPE_RESOURCEREF, "stRef");
+		registerNamespace(TYPE_ST_VERSION, "stVer");
+		registerNamespace(TYPE_ST_JOB, "stJob");
+		registerNamespace(TYPE_MANIFESTITEM, "stMfs");
+		registerNamespace(TYPE_IDENTIFIERQUAL, "xmpidq");
+	}
+	
+
+	
+	// ---------------------------------------------------------------------------------------------
+	// Alias Functions
+
+	
+	/**
+	 * @see XMPSchemaRegistry#resolveAlias(String, String)
+	 */
+	public synchronized XMPAliasInfo resolveAlias(String aliasNS, String aliasProp)
+	{
+		String aliasPrefix = getNamespacePrefix(aliasNS);
+		if (aliasPrefix == null)
+		{	
+			return null;
+		}
+		
+		return (XMPAliasInfo) aliasMap.get(aliasPrefix + aliasProp);
+	}
+
+
+	/**
+	 * @see XMPSchemaRegistry#findAlias(java.lang.String)
+	 */
+	public synchronized XMPAliasInfo findAlias(String qname)
+	{
+		return (XMPAliasInfo) aliasMap.get(qname);
+	}
+
+	
+	/**
+	 * @see XMPSchemaRegistry#findAliases(String)
+	 */
+	public synchronized XMPAliasInfo[] findAliases(String aliasNS)
+	{
+		String prefix = getNamespacePrefix(aliasNS);
+		List result = new ArrayList(); 
+		if (prefix != null)
+		{
+			for (Iterator it = aliasMap.keySet().iterator(); it.hasNext();)
+			{
+				String qname = (String) it.next();
+				if (qname.startsWith(prefix))
+				{
+					result.add(findAlias(qname));
+				}
+			}
+			
+		}
+		return (XMPAliasInfo[]) result.toArray(new XMPAliasInfo[result.size()]);
+	}	
+	
+	
+	/**
+	 * Associates an alias name with an actual name.
+	 * <p>
+	 * Define a alias mapping from one namespace/property to another. Both
+	 * property names must be simple names. An alias can be a direct mapping,
+	 * where the alias and actual have the same data type. It is also possible
+	 * to map a simple alias to an item in an array. This can either be to the
+	 * first item in the array, or to the 'x-default' item in an alt-text array.
+	 * Multiple alias names may map to the same actual, as long as the forms
+	 * match. It is a no-op to reregister an alias in an identical fashion.
+	 * Note: This method is not locking because only called by registerStandardAliases 
+	 * which is only called by the constructor.
+	 * Note2: The method is only package-private so that it can be tested with unittests 
+	 * 
+	 * @param aliasNS
+	 *            The namespace URI for the alias. Must not be null or the empty
+	 *            string.
+	 * @param aliasProp
+	 *            The name of the alias. Must be a simple name, not null or the
+	 *            empty string and not a general path expression.
+	 * @param actualNS
+	 *            The namespace URI for the actual. Must not be null or the
+	 *            empty string.
+	 * @param actualProp
+	 *            The name of the actual. Must be a simple name, not null or the
+	 *            empty string and not a general path expression.
+	 * @param aliasForm
+	 *            Provides options for aliases for simple aliases to array
+	 *            items. This is needed to know what kind of array to create if
+	 *            set for the first time via the simple alias. Pass
+	 *            <code>XMP_NoOptions</code>, the default value, for all
+	 *            direct aliases regardless of whether the actual data type is
+	 *            an array or not (see {@link AliasOptions}).
+	 * @throws XMPException
+	 *             for inconsistant aliases.
+	 */
+	synchronized void registerAlias(String aliasNS, String aliasProp, final String actualNS,
+			final String actualProp, final AliasOptions aliasForm) throws XMPException
+	{
+		ParameterAsserts.assertSchemaNS(aliasNS);
+		ParameterAsserts.assertPropName(aliasProp);
+		ParameterAsserts.assertSchemaNS(actualNS);
+		ParameterAsserts.assertPropName(actualProp);
+		
+		// Fix the alias options
+		final AliasOptions aliasOpts = aliasForm != null ?
+			new AliasOptions(XMPNodeUtils.verifySetOptions(
+				aliasForm.toPropertyOptions(), null).getOptions()) :
+			new AliasOptions();
+	
+		if (p.matcher(aliasProp).find()  ||  p.matcher(actualProp).find())
+		{
+			throw new XMPException("Alias and actual property names must be simple",
+					XMPError.BADXPATH);
+		}
+		
+		// check if both namespaces are registered
+		final String aliasPrefix = getNamespacePrefix(aliasNS);
+		final String actualPrefix = getNamespacePrefix(actualNS);
+		if (aliasPrefix == null)
+		{
+			throw new XMPException("Alias namespace is not registered", XMPError.BADSCHEMA);
+		}
+		else if (actualPrefix == null)
+		{
+			throw new XMPException("Actual namespace is not registered", 
+				XMPError.BADSCHEMA);
+		}
+		
+		String key = aliasPrefix + aliasProp;
+		
+		// check if alias is already existing
+		if (aliasMap.containsKey(key))
+		{
+			throw new XMPException("Alias is already existing", XMPError.BADPARAM);
+		}
+		else if (aliasMap.containsKey(actualPrefix + actualProp))
+		{	
+			throw new XMPException(
+					"Actual property is already an alias, use the base property",
+					XMPError.BADPARAM);
+		}
+		
+		XMPAliasInfo aliasInfo = new XMPAliasInfo()
+		{
+			/**
+			 * @see XMPAliasInfo#getNamespace()
+			 */
+			public String getNamespace()
+			{
+				return actualNS;
+			}
+
+			/**
+			 * @see XMPAliasInfo#getPrefix()
+			 */
+			public String getPrefix()
+			{
+				return actualPrefix;
+			}
+
+			/**
+			 * @see XMPAliasInfo#getPropName()
+			 */
+			public String getPropName()
+			{
+				return actualProp;
+			}
+			
+			/**
+			 * @see XMPAliasInfo#getAliasForm()
+			 */
+			public AliasOptions getAliasForm()
+			{
+				return aliasOpts;
+			}
+			
+			public String toString()
+			{
+				return actualPrefix + actualProp + " NS(" + actualNS + "), FORM ("
+						+ getAliasForm() + ")"; 
+			}
+		};
+		
+		aliasMap.put(key, aliasInfo);
+	}
+
+		
+	/**
+	 * @see XMPSchemaRegistry#getAliases()
+	 */
+	public synchronized Map getAliases()
+	{
+		return Collections.unmodifiableMap(new TreeMap(aliasMap));
+	}
+	
+	
+	/**
+	 * Register the standard aliases.
+	 * Note: This method is not lock because only called by the constructor.
+	 * 
+	 * @throws XMPException If the registrations of at least one alias fails.
+	 */
+	private void registerStandardAliases() throws XMPException
+	{
+		AliasOptions aliasToArrayOrdered = new AliasOptions().setArrayOrdered(true);
+		AliasOptions aliasToArrayAltText = new AliasOptions().setArrayAltText(true);
+		
+		
+		// Aliases from XMP to DC.
+		registerAlias(NS_XMP, "Author", NS_DC, "creator", aliasToArrayOrdered);
+		registerAlias(NS_XMP, "Authors", NS_DC, "creator", null);
+		registerAlias(NS_XMP, "Description", NS_DC, "description", null);
+		registerAlias(NS_XMP, "Format", NS_DC, "format", null);
+		registerAlias(NS_XMP, "Keywords", NS_DC, "subject", null);
+		registerAlias(NS_XMP, "Locale", NS_DC, "language", null);
+		registerAlias(NS_XMP, "Title", NS_DC, "title", null);
+		registerAlias(NS_XMP_RIGHTS, "Copyright", NS_DC, "rights", null);
+
+		// Aliases from PDF to DC and XMP.
+		registerAlias(NS_PDF, "Author", NS_DC, "creator", aliasToArrayOrdered);
+		registerAlias(NS_PDF, "BaseURL", NS_XMP, "BaseURL", null);
+		registerAlias(NS_PDF, "CreationDate", NS_XMP, "CreateDate", null);
+		registerAlias(NS_PDF, "Creator", NS_XMP, "CreatorTool", null);
+		registerAlias(NS_PDF, "ModDate", NS_XMP, "ModifyDate", null);
+		registerAlias(NS_PDF, "Subject", NS_DC, "description", aliasToArrayAltText);
+		registerAlias(NS_PDF, "Title", NS_DC, "title", aliasToArrayAltText);
+
+		// Aliases from PHOTOSHOP to DC and XMP.
+		registerAlias(NS_PHOTOSHOP, "Author", NS_DC, "creator", aliasToArrayOrdered);
+		registerAlias(NS_PHOTOSHOP, "Caption", NS_DC, "description", aliasToArrayAltText);
+		registerAlias(NS_PHOTOSHOP, "Copyright", NS_DC, "rights", aliasToArrayAltText);
+		registerAlias(NS_PHOTOSHOP, "Keywords", NS_DC, "subject", null);
+		registerAlias(NS_PHOTOSHOP, "Marked", NS_XMP_RIGHTS, "Marked", null);
+		registerAlias(NS_PHOTOSHOP, "Title", NS_DC, "title", aliasToArrayAltText);
+		registerAlias(NS_PHOTOSHOP, "WebStatement", NS_XMP_RIGHTS, "WebStatement", null);
+
+		// Aliases from TIFF and EXIF to DC and XMP.
+		registerAlias(NS_TIFF, "Artist", NS_DC, "creator", aliasToArrayOrdered);
+		registerAlias(NS_TIFF, "Copyright", NS_DC, "rights", null);
+		registerAlias(NS_TIFF, "DateTime", NS_XMP, "ModifyDate", null);
+		registerAlias(NS_TIFF, "ImageDescription", NS_DC, "description", null);
+		registerAlias(NS_TIFF, "Software", NS_XMP, "CreatorTool", null);
+
+		// Aliases from PNG (Acrobat ImageCapture) to DC and XMP.
+		registerAlias(NS_PNG, "Author", NS_DC, "creator", aliasToArrayOrdered);
+		registerAlias(NS_PNG, "Copyright", NS_DC, "rights", aliasToArrayAltText);
+		registerAlias(NS_PNG, "CreationTime", NS_XMP, "CreateDate", null);
+		registerAlias(NS_PNG, "Description", NS_DC, "description", aliasToArrayAltText);
+		registerAlias(NS_PNG, "ModificationTime", NS_XMP, "ModifyDate", null);
+		registerAlias(NS_PNG, "Software", NS_XMP, "CreatorTool", null);
+		registerAlias(NS_PNG, "Title", NS_DC, "title", aliasToArrayAltText);
+	}
+}
\ No newline at end of file
diff --git a/XMPCore/src/com/adobe/xmp/impl/XMPSerializerHelper.java b/XMPCore/src/com/adobe/xmp/impl/XMPSerializerHelper.java
new file mode 100644
index 0000000..ba43f01
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/impl/XMPSerializerHelper.java
@@ -0,0 +1,102 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp.impl;
+
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+
+import com.adobe.xmp.XMPException;
+import com.adobe.xmp.options.SerializeOptions;
+
+
+/**
+ * Serializes the <code>XMPMeta</code>-object to an <code>OutputStream</code> according to the
+ * <code>SerializeOptions</code>. 
+ * 
+ * @since   11.07.2006
+ */
+public class XMPSerializerHelper
+{
+	/**
+	 * Static method to serialize the metadata object. For each serialisation, a new XMPSerializer
+	 * instance is created, either XMPSerializerRDF or XMPSerializerPlain so thats its possible to 
+	 * serialialize the same XMPMeta objects in two threads.
+	 * 
+	 * @param xmp a metadata implementation object
+	 * @param out the output stream to serialize to
+	 * @param options serialization options, can be <code>null</code> for default.
+	 * @throws XMPException
+	 */
+	public static void serialize(XMPMetaImpl xmp, OutputStream out, 
+		SerializeOptions options)
+		throws XMPException
+	{
+		options = options != null ? options : new SerializeOptions();		
+		
+		// sort the internal data model on demand
+		if (options.getSort())
+		{
+			xmp.sort();
+		}
+		new XMPSerializerRDF().serialize(xmp, out, options);
+	}		
+	
+
+	/**
+	 * Serializes an <code>XMPMeta</code>-object as RDF into a string.
+	 * <em>Note:</em> Encoding is forced to UTF-16 when serializing to a
+	 * string to ensure the correctness of &quot;exact packet size&quot;.
+	 * 
+	 * @param xmp a metadata implementation object
+	 * @param options Options to control the serialization (see
+	 *            {@link SerializeOptions}).
+	 * @return Returns a string containing the serialized RDF.
+	 * @throws XMPException on serializsation errors.
+	 */
+	public static String serializeToString(XMPMetaImpl xmp, SerializeOptions options)
+		throws XMPException
+	{
+		// forces the encoding to be UTF-16 to get the correct string length
+		options = options != null ? options : new SerializeOptions();		
+		options.setEncodeUTF16BE(true);
+
+		ByteArrayOutputStream out = new ByteArrayOutputStream(2048);
+		serialize(xmp, out, options);
+
+		try
+		{
+			return out.toString(options.getEncoding());
+		}
+		catch (UnsupportedEncodingException e)
+		{
+			// cannot happen as UTF-8/16LE/BE is required to be implemented in
+			// Java
+			return out.toString();
+		}
+	}
+	
+	
+	/**
+	 * Serializes an <code>XMPMeta</code>-object as RDF into a byte buffer.
+	 * 
+	 * @param xmp a metadata implementation object
+	 * @param options Options to control the serialization (see {@link SerializeOptions}).
+	 * @return Returns a byte buffer containing the serialized RDF.
+	 * @throws XMPException on serializsation errors.
+	 */
+	public static byte[] serializeToBuffer(XMPMetaImpl xmp, SerializeOptions options)
+			throws XMPException
+	{
+		ByteArrayOutputStream out = new ByteArrayOutputStream(2048);
+		serialize(xmp, out, options);
+		return out.toByteArray();
+	}
+}
\ No newline at end of file
diff --git a/XMPCore/src/com/adobe/xmp/impl/XMPSerializerRDF.java b/XMPCore/src/com/adobe/xmp/impl/XMPSerializerRDF.java
new file mode 100644
index 0000000..b00877f
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/impl/XMPSerializerRDF.java
@@ -0,0 +1,1276 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp.impl;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import com.adobe.xmp.XMPConst;
+import com.adobe.xmp.XMPError;
+import com.adobe.xmp.XMPException;
+import com.adobe.xmp.XMPMeta;
+import com.adobe.xmp.XMPMetaFactory;
+import com.adobe.xmp.options.SerializeOptions;
+
+
+/**
+ * Serializes the <code>XMPMeta</code>-object using the standard RDF serialization format. 
+ * The output is written to an <code>OutputStream</code> 
+ * according to the <code>SerializeOptions</code>. 
+ * 
+ * @since   11.07.2006
+ */
+public class XMPSerializerRDF
+{
+	/** default padding */
+	private static final int DEFAULT_PAD = 2048;	
+	/** */
+	private static final String PACKET_HEADER  =
+		"<?xpacket begin=\"\uFEFF\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>";
+	/** The w/r is missing inbetween */
+	private static final String PACKET_TRAILER = "<?xpacket end=\"";
+	/** */
+	private static final String PACKET_TRAILER2 = "\"?>"; 
+	/** */
+	private static final String RDF_XMPMETA_START = 
+		"<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"";
+	/** */
+	private static final String RDF_XMPMETA_END   = "</x:xmpmeta>";
+	/** */
+	private static final String RDF_RDF_START = 
+		"<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">";
+	/** */
+	private static final String RDF_RDF_END       = "</rdf:RDF>";
+	
+	/** */
+	private static final String RDF_SCHEMA_START  = "<rdf:Description rdf:about=";
+	/** */
+	private static final String RDF_SCHEMA_END    = "</rdf:Description>";
+	/** */
+	private static final String RDF_STRUCT_START  = "<rdf:Description";
+	/** */
+	private static final String RDF_STRUCT_END    = "</rdf:Description>";
+	/** a set of all rdf attribute qualifier */
+	static final Set RDF_ATTR_QUALIFIER = new HashSet(Arrays.asList(new String[] {
+			XMPConst.XML_LANG, "rdf:resource", "rdf:ID", "rdf:bagID", "rdf:nodeID" }));
+	
+	/** the metadata object to be serialized. */ 
+	private XMPMetaImpl xmp; 
+	/** the output stream to serialize to */ 
+	private CountOutputStream outputStream;
+	/** this writer is used to do the actual serialisation */
+	private OutputStreamWriter writer;
+	/** the stored serialisation options */
+	private SerializeOptions options;
+	/** the size of one unicode char, for UTF-8 set to 1 
+	 *  (Note: only valid for ASCII chars lower than 0x80),
+	 *  set to 2 in case of UTF-16 */
+	private int unicodeSize = 1; // UTF-8
+	/** the padding in the XMP Packet, or the length of the complete packet in
+	 *  case of option <em>exactPacketLength</em>. */ 
+	private int padding;
+
+	
+	/**
+	 * The actual serialisation.
+	 * 
+	 * @param xmp the metadata object to be serialized
+	 * @param out outputStream the output stream to serialize to
+	 * @param options the serialization options
+	 * 
+	 * @throws XMPException If case of wrong options or any other serialisaton error.
+	 */
+	public void serialize(XMPMeta xmp, OutputStream out, 
+			SerializeOptions options) throws XMPException
+	{
+		try
+		{
+			outputStream = new CountOutputStream(out); 
+			writer = new OutputStreamWriter(outputStream, options.getEncoding());
+			
+			this.xmp = (XMPMetaImpl) xmp;
+			this.options = options;
+			this.padding = options.getPadding();
+
+			writer = new OutputStreamWriter(outputStream, options.getEncoding());
+			
+			checkOptionsConsistence();
+			
+			// serializes the whole packet, but don't write the tail yet 
+			// and flush to make sure that the written bytes are calculated correctly
+			String tailStr = serializeAsRDF();
+			writer.flush();
+			
+			// adds padding
+			addPadding(tailStr.length());
+
+			// writes the tail
+			write(tailStr);
+			writer.flush();
+			
+			outputStream.close();
+		}
+		catch (IOException e)
+		{
+			throw new XMPException("Error writing to the OutputStream", XMPError.UNKNOWN);
+		}
+	}
+
+
+	/**
+	 * Calulates the padding according to the options and write it to the stream.
+	 * @param tailLength the length of the tail string 
+	 * @throws XMPException thrown if packet size is to small to fit the padding
+	 * @throws IOException forwards writer errors
+	 */
+	private void addPadding(int tailLength) throws XMPException, IOException
+	{
+		if (options.getExactPacketLength())
+		{
+			// the string length is equal to the length of the UTF-8 encoding
+			int minSize = outputStream.getBytesWritten() + tailLength * unicodeSize;
+			if (minSize > padding)
+			{
+				throw new XMPException("Can't fit into specified packet size",
+					XMPError.BADSERIALIZE);
+			}
+			padding -= minSize;	// Now the actual amount of padding to add.
+		}
+
+		// fix rest of the padding according to Unicode unit size.
+		padding /= unicodeSize;
+		
+		int newlineLen = options.getNewline().length();
+		if (padding >= newlineLen)
+		{
+			padding -= newlineLen;	// Write this newline last.
+			while (padding >= (100 + newlineLen))
+			{
+				writeChars(100, ' ');
+				writeNewline();
+				padding -= (100 + newlineLen);
+			}
+			writeChars(padding, ' ');
+			writeNewline();
+		}
+		else
+		{
+			writeChars(padding, ' ');				
+		}
+	}
+
+
+	/**
+	 * Checks if the supplied options are consistent.
+	 * @throws XMPException Thrown if options are conflicting
+	 */
+	protected void checkOptionsConsistence() throws XMPException
+	{
+		if (options.getEncodeUTF16BE() | options.getEncodeUTF16LE())
+		{
+			unicodeSize = 2;
+		}
+
+		if (options.getExactPacketLength())
+		{
+			if (options.getOmitPacketWrapper() | options.getIncludeThumbnailPad())
+			{
+				throw new XMPException("Inconsistent options for exact size serialize",
+						XMPError.BADOPTIONS);
+			}
+			if ((options.getPadding() & (unicodeSize - 1)) != 0)
+			{
+				throw new XMPException("Exact size must be a multiple of the Unicode element",
+						XMPError.BADOPTIONS);
+			}
+		}
+		else if (options.getReadOnlyPacket())
+		{
+			if (options.getOmitPacketWrapper() | options.getIncludeThumbnailPad())
+			{
+				throw new XMPException("Inconsistent options for read-only packet",
+						XMPError.BADOPTIONS);
+			}
+			padding = 0;
+		}
+		else if (options.getOmitPacketWrapper())
+		{
+			if (options.getIncludeThumbnailPad())
+			{
+				throw new XMPException("Inconsistent options for non-packet serialize",
+						XMPError.BADOPTIONS);
+			}
+			padding = 0;
+		}
+		else
+		{
+			if (padding == 0)
+			{
+				padding = DEFAULT_PAD * unicodeSize;
+			}
+			
+			if (options.getIncludeThumbnailPad())
+			{
+				if (!xmp.doesPropertyExist(XMPConst.NS_XMP, "Thumbnails"))
+				{
+					padding += 10000 * unicodeSize;
+				}
+			}
+		}
+	}
+	
+	
+	/**
+	 * Writes the (optional) packet header and the outer rdf-tags. 
+	 * @return Returns the packet end processing instraction to be written after the padding. 
+	 * @throws IOException Forwarded writer exceptions.
+	 * @throws XMPException 
+	 */
+	private String serializeAsRDF() throws IOException, XMPException
+	{
+		// Write the packet header PI.
+		if (!options.getOmitPacketWrapper())
+		{
+			writeIndent(0);
+			write(PACKET_HEADER);
+			writeNewline();
+		}
+	
+		// Write the xmpmeta element's start tag.
+		writeIndent(0);
+		write(RDF_XMPMETA_START);
+		// Note: this flag can only be set by unit tests
+		if (!options.getOmitVersionAttribute())
+		{	
+			write(XMPMetaFactory.getVersionInfo().getMessage());
+		}	
+		write("\">");
+		writeNewline();
+	
+		// Write the rdf:RDF start tag.
+		writeIndent(1);
+		write(RDF_RDF_START);
+		writeNewline();
+		
+		// Write all of the properties.
+		if (options.getUseCompactFormat())
+		{
+			serializeCompactRDFSchemas();
+		} 
+		else
+		{
+			serializePrettyRDFSchemas();
+		}
+	
+		// Write the rdf:RDF end tag.
+		writeIndent(1);
+		write(RDF_RDF_END);
+		writeNewline();
+	
+		// Write the xmpmeta end tag.
+		writeIndent(0);		
+		write(RDF_XMPMETA_END);
+		writeNewline();
+		
+		// Write the packet trailer PI into the tail string as UTF-8.
+		String tailStr = "";
+		if (!options.getOmitPacketWrapper())
+		{
+			for (int level = options.getBaseIndent(); level > 0; level--)
+			{
+				tailStr += options.getIndent();
+			}
+
+			tailStr += PACKET_TRAILER;
+			tailStr += options.getReadOnlyPacket() ? 'r' : 'w';
+			tailStr += PACKET_TRAILER2;
+		}
+		
+		return tailStr;
+	}
+
+	
+	/**
+	 * Serializes the metadata in pretty-printed manner.
+	 * @throws IOException Forwarded writer exceptions
+	 * @throws XMPException 
+	 */
+	private void serializePrettyRDFSchemas() throws IOException, XMPException
+	{
+		if (xmp.getRoot().getChildrenLength() > 0)
+		{
+			for (Iterator it = xmp.getRoot().iterateChildren(); it.hasNext(); )
+			{
+				XMPNode currSchema = (XMPNode) it.next();
+				serializePrettyRDFSchema(currSchema);
+			}
+		}
+		else
+		{
+			writeIndent(2);
+			write(RDF_SCHEMA_START); // Special case an empty XMP object.
+			writeTreeName();
+			write("/>");
+			writeNewline();
+		}		
+	}
+
+
+	/**
+	 * @throws IOException
+	 */
+	private void writeTreeName() throws IOException
+	{
+		write('"');
+		String name = xmp.getRoot().getName();
+		if (name != null)
+		{	
+			appendNodeValue(name, true);
+		}	
+		write('"');
+	}
+	
+	
+	/**
+	 * Serializes the metadata in compact manner.
+	 * @throws IOException Forwarded writer exceptions
+	 * @throws XMPException 
+	 */
+	private void serializeCompactRDFSchemas() throws IOException, XMPException
+	{
+		// Begin the rdf:Description start tag.
+		writeIndent(2);
+		write(RDF_SCHEMA_START);
+		writeTreeName();
+		
+		// Write all necessary xmlns attributes.
+		Set usedPrefixes = new HashSet();
+		usedPrefixes.add("xml");
+		usedPrefixes.add("rdf");
+
+		for (Iterator it = xmp.getRoot().iterateChildren(); it.hasNext();)
+		{
+			XMPNode schema = (XMPNode) it.next();
+			declareUsedNamespaces(schema, usedPrefixes, 4);
+		}
+	
+		// Write the top level "attrProps" and close the rdf:Description start tag.
+		boolean allAreAttrs = true;
+		for (Iterator it = xmp.getRoot().iterateChildren(); it.hasNext();)
+		{
+			XMPNode schema = (XMPNode) it.next();
+			allAreAttrs &= serializeCompactRDFAttrProps (schema, 3);
+		}
+
+		if (!allAreAttrs)
+		{
+			write('>');
+			writeNewline();
+		}
+		else
+		{
+			write("/>");
+			writeNewline();
+			return;	// ! Done if all properties in all schema are written as attributes.
+		}
+	
+		// Write the remaining properties for each schema.
+		for (Iterator it = xmp.getRoot().iterateChildren(); it.hasNext();)
+		{
+			XMPNode schema = (XMPNode) it.next();
+			serializeCompactRDFElementProps (schema, 3);
+		}
+
+		// Write the rdf:Description end tag.
+		writeIndent(2);
+		write(RDF_SCHEMA_END);
+		writeNewline();
+	}
+
+
+	
+	/**
+	 * Write each of the parent's simple unqualified properties as an attribute. Returns true if all
+	 * of the properties are written as attributes.
+	 * 
+	 * @param parentNode the parent property node
+	 * @param indent the current indent level
+	 * @return Returns true if all properties can be rendered as RDF attribute.
+	 * @throws IOException
+	 */
+	private boolean serializeCompactRDFAttrProps(XMPNode parentNode, int indent) throws IOException
+	{
+		boolean allAreAttrs = true;
+	
+		for (Iterator it = parentNode.iterateChildren(); it.hasNext();)
+		{
+			XMPNode prop = (XMPNode) it.next();
+		
+			if (canBeRDFAttrProp(prop))
+			{
+				writeNewline();
+				writeIndent(indent);
+				write(prop.getName());
+				write("=\"");
+				appendNodeValue(prop.getValue(), true);
+				write('"');
+			}
+			else
+			{
+				allAreAttrs = false;
+			}
+		}
+		return allAreAttrs;
+	}
+
+	
+	/**
+	 * Recursively handles the "value" for a node that must be written as an RDF
+	 * property element. It does not matter if it is a top level property, a
+	 * field of a struct, or an item of an array. The indent is that for the
+	 * property element. The patterns bwlow ignore attribute qualifiers such as
+	 * xml:lang, they don't affect the output form.
+	 * 
+	 * <blockquote>
+	 * 
+	 * <pre>
+	 *  	&lt;ns:UnqualifiedStructProperty-1
+	 *  		... The fields as attributes, if all are simple and unqualified
+	 *  	/&gt;
+	 *  
+	 *  	&lt;ns:UnqualifiedStructProperty-2 rdf:parseType=&quot;Resource&quot;&gt;
+	 *  		... The fields as elements, if none are simple and unqualified
+	 *  	&lt;/ns:UnqualifiedStructProperty-2&gt;
+	 *  
+	 *  	&lt;ns:UnqualifiedStructProperty-3&gt;
+	 *  		&lt;rdf:Description
+	 *  			... The simple and unqualified fields as attributes
+	 *  		&gt;
+	 *  			... The compound or qualified fields as elements
+	 *  		&lt;/rdf:Description&gt;
+	 *  	&lt;/ns:UnqualifiedStructProperty-3&gt;
+	 *  
+	 *  	&lt;ns:UnqualifiedArrayProperty&gt;
+	 *  		&lt;rdf:Bag&gt; or Seq or Alt
+	 *  			... Array items as rdf:li elements, same forms as top level properties
+	 *  		&lt;/rdf:Bag&gt;
+	 *  	&lt;/ns:UnqualifiedArrayProperty&gt;
+	 *  
+	 *  	&lt;ns:QualifiedProperty rdf:parseType=&quot;Resource&quot;&gt;
+	 *  		&lt;rdf:value&gt; ... Property &quot;value&quot; 
+	 *  			following the unqualified forms ... &lt;/rdf:value&gt;
+	 *  		... Qualifiers looking like named struct fields
+	 *  	&lt;/ns:QualifiedProperty&gt;
+	 * </pre>
+	 * 
+	 * </blockquote>
+	 * 
+	 * *** Consider numbered array items, but has compatibility problems. ***
+	 * Consider qualified form with rdf:Description and attributes.
+	 * 
+	 * @param parentNode the parent node
+	 * @param indent the current indent level
+	 * @throws IOException Forwards writer exceptions
+	 * @throws XMPException If qualifier and element fields are mixed.
+	 */
+	private void serializeCompactRDFElementProps(XMPNode parentNode, int indent)
+			throws IOException, XMPException
+	{
+		for (Iterator it = parentNode.iterateChildren(); it.hasNext();)
+		{
+			XMPNode node = (XMPNode) it.next();
+			if (canBeRDFAttrProp (node))
+			{	
+				continue;
+			}
+	
+			boolean emitEndTag = true;
+			boolean indentEndTag = true;
+	
+			// Determine the XML element name, write the name part of the start tag. Look over the
+			// qualifiers to decide on "normal" versus "rdf:value" form. Emit the attribute
+			// qualifiers at the same time.
+			String elemName = node.getName();
+			if (XMPConst.ARRAY_ITEM_NAME.equals(elemName))
+			{
+				elemName = "rdf:li";
+			}
+	
+			writeIndent(indent);
+			write('<');
+			write(elemName);
+	
+			boolean hasGeneralQualifiers = false;
+			boolean hasRDFResourceQual   = false;
+	
+			for (Iterator iq = 	node.iterateQualifier(); iq.hasNext();)
+			{
+				XMPNode qualifier = (XMPNode) iq.next();
+				if (!RDF_ATTR_QUALIFIER.contains(qualifier.getName()))
+				{
+					hasGeneralQualifiers = true;
+				}
+				else
+				{
+					hasRDFResourceQual = "rdf:resource".equals(qualifier.getName());
+					write(' ');
+					write(qualifier.getName());
+					write("=\"");
+					appendNodeValue(qualifier.getValue(), true);
+					write('"');
+				}
+			}
+			
+			
+			// Process the property according to the standard patterns.
+			if (hasGeneralQualifiers)
+			{
+				serializeCompactRDFGeneralQualifier(indent, node);
+			}
+			else
+			{
+				// This node has only attribute qualifiers. Emit as a property element.
+				if (!node.getOptions().isCompositeProperty())
+				{
+					Object[] result = serializeCompactRDFSimpleProp(node);
+					emitEndTag = ((Boolean) result[0]).booleanValue();
+					indentEndTag = ((Boolean) result[1]).booleanValue();
+				}
+				else if (node.getOptions().isArray())
+				{
+					serializeCompactRDFArrayProp(node, indent);
+				}
+				else
+				{
+					emitEndTag = serializeCompactRDFStructProp(
+						node, indent, hasRDFResourceQual);
+				}
+	
+			}
+
+			// Emit the property element end tag.
+			if (emitEndTag)
+			{
+				if (indentEndTag)
+				{
+					writeIndent(indent);
+				}
+				write("</");
+				write(elemName);
+				write('>');
+				writeNewline();
+			}
+	
+		}
+	}
+
+
+	/**
+	 * Serializes a simple property.
+	 * 
+	 * @param node an XMPNode 
+	 * @return Returns an array containing the flags emitEndTag and indentEndTag.
+	 * @throws IOException Forwards the writer exceptions.
+	 */
+	private Object[] serializeCompactRDFSimpleProp(XMPNode node) throws IOException
+	{
+		// This is a simple property.
+		Boolean emitEndTag = Boolean.TRUE;
+		Boolean indentEndTag = Boolean.TRUE;		
+
+		if (node.getOptions().isURI())
+		{
+			write(" rdf:resource=\"");
+			appendNodeValue(node.getValue(), true);
+			write("\"/>");
+			writeNewline();
+			emitEndTag = Boolean.FALSE;
+		}
+		else if (node.getValue() == null  ||  node.getValue().length() == 0)
+		{
+			write("/>");
+			writeNewline();
+			emitEndTag = Boolean.FALSE;
+		}
+		else
+		{
+			write('>');
+			appendNodeValue (node.getValue(), false);
+			indentEndTag = Boolean.FALSE;
+		}
+		
+		return new Object[] {emitEndTag, indentEndTag};
+	}
+
+
+	/**
+	 * Serializes an array property.
+	 * 
+	 * @param node an XMPNode 
+	 * @param indent the current indent level 
+	 * @throws IOException Forwards the writer exceptions.
+	 * @throws XMPException If qualifier and element fields are mixed.
+	 */
+	private void serializeCompactRDFArrayProp(XMPNode node, int indent) throws IOException,
+			XMPException
+	{
+		// This is an array.
+		write('>');
+		writeNewline();
+		emitRDFArrayTag (node, true, indent + 1);
+		
+		if (node.getOptions().isArrayAltText())
+		{
+			XMPNodeUtils.normalizeLangArray (node);
+		}
+		
+		serializeCompactRDFElementProps(node, indent + 2);
+		
+		emitRDFArrayTag(node, false, indent + 1);
+	}
+	
+	
+	/**
+	 * Serializes a struct property.
+	 * 
+	 * @param node an XMPNode 
+	 * @param indent the current indent level 
+	 * @param hasRDFResourceQual Flag if the element has resource qualifier
+	 * @return Returns true if an end flag shall be emitted.
+	 * @throws IOException Forwards the writer exceptions.
+	 * @throws XMPException If qualifier and element fields are mixed.
+	 */
+	private boolean serializeCompactRDFStructProp(XMPNode node, int indent,
+			boolean hasRDFResourceQual) throws XMPException, IOException
+	{
+		// This must be a struct.
+		boolean hasAttrFields = false;
+		boolean hasElemFields = false;
+		boolean emitEndTag = true;
+		
+		for (Iterator ic = node.iterateChildren(); ic.hasNext(); )
+		{
+			XMPNode field = (XMPNode) ic.next();
+			if (canBeRDFAttrProp(field))
+			{
+				hasAttrFields = true;
+			}
+			else
+			{
+				hasElemFields = true;
+			}
+
+			if (hasAttrFields  &&  hasElemFields)
+			{
+				break;	// No sense looking further.
+			}
+		}
+		
+		if (hasRDFResourceQual && hasElemFields)
+		{
+			throw new XMPException(
+					"Can't mix rdf:resource qualifier and element fields",
+					XMPError.BADRDF);
+		}
+
+		if (!node.hasChildren())
+		{
+			// Catch an empty struct as a special case. The case
+			// below would emit an empty
+			// XML element, which gets reparsed as a simple property
+			// with an empty value.
+			write(" rdf:parseType=\"Resource\"/>");
+			writeNewline();
+			emitEndTag = false;
+		
+		}
+		else if (!hasElemFields)
+		{
+			// All fields can be attributes, use the
+			// emptyPropertyElt form.
+			serializeCompactRDFAttrProps(node, indent + 1);
+			write("/>");
+			writeNewline();
+			emitEndTag = false;
+
+		}
+		else if (!hasAttrFields)
+		{
+			// All fields must be elements, use the
+			// parseTypeResourcePropertyElt form.
+			write(" rdf:parseType=\"Resource\">");
+			writeNewline();
+			serializeCompactRDFElementProps(node, indent + 1);
+		
+		}
+		else
+		{
+			// Have a mix of attributes and elements, use an inner rdf:Description.
+			write('>');
+			writeNewline();
+			writeIndent(indent + 1);
+			write(RDF_STRUCT_START);
+			serializeCompactRDFAttrProps(node, indent + 2);
+			write(">");
+			writeNewline();
+			serializeCompactRDFElementProps(node, indent + 1);
+			writeIndent(indent + 1);
+			write(RDF_STRUCT_END);
+			writeNewline();
+		}
+		return emitEndTag;
+	}
+
+
+	/**
+	 * Serializes the general qualifier.  
+	 * @param node the root node of the subtree
+	 * @param indent the current indent level
+	 * @throws IOException Forwards all writer exceptions.
+	 * @throws XMPException If qualifier and element fields are mixed.
+	 */
+	private void serializeCompactRDFGeneralQualifier(int indent, XMPNode node)
+			throws IOException, XMPException
+	{
+		// The node has general qualifiers, ones that can't be
+		// attributes on a property element.
+		// Emit using the qualified property pseudo-struct form. The
+		// value is output by a call
+		// to SerializePrettyRDFProperty with emitAsRDFValue set.
+		write(" rdf:parseType=\"Resource\">");
+		writeNewline();
+
+		serializePrettyRDFProperty(node, true, indent + 1);
+
+		for (Iterator iq = 	node.iterateQualifier(); iq.hasNext();)
+		{
+			XMPNode qualifier = (XMPNode) iq.next();
+			serializePrettyRDFProperty(qualifier, false, indent + 1);
+		}
+	}
+
+
+	/**
+	 * Serializes one schema with all contained properties in pretty-printed
+	 * manner.<br> 
+	 * Each schema's properties are written in a separate
+	 * rdf:Description element. All of the necessary namespaces are declared in
+	 * the rdf:Description element. The baseIndent is the base level for the
+	 * entire serialization, that of the x:xmpmeta element. An xml:lang
+	 * qualifier is written as an attribute of the property start tag, not by
+	 * itself forcing the qualified property form.
+	 * 
+	 * <blockquote>
+	 * 
+	 * <pre>
+	 *  	 &lt;rdf:Description rdf:about=&quot;TreeName&quot; xmlns:ns=&quot;URI&quot; ... &gt;
+	 *  
+	 *  	 	... The actual properties of the schema, see SerializePrettyRDFProperty
+	 *  
+	 *  	 	&lt;!-- ns1:Alias is aliased to ns2:Actual --&gt;  ... If alias comments are wanted
+	 *  
+	 *  	 &lt;/rdf:Description&gt;
+	 * </pre>
+	 * 
+	 * </blockquote>
+	 * 
+	 * @param schemaNode a schema node
+	 * @throws IOException Forwarded writer exceptions
+	 * @throws XMPException 
+	 */
+	private void serializePrettyRDFSchema(XMPNode schemaNode) throws IOException, XMPException
+	{
+		writeIndent(2);
+		write(RDF_SCHEMA_START);
+		writeTreeName();
+		
+		Set usedPrefixes = new HashSet();
+		usedPrefixes.add("xml");
+		usedPrefixes.add("rdf");
+
+		declareUsedNamespaces(schemaNode, usedPrefixes, 4);
+	
+		write('>');
+		writeNewline();
+		
+		// Write each of the schema's actual properties.
+		for (Iterator it = schemaNode.iterateChildren(); it.hasNext();)
+		{
+			XMPNode propNode = (XMPNode) it.next();
+			serializePrettyRDFProperty(propNode, false, 3);
+		}
+		
+		// Write the rdf:Description end tag.
+		writeIndent(2);
+		write(RDF_SCHEMA_END);
+		writeNewline();
+	}
+
+
+	/** 
+	 * Writes all used namespaces of the subtree in node to the output. 
+	 * The subtree is recursivly traversed.
+	 * @param node the root node of the subtree
+	 * @param usedPrefixes a set containing currently used prefixes
+	 * @param indent the current indent level
+	 * @throws IOException Forwards all writer exceptions.
+	 */
+	private void declareUsedNamespaces(XMPNode node, Set usedPrefixes, int indent)
+			throws IOException
+	{
+		if (node.getOptions().isSchemaNode())
+		{
+			// The schema node name is the URI, the value is the prefix.
+			String prefix = node.getValue().substring(0, node.getValue().length() - 1);
+			declareNamespace(prefix, node.getName(), usedPrefixes, indent);
+		}
+		else if (node.getOptions().isStruct())
+		{
+			for (Iterator it = node.iterateChildren(); it.hasNext();)
+			{
+				XMPNode field = (XMPNode) it.next();
+				declareNamespace(field.getName(), null, usedPrefixes, indent);
+			}
+		}
+	
+		for (Iterator it = node.iterateChildren(); it.hasNext();)
+		{
+			XMPNode child = (XMPNode) it.next();
+			declareUsedNamespaces(child, usedPrefixes, indent);
+		}
+
+		for (Iterator it = node.iterateQualifier(); it.hasNext();)
+		{
+			XMPNode qualifier = (XMPNode) it.next();
+			declareNamespace(qualifier.getName(), null, usedPrefixes, indent);
+			declareUsedNamespaces(qualifier, usedPrefixes, indent);
+		}
+	}
+	
+	
+	/**
+	 * Writes one namespace declaration to the output.
+	 * @param prefix a namespace prefix (without colon) or a complete qname (when namespace == null)
+	 * @param namespace the a namespace
+	 * @param usedPrefixes a set containing currently used prefixes
+	 * @param indent the current indent level
+	 * @throws IOException Forwards all writer exceptions.
+	 */
+	private void declareNamespace(String prefix, String namespace, Set usedPrefixes, int indent)
+			throws IOException
+	{
+		if (namespace == null)
+		{
+			// prefix contains qname, extract prefix and lookup namespace with prefix
+			QName qname = new QName(prefix);
+			if (qname.hasPrefix())
+			{
+				prefix = qname.getPrefix();
+				// add colon for lookup
+				namespace = XMPMetaFactory.getSchemaRegistry().getNamespaceURI(prefix + ":");
+				// prefix w/o colon
+				declareNamespace(prefix, namespace, usedPrefixes, indent);
+			}
+			else
+			{
+				return;
+			}
+		}
+		
+		if (!usedPrefixes.contains(prefix))
+		{
+			writeNewline();
+			writeIndent(indent);
+			write("xmlns:");
+			write(prefix);
+			write("=\"");
+			write(namespace);
+			write('"');
+			usedPrefixes.add(prefix);
+		}
+	}
+
+
+	/**
+	 * Recursively handles the "value" for a node. It does not matter if it is a
+	 * top level property, a field of a struct, or an item of an array. The
+	 * indent is that for the property element. An xml:lang qualifier is written
+	 * as an attribute of the property start tag, not by itself forcing the
+	 * qualified property form. The patterns below mostly ignore attribute
+	 * qualifiers like xml:lang. Except for the one struct case, attribute
+	 * qualifiers don't affect the output form.
+	 * 
+	 * <blockquote>
+	 * 
+	 * <pre>
+	 * 	&lt;ns:UnqualifiedSimpleProperty&gt;value&lt;/ns:UnqualifiedSimpleProperty&gt;
+	 * 
+	 * 	&lt;ns:UnqualifiedStructProperty rdf:parseType=&quot;Resource&quot;&gt;	
+	 * 		(If no rdf:resource qualifier)
+	 * 		... Fields, same forms as top level properties
+	 * 	&lt;/ns:UnqualifiedStructProperty&gt;
+	 * 
+	 * 	&lt;ns:ResourceStructProperty rdf:resource=&quot;URI&quot;
+	 * 		... Fields as attributes
+	 * 	&gt;
+	 * 
+	 * 	&lt;ns:UnqualifiedArrayProperty&gt;
+	 * 		&lt;rdf:Bag&gt; or Seq or Alt
+	 * 			... Array items as rdf:li elements, same forms as top level properties
+	 * 		&lt;/rdf:Bag&gt;
+	 * 	&lt;/ns:UnqualifiedArrayProperty&gt;
+	 * 
+	 * 	&lt;ns:QualifiedProperty rdf:parseType=&quot;Resource&quot;&gt;
+	 * 		&lt;rdf:value&gt; ... Property &quot;value&quot; following the unqualified 
+	 * 			forms ... &lt;/rdf:value&gt;
+	 * 		... Qualifiers looking like named struct fields
+	 * 	&lt;/ns:QualifiedProperty&gt;
+	 * </pre>
+	 * 
+	 * </blockquote>
+	 * 
+	 * @param node the property node
+	 * @param emitAsRDFValue property shall be renderes as attribute rather than tag
+	 * @param indent the current indent level
+	 * @throws IOException Forwards all writer exceptions.
+	 * @throws XMPException If &quot;rdf:resource&quot; and general qualifiers are mixed.
+	 */
+	private void serializePrettyRDFProperty(XMPNode node, boolean emitAsRDFValue, int indent)
+			throws IOException, XMPException
+	{
+		boolean emitEndTag   = true;
+		boolean indentEndTag = true;
+	
+		// Determine the XML element name. Open the start tag with the name and
+		// attribute qualifiers.
+		
+		String elemName = node.getName();
+		if (emitAsRDFValue)
+		{
+			elemName = "rdf:value";
+		}
+		else if (XMPConst.ARRAY_ITEM_NAME.equals(elemName))
+		{
+			elemName = "rdf:li";
+		}
+	
+		writeIndent(indent);
+		write('<');
+		write(elemName);
+		
+		boolean hasGeneralQualifiers = false;
+		boolean hasRDFResourceQual   = false;
+		
+		for (Iterator it = node.iterateQualifier(); it.hasNext();)
+		{
+			XMPNode qualifier = (XMPNode) it.next();
+			if (!RDF_ATTR_QUALIFIER.contains(qualifier.getName()))
+			{
+				hasGeneralQualifiers = true;
+			}
+			else
+			{
+				hasRDFResourceQual = "rdf:resource".equals(qualifier.getName());
+				if (!emitAsRDFValue)
+				{
+					write(' ');
+					write(qualifier.getName());
+					write("=\"");
+					appendNodeValue(qualifier.getValue(), true);
+					write('"');
+				}
+			}
+		}
+		
+		// Process the property according to the standard patterns.
+		
+		if (hasGeneralQualifiers &&  !emitAsRDFValue)
+		{
+			// This node has general, non-attribute, qualifiers. Emit using the
+			// qualified property form.
+			// ! The value is output by a recursive call ON THE SAME NODE with
+			// emitAsRDFValue set.
+	
+			if (hasRDFResourceQual)
+			{
+				throw new XMPException("Can't mix rdf:resource and general qualifiers",
+						XMPError.BADRDF);
+			}
+			
+			write(" rdf:parseType=\"Resource\">");
+			writeNewline();
+	
+			serializePrettyRDFProperty(node, true, indent + 1);
+			
+			for (Iterator it = node.iterateQualifier(); it.hasNext();)
+			{
+				XMPNode qualifier = (XMPNode) it.next();
+				if (!RDF_ATTR_QUALIFIER.contains(qualifier.getName()))
+				{
+					serializePrettyRDFProperty(qualifier, false, indent + 1);
+				}
+			}
+		}
+		else
+		{
+			// This node has no general qualifiers. Emit using an unqualified form.
+			
+			if (!node.getOptions().isCompositeProperty())
+			{
+				// This is a simple property.
+				
+				if (node.getOptions().isURI())
+				{
+					write(" rdf:resource=\"");
+					appendNodeValue(node.getValue(), true);
+					write("\"/>");
+					writeNewline();
+					emitEndTag = false;
+				}
+				else if (node.getValue() == null ||  "".equals(node.getValue()))
+				{
+					write("/>");
+					writeNewline();
+					emitEndTag = false;
+				} 
+				else
+				{
+					write('>');
+					appendNodeValue(node.getValue(), false);
+					indentEndTag = false;
+				}
+			}
+			else if (node.getOptions().isArray())
+			{
+				// This is an array.
+				write('>');
+				writeNewline();
+				emitRDFArrayTag(node, true, indent + 1);
+				if (node.getOptions().isArrayAltText())
+				{
+					XMPNodeUtils.normalizeLangArray(node);
+				}
+				for (Iterator it = node.iterateChildren(); it.hasNext();)
+				{
+					XMPNode child = (XMPNode) it.next();
+					serializePrettyRDFProperty(child, false, indent + 2);
+				}
+				emitRDFArrayTag(node, false, indent + 1);
+			
+			
+			} 
+			else if (!hasRDFResourceQual)
+			{
+				// This is a "normal" struct, use the rdf:parseType="Resource" form.
+				if (!node.hasChildren())
+				{
+					write(" rdf:parseType=\"Resource\"/>");
+					writeNewline();
+					emitEndTag = false;
+				}
+				else
+				{
+					write(" rdf:parseType=\"Resource\">");
+					writeNewline();
+					for (Iterator it = node.iterateChildren(); it.hasNext();)
+					{
+						XMPNode child = (XMPNode) it.next();
+						serializePrettyRDFProperty(child, false, indent + 1);
+					}
+				}
+			}
+			else
+			{
+				// This is a struct with an rdf:resource attribute, use the
+				// "empty property element" form.
+				for (Iterator it = node.iterateChildren(); it.hasNext();)
+				{
+					XMPNode child = (XMPNode) it.next();
+					if (!canBeRDFAttrProp(child))
+					{
+						throw new XMPException("Can't mix rdf:resource and complex fields",
+								XMPError.BADRDF);
+					}
+					writeNewline();
+					writeIndent(indent + 1);
+					write(' ');
+					write(child.getName());
+					write("=\"");
+					appendNodeValue(child.getValue(), true);
+					write('"');
+				}
+				write("/>");
+				writeNewline();
+				emitEndTag = false;
+			}
+		}
+		
+		// Emit the property element end tag.
+		if (emitEndTag)
+		{
+			if (indentEndTag) 
+			{
+				writeIndent(indent);
+			}
+			write("</");
+			write(elemName);
+			write('>');
+			writeNewline();
+		}		
+	}
+
+	
+	/**
+	 * Writes the array start and end tags.
+	 *  
+	 * @param arrayNode an array node
+	 * @param isStartTag flag if its the start or end tag
+	 * @param indent the current indent level
+	 * @throws IOException forwards writer exceptions
+	 */
+	private void emitRDFArrayTag(XMPNode arrayNode, boolean isStartTag, int indent) 
+		throws IOException
+	{
+		if (isStartTag  ||  arrayNode.hasChildren())
+		{
+			writeIndent(indent);
+			write(isStartTag ? "<rdf:" : "</rdf:");
+		
+			if (arrayNode.getOptions().isArrayAlternate())
+			{
+				write("Alt");
+			} 
+			else if (arrayNode.getOptions().isArrayOrdered())
+			{
+				write("Seq");
+			}
+			else
+			{
+				write("Bag");
+			}
+		
+			if (isStartTag && !arrayNode.hasChildren())
+			{
+				write("/>");
+			}
+			else
+			{
+				write(">");
+			}
+			
+			writeNewline();
+		}	
+	}
+
+
+	/**
+	 * Serializes the node value in XML encoding. Its used for tag bodies and
+	 * attributes. <em>Note:</em> The attribute is always limited by quotes,
+	 * thats why <code>&amp;apos;</code> is never serialized. <em>Note:</em>
+	 * Control chars are written unescaped, but if the user uses others than tab, LF
+	 * and CR the resulting XML will become invalid.
+	 * 
+	 * @param value the value of the node
+	 * @param forAttribute flag if value is an attribute value
+	 * @throws IOException
+	 */
+	private void appendNodeValue(String value, boolean forAttribute) throws IOException
+	{
+		write (Utils.escapeXML(value, forAttribute, true));
+	}
+	
+	
+	/**
+	 * A node can be serialized as RDF-Attribute, if it meets the following conditions:
+	 * <ul>
+	 *  	<li>is not array item
+	 * 		<li>don't has qualifier
+	 * 		<li>is no URI
+	 * 		<li>is no composite property
+	 * </ul> 
+	 * 
+	 * @param node an XMPNode
+	 * @return Returns true if the node serialized as RDF-Attribute
+	 */
+	private boolean canBeRDFAttrProp(XMPNode node)
+	{
+		return
+			!node.hasQualifier()  &&
+			!node.getOptions().isURI()  &&
+			!node.getOptions().isCompositeProperty()  &&
+			!XMPConst.ARRAY_ITEM_NAME.equals(node.getName());
+	}
+
+
+	/**
+	 * Writes indents and automatically includes the baseindend from the options. 
+	 * @param times number of indents to write
+	 * @throws IOException forwards exception
+	 */
+	private void writeIndent(int times) throws IOException
+	{
+		for (int i = options.getBaseIndent() + times; i > 0; i--)
+		{
+			writer.write(options.getIndent());
+		}
+	}
+	
+	
+	/**
+	 * Writes a char to the output.
+	 * @param c a char
+	 * @throws IOException forwards writer exceptions
+	 */
+	private void write(int c) throws IOException
+	{
+		writer.write(c);
+	}
+	
+	
+	/**
+	 * Writes a String to the output.
+	 * @param str a String
+	 * @throws IOException forwards writer exceptions
+	 */
+	private void write(String str) throws IOException
+	{
+		writer.write(str);
+	}
+	
+	
+	/**
+	 * Writes an amount of chars, mostly spaces
+	 * @param number number of chars
+	 * @param c a char
+	 * @throws IOException
+	 */
+	private void writeChars(int number, char c) throws IOException
+	{
+		for (; number > 0; number--)
+		{
+			writer.write(c);
+		}
+	}
+	
+	
+	/**
+	 * Writes a newline according to the options.
+	 * @throws IOException Forwards exception
+	 */
+	private void writeNewline() throws IOException
+	{
+		writer.write(options.getNewline());
+	}
+}
\ No newline at end of file
diff --git a/XMPCore/src/com/adobe/xmp/impl/XMPUtilsImpl.java b/XMPCore/src/com/adobe/xmp/impl/XMPUtilsImpl.java
new file mode 100644
index 0000000..ab36996
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/impl/XMPUtilsImpl.java
@@ -0,0 +1,1170 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+
+
+package com.adobe.xmp.impl;
+
+import java.util.Iterator;
+
+import com.adobe.xmp.XMPConst;
+import com.adobe.xmp.XMPError;
+import com.adobe.xmp.XMPException;
+import com.adobe.xmp.XMPMeta;
+import com.adobe.xmp.XMPMetaFactory;
+import com.adobe.xmp.XMPUtils;
+import com.adobe.xmp.impl.xpath.XMPPath;
+import com.adobe.xmp.impl.xpath.XMPPathParser;
+import com.adobe.xmp.options.PropertyOptions;
+import com.adobe.xmp.properties.XMPAliasInfo;
+
+
+
+/**
+ * @since 11.08.2006
+ */
+public class XMPUtilsImpl implements XMPConst
+{
+	/** */
+	private static final int UCK_NORMAL = 0;
+	/** */
+	private static final int UCK_SPACE = 1;
+	/** */
+	private static final int UCK_COMMA = 2;
+	/** */
+	private static final int UCK_SEMICOLON = 3;
+	/** */
+	private static final int UCK_QUOTE = 4;
+	/** */
+	private static final int UCK_CONTROL = 5;
+
+
+	/**
+	 * Private constructor, as
+	 */
+	private XMPUtilsImpl()
+	{
+		// EMPTY
+	}
+
+
+	/**
+	 * @see XMPUtils#catenateArrayItems(XMPMeta, String, String, String, String,
+	 *      boolean)
+	 * 
+	 * @param xmp
+	 *            The XMP object containing the array to be catenated.
+	 * @param schemaNS
+	 *            The schema namespace URI for the array. Must not be null or
+	 *            the empty string.
+	 * @param arrayName
+	 *            The name of the array. May be a general path expression, must
+	 *            not be null or the empty string. Each item in the array must
+	 *            be a simple string value.
+	 * @param separator
+	 *            The string to be used to separate the items in the catenated
+	 *            string. Defaults to &quot;; &quot;, ASCII semicolon and space
+	 *            (U+003B, U+0020).
+	 * @param quotes
+	 *            The characters to be used as quotes around array items that
+	 *            contain a separator. Defaults to &apos;&quot;&apos;
+	 * @param allowCommas
+	 *            Option flag to control the catenation.
+	 * @return Returns the string containing the catenated array items.
+	 * @throws XMPException
+	 *             Forwards the Exceptions from the metadata processing
+	 */
+	public static String catenateArrayItems(XMPMeta xmp, String schemaNS, String arrayName,
+			String separator, String quotes, boolean allowCommas) throws XMPException
+	{
+		ParameterAsserts.assertSchemaNS(schemaNS);
+		ParameterAsserts.assertArrayName(arrayName);
+		ParameterAsserts.assertImplementation(xmp);
+		if (separator == null  ||  separator.length() == 0)
+		{
+			separator = "; ";	
+		}
+		if (quotes == null  ||  quotes.length() == 0)
+		{	
+			quotes = "\"";
+		}
+		
+		XMPMetaImpl xmpImpl = (XMPMetaImpl) xmp;
+		XMPNode arrayNode = null;
+		XMPNode currItem = null;
+
+		// Return an empty result if the array does not exist, 
+		// hurl if it isn't the right form.
+		XMPPath arrayPath = XMPPathParser.expandXPath(schemaNS, arrayName);
+		arrayNode = XMPNodeUtils.findNode(xmpImpl.getRoot(), arrayPath, false, null);
+		if (arrayNode == null)
+		{
+			return "";
+		}
+		else if (!arrayNode.getOptions().isArray() || arrayNode.getOptions().isArrayAlternate())
+		{
+			throw new XMPException("Named property must be non-alternate array", XMPError.BADPARAM);
+		}
+
+		// Make sure the separator is OK.
+		checkSeparator(separator);
+		// Make sure the open and close quotes are a legitimate pair.
+		char openQuote = quotes.charAt(0);
+		char closeQuote = checkQuotes(quotes, openQuote);
+
+		// Build the result, quoting the array items, adding separators.
+		// Hurl if any item isn't simple.
+
+		StringBuffer catinatedString = new StringBuffer();
+
+		for (Iterator it = arrayNode.iterateChildren(); it.hasNext();)
+		{
+			currItem = (XMPNode) it.next();
+			if (currItem.getOptions().isCompositeProperty())
+			{
+				throw new XMPException("Array items must be simple", XMPError.BADPARAM);
+			}
+			String str = applyQuotes(currItem.getValue(), openQuote, closeQuote, allowCommas);
+
+			catinatedString.append(str);
+			if (it.hasNext())
+			{
+				catinatedString.append(separator);
+			}
+		}
+
+		return catinatedString.toString();
+	}
+
+
+	/**
+	 * see {@link XMPUtils#separateArrayItems(XMPMeta, String, String, String, 
+	 * PropertyOptions, boolean)}
+	 * 
+	 * @param xmp
+	 *            The XMP object containing the array to be updated.
+	 * @param schemaNS
+	 *            The schema namespace URI for the array. Must not be null or
+	 *            the empty string.
+	 * @param arrayName
+	 *            The name of the array. May be a general path expression, must
+	 *            not be null or the empty string. Each item in the array must
+	 *            be a simple string value.
+	 * @param catedStr
+	 *            The string to be separated into the array items.
+	 * @param arrayOptions
+	 *            Option flags to control the separation.
+	 * @param preserveCommas
+	 *            Flag if commas shall be preserved
+	 * 
+	 * @throws XMPException
+	 *             Forwards the Exceptions from the metadata processing
+	 */
+	public static void separateArrayItems(XMPMeta xmp, String schemaNS, String arrayName,
+			String catedStr, PropertyOptions arrayOptions, boolean preserveCommas)
+			throws XMPException
+	{
+		ParameterAsserts.assertSchemaNS(schemaNS);
+		ParameterAsserts.assertArrayName(arrayName);
+		if (catedStr == null)
+		{
+			throw new XMPException("Parameter must not be null", XMPError.BADPARAM);
+		}
+		ParameterAsserts.assertImplementation(xmp);
+		XMPMetaImpl xmpImpl = (XMPMetaImpl) xmp;
+
+		// Keep a zero value, has special meaning below.
+		XMPNode arrayNode = separateFindCreateArray(schemaNS, arrayName, arrayOptions, xmpImpl);
+
+		// Extract the item values one at a time, until the whole input string is done.
+		String itemValue;
+		int itemStart, itemEnd;
+		int nextKind = UCK_NORMAL, charKind = UCK_NORMAL;
+		char ch = 0, nextChar = 0;
+		
+		itemEnd = 0;
+		int endPos = catedStr.length();
+		while (itemEnd < endPos)
+		{
+			// Skip any leading spaces and separation characters. Always skip commas here.
+			// They can be kept when within a value, but not when alone between values.
+			for (itemStart = itemEnd; itemStart < endPos; itemStart++)
+			{
+				ch = catedStr.charAt(itemStart);
+				charKind = classifyCharacter(ch);
+				if (charKind == UCK_NORMAL || charKind == UCK_QUOTE)
+				{
+					break;
+				}
+			}
+			if (itemStart >= endPos)
+			{
+				break;
+			}
+
+			if (charKind != UCK_QUOTE)
+			{
+				// This is not a quoted value. Scan for the end, create an array
+				// item from the substring.
+				for (itemEnd = itemStart; itemEnd < endPos; itemEnd++)
+				{
+					ch = catedStr.charAt(itemEnd);
+					charKind = classifyCharacter(ch);
+
+					if (charKind == UCK_NORMAL || charKind == UCK_QUOTE  ||
+						(charKind == UCK_COMMA && preserveCommas))
+					{
+						continue;
+					}
+					else if (charKind != UCK_SPACE)
+					{
+						break;
+					}
+					else if ((itemEnd + 1) < endPos)
+					{
+						ch = catedStr.charAt(itemEnd + 1);
+						nextKind = classifyCharacter(ch);
+						if (nextKind == UCK_NORMAL  ||  nextKind == UCK_QUOTE  ||
+							(nextKind == UCK_COMMA && preserveCommas))
+						{
+							continue;
+						}
+					}
+					
+					// Anything left?
+					break; // Have multiple spaces, or a space followed by a
+							// separator.
+				}
+				itemValue = catedStr.substring(itemStart, itemEnd);
+			}
+			else
+			{
+				// Accumulate quoted values into a local string, undoubling
+				// internal quotes that
+				// match the surrounding quotes. Do not undouble "unmatching"
+				// quotes.
+
+				char openQuote = ch;
+				char closeQuote = getClosingQuote(openQuote);
+
+				itemStart++; // Skip the opening quote;
+				itemValue = "";
+
+				for (itemEnd = itemStart; itemEnd < endPos; itemEnd++)
+				{
+					ch = catedStr.charAt(itemEnd);
+					charKind = classifyCharacter(ch);
+
+					if (charKind != UCK_QUOTE || !isSurroundingQuote(ch, openQuote, closeQuote))
+					{
+						// This is not a matching quote, just append it to the
+						// item value.
+						itemValue += ch;
+					}
+					else
+					{
+						// This is a "matching" quote. Is it doubled, or the
+						// final closing quote?
+						// Tolerate various edge cases like undoubled opening
+						// (non-closing) quotes,
+						// or end of input.
+
+						if ((itemEnd + 1) < endPos)
+						{
+							nextChar = catedStr.charAt(itemEnd + 1);
+							nextKind = classifyCharacter(nextChar);
+						}
+						else
+						{
+							nextKind = UCK_SEMICOLON;
+							nextChar = 0x3B;
+						}
+
+						if (ch == nextChar)
+						{
+							// This is doubled, copy it and skip the double.
+							itemValue += ch;
+							// Loop will add in charSize.
+							itemEnd++;
+						}
+						else if (!isClosingingQuote(ch, openQuote, closeQuote))
+						{
+							// This is an undoubled, non-closing quote, copy it.
+							itemValue += ch;
+						}
+						else
+						{
+							// This is an undoubled closing quote, skip it and
+							// exit the loop.
+							itemEnd++;
+							break;
+						}
+					}
+				}
+			}
+
+			// Add the separated item to the array. 
+			// Keep a matching old value in case it had separators.
+			int foundIndex = -1;
+			for (int oldChild = 1; oldChild <= arrayNode.getChildrenLength(); oldChild++)
+			{
+				if (itemValue.equals(arrayNode.getChild(oldChild).getValue()))
+				{
+					foundIndex = oldChild;
+					break;
+				}
+			}
+
+			XMPNode newItem = null;
+			if (foundIndex < 0)
+			{
+				newItem = new XMPNode(ARRAY_ITEM_NAME, itemValue, null);
+				arrayNode.addChild(newItem);
+			}			
+		}
+	}
+
+	
+	/**
+	 * Utility to find or create the array used by <code>separateArrayItems()</code>.
+	 * @param schemaNS a the namespace fo the array
+	 * @param arrayName the name of the array 
+	 * @param arrayOptions the options for the array if newly created
+	 * @param xmp the xmp object
+	 * @return Returns the array node.
+	 * @throws XMPException Forwards exceptions
+	 */
+	private static XMPNode separateFindCreateArray(String schemaNS, String arrayName,
+			PropertyOptions arrayOptions, XMPMetaImpl xmp) throws XMPException
+	{
+		arrayOptions = XMPNodeUtils.verifySetOptions(arrayOptions, null);
+		if (!arrayOptions.isOnlyArrayOptions())
+		{
+			throw new XMPException("Options can only provide array form", XMPError.BADOPTIONS);
+		}
+
+		// Find the array node, make sure it is OK. Move the current children
+		// aside, to be readded later if kept.
+		XMPPath arrayPath = XMPPathParser.expandXPath(schemaNS, arrayName);
+		XMPNode arrayNode = XMPNodeUtils.findNode(xmp.getRoot(), arrayPath, false, null);
+		if (arrayNode != null)
+		{
+			// The array exists, make sure the form is compatible. Zero
+			// arrayForm means take what exists.
+			PropertyOptions arrayForm = arrayNode.getOptions();
+			if (!arrayForm.isArray() || arrayForm.isArrayAlternate())
+			{
+				throw new XMPException("Named property must be non-alternate array", 
+					XMPError.BADXPATH);
+			}
+			if (arrayOptions.equalArrayTypes(arrayForm))
+			{
+				throw new XMPException("Mismatch of specified and existing array form",
+						XMPError.BADXPATH); // *** Right error?
+			}
+		}
+		else
+		{
+			// The array does not exist, try to create it.
+			// don't modify the options handed into the method
+			arrayNode = XMPNodeUtils.findNode(xmp.getRoot(), arrayPath, true, arrayOptions
+					.setArray(true));
+			if (arrayNode == null)
+			{
+				throw new XMPException("Failed to create named array", XMPError.BADXPATH);
+			}
+		}
+		return arrayNode;
+	}
+
+
+	/**
+	 * @see XMPUtils#removeProperties(XMPMeta, String, String, boolean, boolean)
+	 * 
+	 * @param xmp
+	 *            The XMP object containing the properties to be removed.
+	 * 
+	 * @param schemaNS
+	 *            Optional schema namespace URI for the properties to be
+	 *            removed.
+	 * 
+	 * @param propName
+	 *            Optional path expression for the property to be removed.
+	 * 
+	 * @param doAllProperties
+	 *            Option flag to control the deletion: do internal properties in
+	 *            addition to external properties.
+	 * @param includeAliases
+	 *            Option flag to control the deletion: Include aliases in the
+	 *            "named schema" case above.
+	 * @throws XMPException If metadata processing fails
+	 */
+	public static void removeProperties(XMPMeta xmp, String schemaNS, String propName,
+			boolean doAllProperties, boolean includeAliases) throws XMPException
+	{
+		ParameterAsserts.assertImplementation(xmp);
+		XMPMetaImpl xmpImpl = (XMPMetaImpl) xmp;
+
+		if (propName != null && propName.length() > 0)
+		{
+			// Remove just the one indicated property. This might be an alias,
+			// the named schema might not actually exist. So don't lookup the
+			// schema node.
+
+			if (schemaNS == null || schemaNS.length() == 0)
+			{
+				throw new XMPException("Property name requires schema namespace", 
+					XMPError.BADPARAM);
+			}
+
+			XMPPath expPath = XMPPathParser.expandXPath(schemaNS, propName);
+
+			XMPNode propNode = XMPNodeUtils.findNode(xmpImpl.getRoot(), expPath, false, null);
+			if (propNode != null)
+			{
+				if (doAllProperties
+						|| !Utils.isInternalProperty(expPath.getSegment(XMPPath.STEP_SCHEMA)
+								.getName(), expPath.getSegment(XMPPath.STEP_ROOT_PROP).getName()))
+				{
+					XMPNode parent = propNode.getParent();
+					parent.removeChild(propNode);
+					if (parent.getOptions().isSchemaNode()  &&  !parent.hasChildren())
+					{
+						// remove empty schema node
+						parent.getParent().removeChild(parent);
+					}
+						
+				}
+			}
+		}
+		else if (schemaNS != null && schemaNS.length() > 0)
+		{
+
+			// Remove all properties from the named schema. Optionally include
+			// aliases, in which case
+			// there might not be an actual schema node.
+
+			// XMP_NodePtrPos schemaPos;
+			XMPNode schemaNode = XMPNodeUtils.findSchemaNode(xmpImpl.getRoot(), schemaNS, false);
+			if (schemaNode != null)
+			{
+				if (removeSchemaChildren(schemaNode, doAllProperties))
+				{
+					xmpImpl.getRoot().removeChild(schemaNode);
+				}
+			}
+
+			if (includeAliases)
+			{
+				// We're removing the aliases also. Look them up by their
+				// namespace prefix.
+				// But that takes more code and the extra speed isn't worth it.
+				// Lookup the XMP node
+				// from the alias, to make sure the actual exists.
+
+				XMPAliasInfo[] aliases = XMPMetaFactory.getSchemaRegistry().findAliases(schemaNS);
+				for (int i = 0; i < aliases.length; i++)
+				{
+					XMPAliasInfo info = aliases[i];
+					XMPPath path = XMPPathParser.expandXPath(info.getNamespace(), info
+							.getPropName());
+					XMPNode actualProp = XMPNodeUtils
+							.findNode(xmpImpl.getRoot(), path, false, null);
+					if (actualProp != null)
+					{
+						XMPNode parent = actualProp.getParent();
+						parent.removeChild(actualProp);
+					}
+				}
+			}
+		}
+		else
+		{
+			// Remove all appropriate properties from all schema. In this case
+			// we don't have to be
+			// concerned with aliases, they are handled implicitly from the
+			// actual properties.
+			for (Iterator it = xmpImpl.getRoot().iterateChildren(); it.hasNext();)
+			{
+				XMPNode schema = (XMPNode) it.next();
+				if (removeSchemaChildren(schema, doAllProperties))
+				{
+					it.remove();
+				}
+			}
+		}
+	}
+
+
+	/**
+	 * @see XMPUtils#appendProperties(XMPMeta, XMPMeta, boolean, boolean)
+	 * @param source The source XMP object.
+	 * @param destination The destination XMP object.
+	 * @param doAllProperties Do internal properties in addition to external properties.
+	 * @param replaceOldValues Replace the values of existing properties.
+	 * @param deleteEmptyValues Delete destination values if source property is empty. 
+	 * @throws XMPException Forwards the Exceptions from the metadata processing
+	 */
+	public static void appendProperties(XMPMeta source, XMPMeta destination,
+			boolean doAllProperties, boolean replaceOldValues, boolean deleteEmptyValues) 
+		throws XMPException
+	{
+		ParameterAsserts.assertImplementation(source);
+		ParameterAsserts.assertImplementation(destination);
+
+		XMPMetaImpl src = (XMPMetaImpl) source;
+		XMPMetaImpl dest = (XMPMetaImpl) destination;
+
+		for (Iterator it = src.getRoot().iterateChildren(); it.hasNext();)
+		{
+			XMPNode sourceSchema = (XMPNode) it.next();
+			
+			// Make sure we have a destination schema node
+			XMPNode destSchema = XMPNodeUtils.findSchemaNode(dest.getRoot(),
+					sourceSchema.getName(), false);
+			boolean createdSchema = false;
+			if (destSchema == null)
+			{
+				destSchema = new XMPNode(sourceSchema.getName(), sourceSchema.getValue(),
+						new PropertyOptions().setSchemaNode(true));
+				dest.getRoot().addChild(destSchema);
+				createdSchema = true;
+			}
+
+			// Process the source schema's children.			
+			for (Iterator ic = sourceSchema.iterateChildren(); ic.hasNext();)
+			{
+				XMPNode sourceProp = (XMPNode) ic.next();
+				if (doAllProperties
+						|| !Utils.isInternalProperty(sourceSchema.getName(), sourceProp.getName()))
+				{
+					appendSubtree(
+						dest, sourceProp, destSchema, replaceOldValues, deleteEmptyValues);
+				}
+			}
+
+			if (!destSchema.hasChildren()  &&  (createdSchema  ||  deleteEmptyValues))
+			{
+				// Don't create an empty schema / remove empty schema.
+				dest.getRoot().removeChild(destSchema);
+			}
+		}
+	}
+
+
+	/**
+	 * Remove all schema children according to the flag
+	 * <code>doAllProperties</code>. Empty schemas are automatically remove
+	 * by <code>XMPNode</code>
+	 * 
+	 * @param schemaNode
+	 *            a schema node
+	 * @param doAllProperties
+	 *            flag if all properties or only externals shall be removed.
+	 * @return Returns true if the schema is empty after the operation.
+	 */
+	private static boolean removeSchemaChildren(XMPNode schemaNode, boolean doAllProperties)
+	{
+		for (Iterator it = schemaNode.iterateChildren(); it.hasNext();)
+		{
+			XMPNode currProp = (XMPNode) it.next();
+			if (doAllProperties
+					|| !Utils.isInternalProperty(schemaNode.getName(), currProp.getName()))
+			{
+				it.remove();
+			}
+		}
+		
+		return !schemaNode.hasChildren();
+	}
+
+
+	/**
+	 * @see XMPUtilsImpl#appendProperties(XMPMeta, XMPMeta, boolean, boolean, boolean)
+	 * @param destXMP The destination XMP object.
+	 * @param sourceNode the source node
+	 * @param destParent the parent of the destination node
+	 * @param replaceOldValues Replace the values of existing properties.
+	 * @param deleteEmptyValues flag if properties with empty values should be deleted 
+	 * 		   in the destination object.
+	 * @throws XMPException
+	 */
+	private static void appendSubtree(XMPMetaImpl destXMP, XMPNode sourceNode, XMPNode destParent,
+			boolean replaceOldValues, boolean deleteEmptyValues) throws XMPException
+	{
+		XMPNode destNode = XMPNodeUtils.findChildNode(destParent, sourceNode.getName(), false);
+
+		boolean valueIsEmpty = false;
+		if (deleteEmptyValues)
+		{
+			valueIsEmpty = sourceNode.getOptions().isSimple() ?
+				sourceNode.getValue() == null  ||  sourceNode.getValue().length() == 0 :
+				!sourceNode.hasChildren();
+		}
+		
+		if (deleteEmptyValues  &&  valueIsEmpty)
+		{
+			if (destNode != null)
+			{
+				destParent.removeChild(destNode);
+			}
+		} 
+		else if (destNode == null)
+		{
+			// The one easy case, the destination does not exist.
+			destParent.addChild((XMPNode) sourceNode.clone());
+		}
+		else if (replaceOldValues)
+		{
+			// The destination exists and should be replaced.
+			destXMP.setNode(destNode, sourceNode.getValue(), sourceNode.getOptions(), true);
+			destParent.removeChild(destNode);
+			destNode = (XMPNode) sourceNode.clone();
+			destParent.addChild(destNode);
+		}
+		else
+		{
+			// The destination exists and is not totally replaced. Structs and
+			// arrays are merged.
+
+			PropertyOptions sourceForm = sourceNode.getOptions();
+			PropertyOptions destForm = destNode.getOptions();
+			if (sourceForm != destForm)
+			{
+				return;
+			}
+			if (sourceForm.isStruct())
+			{
+				// To merge a struct process the fields recursively. E.g. add simple missing fields.
+				// The recursive call to AppendSubtree will handle deletion for fields with empty 
+				// values.
+				for (Iterator it = sourceNode.iterateChildren(); it.hasNext();)
+				{
+					XMPNode sourceField = (XMPNode) it.next();
+					appendSubtree(destXMP, sourceField, destNode, 
+						replaceOldValues, deleteEmptyValues);
+					if (deleteEmptyValues  &&  !destNode.hasChildren())
+					{
+						destParent.removeChild(destNode);
+					}
+				}
+			}
+			else if (sourceForm.isArrayAltText())
+			{
+				// Merge AltText arrays by the "xml:lang" qualifiers. Make sure x-default is first. 
+				// Make a special check for deletion of empty values. Meaningful in AltText arrays 
+				// because the "xml:lang" qualifier provides unambiguous source/dest correspondence.
+				for (Iterator it = sourceNode.iterateChildren(); it.hasNext();)
+				{
+					XMPNode sourceItem = (XMPNode) it.next();
+					if (!sourceItem.hasQualifier()
+							|| !XMPConst.XML_LANG.equals(sourceItem.getQualifier(1).getName()))
+					{
+						continue;
+					}
+					
+					int destIndex = XMPNodeUtils.lookupLanguageItem(destNode, 
+							sourceItem.getQualifier(1).getValue());
+					if (deleteEmptyValues  &&  
+							(sourceItem.getValue() == null  ||
+							 sourceItem.getValue().length() == 0))
+					{
+						if (destIndex != -1)
+						{
+							destNode.removeChild(destIndex);
+							if (!destNode.hasChildren())
+							{
+								destParent.removeChild(destNode);
+							}
+						}	
+					}
+					else if (destIndex == -1)
+					{
+						// Not replacing, keep the existing item.						
+						if (!XMPConst.X_DEFAULT.equals(sourceItem.getQualifier(1).getValue())
+								|| !destNode.hasChildren())
+						{
+							sourceItem.cloneSubtree(destNode);
+						}
+						else
+						{
+							XMPNode destItem = new XMPNode(
+								sourceItem.getName(), 
+								sourceItem.getValue(), 
+								sourceItem.getOptions());
+							sourceItem.cloneSubtree(destItem);
+							destNode.addChild(1, destItem);
+						}	
+					}
+				}				
+			}
+			else if (sourceForm.isArray())
+			{
+				// Merge other arrays by item values. Don't worry about order or duplicates. Source 
+				// items with empty values do not cause deletion, that conflicts horribly with 
+				// merging.
+
+				for (Iterator is = sourceNode.iterateChildren(); is.hasNext();)
+				{
+					XMPNode sourceItem = (XMPNode) is.next();
+
+					boolean match = false;
+					for (Iterator id = destNode.iterateChildren(); id.hasNext();)
+					{
+						XMPNode destItem = (XMPNode) id.next();
+						if (itemValuesMatch(sourceItem, destItem))
+						{
+							match = true;
+						}
+					}
+					if (!match)
+					{
+						destNode = (XMPNode) sourceItem.clone();
+						destParent.addChild(destNode);
+					}
+				}
+			}
+		}
+	}
+
+
+	/**
+	 * Compares two nodes including its children and qualifier.
+	 * @param leftNode an <code>XMPNode</code>
+	 * @param rightNode an <code>XMPNode</code>
+	 * @return Returns true if the nodes are equal, false otherwise.
+	 * @throws XMPException Forwards exceptions to the calling method.
+	 */
+	private static boolean itemValuesMatch(XMPNode leftNode, XMPNode rightNode) throws XMPException
+	{
+		PropertyOptions leftForm = leftNode.getOptions();
+		PropertyOptions rightForm = rightNode.getOptions();
+
+		if (leftForm.equals(rightForm))
+		{
+			return false;
+		}
+
+		if (leftForm.getOptions() == 0)
+		{
+			// Simple nodes, check the values and xml:lang qualifiers.
+			if (!leftNode.getValue().equals(rightNode.getValue()))
+			{
+				return false;
+			}
+			if (leftNode.getOptions().getHasLanguage() != rightNode.getOptions().getHasLanguage())
+			{
+				return false;
+			}
+			if (leftNode.getOptions().getHasLanguage()
+					&& !leftNode.getQualifier(1).getValue().equals(
+							rightNode.getQualifier(1).getValue()))
+			{
+				return false;
+			}
+		}
+		else if (leftForm.isStruct())
+		{
+			// Struct nodes, see if all fields match, ignoring order.
+
+			if (leftNode.getChildrenLength() != rightNode.getChildrenLength())
+			{
+				return false;
+			}
+
+			for (Iterator it = leftNode.iterateChildren(); it.hasNext();)
+			{
+				XMPNode leftField = (XMPNode) it.next();
+				XMPNode rightField = XMPNodeUtils.findChildNode(rightNode, leftField.getName(),
+						false);
+				if (rightField == null || !itemValuesMatch(leftField, rightField))
+				{
+					return false;
+				}
+			}
+		}
+		else
+		{
+			// Array nodes, see if the "leftNode" values are present in the
+			// "rightNode", ignoring order, duplicates,
+			// and extra values in the rightNode-> The rightNode is the
+			// destination for AppendProperties.
+
+			assert leftForm.isArray();
+
+			for (Iterator il = leftNode.iterateChildren(); il.hasNext();)
+			{
+				XMPNode leftItem = (XMPNode) il.next();
+
+				boolean match = false;
+				for (Iterator ir = rightNode.iterateChildren(); ir.hasNext();)
+				{
+					XMPNode rightItem = (XMPNode) ir.next();
+					if (itemValuesMatch(leftItem, rightItem))
+					{
+						match = true;
+						break;
+					}
+				}
+				if (!match)
+				{
+					return false;
+				}
+			}
+		}
+		return true; // All of the checks passed.
+	}
+
+
+	/**
+	 * Make sure the separator is OK. It must be one semicolon surrounded by
+	 * zero or more spaces. Any of the recognized semicolons or spaces are
+	 * allowed.
+	 * 
+	 * @param separator
+	 * @throws XMPException
+	 */
+	private static void checkSeparator(String separator) throws XMPException
+	{
+		boolean haveSemicolon = false;
+		for (int i = 0; i < separator.length(); i++)
+		{
+			int charKind = classifyCharacter(separator.charAt(i));
+			if (charKind == UCK_SEMICOLON)
+			{
+				if (haveSemicolon)
+				{
+					throw new XMPException("Separator can have only one semicolon", 
+						XMPError.BADPARAM);
+				}
+				haveSemicolon = true;
+			}
+			else if (charKind != UCK_SPACE)
+			{
+				throw new XMPException("Separator can have only spaces and one semicolon",
+						XMPError.BADPARAM);
+			}
+		}
+		if (!haveSemicolon)
+		{
+			throw new XMPException("Separator must have one semicolon", XMPError.BADPARAM);
+		}
+	}
+
+
+	/**
+	 * Make sure the open and close quotes are a legitimate pair and return the
+	 * correct closing quote or an exception.
+	 * 
+	 * @param quotes
+	 *            opened and closing quote in a string
+	 * @param openQuote
+	 *            the open quote
+	 * @return Returns a corresponding closing quote.
+	 * @throws XMPException
+	 */
+	private static char checkQuotes(String quotes, char openQuote) throws XMPException
+	{
+		char closeQuote;
+
+		int charKind = classifyCharacter(openQuote);
+		if (charKind != UCK_QUOTE)
+		{
+			throw new XMPException("Invalid quoting character", XMPError.BADPARAM);
+		}
+
+		if (quotes.length() == 1)
+		{
+			closeQuote = openQuote;
+		}
+		else
+		{
+			closeQuote = quotes.charAt(1);
+			charKind = classifyCharacter(closeQuote);
+			if (charKind != UCK_QUOTE)
+			{
+				throw new XMPException("Invalid quoting character", XMPError.BADPARAM);
+			}
+		}
+
+		if (closeQuote != getClosingQuote(openQuote))
+		{
+			throw new XMPException("Mismatched quote pair", XMPError.BADPARAM);
+		}
+		return closeQuote;
+	}
+
+
+	/**
+	 * Classifies the character into normal chars, spaces, semicola, quotes,
+	 * control chars.
+	 * 
+	 * @param ch
+	 *            a char
+	 * @return Return the character kind.
+	 */
+	private static int classifyCharacter(char ch)
+	{
+		if (SPACES.indexOf(ch) >= 0 || (0x2000 <= ch && ch <= 0x200B))
+		{
+			return UCK_SPACE;
+		}
+		else if (COMMAS.indexOf(ch) >= 0)
+		{
+			return UCK_COMMA;
+		}
+		else if (SEMICOLA.indexOf(ch) >= 0)
+		{
+			return UCK_SEMICOLON;
+		}
+		else if (QUOTES.indexOf(ch) >= 0 || (0x3008 <= ch && ch <= 0x300F)
+				|| (0x2018 <= ch && ch <= 0x201F))
+		{
+			return UCK_QUOTE;
+		}
+		else if (ch < 0x0020 || CONTROLS.indexOf(ch) >= 0)
+		{
+			return UCK_CONTROL;
+		}
+		else
+		{
+			// Assume typical case.
+			return UCK_NORMAL;
+		}
+	}
+
+
+	/**
+	 * @param openQuote
+	 *            the open quote char
+	 * @return Returns the matching closing quote for an open quote.
+	 */
+	private static char getClosingQuote(char openQuote)
+	{
+		switch (openQuote)
+		{
+		case 0x0022:
+			return 0x0022; // ! U+0022 is both opening and closing.
+		case 0x005B:
+			return 0x005D;
+		case 0x00AB:
+			return 0x00BB; // ! U+00AB and U+00BB are reversible.
+		case 0x00BB:
+			return 0x00AB;
+		case 0x2015:
+			return 0x2015; // ! U+2015 is both opening and closing.
+		case 0x2018:
+			return 0x2019;
+		case 0x201A:
+			return 0x201B;
+		case 0x201C:
+			return 0x201D;
+		case 0x201E:
+			return 0x201F;
+		case 0x2039:
+			return 0x203A; // ! U+2039 and U+203A are reversible.
+		case 0x203A:
+			return 0x2039;
+		case 0x3008:
+			return 0x3009;
+		case 0x300A:
+			return 0x300B;
+		case 0x300C:
+			return 0x300D;
+		case 0x300E:
+			return 0x300F;
+		case 0x301D:
+			return 0x301F; // ! U+301E also closes U+301D.
+		default:
+			return 0;
+		}
+	}
+
+
+	/**
+	 * Add quotes to the item.
+	 * 
+	 * @param item
+	 *            the array item
+	 * @param openQuote
+	 *            the open quote character
+	 * @param closeQuote
+	 *            the closing quote character
+	 * @param allowCommas
+	 *            flag if commas are allowed
+	 * @return Returns the value in quotes.
+	 */
+	private static String applyQuotes(String item, char openQuote, char closeQuote,
+			boolean allowCommas)
+	{
+		if (item == null)
+		{
+			item = "";
+		}
+		
+		boolean prevSpace = false;
+		int charOffset;
+		int charKind;
+
+		// See if there are any separators in the value. Stop at the first
+		// occurrance. This is a bit
+		// tricky in order to make typical typing work conveniently. The purpose
+		// of applying quotes
+		// is to preserve the values when splitting them back apart. That is
+		// CatenateContainerItems
+		// and SeparateContainerItems must round trip properly. For the most
+		// part we only look for
+		// separators here. Internal quotes, as in -- Irving "Bud" Jones --
+		// won't cause problems in
+		// the separation. An initial quote will though, it will make the value
+		// look quoted.
+
+		int i;
+		for (i = 0; i < item.length(); i++)
+		{
+			char ch = item.charAt(i);
+			charKind = classifyCharacter(ch);
+			if (i == 0 && charKind == UCK_QUOTE)
+			{
+				break;
+			}
+
+			if (charKind == UCK_SPACE)
+			{
+				// Multiple spaces are a separator.
+				if (prevSpace)
+				{
+					break;
+				}
+				prevSpace = true;
+			}
+			else
+			{
+				prevSpace = false;
+				if ((charKind == UCK_SEMICOLON || charKind == UCK_CONTROL)
+						|| (charKind == UCK_COMMA && !allowCommas))
+				{
+					break;
+				}
+			}
+		}
+
+
+		if (i < item.length())
+		{
+			// Create a quoted copy, doubling any internal quotes that match the
+			// outer ones. Internal quotes did not stop the "needs quoting"
+			// search, but they do need
+			// doubling. So we have to rescan the front of the string for
+			// quotes. Handle the special
+			// case of U+301D being closed by either U+301E or U+301F.
+
+			StringBuffer newItem = new StringBuffer(item.length() + 2);
+			int splitPoint;
+			for (splitPoint = 0; splitPoint <= i; splitPoint++)
+			{
+				if (classifyCharacter(item.charAt(i)) == UCK_QUOTE)
+				{
+					break;
+				}
+			}
+
+			// Copy the leading "normal" portion.
+			newItem.append(openQuote).append(item.substring(0, splitPoint));
+
+			for (charOffset = splitPoint; charOffset < item.length(); charOffset++)
+			{
+				newItem.append(item.charAt(charOffset));
+				if (classifyCharacter(item.charAt(charOffset)) == UCK_QUOTE
+						&& isSurroundingQuote(item.charAt(charOffset), openQuote, closeQuote))
+				{
+					newItem.append(item.charAt(charOffset));
+				}
+			}
+
+			newItem.append(closeQuote);
+
+			item = newItem.toString();
+		}
+
+		return item;
+	}
+
+
+	/**
+	 * @param ch a character
+	 * @param openQuote the opening quote char
+	 * @param closeQuote the closing quote char 
+	 * @return Return it the character is a surrounding quote.
+	 */
+	private static boolean isSurroundingQuote(char ch, char openQuote, char closeQuote)
+	{
+		return ch == openQuote || isClosingingQuote(ch, openQuote, closeQuote);
+	}
+
+
+	/**
+	 * @param ch a character
+	 * @param openQuote the opening quote char
+	 * @param closeQuote the closing quote char 
+	 * @return Returns true if the character is a closing quote.
+	 */
+	private static boolean isClosingingQuote(char ch, char openQuote, char closeQuote)
+	{
+		return ch == closeQuote || (openQuote == 0x301D && ch == 0x301E || ch == 0x301F);
+	}
+	
+	
+	
+	/**
+	 * U+0022 ASCII space<br>
+	 * U+3000, ideographic space<br>
+	 * U+303F, ideographic half fill space<br>
+	 * U+2000..U+200B, en quad through zero width space
+	 */
+	private static final String SPACES = "\u0020\u3000\u303F";
+	/**
+	 * U+002C, ASCII comma<br>
+	 * U+FF0C, full width comma<br>
+	 * U+FF64, half width ideographic comma<br>
+	 * U+FE50, small comma<br>
+	 * U+FE51, small ideographic comma<br>
+	 * U+3001, ideographic comma<br>
+	 * U+060C, Arabic comma<br>
+	 * U+055D, Armenian comma
+	 */
+	private static final String COMMAS = "\u002C\uFF0C\uFF64\uFE50\uFE51\u3001\u060C\u055D";
+	/**
+	 * U+003B, ASCII semicolon<br>
+	 * U+FF1B, full width semicolon<br>
+	 * U+FE54, small semicolon<br>
+	 * U+061B, Arabic semicolon<br>
+	 * U+037E, Greek "semicolon" (really a question mark)
+	 */
+	private static final String SEMICOLA = "\u003B\uFF1B\uFE54\u061B\u037E";
+	/**
+	 * U+0022 ASCII quote<br>
+	 * ASCII '[' (0x5B) and ']' (0x5D) are used as quotes in Chinese and
+	 * Korean.<br>
+	 * U+00AB and U+00BB, guillemet quotes<br>
+	 * U+3008..U+300F, various quotes.<br>
+	 * U+301D..U+301F, double prime quotes.<br>
+	 * U+2015, dash quote.<br>
+	 * U+2018..U+201F, various quotes.<br>
+	 * U+2039 and U+203A, guillemet quotes.
+	 */
+	private static final String QUOTES = 
+		"\"\u005B\u005D\u00AB\u00BB\u301D\u301E\u301F\u2015\u2039\u203A";
+	/**
+	 * U+0000..U+001F ASCII controls<br>
+	 * U+2028, line separator.<br>
+	 * U+2029, paragraph separator.
+	 */
+	private static final String CONTROLS = "\u2028\u2029";	
+}
diff --git a/XMPCore/src/com/adobe/xmp/impl/package.html b/XMPCore/src/com/adobe/xmp/impl/package.html
new file mode 100644
index 0000000..aea0dfa
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/impl/package.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
+	<title>Package overview</title>
+</head>
+
+<body>
+	<p>Package containing the xmpcore implementation.</p>
+</body>
+</html>
diff --git a/XMPCore/src/com/adobe/xmp/impl/xpath/XMPPath.java b/XMPCore/src/com/adobe/xmp/impl/xpath/XMPPath.java
new file mode 100644
index 0000000..ff810dc
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/impl/xpath/XMPPath.java
@@ -0,0 +1,106 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp.impl.xpath;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * Representates an XMP XMPPath with segment accessor methods.
+ *
+ * @since   28.02.2006
+ */
+public class XMPPath
+{
+	// Bits for XPathStepInfo options.
+	
+	/** Marks a struct field step , also for top level nodes (schema "fields"). */
+	public static final int STRUCT_FIELD_STEP = 0x01;
+	/** Marks a qualifier step. 
+	 *  Note: Order is significant to separate struct/qual from array kinds! */
+	public static final int QUALIFIER_STEP = 0x02; 		// 
+	/** Marks an array index step */
+	public static final int ARRAY_INDEX_STEP = 0x03;
+	/** */
+	public static final int ARRAY_LAST_STEP = 0x04;
+	/** */
+	public static final int QUAL_SELECTOR_STEP = 0x05;
+	/** */
+	public static final int FIELD_SELECTOR_STEP = 0x06;
+	/** */
+	public static final int SCHEMA_NODE = 0x80000000;	
+	/** */
+	public static final int STEP_SCHEMA = 0;
+	/** */
+	public static final int STEP_ROOT_PROP = 1;
+
+	
+	/** stores the segments of an XMPPath */
+	private List segments = new ArrayList(5);
+	
+	
+	/**
+	 * Append a path segment
+	 * 
+	 * @param segment the segment to add
+	 */
+	public void add(XMPPathSegment segment)
+	{	
+		segments.add(segment);
+	}
+
+	
+	/**
+	 * @param index the index of the segment to return
+	 * @return Returns a path segment.
+	 */
+	public XMPPathSegment getSegment(int index)
+	{
+		return (XMPPathSegment) segments.get(index);
+	}
+	
+	
+	/**
+	 * @return Returns the size of the xmp path. 
+	 */
+	public int size()
+	{
+		return segments.size();
+	}
+	
+	
+	/**
+	 * Serializes the normalized XMP-path.
+	 * @see Object#toString()
+	 */
+	public String toString()
+	{
+		StringBuffer result = new StringBuffer();
+		int index = 1;
+		while (index < size())
+		{
+			result.append(getSegment(index));
+			if (index < size() - 1)
+			{
+				int kind = getSegment(index + 1).getKind(); 
+				if (kind == STRUCT_FIELD_STEP  || 
+					kind == QUALIFIER_STEP)
+				{	
+					// all but last and array indices
+					result.append('/');
+				}	
+			}
+			index++;			
+		}
+		
+		return result.toString();
+	}
+}
\ No newline at end of file
diff --git a/XMPCore/src/com/adobe/xmp/impl/xpath/XMPPathParser.java b/XMPCore/src/com/adobe/xmp/impl/xpath/XMPPathParser.java
new file mode 100644
index 0000000..9936c06
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/impl/xpath/XMPPathParser.java
@@ -0,0 +1,537 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp.impl.xpath;
+
+import com.adobe.xmp.XMPError;
+import com.adobe.xmp.XMPException;
+import com.adobe.xmp.XMPMetaFactory;
+import com.adobe.xmp.impl.Utils;
+import com.adobe.xmp.properties.XMPAliasInfo;
+
+
+/**
+ * Parser for XMP XPaths.
+ *
+ * @since   01.03.2006
+ */
+public final class XMPPathParser
+{
+	/**
+	 * Private constructor
+	 */
+	private XMPPathParser()
+	{
+		// empty
+	}
+
+
+	/**
+	 * Split an XMPPath expression apart at the conceptual steps, adding the
+	 * root namespace prefix to the first property component. The schema URI is
+	 * put in the first (0th) slot in the expanded XMPPath. Check if the top
+	 * level component is an alias, but don't resolve it.
+	 * <p>
+	 * In the most verbose case steps are separated by '/', and each step can be
+	 * of these forms:
+	 * <dl>
+	 * <dt>prefix:name
+	 * <dd> A top level property or struct field.
+	 * <dt>[index]
+	 * <dd> An element of an array.
+	 * <dt>[last()]
+	 * <dd> The last element of an array.
+	 * <dt>[fieldName=&quot;value&quot;]
+	 * <dd> An element in an array of structs, chosen by a field value.
+	 * <dt>[@xml:lang=&quot;value&quot;]
+	 * <dd> An element in an alt-text array, chosen by the xml:lang qualifier.
+	 * <dt>[?qualName=&quot;value&quot;]
+	 * <dd> An element in an array, chosen by a qualifier value.
+	 * <dt>@xml:lang
+	 * <dd> An xml:lang qualifier.
+	 * <dt>?qualName
+	 * <dd> A general qualifier.
+	 * </dl>
+	 * <p>
+	 * The logic is complicated though by shorthand for arrays, the separating
+	 * '/' and leading '*' are optional. These are all equivalent: array/*[2]
+	 * array/[2] array*[2] array[2] All of these are broken into the 2 steps
+	 * "array" and "[2]".
+	 * <p>
+	 * The value portion in the array selector forms is a string quoted by '''
+	 * or '"'. The value may contain any character including a doubled quoting
+	 * character. The value may be empty.
+	 * <p>
+	 * The syntax isn't checked, but an XML name begins with a letter or '_',
+	 * and contains letters, digits, '.', '-', '_', and a bunch of special
+	 * non-ASCII Unicode characters. An XML qualified name is a pair of names
+	 * separated by a colon.
+	 * @param schemaNS
+	 *            schema namespace
+	 * @param path
+	 *            property name
+	 * @return Returns the expandet XMPPath.
+	 * @throws XMPException
+	 *             Thrown if the format is not correct somehow.
+	 * 
+	 */
+	public static XMPPath expandXPath(String schemaNS, String path) throws XMPException
+	{
+		if (schemaNS == null  ||  path == null)
+		{
+			throw new XMPException("Parameter must not be null", XMPError.BADPARAM);
+		}
+
+		XMPPath expandedXPath = new XMPPath();
+		PathPosition pos = new PathPosition();
+		pos.path = path;
+		
+		// Pull out the first component and do some special processing on it: add the schema
+		// namespace prefix and and see if it is an alias. The start must be a "qualName".
+		parseRootNode(schemaNS, pos, expandedXPath);
+
+		// Now continue to process the rest of the XMPPath string.
+		while (pos.stepEnd < path.length())
+		{
+			pos.stepBegin = pos.stepEnd;
+			
+			skipPathDelimiter(path, pos);
+
+			pos.stepEnd = pos.stepBegin;
+
+			
+			XMPPathSegment segment;
+			if (path.charAt(pos.stepBegin) != '[')
+			{
+				// A struct field or qualifier.
+				segment = parseStructSegment(pos);
+			}
+			else
+			{
+				// One of the array forms.
+				segment = parseIndexSegment(pos);
+			}
+			
+
+			if (segment.getKind() == XMPPath.STRUCT_FIELD_STEP)
+			{
+				if (segment.getName().charAt(0) == '@')
+				{
+					segment.setName("?" + segment.getName().substring(1));
+					if (!"?xml:lang".equals(segment.getName()))
+					{
+						throw new XMPException("Only xml:lang allowed with '@'",
+								XMPError.BADXPATH);
+					}
+				}
+				if (segment.getName().charAt(0) == '?')
+				{
+					pos.nameStart++;
+					segment.setKind(XMPPath.QUALIFIER_STEP);
+				}
+
+				verifyQualName(pos.path.substring(pos.nameStart, pos.nameEnd));
+			}
+			else if (segment.getKind() == XMPPath.FIELD_SELECTOR_STEP)
+			{
+				if (segment.getName().charAt(1) == '@')
+				{
+					segment.setName("[?" + segment.getName().substring(2));
+					if (!segment.getName().startsWith("[?xml:lang="))
+					{
+						throw new XMPException("Only xml:lang allowed with '@'",
+								XMPError.BADXPATH);
+					}
+				}
+
+				if (segment.getName().charAt(1) == '?')
+				{
+					pos.nameStart++;
+					segment.setKind(XMPPath.QUAL_SELECTOR_STEP);
+					verifyQualName(pos.path.substring(pos.nameStart, pos.nameEnd));
+				}
+			}			
+
+			expandedXPath.add(segment);
+		}
+		return expandedXPath;
+	}
+
+
+	/**
+	 * @param path
+	 * @param pos
+	 * @throws XMPException
+	 */
+	private static void skipPathDelimiter(String path, PathPosition pos) throws XMPException
+	{
+		if (path.charAt(pos.stepBegin) == '/')
+		{
+			// skip slash
+
+			pos.stepBegin++;
+
+			// added for Java
+			if (pos.stepBegin >= path.length())
+			{
+				throw new XMPException("Empty XMPPath segment", XMPError.BADXPATH);
+			}
+		}
+
+		if (path.charAt(pos.stepBegin) == '*')
+		{
+			// skip asterisk
+
+			pos.stepBegin++;
+			if (pos.stepBegin >= path.length() || path.charAt(pos.stepBegin) != '[')
+			{
+				throw new XMPException("Missing '[' after '*'", XMPError.BADXPATH);
+			}
+		}
+	}
+
+
+	/**
+	 * Parses a struct segment
+	 * @param pos the current position in the path
+	 * @return Retusn the segment or an errror
+	 * @throws XMPException If the sement is empty
+	 */
+	private static XMPPathSegment parseStructSegment(PathPosition pos) throws XMPException
+	{
+		pos.nameStart = pos.stepBegin;
+		while (pos.stepEnd < pos.path.length() && "/[*".indexOf(pos.path.charAt(pos.stepEnd)) < 0)
+		{
+			pos.stepEnd++;
+		}
+		pos.nameEnd = pos.stepEnd;
+
+		if (pos.stepEnd == pos.stepBegin)
+		{
+			throw new XMPException("Empty XMPPath segment", XMPError.BADXPATH);
+		}
+
+		// ! Touch up later, also changing '@' to '?'.
+		XMPPathSegment segment = new XMPPathSegment(pos.path.substring(pos.stepBegin, pos.stepEnd),
+				XMPPath.STRUCT_FIELD_STEP);
+		return segment;
+	}
+
+
+	/**
+	 * Parses an array index segment.
+	 * 
+	 * @param pos the xmp path 
+	 * @return Returns the segment or an error
+	 * @throws XMPException thrown on xmp path errors
+	 * 
+	 */
+	private static XMPPathSegment parseIndexSegment(PathPosition pos) throws XMPException
+	{
+		XMPPathSegment segment;
+		pos.stepEnd++; // Look at the character after the leading '['.
+
+		if ('0' <= pos.path.charAt(pos.stepEnd) && pos.path.charAt(pos.stepEnd) <= '9')
+		{
+			// A numeric (decimal integer) array index.
+			while (pos.stepEnd < pos.path.length() && '0' <= pos.path.charAt(pos.stepEnd)
+					&& pos.path.charAt(pos.stepEnd) <= '9')
+			{
+				pos.stepEnd++;
+			}
+
+			segment = new XMPPathSegment(null, XMPPath.ARRAY_INDEX_STEP);
+		}
+		else
+		{
+			// Could be "[last()]" or one of the selector forms. Find the ']' or '='.
+
+			while (pos.stepEnd < pos.path.length() && pos.path.charAt(pos.stepEnd) != ']'
+					&& pos.path.charAt(pos.stepEnd) != '=')
+			{
+				pos.stepEnd++;
+			}
+			
+			if (pos.stepEnd >= pos.path.length())
+			{
+				throw new XMPException("Missing ']' or '=' for array index", XMPError.BADXPATH);
+			}
+
+			if (pos.path.charAt(pos.stepEnd) == ']')
+			{
+				if (!"[last()".equals(pos.path.substring(pos.stepBegin, pos.stepEnd)))
+				{
+					throw new XMPException(
+						"Invalid non-numeric array index", XMPError.BADXPATH);
+				}
+				segment = new XMPPathSegment(null, XMPPath.ARRAY_LAST_STEP);
+			}
+			else
+			{
+				pos.nameStart = pos.stepBegin + 1;
+				pos.nameEnd = pos.stepEnd;
+				pos.stepEnd++; // Absorb the '=', remember the quote.
+				char quote = pos.path.charAt(pos.stepEnd);
+				if (quote != '\'' && quote != '"')
+				{
+					throw new XMPException(
+						"Invalid quote in array selector", XMPError.BADXPATH);
+				}
+
+				pos.stepEnd++; // Absorb the leading quote.
+				while (pos.stepEnd < pos.path.length())
+				{
+					if (pos.path.charAt(pos.stepEnd) == quote)
+					{
+						// check for escaped quote
+						if (pos.stepEnd + 1 >= pos.path.length()
+								|| pos.path.charAt(pos.stepEnd + 1) != quote)
+						{
+							break;
+						}
+						pos.stepEnd++;
+					}
+					pos.stepEnd++;
+				}
+
+				if (pos.stepEnd >= pos.path.length())
+				{
+					throw new XMPException("No terminating quote for array selector",
+							XMPError.BADXPATH);
+				}
+				pos.stepEnd++; // Absorb the trailing quote.
+
+				// ! Touch up later, also changing '@' to '?'.
+				segment = new XMPPathSegment(null, XMPPath.FIELD_SELECTOR_STEP);
+			}
+		}
+		
+
+		if (pos.stepEnd >= pos.path.length() || pos.path.charAt(pos.stepEnd) != ']')
+		{
+			throw new XMPException("Missing ']' for array index", XMPError.BADXPATH);
+		}
+		pos.stepEnd++;
+		segment.setName(pos.path.substring(pos.stepBegin, pos.stepEnd));
+		
+		return segment;
+	}
+
+
+	/**
+	 * Parses the root node of an XMP Path, checks if namespace and prefix fit together
+	 * and resolve the property to the base property if it is an alias. 
+	 * @param schemaNS the root namespace
+	 * @param pos the parsing position helper
+	 * @param expandedXPath  the path to contribute to
+	 * @throws XMPException If the path is not valid.
+	 */
+	private static void parseRootNode(String schemaNS, PathPosition pos, XMPPath expandedXPath)
+			throws XMPException
+	{
+		while (pos.stepEnd < pos.path.length() && "/[*".indexOf(pos.path.charAt(pos.stepEnd)) < 0)
+		{
+			pos.stepEnd++;
+		}
+
+		if (pos.stepEnd == pos.stepBegin)
+		{
+			throw new XMPException("Empty initial XMPPath step", XMPError.BADXPATH);
+		}
+		
+		String rootProp = verifyXPathRoot(schemaNS, pos.path.substring(pos.stepBegin, pos.stepEnd));
+		XMPAliasInfo aliasInfo = XMPMetaFactory.getSchemaRegistry().findAlias(rootProp);
+		if (aliasInfo == null)
+		{
+			// add schema xpath step
+			expandedXPath.add(new XMPPathSegment(schemaNS, XMPPath.SCHEMA_NODE));
+			XMPPathSegment rootStep = new XMPPathSegment(rootProp, XMPPath.STRUCT_FIELD_STEP);
+			expandedXPath.add(rootStep);
+		}
+		else
+		{
+			// add schema xpath step and base step of alias
+			expandedXPath.add(new XMPPathSegment(aliasInfo.getNamespace(), XMPPath.SCHEMA_NODE));
+			XMPPathSegment rootStep = new XMPPathSegment(verifyXPathRoot(aliasInfo.getNamespace(),
+					aliasInfo.getPropName()),
+					XMPPath.STRUCT_FIELD_STEP);
+			rootStep.setAlias(true);
+			rootStep.setAliasForm(aliasInfo.getAliasForm().getOptions());
+			expandedXPath.add(rootStep);
+			
+			if (aliasInfo.getAliasForm().isArrayAltText())
+			{
+				XMPPathSegment qualSelectorStep = new XMPPathSegment("[?xml:lang='x-default']",
+						XMPPath.QUAL_SELECTOR_STEP);
+				qualSelectorStep.setAlias(true);
+				qualSelectorStep.setAliasForm(aliasInfo.getAliasForm().getOptions());
+				expandedXPath.add(qualSelectorStep);
+			}
+			else if (aliasInfo.getAliasForm().isArray())
+			{
+				XMPPathSegment indexStep = new XMPPathSegment("[1]",
+					XMPPath.ARRAY_INDEX_STEP);
+				indexStep.setAlias(true);
+				indexStep.setAliasForm(aliasInfo.getAliasForm().getOptions());
+				expandedXPath.add(indexStep);
+			}
+		}
+	}
+
+
+	/**
+	 * Verifies whether the qualifier name is not XML conformant or the
+	 * namespace prefix has not been registered.
+	 * 
+	 * @param qualName
+	 *            a qualifier name
+	 * @throws XMPException
+	 *             If the name is not conformant
+	 */
+	private static void verifyQualName(String qualName) throws XMPException
+	{
+		int colonPos = qualName.indexOf(':');
+		if (colonPos > 0)
+		{	
+			String prefix = qualName.substring(0, colonPos);		
+			if (Utils.isXMLNameNS(prefix))
+			{	
+				String regURI = XMPMetaFactory.getSchemaRegistry().getNamespaceURI(
+						prefix);
+				if (regURI != null)
+				{
+					return;
+				}
+
+				throw new XMPException("Unknown namespace prefix for qualified name",
+						XMPError.BADXPATH);
+			}
+		}
+		
+		throw new XMPException("Ill-formed qualified name", XMPError.BADXPATH);
+	}
+
+
+	/**
+	 * Verify if an XML name is conformant.
+	 * 
+	 * @param name
+	 *            an XML name
+	 * @throws XMPException
+	 *             When the name is not XML conformant
+	 */
+	private static void verifySimpleXMLName(String name) throws XMPException
+	{
+		if (!Utils.isXMLName(name))
+		{
+			throw new XMPException("Bad XML name", XMPError.BADXPATH);
+		}
+	}
+
+
+	/**
+	 * Set up the first 2 components of the expanded XMPPath. Normalizes the various cases of using
+	 * the full schema URI and/or a qualified root property name. Returns true for normal
+	 * processing. If allowUnknownSchemaNS is true and the schema namespace is not registered, false
+	 * is returned. If allowUnknownSchemaNS is false and the schema namespace is not registered, an
+	 * exception is thrown
+	 * <P>
+	 * (Should someday check the full syntax:)
+	 * 
+	 * @param schemaNS schema namespace
+	 * @param rootProp the root xpath segment
+	 * @return Returns root QName.
+	 * @throws XMPException Thrown if the format is not correct somehow.
+	 */
+	private static String verifyXPathRoot(String schemaNS, String rootProp)
+		throws XMPException
+	{
+		// Do some basic checks on the URI and name. Try to lookup the URI. See if the name is
+		// qualified.
+
+		if (schemaNS == null || schemaNS.length() == 0)
+		{
+			throw new XMPException(
+				"Schema namespace URI is required", XMPError.BADSCHEMA);
+		}
+
+		if ((rootProp.charAt(0) == '?') || (rootProp.charAt(0) == '@'))
+		{
+			throw new XMPException("Top level name must not be a qualifier", XMPError.BADXPATH);
+		}
+
+		if (rootProp.indexOf('/') >= 0 || rootProp.indexOf('[') >= 0)
+		{
+			throw new XMPException("Top level name must be simple", XMPError.BADXPATH);
+		}
+
+		String prefix = XMPMetaFactory.getSchemaRegistry().getNamespacePrefix(schemaNS);
+		if (prefix == null)
+		{
+			throw new XMPException("Unregistered schema namespace URI", XMPError.BADSCHEMA);
+		}
+
+		// Verify the various URI and prefix combinations. Initialize the
+		// expanded XMPPath.
+		int colonPos = rootProp.indexOf(':');
+		if (colonPos < 0)
+		{
+			// The propName is unqualified, use the schemaURI and associated
+			// prefix.
+			verifySimpleXMLName(rootProp); // Verify the part before any colon
+			return prefix + rootProp;
+		}
+		else
+		{
+			// The propName is qualified. Make sure the prefix is legit. Use the associated URI and
+			// qualified name.
+
+			// Verify the part before any colon
+			verifySimpleXMLName(rootProp.substring(0, colonPos));
+			verifySimpleXMLName(rootProp.substring(colonPos));
+			
+			prefix = rootProp.substring(0, colonPos + 1);
+
+			String regPrefix = XMPMetaFactory.getSchemaRegistry().getNamespacePrefix(schemaNS);
+			if (regPrefix == null)
+			{
+				throw new XMPException("Unknown schema namespace prefix", XMPError.BADSCHEMA);
+			}
+			if (!prefix.equals(regPrefix))
+			{
+				throw new XMPException("Schema namespace URI and prefix mismatch",
+						XMPError.BADSCHEMA);
+			}
+
+			return rootProp;
+		}
+	}
+}
+
+
+
+
+
+/**
+ * This objects contains all needed char positions to parse.
+ */
+class PathPosition
+{
+	/** the complete path */
+	public String path = null;
+	/** the start of a segment name */
+	int nameStart = 0;
+	/** the end of a segment name */
+	int nameEnd = 0;
+	/** the begin of a step */
+	int stepBegin = 0;
+	/** the end of a step */
+	int stepEnd = 0;
+}
+
diff --git a/XMPCore/src/com/adobe/xmp/impl/xpath/XMPPathSegment.java b/XMPCore/src/com/adobe/xmp/impl/xpath/XMPPathSegment.java
new file mode 100644
index 0000000..88b42a1
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/impl/xpath/XMPPathSegment.java
@@ -0,0 +1,147 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp.impl.xpath;
+
+
+/**
+ * A segment of a parsed <code>XMPPath</code>.
+ *  
+ * @since   23.06.2006
+ */
+public class XMPPathSegment
+{
+	/** name of the path segment */
+	private String name;
+	/** kind of the path segment */
+	private int kind;
+	/** flag if segment is an alias */
+	private boolean alias;
+	/** alias form if applicable */
+	private int aliasForm;
+
+
+	/**
+	 * Constructor with initial values.
+	 * 
+	 * @param name the name of the segment
+	 */
+	public XMPPathSegment(String name)
+	{
+		this.name = name;
+	}
+
+
+	/**
+	 * Constructor with initial values.
+	 * 
+	 * @param name the name of the segment
+	 * @param kind the kind of the segment
+	 */
+	public XMPPathSegment(String name, int kind)
+	{
+		this.name = name;
+		this.kind = kind;
+	}
+
+
+	/**
+	 * @return Returns the kind.
+	 */
+	public int getKind()
+	{
+		return kind;
+	}
+
+
+	/**
+	 * @param kind The kind to set.
+	 */
+	public void setKind(int kind)
+	{
+		this.kind = kind;
+	}
+
+
+	/**
+	 * @return Returns the name.
+	 */
+	public String getName()
+	{
+		return name;
+	}
+
+
+	/**
+	 * @param name The name to set.
+	 */
+	public void setName(String name)
+	{
+		this.name = name;
+	}
+
+
+	/**
+	 * @param alias the flag to set
+	 */
+	public void setAlias(boolean alias)
+	{
+		this.alias = alias;
+	}
+
+
+	/**
+	 * @return Returns the alias.
+	 */
+	public boolean isAlias()
+	{
+		return alias;
+	}
+	
+	
+	/** 
+	 * @return Returns the aliasForm if this segment has been created by an alias.
+	 */ 
+	public int getAliasForm()
+	{
+		return aliasForm;
+	}
+
+	
+	/**
+	 * @param aliasForm the aliasForm to set
+	 */
+	public void setAliasForm(int aliasForm)
+	{
+		this.aliasForm = aliasForm;
+	}
+	
+	
+	/**
+	 * @see Object#toString()
+	 */
+	public String toString()
+	{
+		switch (kind)
+		{
+			case XMPPath.STRUCT_FIELD_STEP:
+			case XMPPath.ARRAY_INDEX_STEP: 
+			case XMPPath.QUALIFIER_STEP: 
+			case XMPPath.ARRAY_LAST_STEP: 
+				return name;
+			case XMPPath.QUAL_SELECTOR_STEP: 
+			case XMPPath.FIELD_SELECTOR_STEP: 
+			return name;
+
+		default:
+			// no defined step
+			return name;
+		}
+	}
+}
diff --git a/XMPCore/src/com/adobe/xmp/impl/xpath/package.html b/XMPCore/src/com/adobe/xmp/impl/xpath/package.html
new file mode 100644
index 0000000..e444a87
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/impl/xpath/package.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
+	<title>Package overview</title>
+</head>
+
+<body>
+	<p>Package containing the XMPPath handling.</p>
+	<p>An XMPPath a simplified form of an XPath, used only to create or retrieve properties in an XMPMeta object.<p>
+</body>
+</html>
diff --git a/XMPCore/src/com/adobe/xmp/options/AliasOptions.java b/XMPCore/src/com/adobe/xmp/options/AliasOptions.java
new file mode 100644
index 0000000..cf58273
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/options/AliasOptions.java
@@ -0,0 +1,184 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp.options;
+
+import com.adobe.xmp.XMPException;
+
+
+/**
+ * Options for XMPSchemaRegistryImpl#registerAlias.
+ * 
+ * @since 20.02.2006
+ */
+public final class AliasOptions extends Options
+{
+	/** This is a direct mapping. The actual data type does not matter. */
+	public static final int PROP_DIRECT = 0;
+	/** The actual is an unordered array, the alias is to the first element of the array. */
+	public static final int PROP_ARRAY = PropertyOptions.ARRAY;
+	/** The actual is an ordered array, the alias is to the first element of the array. */
+	public static final int PROP_ARRAY_ORDERED = PropertyOptions.ARRAY_ORDERED;
+	/** The actual is an alternate array, the alias is to the first element of the array. */
+	public static final int PROP_ARRAY_ALTERNATE = PropertyOptions.ARRAY_ALTERNATE;
+	/**
+	 * The actual is an alternate text array, the alias is to the 'x-default' element of the array.
+	 */
+	public static final int PROP_ARRAY_ALT_TEXT = PropertyOptions.ARRAY_ALT_TEXT;
+
+	
+	/**
+	 * @see Options#Options()
+	 */
+	public AliasOptions()
+	{
+		// EMPTY
+	}
+
+	
+	/**
+	 * @param options the options to init with
+	 * @throws XMPException If options are not consistant
+	 */
+	public AliasOptions(int options) throws XMPException
+	{
+		super(options);
+	}
+
+
+	/**
+	 * @return Returns if the alias is of the simple form.
+	 */
+	public boolean isSimple()
+	{
+		return getOptions() == PROP_DIRECT;
+	}
+
+	
+	/**
+	 * @return Returns the option.
+	 */
+	public boolean isArray()
+	{
+		return getOption(PROP_ARRAY);
+	}
+
+
+	/**
+	 * @param value the value to set
+	 * @return Returns the instance to call more set-methods.
+	 */
+	public AliasOptions setArray(boolean value)
+	{
+		setOption(PROP_ARRAY, value);
+		return this;
+	}
+
+
+	/**
+	 * @return Returns the option.
+	 */
+	public boolean isArrayOrdered()
+	{
+		return getOption(PROP_ARRAY_ORDERED);
+	}
+
+
+	/**
+	 * @param value the value to set
+	 * @return Returns the instance to call more set-methods.
+	 */
+	public AliasOptions setArrayOrdered(boolean value)
+	{
+		setOption(PROP_ARRAY | PROP_ARRAY_ORDERED, value);
+		return this;
+	}
+
+
+	/**
+	 * @return Returns the option.
+	 */
+	public boolean isArrayAlternate()
+	{
+		return getOption(PROP_ARRAY_ALTERNATE);
+	}
+
+
+	/**
+	 * @param value the value to set
+	 * @return Returns the instance to call more set-methods.
+	 */
+	public AliasOptions setArrayAlternate(boolean value)
+	{
+		setOption(PROP_ARRAY | PROP_ARRAY_ORDERED | PROP_ARRAY_ALTERNATE, value);
+		return this;
+	}
+
+
+	/**
+	 * @return Returns the option.
+	 */
+	public boolean isArrayAltText()
+	{
+		return getOption(PROP_ARRAY_ALT_TEXT);
+	}
+
+
+	/**
+	 * @param value the value to set
+	 * @return Returns the instance to call more set-methods.
+	 */
+	public AliasOptions setArrayAltText(boolean value)
+	{
+		setOption(PROP_ARRAY | PROP_ARRAY_ORDERED | 
+			PROP_ARRAY_ALTERNATE | PROP_ARRAY_ALT_TEXT, value);
+		return this;
+	}
+
+
+	/**
+	 * @return returns a {@link PropertyOptions}s object
+	 * @throws XMPException If the options are not consistant. 
+	 */
+	public PropertyOptions toPropertyOptions() throws XMPException
+	{
+		return new PropertyOptions(getOptions());
+	}
+
+
+	/**
+	 * @see Options#defineOptionName(int)
+	 */
+	protected String defineOptionName(int option)
+	{
+		switch (option)
+		{
+			case PROP_DIRECT : 			return "PROP_DIRECT";
+			case PROP_ARRAY :			return "ARRAY";
+			case PROP_ARRAY_ORDERED :	return "ARRAY_ORDERED";
+			case PROP_ARRAY_ALTERNATE :	return "ARRAY_ALTERNATE";
+			case PROP_ARRAY_ALT_TEXT :	return "ARRAY_ALT_TEXT";
+			default: 					return null;
+		}
+	}
+
+	
+	/**
+	 * @see Options#getValidOptions()
+	 */
+	protected int getValidOptions()
+	{
+		return 
+			PROP_DIRECT |
+			PROP_ARRAY |
+			PROP_ARRAY_ORDERED |
+			PROP_ARRAY_ALTERNATE |
+			PROP_ARRAY_ALT_TEXT;
+	}
+}	
\ No newline at end of file
diff --git a/XMPCore/src/com/adobe/xmp/options/IteratorOptions.java b/XMPCore/src/com/adobe/xmp/options/IteratorOptions.java
new file mode 100644
index 0000000..3dcb524
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/options/IteratorOptions.java
@@ -0,0 +1,148 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp.options;
+
+
+/**
+ * Options for <code>XMPIterator</code> construction.
+ * 
+ * @since 24.01.2006
+ */
+public final class IteratorOptions extends Options
+{
+	/** Just do the immediate children of the root, default is subtree. */
+	public static final int JUST_CHILDREN = 0x0100;
+	/** Just do the leaf nodes, default is all nodes in the subtree. */
+	public static final int JUST_LEAFNODES = 0x0200;
+	/** Return just the leaf part of the path, default is the full path. */
+	public static final int JUST_LEAFNAME = 0x0400;
+	/** Include aliases, default is just actual properties. <em>Note:</em> Not supported. 
+	 *  @deprecated it is commonly preferred to work with the base properties */
+	public static final int INCLUDE_ALIASES = 0x0800;
+	/** Omit all qualifiers. */
+	public static final int OMIT_QUALIFIERS = 0x1000;
+
+
+	/**
+	 * @return Returns whether the option is set.
+	 */
+	public boolean isJustChildren()
+	{
+		return getOption(JUST_CHILDREN);
+	}
+
+
+	/**
+	 * @return Returns whether the option is set.
+	 */
+	public boolean isJustLeafname()
+	{
+		return getOption(JUST_LEAFNAME);
+	}
+
+
+	/**
+	 * @return Returns whether the option is set.
+	 */
+	public boolean isJustLeafnodes()
+	{
+		return getOption(JUST_LEAFNODES);
+	}
+
+
+	/**
+	 * @return Returns whether the option is set.
+	 */
+	public boolean isOmitQualifiers()
+	{
+		return getOption(OMIT_QUALIFIERS);
+	}
+
+
+	/**
+	 * Sets the option and returns the instance.
+	 * 
+	 * @param value the value to set
+	 * @return Returns the instance to call more set-methods.
+	 */
+	public IteratorOptions setJustChildren(boolean value)
+	{
+		setOption(JUST_CHILDREN, value);
+		return this;
+	}
+
+
+	/**
+	 * Sets the option and returns the instance.
+	 * 
+	 * @param value the value to set
+	 * @return Returns the instance to call more set-methods.
+	 */
+	public IteratorOptions setJustLeafname(boolean value)
+	{
+		setOption(JUST_LEAFNAME, value);
+		return this;
+	}
+
+
+	/**
+	 * Sets the option and returns the instance.
+	 * 
+	 * @param value the value to set
+	 * @return Returns the instance to call more set-methods.
+	 */
+	public IteratorOptions setJustLeafnodes(boolean value)
+	{
+		setOption(JUST_LEAFNODES, value);
+		return this;
+	}
+
+
+	/**
+	 * Sets the option and returns the instance.
+	 * 
+	 * @param value the value to set
+	 * @return Returns the instance to call more set-methods.
+	 */
+	public IteratorOptions setOmitQualifiers(boolean value)
+	{
+		setOption(OMIT_QUALIFIERS, value);
+		return this;
+	}
+
+
+	/**
+	 * @see Options#defineOptionName(int)
+	 */
+	protected String defineOptionName(int option)
+	{
+		switch (option)
+		{
+			case JUST_CHILDREN : 	return "JUST_CHILDREN";
+			case JUST_LEAFNODES :	return "JUST_LEAFNODES";
+			case JUST_LEAFNAME :	return "JUST_LEAFNAME";
+			case OMIT_QUALIFIERS :	return "OMIT_QUALIFIERS";
+			default: 				return null;
+		}
+	}
+
+
+	/**
+	 * @see Options#getValidOptions()
+	 */
+	protected int getValidOptions()
+	{
+		return 
+			JUST_CHILDREN |
+			JUST_LEAFNODES |
+			JUST_LEAFNAME |
+			OMIT_QUALIFIERS;
+	}
+}
\ No newline at end of file
diff --git a/XMPCore/src/com/adobe/xmp/options/Options.java b/XMPCore/src/com/adobe/xmp/options/Options.java
new file mode 100644
index 0000000..6d6bd98
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/options/Options.java
@@ -0,0 +1,290 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp.options;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.adobe.xmp.XMPError;
+import com.adobe.xmp.XMPException;
+
+/**
+ * The base class for a collection of 32 flag bits. Individual flags are defined as enum value bit
+ * masks. Inheriting classes add convenience accessor methods.
+ * 
+ * @since 24.01.2006
+ */
+public abstract class Options
+{
+	/** the internal int containing all options */
+	private int options = 0;
+	/** a map containing the bit names */
+	private Map optionNames = null;
+
+
+	/**
+	 * The default constructor.
+	 */
+	public Options()
+	{
+		// EMTPY
+	}
+
+
+	/**
+	 * Constructor with the options bit mask. 
+	 * 
+	 * @param options the options bit mask
+	 * @throws XMPException If the options are not correct
+	 */
+	public Options(int options) throws XMPException
+	{
+		assertOptionsValid(options);
+		setOptions(options);
+	}
+
+	
+	/**
+	 * Resets the options.
+	 */
+	public void clear()
+	{
+		options = 0;
+	}
+	
+	
+	/**
+	 * @param optionBits an option bitmask
+	 * @return Returns true, if this object is equal to the given options. 
+	 */
+	public boolean isExactly(int optionBits)
+	{
+		return getOptions() == optionBits;
+	}
+
+
+	/**
+	 * @param optionBits an option bitmask
+	 * @return Returns true, if this object contains all given options. 
+	 */
+	public boolean containsAllOptions(int optionBits)
+	{
+		return (getOptions() & optionBits) == optionBits;
+	}
+
+
+	/**
+	 * @param optionBits an option bitmask
+	 * @return Returns true, if this object contain at least one of the given options. 
+	 */
+	public boolean containsOneOf(int optionBits)
+	{
+		return ((getOptions()) & optionBits) != 0;
+	}	
+
+	
+	/**
+	 * @param optionBit the binary bit or bits that are requested
+	 * @return Returns if <emp>all</emp> of the requested bits are set or not.
+	 */
+	protected boolean getOption(int optionBit)
+	{
+		return (options & optionBit) != 0;
+	}
+
+
+	/**
+	 * @param optionBits the binary bit or bits that shall be set to the given value
+	 * @param value the boolean value to set
+	 */
+	public void setOption(int optionBits, boolean value)
+	{
+		options = value ? options | optionBits : options & ~optionBits;
+	}
+
+	
+	/**
+	 * Is friendly to access it during the tests.
+	 * @return Returns the options.
+	 */
+	public int getOptions()
+	{
+		return options;
+	}
+
+
+	/**
+	 * @param options The options to set.
+	 * @throws XMPException 
+	 */
+	public void setOptions(int options) throws XMPException
+	{
+		assertOptionsValid(options);
+		this.options = options;
+	}
+	
+	
+	/**
+	 * @see Object#equals(Object)
+	 */
+	public boolean equals(Object obj)
+	{
+		return getOptions() == ((Options) obj).getOptions();
+	}
+	
+	
+	/**
+	 * @see java.lang.Object#hashCode()
+	 */
+	public int hashCode()
+	{
+		return getOptions();
+	}
+	
+	
+	/**
+	 * Creates a human readable string from the set options. <em>Note:</em> This method is quite
+	 * expensive and should only be used within tests or as
+	 * @return Returns a String listing all options that are set to <code>true</code> by their name,
+	 * like &quot;option1 | option4&quot;.
+	 */
+	public String getOptionsString()
+	{
+		if (options != 0)
+		{	
+			StringBuffer sb = new StringBuffer();
+			int theBits = options;
+			while (theBits != 0)
+			{
+				int oneLessBit = theBits & (theBits - 1); // clear rightmost one bit
+				int singleBit = theBits ^ oneLessBit;
+				String bitName = getOptionName(singleBit);
+				sb.append(bitName);
+				if (oneLessBit != 0)
+				{	
+					sb.append(" | ");
+				}	
+				theBits = oneLessBit;
+			}
+			return sb.toString();
+		}	
+		else
+		{
+			return "<none>";
+		}
+	}
+
+	
+	/**
+	 * @return Returns the options as hex bitmask. 
+	 */
+	public String toString()
+	{
+		return "0x" + Integer.toHexString(options);
+	}
+	
+	
+	/**
+	 * To be implemeted by inheritants.
+	 * @return Returns a bit mask where all valid option bits are set.
+	 */
+	protected abstract int getValidOptions();
+	
+	
+	/**
+	 * To be implemeted by inheritants.
+	 * @param option a single, valid option bit.
+	 * @return Returns a human readable name for an option bit.
+	 */
+	protected abstract String defineOptionName(int option);
+	
+	
+	/**
+	 * The inheriting option class can do additional checks on the options.
+	 * <em>Note:</em> For performance reasons this method is only called 
+	 * when setting bitmasks directly.
+	 * When get- and set-methods are used, this method must be called manually,
+	 * normally only when the Options-object has been created from a client
+	 * (it has to be made public therefore).
+	 * 
+	 * @param options the bitmask to check.
+	 * @throws XMPException Thrown if the options are not consistent.
+	 */
+	protected void assertConsistency(int options) throws XMPException
+	{
+		// empty, no checks
+	}
+	
+	
+	/**
+	 * Checks options before they are set.
+	 * First it is checked if only defined options are used,
+	 * second the additional {@link Options#assertConsistency(int)}-method is called.
+	 *  
+	 * @param options the options to check
+	 * @throws XMPException Thrown if the options are invalid.
+	 */
+	private void assertOptionsValid(int options) throws XMPException
+	{
+		int invalidOptions = options & ~getValidOptions();
+		if (invalidOptions == 0)
+		{
+			assertConsistency(options);
+		}
+		else
+		{
+			throw new XMPException("The option bit(s) 0x" + Integer.toHexString(invalidOptions)
+					+ " are invalid!", XMPError.BADOPTIONS);
+		}
+	}
+	
+	
+	
+	/**
+	 * Looks up or asks the inherited class for the name of an option bit.
+	 * Its save that there is only one valid option handed into the method.
+	 * @param option a single option bit
+	 * @return Returns the option name or undefined.
+	 */
+	private String getOptionName(int option)
+	{
+		Map optionsNames = procureOptionNames();
+		
+		Integer key = new Integer(option);
+		String result = (String) optionsNames.get(key);
+		if (result == null)
+		{
+			result = defineOptionName(option);
+			if (result != null)
+			{
+				optionsNames.put(key, result); 
+			}
+			else
+			{
+				result = "<option name not defined>";
+			}
+		}
+		
+		return result;
+	}
+
+
+	/**
+	 * @return Returns the optionNames map and creates it if required.
+	 */
+	private Map procureOptionNames()
+	{
+		if (optionNames == null)
+		{	
+			optionNames = new HashMap();
+		}
+		return optionNames;
+	}
+}
diff --git a/XMPCore/src/com/adobe/xmp/options/ParseOptions.java b/XMPCore/src/com/adobe/xmp/options/ParseOptions.java
new file mode 100644
index 0000000..c05f871
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/options/ParseOptions.java
@@ -0,0 +1,174 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp.options;
+
+import java.io.InputStream;
+
+import com.adobe.xmp.XMPMetaFactory;
+
+
+/**
+ * Options for {@link XMPMetaFactory#parse(InputStream, ParseOptions)}.
+ * 
+ * @since 24.01.2006
+ */
+public final class ParseOptions extends Options
+{
+	/** Require a surrounding &quot;x:xmpmeta&quot; element in the xml-document. */
+	public static final int REQUIRE_XMP_META = 0x0001;
+	/** Do not reconcile alias differences, throw an exception instead. */
+	public static final int STRICT_ALIASING = 0x0004;
+	/** Convert ASCII control characters 0x01 - 0x1F (except tab, cr, and lf) to spaces. */
+	public static final int FIX_CONTROL_CHARS = 0x0008;
+	/** If the input is not unicode, try to parse it as ISO-8859-1. */
+	public static final int ACCEPT_LATIN_1 = 0x0010;
+	/** Do not carry run the XMPNormalizer on a packet, leave it as it is. */
+	public static final int OMIT_NORMALIZATION = 0x0020;
+
+	
+	/**
+	 * Sets the options to the default values.
+	 */
+	public ParseOptions()
+	{
+		setOption(FIX_CONTROL_CHARS | ACCEPT_LATIN_1, true);
+	}
+	
+	
+	/**
+	 * @return Returns the requireXMPMeta.
+	 */
+	public boolean getRequireXMPMeta()
+	{
+		return getOption(REQUIRE_XMP_META);
+	}
+
+
+	/**
+	 * @param value the value to set
+	 * @return Returns the instance to call more set-methods.
+	 */
+	public ParseOptions setRequireXMPMeta(boolean value)
+	{
+		setOption(REQUIRE_XMP_META, value);
+		return this;
+	}
+
+	
+	/**
+	 * @return Returns the strictAliasing.
+	 */
+	public boolean getStrictAliasing()
+	{
+		return getOption(STRICT_ALIASING);
+	}
+
+	
+	/**
+	 * @param value the value to set
+	 * @return Returns the instance to call more set-methods.
+	 */
+	public ParseOptions setStrictAliasing(boolean value)
+	{
+		setOption(STRICT_ALIASING, value);
+		return this;
+	}
+	
+
+	/**
+	 * @return Returns the strictAliasing.
+	 */
+	public boolean getFixControlChars()
+	{
+		return getOption(FIX_CONTROL_CHARS);
+	}
+
+	
+	/**
+	 * @param value the value to set
+	 * @return Returns the instance to call more set-methods.
+	 */
+	public ParseOptions setFixControlChars(boolean value)
+	{
+		setOption(FIX_CONTROL_CHARS, value);
+		return this;
+	}
+	
+	
+	/**
+	 * @return Returns the strictAliasing.
+	 */
+	public boolean getAcceptLatin1()
+	{
+		return getOption(ACCEPT_LATIN_1);
+	}
+
+	
+	/**
+	 * @param value the value to set
+	 * @return Returns the instance to call more set-methods.
+	 */
+	public ParseOptions setOmitNormalization(boolean value)
+	{
+		setOption(OMIT_NORMALIZATION, value);
+		return this;
+	}
+
+
+	/**
+	 * @return Returns the option "omit normalization".
+	 */
+	public boolean getOmitNormalization()
+	{
+		return getOption(OMIT_NORMALIZATION);
+	}
+
+	
+	/**
+	 * @param value the value to set
+	 * @return Returns the instance to call more set-methods.
+	 */
+	public ParseOptions setAcceptLatin1(boolean value)
+	{
+		setOption(ACCEPT_LATIN_1, value);
+		return this;
+	}
+
+	
+	/**
+	 * @see Options#defineOptionName(int)
+	 */
+	protected String defineOptionName(int option)
+	{
+		switch (option)
+		{
+			case REQUIRE_XMP_META :		return "REQUIRE_XMP_META";
+			case STRICT_ALIASING :		return "STRICT_ALIASING";
+			case FIX_CONTROL_CHARS:		return "FIX_CONTROL_CHARS";
+			case ACCEPT_LATIN_1:		return "ACCEPT_LATIN_1";
+			case OMIT_NORMALIZATION:	return "OMIT_NORMALIZATION";
+			default: 					return null;
+		}
+	}
+
+	
+	/**
+	 * @see Options#getValidOptions()
+	 */
+	protected int getValidOptions()
+	{
+		return 
+			REQUIRE_XMP_META |
+			STRICT_ALIASING |
+			FIX_CONTROL_CHARS |
+			ACCEPT_LATIN_1 |
+			OMIT_NORMALIZATION;
+	}
+}
\ No newline at end of file
diff --git a/XMPCore/src/com/adobe/xmp/options/PropertyOptions.java b/XMPCore/src/com/adobe/xmp/options/PropertyOptions.java
new file mode 100644
index 0000000..ae280fe
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/options/PropertyOptions.java
@@ -0,0 +1,429 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp.options;
+
+import com.adobe.xmp.XMPError;
+import com.adobe.xmp.XMPException;
+
+
+/**
+ * The property flags are used when properties are fetched from the <code>XMPMeta</code>-object
+ * and provide more detailed information about the property.
+ * 
+ * @since   03.07.2006
+ */
+public final class PropertyOptions extends Options
+{
+	/** */
+	public static final int NO_OPTIONS = 0x00000000;
+	/** */
+	public static final int URI = 0x00000002;
+	/** */
+	public static final int HAS_QUALIFIERS = 0x00000010;
+	/** */
+	public static final int QUALIFIER = 0x00000020;
+	/** */
+	public static final int HAS_LANGUAGE = 0x00000040;
+	/** */
+	public static final int HAS_TYPE = 0x00000080;
+	/** */
+	public static final int STRUCT = 0x00000100;
+	/** */
+	public static final int ARRAY = 0x00000200;
+	/** */
+	public static final int ARRAY_ORDERED = 0x00000400;
+	/** */
+	public static final int ARRAY_ALTERNATE = 0x00000800;
+	/** */
+	public static final int ARRAY_ALT_TEXT = 0x00001000;
+	/** */
+	public static final int SCHEMA_NODE = 0x80000000;
+	/** may be used in the future */
+	public static final int DELETE_EXISTING = 0x20000000;
+	
+	
+	/**
+	 * Default constructor
+	 */
+	public PropertyOptions()
+	{
+		// reveal default constructor
+	}
+	
+	
+	/**
+	 * Intialization constructor
+	 * 
+	 * @param options the initialization options
+	 * @throws XMPException If the options are not valid 
+	 */
+	public PropertyOptions(int options) throws XMPException
+	{
+		super(options);
+	}
+
+	
+	/**
+	 * @return Return whether the property value is a URI. It is serialized to RDF using the
+	 *         <tt>rdf:resource</tt> attribute. Not mandatory for URIs, but considered RDF-savvy.
+	 */
+	public boolean isURI()
+	{
+		return getOption(URI);
+	}
+
+	
+	/**
+	 * @param value the value to set
+	 * @return Returns this to enable cascaded options.
+	 */
+	public PropertyOptions setURI(boolean value)
+	{
+		setOption(URI, value);
+		return this;
+	}
+	
+
+	/**
+	 * @return Return whether the property has qualifiers. These could be an <tt>xml:lang</tt>
+	 *         attribute, an <tt>rdf:type</tt> property, or a general qualifier. See the
+	 *         introductory discussion of qualified properties for more information.
+	 */
+	public boolean getHasQualifiers()
+	{
+		return getOption(HAS_QUALIFIERS);
+	}
+
+	
+	/**
+	 * @param value the value to set
+	 * @return Returns this to enable cascaded options.
+	 */
+	public PropertyOptions setHasQualifiers(boolean value)
+	{
+		setOption(HAS_QUALIFIERS, value);
+		return this;
+	}
+	
+
+	/**
+	 * @return Return whether this property is a qualifier for some other property. Note that if the
+	 *         qualifier itself has a structured value, this flag is only set for the top node of
+	 *         the qualifier's subtree. Qualifiers may have arbitrary structure, and may even have
+	 *         qualifiers.
+	 */
+	public boolean isQualifier()
+	{
+		return getOption(QUALIFIER);
+	}
+
+	
+	/**
+	 * @param value the value to set
+	 * @return Returns this to enable cascaded options.
+	 */
+	public PropertyOptions setQualifier(boolean value)
+	{
+		setOption(QUALIFIER, value);
+		return this;
+	}
+	
+
+	/** @return Return whether this property has an <tt>xml:lang</tt> qualifier. */
+	public boolean getHasLanguage()
+	{
+		return getOption(HAS_LANGUAGE);
+	}
+
+	
+	/**
+	 * @param value the value to set
+	 * @return Returns this to enable cascaded options.
+	 */
+	public PropertyOptions setHasLanguage(boolean value)
+	{
+		setOption(HAS_LANGUAGE, value);
+		return this;
+	}
+	
+
+	/** @return Return whether this property has an <tt>rdf:type</tt> qualifier. */
+	public boolean getHasType()
+	{
+		return getOption(HAS_TYPE);
+	}
+
+	
+	/**
+	 * @param value the value to set
+	 * @return Returns this to enable cascaded options.
+	 */
+	public PropertyOptions setHasType(boolean value)
+	{
+		setOption(HAS_TYPE, value);
+		return this;
+	}
+	
+
+	/** @return Return whether this property contains nested fields. */
+	public boolean isStruct()
+	{
+		return getOption(STRUCT);
+	}
+
+	
+	/**
+	 * @param value the value to set
+	 * @return Returns this to enable cascaded options.
+	 */
+	public PropertyOptions setStruct(boolean value)
+	{
+		setOption(STRUCT, value);
+		return this;
+	}
+	
+
+	/**
+	 * @return Return whether this property is an array. By itself this indicates a general
+	 *         unordered array. It is serialized using an <tt>rdf:Bag</tt> container.
+	 */
+	public boolean isArray()
+	{
+		return getOption(ARRAY);
+	}
+
+
+	/**
+	 * @param value the value to set
+	 * @return Returns this to enable cascaded options.
+	 */
+	public PropertyOptions setArray(boolean value)
+	{
+		setOption(ARRAY, value);
+		return this;
+	}
+	
+
+	/**
+	 * @return Return whether this property is an ordered array. Appears in conjunction with
+	 *         getPropValueIsArray(). It is serialized using an <tt>rdf:Seq</tt> container.
+	 */
+	public boolean isArrayOrdered()
+	{
+		return getOption(ARRAY_ORDERED);
+	}
+
+
+	/**
+	 * @param value the value to set
+	 * @return Returns this to enable cascaded options.
+	 */
+	public PropertyOptions setArrayOrdered(boolean value)
+	{
+		setOption(ARRAY_ORDERED, value);
+		return this;
+	}
+
+	
+	/**
+	 * @return Return whether this property is an alternative array. Appears in conjunction with
+	 *         getPropValueIsArray(). It is serialized using an <tt>rdf:Alt</tt> container.
+	 */
+	public boolean isArrayAlternate()
+	{
+		return getOption(ARRAY_ALTERNATE);
+	}
+
+	
+	/**
+	 * @param value the value to set
+	 * @return Returns this to enable cascaded options.
+	 */
+	public PropertyOptions setArrayAlternate(boolean value)
+	{
+		setOption(ARRAY_ALTERNATE, value);
+		return this;
+	}
+	
+
+	/**
+	 * @return Return whether this property is an alt-text array. Appears in conjunction with
+	 *         getPropArrayIsAlternate(). It is serialized using an <tt>rdf:Alt</tt> container.
+	 *         Each array element is a simple property with an <tt>xml:lang</tt> attribute.
+	 */
+	public boolean isArrayAltText()
+	{
+		return getOption(ARRAY_ALT_TEXT);
+	}
+
+	
+	/**
+	 * @param value the value to set
+	 * @return Returns this to enable cascaded options.
+	 */
+	public PropertyOptions setArrayAltText(boolean value)
+	{
+		setOption(ARRAY_ALT_TEXT, value);
+		return this;
+	}
+	
+
+	/**
+	 * @param value the value to set
+	 * @return Returns this to enable cascaded options.
+	 */
+	
+	
+	/**
+	 * @return Returns whether the SCHEMA_NODE option is set.
+	 */
+	public boolean isSchemaNode()
+	{
+		return getOption(SCHEMA_NODE);
+	}
+
+
+	/**
+	 * @param value the option DELETE_EXISTING to set
+	 * @return Returns this to enable cascaded options.
+	 */
+	public PropertyOptions setSchemaNode(boolean value)
+	{
+		setOption(SCHEMA_NODE, value);
+		return this;
+	}
+	
+	
+	//-------------------------------------------------------------------------- convenience methods
+	
+	/**
+	 * @return Returns whether the property is of composite type - an array or a struct.
+	 */
+	public boolean isCompositeProperty()
+	{
+		return (getOptions() & (ARRAY | STRUCT)) > 0;
+	}
+
+	
+	/**
+	 * @return Returns whether the property is of composite type - an array or a struct.
+	 */
+	public boolean isSimple()
+	{
+		return (getOptions() & (ARRAY | STRUCT)) == 0;
+	}
+	
+	
+	/**
+	 * Compares two options set for array compatibility.
+	 * 
+	 * @param options other options
+	 * @return Returns true if the array options of the sets are equal.
+	 */
+	public boolean equalArrayTypes(PropertyOptions options)
+	{
+		return
+			isArray()			== options.isArray()  			&&
+			isArrayOrdered()	== options.isArrayOrdered()  	&&
+			isArrayAlternate()	== options.isArrayAlternate()	&&
+			isArrayAltText()	== options.isArrayAltText();
+	}
+	
+	
+	
+	/**
+	 * Merges the set options of a another options object with this.
+	 * If the other options set is null, this objects stays the same.
+	 * @param options other options
+	 * @throws XMPException If illegal options are provided 
+	 */
+	public void mergeWith(PropertyOptions options) throws XMPException
+	{
+		if (options != null)
+		{	
+			setOptions(getOptions() | options.getOptions());
+		}
+	}
+
+
+	/**
+	 * @return Returns true if only array options are set.
+	 */
+	public boolean isOnlyArrayOptions()
+	{
+		return (getOptions() & 
+			~(ARRAY | ARRAY_ORDERED | ARRAY_ALTERNATE | ARRAY_ALT_TEXT)) == 0;
+	}
+
+
+	/**
+	 * @see Options#getValidOptions()
+	 */
+	protected int getValidOptions()
+	{
+		return
+			URI |
+			HAS_QUALIFIERS |
+			QUALIFIER |
+			HAS_LANGUAGE |
+			HAS_TYPE |
+			STRUCT |
+			ARRAY |
+			ARRAY_ORDERED |
+			ARRAY_ALTERNATE |
+			ARRAY_ALT_TEXT |
+			SCHEMA_NODE;			
+	}
+
+	
+	/**
+	 * @see Options#defineOptionName(int)
+	 */
+	protected String defineOptionName(int option)
+	{
+		switch (option)
+		{
+			case URI : 				return "URI";
+			case HAS_QUALIFIERS :	return "HAS_QUALIFIER";
+			case QUALIFIER :		return "QUALIFIER";
+			case HAS_LANGUAGE :		return "HAS_LANGUAGE";
+			case HAS_TYPE:			return "HAS_TYPE";
+			case STRUCT :			return "STRUCT";
+			case ARRAY :			return "ARRAY";
+			case ARRAY_ORDERED :	return "ARRAY_ORDERED";
+			case ARRAY_ALTERNATE :	return "ARRAY_ALTERNATE";
+			case ARRAY_ALT_TEXT : 	return "ARRAY_ALT_TEXT";
+			case SCHEMA_NODE : 		return "SCHEMA_NODE";
+			default: 				return null;
+		}
+	}
+
+
+	/**
+	 * Checks that a node not a struct and array at the same time;
+	 * and URI cannot be a struct.
+	 * 
+	 * @param options the bitmask to check.
+	 * @throws XMPException Thrown if the options are not consistent.
+	 */
+	public void assertConsistency(int options) throws XMPException
+	{
+		if ((options & STRUCT) > 0  &&  (options & ARRAY) > 0)
+		{
+			throw new XMPException("IsStruct and IsArray options are mutually exclusive",
+					XMPError.BADOPTIONS);
+		}
+		else if ((options & URI) > 0  &&  (options & (ARRAY | STRUCT)) > 0)
+		{	
+			throw new XMPException("Structs and arrays can't have \"value\" options",
+				XMPError.BADOPTIONS);
+		}
+	}
+}
\ No newline at end of file
diff --git a/XMPCore/src/com/adobe/xmp/options/SerializeOptions.java b/XMPCore/src/com/adobe/xmp/options/SerializeOptions.java
new file mode 100644
index 0000000..37ed7bd
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/options/SerializeOptions.java
@@ -0,0 +1,436 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp.options;
+
+import com.adobe.xmp.XMPException;
+import com.adobe.xmp.XMPMeta;
+import com.adobe.xmp.XMPMetaFactory;
+
+
+/**
+ * Options for {@link XMPMetaFactory#serializeToBuffer(XMPMeta, SerializeOptions)}.
+ * 
+ * @since 24.01.2006
+ */
+public final class SerializeOptions extends Options
+{
+	/** Omit the XML packet wrapper. */
+	public static final int OMIT_PACKET_WRAPPER = 0x0010;
+	/** Mark packet as read-only. Default is a writeable packet. */
+	public static final int READONLY_PACKET = 0x0020;
+	/** Use a compact form of RDF. */
+	public static final int USE_COMPACT_FORMAT = 0x0040;
+	/**
+	 * Include a padding allowance for a thumbnail image. If no <tt>xmp:Thumbnails</tt> property
+	 * is present, the typical space for a JPEG thumbnail is used.
+	 */
+	public static final int INCLUDE_THUMBNAIL_PAD = 0x0100;
+	/**
+	 * The padding parameter provides the overall packet length. The actual amount of padding is
+	 * computed. An exception is thrown if the packet exceeds this length with no padding.
+	 */
+	public static final int EXACT_PACKET_LENGTH = 0x0200;
+	/** Sort the struct properties and qualifier before serializing */
+	public static final int SORT = 0x1000;
+
+	// ---------------------------------------------------------------------------------------------
+	// encoding bit constants
+
+	/** Bit indicating little endian encoding, unset is big endian */
+	private static final int LITTLEENDIAN_BIT = 0x0001;
+	/** Bit indication UTF16 encoding. */
+	private static final int UTF16_BIT = 0x0002;
+	/** UTF8 encoding; this is the default */
+	public static final int ENCODE_UTF8 = 0;
+	/** UTF16BE encoding */
+	public static final int ENCODE_UTF16BE = UTF16_BIT;
+	/** UTF16LE encoding */
+	public static final int ENCODE_UTF16LE = UTF16_BIT | LITTLEENDIAN_BIT;
+	/** */
+	private static final int ENCODING_MASK = UTF16_BIT | LITTLEENDIAN_BIT;			
+
+	/**
+	 * The amount of padding to be added if a writeable XML packet is created. If zero is passed
+	 * (the default) an appropriate amount of padding is computed.
+	 */
+	private int padding = 2048;
+	/**
+	 * The string to be used as a line terminator. If empty it defaults to; linefeed, U+000A, the
+	 * standard XML newline.
+	 */
+	private String newline = "\n";
+	/**
+	 * The string to be used for each level of indentation in the serialized
+	 * RDF. If empty it defaults to two ASCII spaces, U+0020.
+	 */
+	private String indent = "  ";
+	/**
+	 * The number of levels of indentation to be used for the outermost XML element in the
+	 * serialized RDF. This is convenient when embedding the RDF in other text, defaults to 0.
+	 */
+	private int baseIndent = 0;
+	/** Omits the Toolkit version attribute, not published, only used for Unit tests. */
+	private boolean omitVersionAttribute = false;
+	
+	
+	/**
+	 * Default constructor.
+	 */
+	public SerializeOptions()
+	{
+		// reveal default constructor
+	}
+
+	
+	/**
+	 * Constructor using inital options
+	 * @param options the inital options
+	 * @throws XMPException Thrown if options are not consistant.
+	 */
+	public SerializeOptions(int options) throws XMPException
+	{
+		super(options);
+	}
+
+
+	/**
+	 * @return Returns the option.
+	 */
+	public boolean getOmitPacketWrapper()
+	{
+		return getOption(OMIT_PACKET_WRAPPER);
+	}
+
+
+	/**
+	 * @param value the value to set
+	 * @return Returns the instance to call more set-methods.
+	 */
+	public SerializeOptions setOmitPacketWrapper(boolean value)
+	{
+		setOption(OMIT_PACKET_WRAPPER, value);
+		return this;
+	}
+
+
+	/**
+	 * @return Returns the option.
+	 */
+	public boolean getReadOnlyPacket()
+	{
+		return getOption(READONLY_PACKET);
+	}
+
+
+	/**
+	 * @param value the value to set
+	 * @return Returns the instance to call more set-methods.
+	 */
+	public SerializeOptions setReadOnlyPacket(boolean value)
+	{
+		setOption(READONLY_PACKET, value);
+		return this;
+	}
+
+
+	/**
+	 * @return Returns the option.
+	 */
+	public boolean getUseCompactFormat()
+	{
+		return getOption(USE_COMPACT_FORMAT);
+	}
+
+
+	/**
+	 * @param value the value to set
+	 * @return Returns the instance to call more set-methods.
+	 */
+	public SerializeOptions setUseCompactFormat(boolean value)
+	{
+		setOption(USE_COMPACT_FORMAT, value);
+		return this;
+	}
+
+	/**
+	 * @return Returns the option.
+	 */
+	public boolean getIncludeThumbnailPad()
+	{
+		return getOption(INCLUDE_THUMBNAIL_PAD);
+	}
+
+
+	/**
+	 * @param value the value to set
+	 * @return Returns the instance to call more set-methods.
+	 */
+	public SerializeOptions setIncludeThumbnailPad(boolean value)
+	{
+		setOption(INCLUDE_THUMBNAIL_PAD, value);
+		return this;
+	}
+
+
+	/**
+	 * @return Returns the option.
+	 */
+	public boolean getExactPacketLength()
+	{
+		return getOption(EXACT_PACKET_LENGTH);
+	}
+
+
+	/**
+	 * @param value the value to set
+	 * @return Returns the instance to call more set-methods.
+	 */
+	public SerializeOptions setExactPacketLength(boolean value)
+	{
+		setOption(EXACT_PACKET_LENGTH, value);
+		return this;
+	}
+
+
+	/**
+	 * @return Returns the option.
+	 */
+	public boolean getSort()
+	{
+		return getOption(SORT);
+	}
+
+
+	/**
+	 * @param value the value to set
+	 * @return Returns the instance to call more set-methods.
+	 */
+	public SerializeOptions setSort(boolean value)
+	{
+		setOption(SORT, value);
+		return this;
+	}
+	
+
+	/**
+	 * @return Returns the option.
+	 */
+	public boolean getEncodeUTF16BE()
+	{
+		return (getOptions() & ENCODING_MASK) == ENCODE_UTF16BE;
+	}
+
+
+	/**
+	 * @param value the value to set
+	 * @return Returns the instance to call more set-methods.
+	 */
+	public SerializeOptions setEncodeUTF16BE(boolean value)
+	{
+		// clear unicode bits
+		setOption(UTF16_BIT | LITTLEENDIAN_BIT, false);
+		setOption(ENCODE_UTF16BE, value);
+		return this;
+	}
+
+
+	/**
+	 * @return Returns the option.
+	 */
+	public boolean getEncodeUTF16LE()
+	{
+		return (getOptions() & ENCODING_MASK) == ENCODE_UTF16LE;
+	}
+
+
+	/**
+	 * @param value the value to set
+	 * @return Returns the instance to call more set-methods.
+	 */
+	public SerializeOptions setEncodeUTF16LE(boolean value)
+	{
+		// clear unicode bits
+		setOption(UTF16_BIT | LITTLEENDIAN_BIT, false);
+		setOption(ENCODE_UTF16LE, value);
+		return this;
+	}
+
+
+	/**
+	 * @return Returns the baseIndent.
+	 */
+	public int getBaseIndent()
+	{
+		return baseIndent;
+	}
+
+
+	/**
+	 * @param baseIndent
+	 *            The baseIndent to set.
+	 * @return Returns the instance to call more set-methods.
+	 */
+	public SerializeOptions setBaseIndent(int baseIndent)
+	{
+		this.baseIndent = baseIndent;
+		return this;
+	}
+
+
+	/**
+	 * @return Returns the indent.
+	 */
+	public String getIndent()
+	{
+		return indent;
+	}
+
+
+	/**
+	 * @param indent
+	 *            The indent to set.
+	 * @return Returns the instance to call more set-methods.
+	 */
+	public SerializeOptions setIndent(String indent)
+	{
+		this.indent = indent;
+		return this;
+	}
+
+
+	/**
+	 * @return Returns the newline.
+	 */
+	public String getNewline()
+	{
+		return newline;
+	}
+
+
+	/**
+	 * @param newline
+	 *            The newline to set.
+	 * @return Returns the instance to call more set-methods.
+	 */
+	public SerializeOptions setNewline(String newline)
+	{
+		this.newline = newline;
+		return this;
+	}
+
+
+	/**
+	 * @return Returns the padding.
+	 */
+	public int getPadding()
+	{
+		return padding;
+	}
+
+
+	/**
+	 * @param padding
+	 *            The padding to set.
+	 * @return Returns the instance to call more set-methods.
+	 */
+	public SerializeOptions setPadding(int padding)
+	{
+		this.padding = padding;
+		return this;
+	}
+	
+	
+	/**
+	 * @return Returns whether the Toolkit version attribute shall be omitted.
+	 * <em>Note:</em> This options can only be set by unit tests.
+	 */
+	public boolean getOmitVersionAttribute()
+	{
+		return omitVersionAttribute;
+	}
+	
+	
+	/**
+	 * @return Returns the encoding as Java encoding String. 
+	 */
+	public String getEncoding()
+	{
+		if (getEncodeUTF16BE())
+		{
+			return "UTF-16BE";
+		}
+		else if (getEncodeUTF16LE())
+		{
+			return "UTF-16LE";
+		}
+		else
+		{
+			return "UTF-8";
+		}
+	}
+	
+	
+	/**
+	 * 
+	 * @return Returns clone of this SerializeOptions-object with the same options set. 
+	 * @throws CloneNotSupportedException Cannot happen in this place.  
+	 */
+	public Object clone() throws CloneNotSupportedException
+	{
+		SerializeOptions clone;
+		try
+		{
+			clone = new SerializeOptions(getOptions());
+			clone.setBaseIndent(baseIndent);
+			clone.setIndent(indent);
+			clone.setNewline(newline);
+			clone.setPadding(padding);
+			return clone;
+		}
+		catch (XMPException e)
+		{
+			// This cannot happen, the options are already checked in "this" object.
+			return null;
+		}
+	}
+
+
+	/**
+	 * @see Options#defineOptionName(int)
+	 */
+	protected String defineOptionName(int option)
+	{
+		switch (option)
+		{
+			case OMIT_PACKET_WRAPPER : 		return "OMIT_PACKET_WRAPPER";
+			case READONLY_PACKET :			return "READONLY_PACKET";
+			case USE_COMPACT_FORMAT :		return "USE_COMPACT_FORMAT";
+			case INCLUDE_THUMBNAIL_PAD :	return "INCLUDE_THUMBNAIL_PAD";
+			case EXACT_PACKET_LENGTH :		return "EXACT_PACKET_LENGTH";
+			case SORT :				return "NORMALIZED";
+			default: 						return null;
+		}
+	}
+
+	
+	/**
+	 * @see Options#getValidOptions()
+	 */
+	protected int getValidOptions()
+	{
+		return 
+		OMIT_PACKET_WRAPPER |
+		READONLY_PACKET |
+		USE_COMPACT_FORMAT |
+		INCLUDE_THUMBNAIL_PAD |
+		EXACT_PACKET_LENGTH |
+		SORT;
+	}
+}
\ No newline at end of file
diff --git a/XMPCore/src/com/adobe/xmp/options/package.html b/XMPCore/src/com/adobe/xmp/options/package.html
new file mode 100644
index 0000000..d2e56d0
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/options/package.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
+	<title>Package overview</title>
+</head>
+
+<body>
+	<p>Package containing the option classes.</p>
+	<p>These are used to configure diverse function calls of xmpcore:<p>
+	<ul>
+		<li>PropertyOptions - these are used to create properties and also to retrieve information about simple, array or struct properties, as well as qualifiers
+		<li>ParseOptions - used to configure the parsing of xmp metadata packets
+		<li>SerializationOptions - used to control the serialization of xmp metadata packets
+		<li>AliasOptions - used by XMPSchemaRegistry#registerAlias()
+		<li>IteratorOptions - used to set up an XMPIterator
+		<li>Options - the base class of all option classes
+	</ul>
+</body>
+</html>
diff --git a/XMPCore/src/com/adobe/xmp/package.html b/XMPCore/src/com/adobe/xmp/package.html
new file mode 100644
index 0000000..8afb896
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/package.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
+	<title>Package overview</title>
+</head>
+
+<body>
+	<p>Package containing the xmpcore interface.</p>
+</body>
+</html>
diff --git a/XMPCore/src/com/adobe/xmp/properties/XMPAliasInfo.java b/XMPCore/src/com/adobe/xmp/properties/XMPAliasInfo.java
new file mode 100644
index 0000000..e9078d4
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/properties/XMPAliasInfo.java
@@ -0,0 +1,48 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp.properties;
+
+import com.adobe.xmp.options.AliasOptions;
+
+
+/**
+ * This interface is used to return info about an alias.
+ * 
+ * @since   27.01.2006
+ */
+public interface XMPAliasInfo
+{
+	/**
+	 * @return Returns Returns the namespace URI for the base property.
+	 */
+	String getNamespace();
+
+
+	/**
+	 * @return Returns the default prefix for the given base property. 
+	 */
+	String getPrefix();
+
+	
+	/**
+	 * @return Returns the path of the base property.
+	 */
+	String getPropName();
+
+	
+	/**
+	 * @return Returns the kind of the alias. This can be a direct alias
+	 *         (ARRAY), a simple property to an ordered array
+	 *         (ARRAY_ORDERED), to an alternate array
+	 *         (ARRAY_ALTERNATE) or to an alternate text array
+	 *         (ARRAY_ALT_TEXT).
+	 */
+	AliasOptions getAliasForm();
+}
\ No newline at end of file
diff --git a/XMPCore/src/com/adobe/xmp/properties/XMPProperty.java b/XMPCore/src/com/adobe/xmp/properties/XMPProperty.java
new file mode 100644
index 0000000..767c94b
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/properties/XMPProperty.java
@@ -0,0 +1,40 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp.properties;
+
+import com.adobe.xmp.XMPMeta;
+import com.adobe.xmp.options.PropertyOptions;
+
+
+/**
+ * This interface is used to return a text property together with its and options.
+ * 
+ * @since   23.01.2006
+ */
+public interface XMPProperty 
+{
+	/**
+	 * @return Returns the value of the property.
+	 */
+	Object getValue();
+	
+	
+	/**
+	 * @return Returns the options of the property.
+	 */
+	PropertyOptions getOptions();
+	
+	
+	/**
+	 * Only set by {@link XMPMeta#getLocalizedText(String, String, String, String)}. 
+	 * @return Returns the language of the alt-text item.
+	 */
+	String getLanguage();
+}
diff --git a/XMPCore/src/com/adobe/xmp/properties/XMPPropertyInfo.java b/XMPCore/src/com/adobe/xmp/properties/XMPPropertyInfo.java
new file mode 100644
index 0000000..8a9c519
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/properties/XMPPropertyInfo.java
@@ -0,0 +1,45 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+package com.adobe.xmp.properties;
+
+import com.adobe.xmp.options.PropertyOptions;
+
+
+/**
+ * This interface is used to return a property together with its path and namespace.
+ * It is returned when properties are iterated with the <code>XMPIterator</code>.
+ * 
+ * @since   06.07.2006
+ */
+public interface XMPPropertyInfo extends XMPProperty
+{
+	/**
+	 * @return Returns the namespace of the property
+	 */
+	String getNamespace();
+
+	
+	/**
+	 * @return Returns the path of the property, but only if returned by the iterator.
+	 */
+	String getPath();
+
+	
+	/**
+	 * @return Returns the value of the property.
+	 */
+	Object getValue();
+	
+	
+	/**
+	 * @return Returns the options of the property.
+	 */
+	PropertyOptions getOptions();
+}
diff --git a/XMPCore/src/com/adobe/xmp/properties/package.html b/XMPCore/src/com/adobe/xmp/properties/package.html
new file mode 100644
index 0000000..19e7427
--- /dev/null
+++ b/XMPCore/src/com/adobe/xmp/properties/package.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
+	<title>Package overview</title>
+</head>
+
+<body>
+	<p>Package containing the property information classes.</p>
+	<p>XMPProperty and XMPPropertyInfo are used to report properties when they are retrieved by get-methods or by the iterator. 
+	   XMPAliasInfo informs about a certain property-to-property alias.<p>
+</body>
+</html>