Index: tika-xmp/src/test/java/org/apache/tika/xmp/TikaToXMPTest.java
===================================================================
--- tika-xmp/src/test/java/org/apache/tika/xmp/TikaToXMPTest.java	(revision 0)
+++ tika-xmp/src/test/java/org/apache/tika/xmp/TikaToXMPTest.java	(revision 0)
@@ -0,0 +1,243 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * IPTC Metadata Descriptions taken from the IPTC Photo Metadata (July 2010) 
+ * standard. These parts Copyright 2010 International Press Telecommunications 
+ * Council.
+ */
+package org.apache.tika.xmp;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.tika.exception.TikaException;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.OfficeOpenXMLCore;
+import org.apache.tika.metadata.TikaCoreProperties;
+import org.apache.tika.xmp.convert.ITikaToXMPConverter;
+import org.apache.tika.xmp.convert.MSOfficeXMLConverter;
+import org.apache.tika.xmp.convert.TikaToXMP;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.adobe.xmp.XMPConst;
+import com.adobe.xmp.XMPException;
+import com.adobe.xmp.XMPIterator;
+import com.adobe.xmp.XMPMeta;
+import com.adobe.xmp.XMPMetaFactory;
+import com.adobe.xmp.properties.XMPProperty;
+
+/**
+ * Tests the Tika <code>Metadata</code> to XMP conversion functionatlity
+ */
+public class TikaToXMPTest 
+{
+	private Metadata tikaMetadata;
+	
+	private static final String OOXML_MIMETYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
+	private static final String GENERIC_MIMETYPE = "generic/mimetype";
+	
+	// --- Set up ---
+	@Before
+	public void setup()
+	{
+		tikaMetadata = new Metadata();
+	}
+	
+	private void setupOOXMLMetadata(Metadata metadata)
+	{
+		// simple property
+		metadata.set(TikaCoreProperties.LANGUAGE, "language");
+		// language alternative
+		metadata.set(TikaCoreProperties.TITLE, "title");
+		// comma separated array
+		metadata.set(TikaCoreProperties.KEYWORDS, "keyword1,keyword2");
+		// OOXML specific simple prop
+		metadata.set(TikaCoreProperties.LAST_AUTHOR, "lastModifiedBy");
+	}
+	
+	private void checkOOXMLMetadata(XMPMeta xmp) throws XMPException
+	{
+		// check simple property
+		XMPProperty prop = xmp.getProperty(XMPConst.NS_DC, "language");
+		assertNotNull(prop);
+		assertEquals("language", prop.getValue());
+		
+		// check lang alt
+		prop = xmp.getLocalizedText(XMPConst.NS_DC, "title", null, XMPConst.X_DEFAULT);
+		assertNotNull(prop);
+		assertEquals("title", prop.getValue());
+		
+		// check array
+		prop = xmp.getArrayItem(XMPConst.NS_DC, "subject", 1);
+		assertNotNull(prop);
+		assertEquals("keyword1", prop.getValue());
+		prop = xmp.getArrayItem(XMPConst.NS_DC, "subject", 2);
+		assertNotNull(prop);
+		assertEquals("keyword2", prop.getValue());
+		
+		// check OOXML specific simple property
+		prop = xmp.getProperty(OfficeOpenXMLCore.NAMESPACE_URI, "lastModifiedBy");
+		assertNotNull(prop);
+		assertEquals("lastModifiedBy", prop.getValue());
+	}
+	
+	
+	// --- TESTS ---
+	@Test
+	public void convert_OOXMLMetadataWithMimetype_everythingConverted() throws XMPException, TikaException 
+	{
+		setupOOXMLMetadata(tikaMetadata);
+		tikaMetadata.set(Metadata.CONTENT_TYPE, OOXML_MIMETYPE);
+
+		XMPMeta xmp = TikaToXMP.convert(tikaMetadata);
+		
+		checkOOXMLMetadata(xmp);
+	}
+
+	
+	@Test
+	public void convert_OOXMLMetadataWithExtraMimetype_everythingConverted() throws XMPException, TikaException 
+	{
+		setupOOXMLMetadata(tikaMetadata);
+		
+		XMPMeta xmp = TikaToXMP.convert(tikaMetadata, OOXML_MIMETYPE);
+		
+		checkOOXMLMetadata(xmp);
+	}
+
+
+	@Test
+	public void convert_OOXMLMetadataWithoutMimetype_onlyGeneralMetadataconverted() throws XMPException, TikaException 
+	{
+		setupOOXMLMetadata(tikaMetadata);
+		
+		XMPMeta xmp = TikaToXMP.convert(tikaMetadata, null);
+		
+		// general metadata is converted
+		// check simple property
+		XMPProperty prop = xmp.getProperty(XMPConst.NS_DC, "language");
+		assertNotNull(prop);
+		assertEquals("language", prop.getValue());
+		
+		// check lang alt
+		prop = xmp.getLocalizedText(XMPConst.NS_DC, "title", null, XMPConst.X_DEFAULT);
+		assertNotNull(prop);
+		assertEquals("title", prop.getValue());
+		
+		// OOXML one is not, the namespace has also not been registiered as the converter has not been used
+		XMPMetaFactory.getSchemaRegistry().registerNamespace(OfficeOpenXMLCore.NAMESPACE_URI, OfficeOpenXMLCore.PREFIX);
+		prop = xmp.getProperty(OfficeOpenXMLCore.NAMESPACE_URI, "lastModifiedBy");
+		assertNull(prop);
+	}
+	
+	
+	@Test
+	public void convert_genericMetadataAllQualified_allConverted() throws XMPException, TikaException 
+	{
+		// simple property
+		tikaMetadata.set(TikaCoreProperties.FORMAT, GENERIC_MIMETYPE);
+		// language alternative
+		tikaMetadata.set(TikaCoreProperties.TITLE, "title");
+		// array
+		tikaMetadata.set(TikaCoreProperties.SUBJECT, new String[] {"keyword1", "keyword2"});
+		
+		
+		XMPMeta xmp = TikaToXMP.convert(tikaMetadata, null);
+		
+		// check simple property
+		XMPProperty prop = xmp.getProperty(XMPConst.NS_DC, "format");
+		assertNotNull(prop);
+		assertEquals(GENERIC_MIMETYPE, prop.getValue());
+		
+		// check lang alt
+		prop = xmp.getLocalizedText(XMPConst.NS_DC, "title", null, XMPConst.X_DEFAULT);
+		assertNotNull(prop);
+		assertEquals("title", prop.getValue());
+		
+		// check array
+		prop = xmp.getArrayItem(XMPConst.NS_DC, "subject", 1);
+		assertNotNull(prop);
+		assertEquals("keyword1", prop.getValue());
+		prop = xmp.getArrayItem(XMPConst.NS_DC, "subject", 2);
+		assertNotNull(prop);
+		assertEquals("keyword2", prop.getValue());
+	}
+	
+	
+	@Test
+	public void convert_wrongGenericMetadata_notConverted() throws XMPException, TikaException 
+	{
+		// unknown prefix
+		tikaMetadata.set("unknown:key", "unknownPrefixValue");
+		// not qualified key
+		tikaMetadata.set("wrongKey", "wrongKeyValue");
+		
+		XMPMeta xmp = TikaToXMP.convert(tikaMetadata, null);
+		
+		// XMP is empty
+		XMPIterator iter = xmp.iterator();
+		assertFalse(iter.hasNext());
+	}
+	
+	@Test(expected=IllegalArgumentException.class)
+	public void convert_nullInput_throw() throws TikaException 
+	{
+		TikaToXMP.convert(null);
+	}
+	
+	@Test
+	public void isConverterAvailable_availableMime_true() 
+	{
+		assertTrue(TikaToXMP.isConverterAvailable(OOXML_MIMETYPE));
+	}
+
+	@Test
+	public void isConverterAvailable_noAvailableMime_false() 
+	{
+		assertFalse(TikaToXMP.isConverterAvailable(GENERIC_MIMETYPE));
+	}
+	
+	@Test
+	public void isConverterAvailable_nullInput_false() 
+	{
+		assertFalse(TikaToXMP.isConverterAvailable(null));
+	}
+	
+	@Test
+	public void getConverter_ConverterAvailable_class() throws TikaException
+	{
+		ITikaToXMPConverter converter = TikaToXMP.getConverter(OOXML_MIMETYPE);
+		assertNotNull(converter);
+		assertTrue(converter instanceof MSOfficeXMLConverter);
+	}
+
+	@Test
+	public void getConverter_noConverterAvailable_null() throws TikaException 
+	{
+		ITikaToXMPConverter converter = TikaToXMP.getConverter(GENERIC_MIMETYPE);
+		assertNull(converter);
+	}
+	
+	@Test(expected=IllegalArgumentException.class)
+	public void getConverter_nullInput_throw() throws TikaException 
+	{
+		TikaToXMP.getConverter(null);
+	}
+}
Index: tika-xmp/src/test/java/org/apache/tika/xmp/XMPMetadataTest.java
===================================================================
--- tika-xmp/src/test/java/org/apache/tika/xmp/XMPMetadataTest.java	(revision 0)
+++ tika-xmp/src/test/java/org/apache/tika/xmp/XMPMetadataTest.java	(revision 0)
@@ -0,0 +1,283 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * IPTC Metadata Descriptions taken from the IPTC Photo Metadata (July 2010) 
+ * standard. These parts Copyright 2010 International Press Telecommunications 
+ * Council.
+ */
+package org.apache.tika.xmp;
+
+import static org.junit.Assert.*;
+
+import java.util.Date;
+import java.util.Properties;
+
+import org.apache.tika.exception.TikaException;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.Property;
+import org.apache.tika.metadata.PropertyTypeException;
+import org.apache.tika.metadata.TikaCoreProperties;
+import org.apache.tika.metadata.XMPRights;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.adobe.xmp.XMPConst;
+import com.adobe.xmp.XMPException;
+import com.adobe.xmp.XMPMeta;
+import com.adobe.xmp.XMPUtils;
+import com.adobe.xmp.properties.XMPProperty;
+
+public class XMPMetadataTest 
+{
+	private Metadata tikaMetadata;
+	private XMPMetadata xmpMeta;
+	
+	private static final String GENERIC_MIMETYPE = "generic/mimetype";
+	
+	// --- SETUP ---
+	@Before
+	public void setUp() throws Exception 
+	{
+		xmpMeta = new XMPMetadata();
+		tikaMetadata = new Metadata();
+		setupMetadata(tikaMetadata);
+	}
+
+	private void setupMetadata(Metadata metadata)
+	{
+		// simple property
+		metadata.set(TikaCoreProperties.FORMAT, GENERIC_MIMETYPE);
+		// language alternative
+		metadata.set(TikaCoreProperties.TITLE, "title");
+		// array
+		metadata.set(TikaCoreProperties.SUBJECT, new String[] {"keyword1", "keyword2"});
+		// date
+		metadata.set(TikaCoreProperties.MODIFIED,"2001-01-01T01:01" );
+		// int simple property
+		metadata.set(Property.internalInteger("xmp:Integer"), "2");
+	}
+	
+	// --- HELPER ---
+	private void checkArrayValues(String[] values, String baseValue)
+	{
+		int i = 1;
+		for(String value : values)
+		{
+			assertEquals(baseValue+i, value);
+			i++;
+		}
+	}
+	
+	
+	// --- TESTS ---
+	@Test
+	public void process_genericConversion_ok() throws TikaException, XMPException
+	{
+		xmpMeta.process(tikaMetadata, GENERIC_MIMETYPE);
+		
+		XMPMeta xmp = xmpMeta.getXMPData();
+		
+		// check simple property
+		XMPProperty prop = xmp.getProperty(XMPConst.NS_DC, "format");
+		assertNotNull(prop);
+		assertEquals(GENERIC_MIMETYPE, prop.getValue());
+		
+		// check lang alt
+		prop = xmp.getLocalizedText(XMPConst.NS_DC, "title", null, XMPConst.X_DEFAULT);
+		assertNotNull(prop);
+		assertEquals("title", prop.getValue());
+		
+		// check array
+		prop = xmp.getArrayItem(XMPConst.NS_DC, "subject", 1);
+		assertNotNull(prop);
+		assertEquals("keyword1", prop.getValue());
+		prop = xmp.getArrayItem(XMPConst.NS_DC, "subject", 2);
+		assertNotNull(prop);
+		assertEquals("keyword2", prop.getValue());
+	}
+	
+	@Test
+	public void isMultiValued_multiProp_true() throws TikaException 
+	{
+		xmpMeta.process(tikaMetadata);
+		
+		assertTrue(xmpMeta.isMultiValued(TikaCoreProperties.SUBJECT));
+	}
+
+	@Test
+	public void isMultiValued_simpleProp_false() throws TikaException 
+	{
+		xmpMeta.process(tikaMetadata);
+		
+		assertFalse(xmpMeta.isMultiValued(TikaCoreProperties.FORMAT));
+	}
+
+	@Test
+	public void get_simpleProp_valueReturned() throws TikaException 
+	{
+		xmpMeta.process(tikaMetadata);
+		
+		assertEquals(GENERIC_MIMETYPE, xmpMeta.get(TikaCoreProperties.FORMAT));
+	}
+
+	@Test
+	public void get_arrayProp_firstValueReturned() throws TikaException 
+	{
+		xmpMeta.process(tikaMetadata);
+		
+		assertEquals("keyword1", xmpMeta.get(TikaCoreProperties.SUBJECT));
+	}
+
+	@Test
+	public void get_notExistingProp_null() throws TikaException 
+	{
+		assertNull(xmpMeta.get(TikaCoreProperties.FORMAT));
+	}
+	
+	@Test(expected=PropertyTypeException.class)
+	public void get_nullInput_throw() 
+	{
+		String notInitialized = null;
+		xmpMeta.get(notInitialized);
+	}
+	
+	@Test(expected=PropertyTypeException.class)
+	public void get_notQualifiedKey_throw() 
+	{
+		xmpMeta.get("wrongKey");
+	}
+	
+	@Test(expected=PropertyTypeException.class)
+	public void get_unknownPrefixKey_throw() 
+	{
+		xmpMeta.get("unknown:key");
+	}
+	
+	@Test
+	public void getInt_IntegerProperty_valueReturned() throws TikaException 
+	{
+		xmpMeta.process(tikaMetadata);
+		
+		assertEquals(new Integer(2), xmpMeta.getInt(Property.get("xmp:Integer")));
+	}
+
+	@Test
+	public void getDate_DateProperty_valueReturned()  throws TikaException, XMPException 
+	{
+		xmpMeta.process(tikaMetadata);
+		
+		Date date = XMPUtils.convertToDate("2001-01-01T01:01").getCalendar().getTime();
+		assertTrue(date.equals(xmpMeta.getDate(TikaCoreProperties.MODIFIED)));
+	}
+
+	@Test
+	public void getValues_arrayProperty_allElementsReturned() throws TikaException 
+	{
+		xmpMeta.process(tikaMetadata);
+		
+		String[] values = xmpMeta.getValues(TikaCoreProperties.SUBJECT);
+		assertEquals(2, values.length);
+		
+		checkArrayValues(values, "keyword");
+	}
+
+	@Test
+	public void testSetAll() 
+	{
+		Properties props = new Properties();
+		props.put(TikaCoreProperties.FORMAT.getName(), "format");
+		props.put(TikaCoreProperties.SUBJECT.getName(), "keyword");
+		
+		xmpMeta.setAll(props);
+		
+		assertEquals("format", xmpMeta.get(TikaCoreProperties.FORMAT));
+		
+		String[] values = xmpMeta.getValues(TikaCoreProperties.SUBJECT);
+		assertEquals(1, values.length);
+		
+		assertEquals("keyword", values[0]);
+	}
+
+	@Test
+	public void set_simpleProp_ok() 
+	{
+		xmpMeta.set(TikaCoreProperties.FORMAT, GENERIC_MIMETYPE);
+		
+		assertEquals(GENERIC_MIMETYPE, xmpMeta.get(TikaCoreProperties.FORMAT));
+	}
+	
+	@Test(expected=PropertyTypeException.class)
+	public void set_nullInput_throw() 
+	{
+		String notInitialized = null;
+		xmpMeta.set(notInitialized,"value");
+	}
+	
+	@Test(expected=PropertyTypeException.class)
+	public void set_notQualifiedKey_throw() 
+	{
+		xmpMeta.set("wrongKey","value");
+	}
+	
+	@Test(expected=PropertyTypeException.class)
+	public void set_unknownPrefixKey_throw() 
+	{
+		xmpMeta.set("unknown:key","value");
+	}
+
+	@Test
+	public void set_arrayProperty_ok() 
+	{
+		xmpMeta.set(TikaCoreProperties.SUBJECT, new String[] {"keyword1", "keyword2"});
+
+		String[] values = xmpMeta.getValues(TikaCoreProperties.SUBJECT);
+		assertEquals(2, values.length);
+		
+		checkArrayValues(values, "keyword");
+	}
+
+	@Test(expected=PropertyTypeException.class)
+	public void set_simplePropWithMultipleValues_throw() 
+	{
+		xmpMeta.set(TikaCoreProperties.FORMAT,new String[] {"value1", "value2"});
+	}
+	
+
+	@Test
+	public void remove_existingProperty_propertyRemoved() throws TikaException 
+	{
+		xmpMeta.process(tikaMetadata);
+		
+		assertNotNull(xmpMeta.get(TikaCoreProperties.FORMAT));
+		
+		xmpMeta.remove(TikaCoreProperties.FORMAT);
+		
+		assertNull(xmpMeta.get(TikaCoreProperties.FORMAT));
+	}
+
+	@Test
+	public void size_numberOfNamespacesReturned() throws TikaException
+	{
+		xmpMeta.process(tikaMetadata);
+		
+		assertEquals(2, xmpMeta.size());
+		
+		xmpMeta.set(XMPRights.OWNER, "owner");
+		
+		assertEquals(3, xmpMeta.size());
+	}
+
+}
Index: tika-xmp/src/main/java/org/apache/tika/xmp/convert/TikaToXMP.java
===================================================================
--- tika-xmp/src/main/java/org/apache/tika/xmp/convert/TikaToXMP.java	(revision 0)
+++ tika-xmp/src/main/java/org/apache/tika/xmp/convert/TikaToXMP.java	(revision 0)
@@ -0,0 +1,225 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * IPTC Metadata Descriptions taken from the IPTC Photo Metadata (July 2010) 
+ * standard. These parts Copyright 2010 International Press Telecommunications 
+ * Council.
+ */
+package org.apache.tika.xmp.convert;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.tika.exception.TikaException;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.TikaCoreProperties;
+import org.apache.tika.mime.MediaType;
+import org.apache.tika.parser.ParseContext;
+import org.apache.tika.parser.microsoft.OfficeParser;
+import org.apache.tika.parser.microsoft.ooxml.OOXMLParser;
+import org.apache.tika.parser.odf.OpenDocumentParser;
+import org.apache.tika.parser.rtf.RTFParser;
+
+import com.adobe.xmp.XMPException;
+import com.adobe.xmp.XMPMeta;
+import com.adobe.xmp.XMPMetaFactory;
+
+public class TikaToXMP
+{
+	/** 
+	 * Map from mimetype to converter class 
+	 * Must only be accessed through <code>getConverterMap</code>
+	 */
+	private static Map<MediaType, Class<? extends ITikaToXMPConverter>> converterMap;		
+
+	// --- public API implementation---
+	
+	public TikaToXMP() 
+	{
+		// Nothing to do
+	}
+	
+
+	/**
+	 * @see ITikaToXMP#convert(Metadata, String)
+	 * But the mimetype is retrieved from the metadata map.
+	 */
+	public static XMPMeta convert(Metadata tikaMetadata) throws TikaException 
+	{
+		if( tikaMetadata == null )
+		{
+			throw new IllegalArgumentException("Metadata parameter must not be null");
+		}
+		
+		String mimetype = tikaMetadata.get(Metadata.CONTENT_TYPE);
+		if(mimetype == null)
+		{
+			mimetype = tikaMetadata.get(TikaCoreProperties.FORMAT);
+		}
+		
+		return convert(tikaMetadata, mimetype);
+	}
+
+
+	/**
+	 * Convert the given Tika metadata map to XMP object.
+	 * If a mimetype is provided in the Metadata map, a specific converter can be used, that converts all
+     * available metadata.
+     * If there is no mimetype provided or no specific converter available a generic conversion is done
+     * which will convert only those properties that are in known namespaces and are using the correct prefixes.
+	 *
+	 * @param tikaMetadata the Metadata map from Tika
+	 * @param mimetype depicts the format's converter to use
+	 * @return XMP object
+	 * @throws TikaException
+	 */
+	public static XMPMeta convert(Metadata tikaMetadata, String mimetype) throws TikaException 
+	{
+		if( tikaMetadata == null )
+		{
+			throw new IllegalArgumentException("Metadata parameter must not be null");
+		}
+		
+		ITikaToXMPConverter converter = null;
+		
+		if( isConverterAvailable(mimetype) )
+		{
+			converter = getConverter(mimetype);
+		}
+		else
+		{
+			converter = new GenericConverter();
+		}
+		
+		XMPMeta xmp = null;
+		
+		if( converter != null )
+		{
+			try 
+			{
+				xmp = converter.process(tikaMetadata);
+			} 
+			catch (XMPException e) 
+			{
+				throw new TikaException("Tika metadata could not be converted to XMP", e);
+			}
+		}
+		else
+		{
+			xmp = XMPMetaFactory.create(); // empty packet
+		}
+		
+		return xmp;
+	}
+
+	/** 
+	 * Check if there is a converter available which allows to convert the
+	 * Tika metadata to XMP
+	 * @param mimetype the Mimetype
+	 * @return true if the Metadata object can be converted or false if not
+	 */
+	public static boolean isConverterAvailable(String mimetype) 
+	{
+		MediaType type = MediaType.parse(mimetype);
+		
+        if (type != null) 
+        {
+        	return (getConverterMap().get(type) != null);
+        }
+        
+        return false;
+	}
+	
+	/**
+	 * Retrieve a specific converter according to the mimetype
+	 * @param mimetype the Mimetype
+	 * @return the converter or null, if none exists
+	 * @throws TikaException
+	 */
+	public static ITikaToXMPConverter getConverter(String mimetype) throws TikaException 
+	{
+		if( mimetype == null )
+		{
+			throw new IllegalArgumentException("mimetype must not be null");
+		}
+		
+		ITikaToXMPConverter converter = null;
+		
+		MediaType type = MediaType.parse(mimetype);
+		
+        if (type != null) 
+        {
+			Class<? extends ITikaToXMPConverter> clazz = getConverterMap().get( type );
+			if (clazz != null)
+			{
+				try
+				{
+					converter = clazz.newInstance();
+				}
+				catch (Exception e)
+				{
+					throw new TikaException("TikaToXMP converter class cannot be instantiated for mimetype: " + type.toString(), e);
+				}
+			}
+        }
+        
+        return converter;
+	}
+
+	// --- Private methods ---
+	
+	private static Map<MediaType, Class<? extends ITikaToXMPConverter>> getConverterMap()
+	{
+		if( converterMap == null )
+		{
+			converterMap = new HashMap<MediaType, Class<? extends ITikaToXMPConverter>>();
+			initialize();
+		}
+		return converterMap;
+	}
+	
+	
+	/**
+	 * Initializes the map with supported converters.
+	 */
+	private static void initialize()
+	{
+		// No particular parsing context is needed
+		ParseContext parseContext = new ParseContext();
+		
+		// MS Office Binary File Format
+		addConverter(new OfficeParser().getSupportedTypes(parseContext), MSOfficeBinaryConverter.class);
+		
+		// Rich Text Format
+		addConverter(new RTFParser().getSupportedTypes(parseContext), RTFConverter.class);
+
+		// MS Open XML Format
+		addConverter(new OOXMLParser().getSupportedTypes(parseContext), MSOfficeXMLConverter.class);
+		
+		// Open document format
+		addConverter(new OpenDocumentParser().getSupportedTypes(parseContext), OpenDocumentConverter.class);
+	}
+	
+	
+	private static void addConverter(Set<MediaType> supportedTypes, Class<? extends ITikaToXMPConverter> converter)
+	{
+		for( MediaType type : supportedTypes )
+		{
+			getConverterMap().put(type, converter);
+		}
+	}
+}
Index: tika-xmp/src/main/java/org/apache/tika/xmp/convert/OpenDocumentConverter.java
===================================================================
--- tika-xmp/src/main/java/org/apache/tika/xmp/convert/OpenDocumentConverter.java	(revision 0)
+++ tika-xmp/src/main/java/org/apache/tika/xmp/convert/OpenDocumentConverter.java	(revision 0)
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * IPTC Metadata Descriptions taken from the IPTC Photo Metadata (July 2010) 
+ * standard. These parts Copyright 2010 International Press Telecommunications 
+ * Council.
+ */
+package org.apache.tika.xmp.convert;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.tika.exception.TikaException;
+import org.apache.tika.metadata.HttpHeaders;
+import org.apache.tika.metadata.MSOffice;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.Office;
+import org.apache.tika.metadata.PagedText;
+import org.apache.tika.metadata.TikaCoreProperties;
+
+import com.adobe.xmp.XMPConst;
+import com.adobe.xmp.XMPException;
+import com.adobe.xmp.XMPMeta;
+import com.adobe.xmp.options.PropertyOptions;
+
+/**
+ * Tika to XMP mapping for the Open Document formats:
+ * Text (.odt), Spreatsheet (.ods), Graphics (.odg) and Presentation (.odp).
+ */
+public class OpenDocumentConverter extends AbstractConverter
+{
+	protected static final Set<Namespace> ADDITIONAL_NAMESPACES =
+	        Collections.unmodifiableSet(new HashSet<Namespace>(Arrays.asList(
+	        		new Namespace(Office.NAMESPACE_URI_DOC_META, Office.PREFIX_DOC_META)
+	        		)));
+	
+	public OpenDocumentConverter() throws TikaException 
+	{
+		super();
+	}
+
+	/**
+	 * @throws XMPException Forwards XMP errors
+	 * @see XMPFilesProcessor.onverter#process(Metadata)
+	 */
+	@Override
+	public XMPMeta process(Metadata metadata) throws XMPException
+	{
+		super.setMetadata(metadata);
+
+		createProperty(HttpHeaders.CONTENT_TYPE, XMPConst.NS_DC, "format");
+		
+		createProperty(Office.CHARACTER_COUNT, Office.NAMESPACE_URI_DOC_META, "character-count");
+		createProperty(TikaCoreProperties.CREATION_DATE, XMPConst.NS_XMP, "CreateDate");
+		createCommaSeparatedArray(TikaCoreProperties.CREATOR, XMPConst.NS_DC, "creator", PropertyOptions.ARRAY_ORDERED);
+		createProperty(TikaCoreProperties.DATE, XMPConst.NS_XMP, "ModifyDate");
+		createProperty(TikaCoreProperties.DESCRIPTION, XMPConst.NS_PDFX, "Comments");
+		createCommaSeparatedArray(TikaCoreProperties.KEYWORDS, XMPConst.NS_DC, "subject", PropertyOptions.ARRAY);
+		createLangAltProperty(TikaCoreProperties.SUBJECT, XMPConst.NS_DC, "description");
+		createProperty(MSOffice.EDIT_TIME, Office.NAMESPACE_URI_DOC_META, "editing-duration");
+		createProperty("editing-cycles", Office.NAMESPACE_URI_DOC_META, "editing-cycles");
+		createProperty("generator", XMPConst.NS_XMP, "CreatorTool");
+		createProperty(Office.IMAGE_COUNT, Office.NAMESPACE_URI_DOC_META, "image-count");
+		createProperty("initial-creator", Office.NAMESPACE_URI_DOC_META, "initial-creator");
+		createProperty(Office.OBJECT_COUNT, Office.NAMESPACE_URI_DOC_META, "object-count");
+		createProperty(PagedText.N_PAGES, XMPConst.TYPE_PAGEDFILE, "NPages");
+		createProperty(Office.PARAGRAPH_COUNT, Office.NAMESPACE_URI_DOC_META, "paragraph-count");
+		createProperty(Office.TABLE_COUNT, Office.NAMESPACE_URI_DOC_META, "table-count");
+		createLangAltProperty(TikaCoreProperties.TITLE, XMPConst.NS_DC, "title");
+		createProperty(Office.WORD_COUNT, Office.NAMESPACE_URI_DOC_META, "word-count");
+
+		// duplicate properties not mapped:
+		//		nbImg | 0
+		//		nbObject | 0
+		//		nbPage | 1
+		//		nbPara | 3
+		//		nbTab | 0
+		//		nbWord | 5
+		
+		return super.getXMPMeta();
+	}
+
+	@Override
+	protected Set<Namespace> getAdditionalNamespaces() 
+	{
+		return ADDITIONAL_NAMESPACES;
+	}
+}
Index: tika-xmp/src/main/java/org/apache/tika/xmp/convert/AbstractConverter.java
===================================================================
--- tika-xmp/src/main/java/org/apache/tika/xmp/convert/AbstractConverter.java	(revision 0)
+++ tika-xmp/src/main/java/org/apache/tika/xmp/convert/AbstractConverter.java	(revision 0)
@@ -0,0 +1,199 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * IPTC Metadata Descriptions taken from the IPTC Photo Metadata (July 2010) 
+ * standard. These parts Copyright 2010 International Press Telecommunications 
+ * Council.
+ */
+package org.apache.tika.xmp.convert;
+
+import java.util.Set;
+
+import org.apache.tika.exception.TikaException;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.Property;
+
+import com.adobe.xmp.XMPConst;
+import com.adobe.xmp.XMPException;
+import com.adobe.xmp.XMPMeta;
+import com.adobe.xmp.XMPMetaFactory;
+import com.adobe.xmp.XMPSchemaRegistry;
+import com.adobe.xmp.XMPUtils;
+import com.adobe.xmp.options.PropertyOptions;
+
+/**
+ * Base class for Tika Metadata to XMP converter which provides some needed
+ * common functionality.
+ */
+public abstract class AbstractConverter implements ITikaToXMPConverter
+{
+	private Metadata metadata;
+	protected XMPMeta meta;
+
+	abstract public XMPMeta process(Metadata metadata) throws XMPException;
+	
+	/**
+	 * Every Converter has to provide information about namespaces
+	 * that are used additionally to the core set of XMP namespaces.
+	 * 
+	 * @return the additional namespace information
+	 */
+	abstract protected Set<Namespace> getAdditionalNamespaces();
+	
+	public AbstractConverter() throws TikaException
+	{
+		meta = XMPMetaFactory.create();
+		metadata = new Metadata();
+		registerNamespaces(getAdditionalNamespaces());
+	}
+
+	public void setMetadata(Metadata metadata)
+	{
+		this.metadata = metadata;
+	}
+
+	public XMPMeta getXMPMeta()
+	{
+		return meta;
+	}
+
+	// --- utility methods used by sub-classes ---
+	
+	/** 
+	 * Registers a number <code>Namespace</code> information with XMPCore.
+	 * Any already registered namespace is not registered again.
+	 * 
+	 * @param namespaces the list of namespaces to be registered
+	 * @throws TikaException in case a namespace oculd not be registered
+	 */
+	protected void registerNamespaces(Set<Namespace> namespaces) throws TikaException
+	{
+		XMPSchemaRegistry registry = XMPMetaFactory.getSchemaRegistry();
+
+		for( Namespace namespace : namespaces)
+		{
+			// Any already registered namespace is not registered again
+			try 
+			{
+				registry.registerNamespace(namespace.uri, namespace.prefix);
+			} 
+			catch (XMPException e) 
+			{
+				throw new TikaException("Namespace needed by converter could not be registiered with XMPCore", e);
+			}
+		}
+	}
+	
+	/**
+	 * @see AbstractConverter#createProperty(String, String, String);
+	 */
+	protected void createProperty(Property metadataProperty, String ns, String propertyName) throws XMPException
+	{
+		createProperty(metadataProperty.getName(), ns, propertyName);
+	}
+
+	/**
+	 * Creates a simple property.
+	 * @param tikaKey Key in the Tika metadata map
+	 * @param ns namespace the property should be created in
+	 * @param propertyName name of the property
+	 * @throws XMPException if the property could not be created
+	 */
+	protected void createProperty(String tikaKey, String ns, String propertyName) throws XMPException
+	{
+		String value = metadata.get(tikaKey);
+		if (value != null  &&  value.length() > 0)
+		{
+			meta.setProperty(ns, propertyName, value);
+		}
+	}
+
+	/**
+	 * @see AbstractConverter#createLangAltProperty(String, String, String);
+	 */
+	protected void createLangAltProperty(Property metadataProperty, String ns, String propertyName) throws XMPException
+	{
+		createLangAltProperty(metadataProperty.getName(), ns, propertyName);
+	}
+	
+	/**
+	 * Creates a language alternative property in the x-default language
+	 * @param tikaKey Key in the Tika metadata map
+	 * @param ns namespace the property should be created in
+	 * @param propertyName name of the property
+	 * @throws XMPException if the property could not be created
+	 */
+	protected void createLangAltProperty(String tikaKey, String ns, String propertyName) throws XMPException
+	{
+		String value = metadata.get(tikaKey);
+		if (value != null  &&  value.length() > 0)
+		{
+			meta.setLocalizedText(ns, propertyName, null, XMPConst.X_DEFAULT, value);
+		}
+	}
+
+	protected void createArrayProperty(Property metadataProperty, String nsDc, String arrayProperty, int arrayType) throws XMPException
+	{
+		createArrayProperty(metadataProperty.getName(), nsDc, arrayProperty, arrayType);
+	}
+	
+	/**
+	 * Creates an array property from a list of values.
+	 * @param tikaKey Key in the Tika metadata map
+	 * @param ns namespace the property should be created in
+	 * @param propertyName name of the property
+	 * @param arrayType depicts which kind of array shall be created
+	 * @throws XMPException if the property could not be created
+	 */
+	protected void createArrayProperty(String tikaKey, String ns, String propertyName, int arrayType) throws XMPException
+	{
+		String[] values = metadata.getValues(tikaKey);
+		if (values != null)
+		{
+			meta.setProperty(ns, propertyName, null, new PropertyOptions(arrayType));
+			for( String value : values )
+			{
+				meta.appendArrayItem( ns, propertyName, value );
+			}
+		}
+	}
+	
+	protected void createCommaSeparatedArray(Property metadataProperty, String nsDc, String arrayProperty, int arrayType) throws XMPException
+	{
+		createCommaSeparatedArray(metadataProperty.getName(), nsDc, arrayProperty, arrayType);
+	}
+	
+	/**
+	 * Creates an array property from a comma separated list.
+	 * @param tikaKey Key in the Tika metadata map
+	 * @param ns namespace the property should be created in
+	 * @param propertyName name of the property
+	 * @param arrayType depicts which kind of array shall be created
+	 * @throws XMPException if the property could not be created
+	 */
+	protected void createCommaSeparatedArray(String tikaKey, String ns, String propertyName, int arrayType) throws XMPException
+	{
+		String value = metadata.get(tikaKey);
+		if (value != null  &&  value.length() > 0)
+		{
+			XMPUtils.separateArrayItems(meta, ns, propertyName, value, new PropertyOptions(arrayType), false);
+		}
+	}
+
+
+
+
+}
Index: tika-xmp/src/main/java/org/apache/tika/xmp/convert/MSOfficeBinaryConverter.java
===================================================================
--- tika-xmp/src/main/java/org/apache/tika/xmp/convert/MSOfficeBinaryConverter.java	(revision 0)
+++ tika-xmp/src/main/java/org/apache/tika/xmp/convert/MSOfficeBinaryConverter.java	(revision 0)
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * IPTC Metadata Descriptions taken from the IPTC Photo Metadata (July 2010) 
+ * standard. These parts Copyright 2010 International Press Telecommunications 
+ * Council.
+ */
+package org.apache.tika.xmp.convert;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.tika.exception.TikaException;
+import org.apache.tika.metadata.HttpHeaders;
+import org.apache.tika.metadata.MSOffice;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.Office;
+import org.apache.tika.metadata.OfficeOpenXMLCore;
+import org.apache.tika.metadata.OfficeOpenXMLExtended;
+import org.apache.tika.metadata.TikaCoreProperties;
+
+import com.adobe.xmp.XMPConst;
+import com.adobe.xmp.XMPException;
+import com.adobe.xmp.XMPMeta;
+import com.adobe.xmp.options.PropertyOptions;
+
+/**
+ * Tika to XMP mapping for the binary MS formats Word (.doc), Excel (.xls) and PowerPoint (.ppt).
+ */
+public class MSOfficeBinaryConverter extends AbstractConverter
+{
+	 public MSOfficeBinaryConverter() throws TikaException 
+	 {
+		super();
+	}
+
+	protected static final Set<Namespace> ADDITIONAL_NAMESPACES =
+		        Collections.unmodifiableSet(new HashSet<Namespace>(Arrays.asList(
+		        		new Namespace(OfficeOpenXMLCore.NAMESPACE_URI, OfficeOpenXMLCore.PREFIX),
+		        		new Namespace(OfficeOpenXMLExtended.NAMESPACE_URI, OfficeOpenXMLExtended.PREFIX)
+		        		)));
+		        		
+	/**
+	 * @throws XMPException Forwards XMP errors
+	 * @see XMPFilesProcessor.MSOfficeXMLConverter.onverter#process(Metadata)
+	 */
+	public XMPMeta process(Metadata metadata) throws XMPException
+	{
+		super.setMetadata(metadata);
+
+		// For all formats, Tika uses the same keys 
+		createProperty(HttpHeaders.CONTENT_TYPE, XMPConst.NS_DC, "format");
+		createProperty(OfficeOpenXMLExtended.APPLICATION, XMPConst.NS_XMP, "CreatorTool");
+		createCommaSeparatedArray(TikaCoreProperties.AUTHOR, XMPConst.NS_DC, "creator", PropertyOptions.ARRAY_ORDERED);
+		createProperty(OfficeOpenXMLCore.CATEGORY, XMPConst.NS_IPTCCORE, "intellectualGenre");
+		createProperty(TikaCoreProperties.CREATION_DATE, XMPConst.NS_XMP, "CreateDate");
+		createProperty(Office.CHARACTER_COUNT, OfficeOpenXMLExtended.NAMESPACE_URI, "Characters");
+		createProperty(MSOffice.COMMENTS, XMPConst.NS_PDFX, "Comments");
+		createProperty(OfficeOpenXMLExtended.COMPANY, OfficeOpenXMLExtended.NAMESPACE_URI, "Company");
+		createCommaSeparatedArray(TikaCoreProperties.KEYWORDS, XMPConst.NS_DC, "subject", PropertyOptions.ARRAY);
+		createLangAltProperty(TikaCoreProperties.SUBJECT, XMPConst.NS_DC, "description");
+		createProperty(TikaCoreProperties.LANGUAGE, OfficeOpenXMLCore.NAMESPACE_URI, "language");
+		createProperty(TikaCoreProperties.PRINT_DATE, OfficeOpenXMLCore.NAMESPACE_URI, "lastPrinted");
+		createProperty(TikaCoreProperties.SAVE_DATE, XMPConst.NS_XMP, "ModifyDate");
+		createProperty(Office.PAGE_COUNT, XMPConst.TYPE_PAGEDFILE, "NPages");
+		createProperty(OfficeOpenXMLCore.REVISION, OfficeOpenXMLCore.NAMESPACE_URI, "revision");
+		createProperty(Office.SLIDE_COUNT, OfficeOpenXMLExtended.NAMESPACE_URI, "Pages");
+		createProperty(OfficeOpenXMLExtended.TEMPLATE, OfficeOpenXMLExtended.NAMESPACE_URI, "Template");
+		createLangAltProperty(TikaCoreProperties.TITLE, XMPConst.NS_DC, "title");
+		createProperty(Office.WORD_COUNT, OfficeOpenXMLExtended.NAMESPACE_URI, "Words");
+		// Not mapped: (MSOffice) Edit-Time 	???
+		// Not mapped:	(MSOffice) Last-Author 	???
+		// not mapped: (MSOffice) Security 	???
+		
+		return super.getXMPMeta();
+	}
+
+	protected Set<Namespace> getAdditionalNamespaces() 
+	{
+		return ADDITIONAL_NAMESPACES;
+	}
+}
Index: tika-xmp/src/main/java/org/apache/tika/xmp/convert/ITikaToXMPConverter.java
===================================================================
--- tika-xmp/src/main/java/org/apache/tika/xmp/convert/ITikaToXMPConverter.java	(revision 0)
+++ tika-xmp/src/main/java/org/apache/tika/xmp/convert/ITikaToXMPConverter.java	(revision 0)
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * IPTC Metadata Descriptions taken from the IPTC Photo Metadata (July 2010) 
+ * standard. These parts Copyright 2010 International Press Telecommunications 
+ * Council.
+ */
+package org.apache.tika.xmp.convert;
+
+import org.apache.tika.metadata.Metadata;
+
+import com.adobe.xmp.XMPException;
+import com.adobe.xmp.XMPMeta;
+
+/**
+ * Interface for the specific <code>Metadata</code> to XMP converters
+ */
+public interface ITikaToXMPConverter 
+{
+	/**
+	 * Converts a Tika {@link Metadata}-object into an {@link XMPMeta} containing 
+	 * the useful properties.
+	 * 
+	 * @param metadata a Tika Metadata object
+	 * @return Returns an XMPMeta object.
+	 * @throws XMPException If an error occurs during the creation of the XMP object.
+	 */
+	XMPMeta process(Metadata metadata) throws XMPException;	
+}
Index: tika-xmp/src/main/java/org/apache/tika/xmp/convert/RTFConverter.java
===================================================================
--- tika-xmp/src/main/java/org/apache/tika/xmp/convert/RTFConverter.java	(revision 0)
+++ tika-xmp/src/main/java/org/apache/tika/xmp/convert/RTFConverter.java	(revision 0)
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * IPTC Metadata Descriptions taken from the IPTC Photo Metadata (July 2010) 
+ * standard. These parts Copyright 2010 International Press Telecommunications 
+ * Council.
+ */
+package org.apache.tika.xmp.convert;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.tika.exception.TikaException;
+import org.apache.tika.metadata.ClimateForcast;
+import org.apache.tika.metadata.HttpHeaders;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.OfficeOpenXMLCore;
+import org.apache.tika.metadata.OfficeOpenXMLExtended;
+import org.apache.tika.metadata.TikaCoreProperties;
+
+import com.adobe.xmp.XMPConst;
+import com.adobe.xmp.XMPException;
+import com.adobe.xmp.XMPMeta;
+import com.adobe.xmp.options.PropertyOptions;
+
+/**
+ * Tika to XMP mapping for the RTF format.
+ */
+public class RTFConverter extends AbstractConverter
+{
+	protected static final Set<Namespace> ADDITIONAL_NAMESPACES =
+	        Collections.unmodifiableSet(new HashSet<Namespace>(Arrays.asList(
+	        		new Namespace(OfficeOpenXMLExtended.NAMESPACE_URI, OfficeOpenXMLExtended.PREFIX)
+	        		)));
+	
+	public RTFConverter() throws TikaException 
+	{
+		super();
+	}
+
+	@Override
+	public XMPMeta process(Metadata metadata) throws XMPException
+	{
+		setMetadata(metadata);
+		
+		createProperty(HttpHeaders.CONTENT_TYPE, XMPConst.NS_DC, "format");
+
+		createCommaSeparatedArray(TikaCoreProperties.AUTHOR, XMPConst.NS_DC, "creator", PropertyOptions.ARRAY_ORDERED);
+		createLangAltProperty(TikaCoreProperties.TITLE, XMPConst.NS_DC, "title");
+		createLangAltProperty(TikaCoreProperties.SUBJECT, XMPConst.NS_DC, "description");
+		createCommaSeparatedArray(TikaCoreProperties.KEYWORDS, XMPConst.NS_DC, "subject", PropertyOptions.ARRAY);
+		createProperty(OfficeOpenXMLCore.CATEGORY, XMPConst.NS_IPTCCORE, "intellectualGenre");
+		createProperty(OfficeOpenXMLExtended.TEMPLATE, OfficeOpenXMLExtended.NAMESPACE_URI, "Template");
+		createProperty(ClimateForcast.COMMENT, XMPConst.NS_PDFX, "Comments");
+		createProperty(OfficeOpenXMLExtended.COMPANY, OfficeOpenXMLExtended.NAMESPACE_URI, "Company");
+		createProperty(OfficeOpenXMLExtended.MANAGER, OfficeOpenXMLExtended.NAMESPACE_URI, "Manager");
+				
+		return getXMPMeta();
+	}
+
+	@Override
+	protected Set<Namespace> getAdditionalNamespaces() 
+	{
+		return ADDITIONAL_NAMESPACES;
+	}
+}
Index: tika-xmp/src/main/java/org/apache/tika/xmp/convert/GenericConverter.java
===================================================================
--- tika-xmp/src/main/java/org/apache/tika/xmp/convert/GenericConverter.java	(revision 0)
+++ tika-xmp/src/main/java/org/apache/tika/xmp/convert/GenericConverter.java	(revision 0)
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * IPTC Metadata Descriptions taken from the IPTC Photo Metadata (July 2010) 
+ * standard. These parts Copyright 2010 International Press Telecommunications 
+ * Council.
+ */
+package org.apache.tika.xmp.convert;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.tika.exception.TikaException;
+import org.apache.tika.metadata.DublinCore;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.Property;
+import org.apache.tika.metadata.XMPRights;
+import org.apache.tika.metadata.Property.PropertyType;
+
+import com.adobe.xmp.XMPException;
+import com.adobe.xmp.XMPMeta;
+import com.adobe.xmp.XMPMetaFactory;
+import com.adobe.xmp.XMPSchemaRegistry;
+import com.adobe.xmp.options.PropertyOptions;
+
+/**
+ * Trys to convert as much of the properties in the <code>Metadata</code> map
+ * to XMP namespaces. only those properties will be cnverted where the name contains 
+ * a prefix and this prefix correlates with a "known" prefix for a standard namespace.
+ * For example "dc:title" would be mapped to the "title" property in the DublinCore namespace.
+ */
+public class GenericConverter extends AbstractConverter 
+{
+	public GenericConverter() throws TikaException 
+	{
+		super();
+	}
+
+	@Override
+	public XMPMeta process(Metadata metadata) throws XMPException 
+	{
+		setMetadata(metadata);
+		XMPSchemaRegistry registry = XMPMetaFactory.getSchemaRegistry();
+		
+		String [] keys = metadata.names();
+		for( String key : keys)
+		{
+			String[] keyParts = key.split(Metadata.NAMESPACE_PREFIX_DELIMITER);
+			if (keyParts.length > 0 && keyParts.length <= 2)
+			{
+				String uri = registry.getNamespaceURI(keyParts[0]);
+				
+				if( uri != null )
+				{
+					// Tika properties where the type differs from the XMP specification
+					if( key.equals(DublinCore.TITLE.getName()) || 
+						key.equals(DublinCore.DESCRIPTION.getName()) || 
+						key.equals(XMPRights.USAGE_TERMS.getName()) )
+					{
+						createLangAltProperty(key, uri, keyParts[1]);
+					}
+					else if( key.equals(DublinCore.CREATOR.getName()) )
+					{
+						createArrayProperty(key, uri, keyParts[1], PropertyOptions.ARRAY_ORDERED);
+					}
+					else
+					{
+						PropertyType type = Property.getPropertyType(key);
+						if( type != null )
+						{
+							switch( type )
+							{
+								case SIMPLE:
+									createProperty(key, uri, keyParts[1]);
+									break;
+								case BAG:
+									createArrayProperty(key, uri, keyParts[1], PropertyOptions.ARRAY);
+									break;
+								case SEQ:
+									createArrayProperty(key, uri, keyParts[1], PropertyOptions.ARRAY_ORDERED);
+									break;
+								case ALT:
+									createArrayProperty(key, uri, keyParts[1], PropertyOptions.ARRAY_ALTERNATE);
+									break;
+								// TODO Add support for structs and lang-alts, but those types are currently not used in Tika
+							}
+						}
+					}
+				}
+			} // ignore keys that are not qualified
+		}
+				
+		return getXMPMeta();
+	}
+
+	@Override
+	public Set<Namespace> getAdditionalNamespaces() 
+	{
+		// no additional namespaces needed
+		return Collections.unmodifiableSet(new HashSet<Namespace>());
+	}
+}
Index: tika-xmp/src/main/java/org/apache/tika/xmp/convert/Namespace.java
===================================================================
--- tika-xmp/src/main/java/org/apache/tika/xmp/convert/Namespace.java	(revision 0)
+++ tika-xmp/src/main/java/org/apache/tika/xmp/convert/Namespace.java	(revision 0)
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * IPTC Metadata Descriptions taken from the IPTC Photo Metadata (July 2010) 
+ * standard. These parts Copyright 2010 International Press Telecommunications 
+ * Council.
+ */
+package org.apache.tika.xmp.convert;
+
+/**
+ * Utility class to hold namespace information.
+ */
+public class Namespace 
+{
+	public String uri;
+	public String prefix;
+
+	public Namespace(String uri, String prefix) 
+	{
+		this.uri = uri;
+		this.prefix = prefix;
+	}
+}
Index: tika-xmp/src/main/java/org/apache/tika/xmp/convert/MSOfficeXMLConverter.java
===================================================================
--- tika-xmp/src/main/java/org/apache/tika/xmp/convert/MSOfficeXMLConverter.java	(revision 0)
+++ tika-xmp/src/main/java/org/apache/tika/xmp/convert/MSOfficeXMLConverter.java	(revision 0)
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * IPTC Metadata Descriptions taken from the IPTC Photo Metadata (July 2010) 
+ * standard. These parts Copyright 2010 International Press Telecommunications 
+ * Council.
+ */
+package org.apache.tika.xmp.convert;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.tika.exception.TikaException;
+import org.apache.tika.metadata.HttpHeaders;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.Office;
+import org.apache.tika.metadata.OfficeOpenXMLCore;
+import org.apache.tika.metadata.OfficeOpenXMLExtended;
+import org.apache.tika.metadata.TikaCoreProperties;
+
+import com.adobe.xmp.XMPConst;
+import com.adobe.xmp.XMPException;
+import com.adobe.xmp.XMPMeta;
+import com.adobe.xmp.options.PropertyOptions;
+
+/**
+ * Tika to XMP mapping for the Office Open XML formats Word (.docx), Excel (.xlsx) and PowerPoint (.pptx).
+ */
+public class MSOfficeXMLConverter extends AbstractConverter 
+{
+	protected static final Set<Namespace> ADDITIONAL_NAMESPACES =
+	        Collections.unmodifiableSet(new HashSet<Namespace>(Arrays.asList(
+	        		new Namespace(OfficeOpenXMLCore.NAMESPACE_URI, OfficeOpenXMLCore.PREFIX),
+	        		new Namespace(OfficeOpenXMLExtended.NAMESPACE_URI, OfficeOpenXMLExtended.PREFIX)
+	        		)));
+	
+	public MSOfficeXMLConverter() throws TikaException 
+	{
+		super();
+	}
+
+	@Override
+	public XMPMeta process(Metadata metadata) throws XMPException 
+	{
+		super.setMetadata(metadata);
+		
+		createProperty(HttpHeaders.CONTENT_TYPE, XMPConst.NS_DC, "format");
+		
+		// Core Properties
+		createProperty(OfficeOpenXMLCore.CATEGORY, XMPConst.NS_IPTCCORE, "intellectualGenre");
+		createProperty(OfficeOpenXMLCore.CONTENT_STATUS, OfficeOpenXMLCore.NAMESPACE_URI, "contentStatus");
+		createProperty(TikaCoreProperties.CREATION_DATE, XMPConst.NS_XMP, "CreateDate");
+		createCommaSeparatedArray(TikaCoreProperties.CREATOR, XMPConst.NS_DC, "creator", PropertyOptions.ARRAY_ORDERED);
+		createProperty(TikaCoreProperties.DESCRIPTION, XMPConst.NS_PDFX, "Comments");
+		createProperty(TikaCoreProperties.IDENTIFIER, XMPConst.NS_DC, "identifier");
+		createCommaSeparatedArray(TikaCoreProperties.KEYWORDS, XMPConst.NS_DC, "subject", PropertyOptions.ARRAY); 
+		createLangAltProperty(TikaCoreProperties.SUBJECT, XMPConst.NS_DC, "description"); 
+		createProperty(TikaCoreProperties.LANGUAGE, XMPConst.NS_DC, "language");
+		createProperty(TikaCoreProperties.LAST_AUTHOR, OfficeOpenXMLCore.NAMESPACE_URI, "lastModifiedBy");
+		createProperty(TikaCoreProperties.PRINT_DATE, OfficeOpenXMLCore.NAMESPACE_URI, "lastPrinted");
+		createProperty(TikaCoreProperties.MODIFIED, XMPConst.NS_XMP, "ModifyDate");
+		createProperty(OfficeOpenXMLCore.REVISION, OfficeOpenXMLCore.NAMESPACE_URI, "revision");
+		createLangAltProperty(TikaCoreProperties.TITLE, XMPConst.NS_DC, "title");
+		createProperty(OfficeOpenXMLCore.VERSION, OfficeOpenXMLCore.NAMESPACE_URI, "version");
+		
+		// Extended Properties
+		
+		// Put both App name and version in xmp:CreatorTool 
+		String creatorTool = "";
+		String value = metadata.get(OfficeOpenXMLExtended.APPLICATION);
+		if (value != null  &&  value.length() > 0)
+		{
+			creatorTool = value;
+
+			value = metadata.get(OfficeOpenXMLExtended.APP_VERSION);
+			if (value != null  &&  value.length() > 0)
+			{
+				creatorTool += " " + value;
+			}
+		}
+		
+		if( ! creatorTool.isEmpty() ) 
+		{
+			meta.setProperty(XMPConst.NS_XMP, "CreatorTool", creatorTool);
+		}
+		
+		createProperty(Office.CHARACTER_COUNT, OfficeOpenXMLExtended.NAMESPACE_URI, "Characters");
+		createProperty(Office.CHARACTER_COUNT_WITH_SPACES, OfficeOpenXMLExtended.NAMESPACE_URI, "CharactersWithSpaces");
+		createProperty(TikaCoreProperties.PUBLISHER, OfficeOpenXMLExtended.NAMESPACE_URI, "Company");
+		createProperty(Office.LINE_COUNT, OfficeOpenXMLExtended.NAMESPACE_URI, "Lines");
+		createProperty(OfficeOpenXMLExtended.MANAGER, OfficeOpenXMLExtended.NAMESPACE_URI, "Manager");
+		createProperty(OfficeOpenXMLExtended.NOTES, OfficeOpenXMLExtended.NAMESPACE_URI, "Notes");
+		createProperty(Office.PAGE_COUNT, XMPConst.TYPE_PAGEDFILE, "NPages");
+		createProperty(Office.PARAGRAPH_COUNT, OfficeOpenXMLExtended.NAMESPACE_URI, "Paragraphs");
+		createProperty(OfficeOpenXMLExtended.PRESENTATION_FORMAT, OfficeOpenXMLExtended.NAMESPACE_URI, "PresentationFormat");
+		createProperty(Office.SLIDE_COUNT, OfficeOpenXMLExtended.NAMESPACE_URI, "Slides");
+		createProperty(OfficeOpenXMLExtended.TEMPLATE, OfficeOpenXMLExtended.NAMESPACE_URI, "Template");
+		createProperty(OfficeOpenXMLExtended.TOTAL_TIME, OfficeOpenXMLExtended.NAMESPACE_URI, "TotalTime");
+		createProperty(Office.WORD_COUNT, OfficeOpenXMLExtended.NAMESPACE_URI, "Words");
+		
+		return super.getXMPMeta();
+	}
+
+	@Override
+	protected Set<Namespace> getAdditionalNamespaces() 
+	{
+		return ADDITIONAL_NAMESPACES;
+	}
+
+}
Index: tika-xmp/src/main/java/org/apache/tika/xmp/XMPMetadata.java
===================================================================
--- tika-xmp/src/main/java/org/apache/tika/xmp/XMPMetadata.java	(revision 0)
+++ tika-xmp/src/main/java/org/apache/tika/xmp/XMPMetadata.java	(revision 0)
@@ -0,0 +1,753 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * IPTC Metadata Descriptions taken from the IPTC Photo Metadata (July 2010) 
+ * standard. These parts Copyright 2010 International Press Telecommunications 
+ * Council.
+ */
+package org.apache.tika.xmp;
+
+import java.io.IOException;
+import java.io.NotSerializableException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.tika.exception.TikaException;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.Property;
+import org.apache.tika.metadata.Property.PropertyType;
+import org.apache.tika.metadata.PropertyTypeException;
+import org.apache.tika.xmp.convert.TikaToXMP;
+
+import com.adobe.xmp.XMPDateTime;
+import com.adobe.xmp.XMPException;
+import com.adobe.xmp.XMPIterator;
+import com.adobe.xmp.XMPMeta;
+import com.adobe.xmp.XMPMetaFactory;
+import com.adobe.xmp.XMPSchemaRegistry;
+import com.adobe.xmp.XMPUtils;
+import com.adobe.xmp.options.IteratorOptions;
+import com.adobe.xmp.options.PropertyOptions;
+import com.adobe.xmp.options.SerializeOptions;
+import com.adobe.xmp.properties.XMPProperty;
+
+/**
+ * Provides a conversion of the Metadata map from Tika to the XMP data model
+ * by also providing the Metadata API for clients to ease transition.
+ * But clients can also work directly on the XMP data model, by getting the XMPMeta 
+ * reference from this class.
+ * Usually the instance would be initialized by providing the Metadata object that
+ * had been returned from Tika-core which populates the XMP data model with
+ * all properties that can be converted.
+ * 
+ * This class is not serializable!
+ */
+@SuppressWarnings("serial")
+public class XMPMetadata extends Metadata
+{
+	/** The XMP data */
+	private XMPMeta xmpData;
+	/** Use the XMP namespace registry implementation */
+	private static final XMPSchemaRegistry registry = XMPMetaFactory.getSchemaRegistry();
+
+	/**
+	 *  Initializes with an empty XMP packet 
+	 */
+    public XMPMetadata() 
+    {
+    	xmpData = XMPMetaFactory.create();
+    }
+    
+	/**
+     * @see org.apache.tika.xmp.XMPMetadata(org.apache.tika.metadata.Metadata, java.lang.String)
+	 * But the mimetype is retrieved from the metadata map.
+	 */
+    public XMPMetadata (Metadata meta) throws TikaException
+    {
+    	this.xmpData = TikaToXMP.convert(meta);
+    }
+    
+    /**
+     * Initializes the data by converting the Metadata information to XMP.
+     * If a mimetype is provided, a specific converter can be used, that converts all
+     * available metadata. If there is no mimetype provided or no specific converter 
+     * available a generic conversion is done which will convert only those properties 
+     * that are in known namespaces and are using the correct prefixes
+     * 
+     * @param meta the Metadata information from Tika-core
+     * @param mimetype mimetype information
+     * @throws In case an error occured during conversion
+     */
+    public XMPMetadata (Metadata meta, String mimetype) throws TikaException
+    {
+    	this.xmpData = TikaToXMP.convert(meta, mimetype);
+    }
+    
+    /**
+     * @see org.apache.tika.xmp.XMPMetadata#process(org.apache.tika.metadata.Metadata, java.lang.String)
+	 * But the mimetype is retrieved from the metadata map.
+	 */
+    public void process(Metadata meta) throws TikaException
+    {  
+    	this.xmpData = TikaToXMP.convert(meta);
+    }
+
+    /**
+     * Converts the Metadata information to XMP.
+     * If a mimetype is provided, a specific converter can be used, that converts all
+     * available metadata. If there is no mimetype provided or no specific converter 
+     * available a generic conversion is done which will convert only those properties 
+     * that are in known namespaces and are using the correct prefixes
+     * 
+     * @param meta the Metadata information from Tika-core
+     * @param mimetype mimetype information
+     * @throws In case an error occured during conversion
+     */
+    public void process(Metadata meta, String mimetype) throws TikaException
+    {  
+    	this.xmpData = TikaToXMP.convert(meta, mimetype);
+    }
+
+    
+    /**
+     * Provides direct access to the XMP data model, in case a client prefers to work directly on it
+     * instead of using the Metadata API
+     * @return the "internal" XMP data object
+     */
+    public XMPMeta getXMPData() { return xmpData; }
+
+    
+    // === Namespace Registry API === //
+    /**
+	 * 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
+	 */
+	public static String registerNamespace(String namespaceURI, String suggestedPrefix) throws XMPException
+	{
+		return registry.registerNamespace(namespaceURI, suggestedPrefix);
+	}
+
+	/**
+	 * Obtain the prefix for a registered namespace URI.
+	 * <p>
+	 * It is not an error if the namespace URI is not registered.
+	 *
+	 * @param namespaceURI
+	 *            The URI for the namespace. Must not be null or the empty
+	 *            string.
+	 * @return Returns the prefix registered for this namespace URI or null.
+	 */
+	public static String getNamespacePrefix(String namespaceURI)
+	{
+		return registry.getNamespacePrefix(namespaceURI);
+	}
+
+
+	/**
+	 * Obtain the URI for a registered namespace prefix.
+	 * <p>
+	 * It is not an error 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 or null.
+	 */
+	public static String getNamespaceURI(String namespacePrefix)
+	{
+		return registry.getNamespaceURI(namespacePrefix);
+	}
+
+
+	/**
+	 * @return Returns the registered prefix/namespace-pairs as map, where the keys are the
+	 *         namespaces and the values are the prefixes.
+	 */
+	@SuppressWarnings("unchecked")
+	public static Map<String, String> getNamespaces()
+	{
+		return registry.getNamespaces();
+	}
+
+
+	/**
+	 * @return Returns the registered namespace/prefix-pairs as map, where the keys are the
+	 *         prefixes and the values are the namespaces.
+	 */
+	@SuppressWarnings("unchecked")
+	public static Map<String, String> getPrefixes()
+	{
+		return registry.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.
+	 */
+	public static void deleteNamespace(String namespaceURI)
+	{
+		registry.deleteNamespace(namespaceURI);
+	}
+	
+	
+	// === Metadata API === //
+	/**
+	 * @see org.apache.tika.xmp.XMPMetadata#isMultiValued(java.lang.String)
+	 */
+	@Override
+	public boolean isMultiValued(Property property) 
+	{
+		return this.isMultiValued(property.getName());
+	}
+
+	/**
+	 * Checks if the named property is an array.
+	 * @see org.apache.tika.metadata.Metadata#isMultiValued(java.lang.String)
+	 */
+	@Override
+	public boolean isMultiValued(String name)
+	{
+		checkKey(name);
+
+		String[] keyParts = splitKey(name);
+			
+		String ns = registry.getNamespaceURI(keyParts[0]);
+		if( ns != null )
+		{
+			try 
+			{
+				XMPProperty prop = xmpData.getProperty(ns, keyParts[1]);
+				
+				return prop.getOptions().isArray();
+			} 
+			catch (XMPException e) 
+			{
+				// Ignore
+			}
+		}
+		
+		return false;
+	}
+
+	/**
+	 * For XMP it is not clear what that API should return, therefor not implemented
+	 */
+	@Override
+	public String[] names() 
+	{
+		throw new UnsupportedOperationException("Not implemented");
+	}
+
+	/**
+	 * Returns the value of a simple property or the first one of an array. 
+	 * The given name must contain a namespace prefix of a registered namespace.
+	 * 
+	 * @see org.apache.tika.metadata.Metadata#get(java.lang.String)
+	 */
+	@Override
+	public String get(String name) 
+	{
+		checkKey(name);
+
+		String value = null;
+		String[] keyParts = splitKey(name);
+		
+		String ns = registry.getNamespaceURI(keyParts[0]);
+		if( ns != null )
+		{
+			try 
+			{
+				XMPProperty prop = xmpData.getProperty(ns, keyParts[1]);
+				
+				if( prop != null && prop.getOptions().isSimple() )
+				{
+					value = prop.getValue();
+				}
+				else if( prop != null && prop.getOptions().isArray() )
+				{
+					prop = xmpData.getArrayItem(ns, keyParts[1], 1);
+					value = prop.getValue();
+				}
+				// in all other cases, null is returned
+			} 
+			catch (XMPException e) 
+			{
+				// Ignore
+			}
+		}
+		
+		return value;
+	}
+
+	/**
+	 * @see org.apache.tika.xmp.XMPMetadata#get(java.lang.String)
+	 */
+	@Override
+	public String get(Property property) {
+		return this.get(property.getName());
+	}
+
+	/**
+	 * @see org.apache.tika.xmp.XMPMetadata#get(java.lang.String)
+	 */
+	@Override
+	public Integer getInt(Property property) 
+	{
+		Integer result = null;
+		
+		try 
+		{
+			result = new Integer(XMPUtils.convertToInteger(this.get(property.getName())));
+		} 
+		catch (XMPException e) 
+		{
+			//Ignore
+		}
+		
+		return result;
+	}
+
+	/**
+	 * @see org.apache.tika.xmp.XMPMetadata#get(java.lang.String)
+	 */
+	@Override
+	public Date getDate(Property property) 
+	{
+		Date result = null;
+		
+		try 
+		{
+			XMPDateTime xmpDate = XMPUtils.convertToDate(this.get(property.getName()));
+			if (xmpDate != null)
+			{
+				Calendar cal = xmpDate.getCalendar();
+				// TODO Timezone is currently lost
+				// need another solution that preserves the timezone
+				result = cal.getTime();
+			}
+		} 
+		catch (XMPException e) 
+		{
+			//Ignore
+		}
+		
+		return result;
+	}
+
+	/**
+	 * @see org.apache.tika.xmp.XMPMetadata#getValues(java.lang.String)
+	 */
+	@Override
+	public String[] getValues(Property property) 
+	{
+		return this.getValues(property.getName());
+	}
+
+	/**
+	 * Returns the value of a simple property or all if the property is an array and the elements are of simple type. 
+	 * The given name must contain a namespace prefix of a registered namespace.
+	 * @see org.apache.tika.metadata.Metadata#getValues(java.lang.String)
+	 */
+	@Override
+	public String[] getValues(String name) {
+		checkKey(name);
+
+		String[] value = null;
+		String[] keyParts = splitKey(name);
+		
+		String ns = registry.getNamespaceURI(keyParts[0]);
+		if( ns != null )
+		{
+			try
+			{
+				XMPProperty prop = xmpData.getProperty(ns, keyParts[1]);
+				
+				if( prop != null && prop.getOptions().isSimple() )
+				{
+					value = new String[1];
+					value[0] = prop.getValue();
+				}
+				else if( prop != null && prop.getOptions().isArray() )
+				{
+					int size = xmpData.countArrayItems(ns, keyParts[1]);
+					value = new String[size];
+					boolean onlySimpleChildren = true;
+					
+					for( int i = 0 ; i < size && onlySimpleChildren ; i++)
+					{
+						prop = xmpData.getArrayItem(ns, keyParts[1], i+1);
+						if( prop.getOptions().isSimple() )
+						{
+							value[i] = prop.getValue();
+						}
+						else
+						{
+							onlySimpleChildren = false;
+						}
+					}
+					
+					if( ! onlySimpleChildren )
+					{
+						value = null;
+					}
+				}
+				// in all other cases, null is returned
+			} 
+			catch (XMPException e) 
+			{
+				// Ignore
+			}
+		}
+		
+		return value;
+	}
+
+
+
+	/**
+	 * As this API could only possibly work for simple properties in XMP,
+	 * it just calls the set method, which replaces any existing value
+	 * @see org.apache.tika.metadata.Metadata#add(java.lang.String, java.lang.String)
+	 */
+	@Override
+	public void add(String name, String value) 
+	{
+		set(name, value);
+	}
+
+	/**
+	 * Sets the given property. If the property already exists, it is overwritten. 
+	 * Only simple properties that use a registered prefix are stored in the XMP.
+	 *  
+	 * @see org.apache.tika.metadata.Metadata#set(java.lang.String, java.lang.String)
+	 */
+	@Override
+	public void set(String name, String value) 
+	{
+		checkKey(name);
+		
+		String[] keyParts = splitKey(name);
+		
+		String ns = registry.getNamespaceURI(keyParts[0]);
+		if( ns != null )
+		{
+			try 
+			{
+				xmpData.setProperty(ns, keyParts[1], value);
+			} 
+			catch (XMPException e) 
+			{
+				// Ignore
+			}
+		}
+	}
+
+	/**
+	 * @see org.apache.tika.xmp.XMPMetadata#set(java.lang.String, java.lang.String)
+	 */
+	@Override
+	public void set(Property property, String value) 
+	{
+		this.set(property.getName(), value);
+	}
+	
+	/**
+	 * @see org.apache.tika.xmp.XMPMetadata#set(java.lang.String, java.lang.String)
+	 */
+	@Override
+	public void set(Property property, int value) 
+	{
+		// Can reuse the checks from the base class implementation which will call 
+		// the set(String, String) method in the end 
+		super.set(property, value);
+	}
+	
+	/**
+	 * @see org.apache.tika.xmp.XMPMetadata#set(java.lang.String, java.lang.String)
+	 */
+	@Override
+	public void set(Property property, double value) 
+	{
+		super.set(property, value);
+	}
+	
+	/**
+	 * @see org.apache.tika.xmp.XMPMetadata#set(java.lang.String, java.lang.String)
+	 */
+	@Override
+	public void set(Property property, Date date) 
+	{
+		super.set(property, date);
+	}
+	
+	/**
+	 * Sets array properties. If the property already exists, it is overwritten. 
+	 * Only array properties that use a registered prefix are stored in the XMP.
+	 * @see org.apache.tika.metadata.Metadata#set(org.apache.tika.metadata.Property, java.lang.String[])
+	 */
+	@Override
+	public void set(Property property, String[] values) 
+	{
+		checkKey(property.getName());
+	
+		if(	! property.isMultiValuePermitted() ) 
+		{
+            throw new PropertyTypeException("Property is not of an array type");
+        }
+		
+		String[] keyParts = splitKey(property.getName());
+		
+		String ns = registry.getNamespaceURI(keyParts[0]);
+		if( ns != null )
+		{
+			try 
+			{
+				int arrayType = tikaToXMPArrayType(property.getPrimaryProperty().getPropertyType());
+				xmpData.setProperty(ns, keyParts[1], null, new PropertyOptions(arrayType));
+				
+				for( String value : values )
+				{
+					xmpData.appendArrayItem(ns, keyParts[1], value);
+				}
+			} 
+			catch (XMPException e) 
+			{
+				// Ignore
+			}
+		}
+	}
+	
+	/**
+	 * It will set all simple and array properties that have QName keys in registered namespaces.
+	 * @see org.apache.tika.metadata.Metadata#setAll(java.util.Properties)
+	 */
+	@Override
+	public void setAll(Properties properties) 
+	{
+		@SuppressWarnings("unchecked")
+		Enumeration<String> names = (Enumeration<String>) properties.propertyNames();
+        
+		while (names.hasMoreElements()) 
+		{
+            String name = names.nextElement();
+            Property property = Property.get(name);
+            if( property == null )
+            {
+            	throw new PropertyTypeException("Unknown property: " + name);
+            }
+            
+            String value = properties.getProperty(name);
+            
+            if( property.isMultiValuePermitted() )
+            {
+            	this.set(property,  new String[] { value });
+            }
+            else
+            {
+            	this.set(property, value);
+            }
+        }
+	}
+
+
+	/**
+	 * @see org.apache.tika.xmp.XMPMetadata#remove(java.lang.String)
+	 */
+	public void remove(Property property) 
+	{
+		this.remove(property.getName());
+	}
+	
+	/**
+	 * Removes the given property from the XMP data. 
+	 * If it is a complex property the whole subtree is removed
+	 * @see org.apache.tika.metadata.Metadata#remove(java.lang.String)
+	 */
+	@Override
+	public void remove(String name) 
+	{
+		checkKey(name);
+		
+		String[] keyParts = splitKey(name);
+			
+		String ns = registry.getNamespaceURI(keyParts[0]);
+		if( ns != null )
+		{
+			xmpData.deleteProperty(ns, keyParts[1]);
+		}
+	}
+	
+	/**
+	 * Returns the number of top-level namespaces
+	 */
+	@Override
+	public int size() 
+	{
+		int size = 0;
+
+		try 
+		{
+			// Get an iterator for the XMP packet, starting at the top level schema nodes
+			XMPIterator nsIter = xmpData.iterator( new IteratorOptions().setJustChildren(true).setOmitQualifiers(true) );
+			// iterate all top level namespaces
+			while (nsIter.hasNext())
+			{
+				nsIter.next();
+				size++;
+			}
+		} 
+		catch (XMPException e) 
+		{
+			// ignore
+		}
+		
+		return size;
+	}
+
+	/**
+	 * This method is not implemented, yet. It is very tedious to check for semantic equality of XMP packets
+	 */
+	@Override
+	public boolean equals(Object o) 
+	{
+		throw new UnsupportedOperationException("Not implemented");
+	}
+
+	/**
+	 * Serializes the XMP data in compact form without packet wrapper
+	 * @see org.apache.tika.metadata.Metadata#toString()
+	 */
+	@Override
+	public String toString() 
+	{
+		String result = null;
+		try 
+		{
+			result = XMPMetaFactory.serializeToString(xmpData, new SerializeOptions().setOmitPacketWrapper(true).setUseCompactFormat(true));
+		} 
+		catch (XMPException e) 
+		{
+			// ignore
+		}
+		return result;
+	}
+
+	// The XMP object is not serializable!
+	private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException 
+	{
+		throw new NotSerializableException();
+	}
+
+	// The XMP object is not serializable!
+	private void writeObject(ObjectOutputStream ois) throws IOException 
+	{
+		throw new NotSerializableException();
+	}
+
+	/**
+	 * Checks if the given key is a valid QName with a known standard namespace prefix
+	 * 
+	 * @param key
+	 *            the key to check
+	 * @return true if the key is valid otherwise false
+	 */
+	private void checkKey(String key) throws PropertyTypeException
+	{
+		if( key == null || key.length() == 0 )
+		{
+			throw new PropertyTypeException("Key must not be null");
+		}
+		
+		String[] keyParts = splitKey(key);
+		if( keyParts == null )
+		{
+			throw new PropertyTypeException("Key must be a QName in the form prefix:localName");
+		}
+
+		if( registry.getNamespaceURI(keyParts[0]) == null )
+		{
+			throw new PropertyTypeException("Key does not use a registered Namespace prefix");
+		}
+	}
+	
+	/**
+	 * Split the given key at the namespace prefix delimiter
+	 * @param key the key to split
+	 * @return prefix and local name of the property or null if the key did not
+	 * contain a delimiter or too much of them
+	 */
+	private String[] splitKey(String key)
+	{
+		String[] keyParts = key.split(Metadata.NAMESPACE_PREFIX_DELIMITER);
+		if (keyParts.length > 0 && keyParts.length <= 2)
+		{
+			return keyParts;
+		}
+		
+		return null;
+	}// checkKeyPrefix
+
+	/**
+	 * Convert Tika array types to XMP array types
+	 * @param type
+	 * @return
+	 */
+	private int tikaToXMPArrayType(PropertyType type)
+	{
+		int result = 0;
+		switch(type)
+		{
+			case BAG:
+				result = PropertyOptions.ARRAY;
+				break;
+			case SEQ:
+				result = PropertyOptions.ARRAY_ORDERED;
+				break;
+			case ALT:
+				result = PropertyOptions.ARRAY_ALTERNATE;
+				break;
+		}
+		return result;
+	}
+}
Index: tika-xmp/pom.xml
===================================================================
--- tika-xmp/pom.xml	(revision 0)
+++ tika-xmp/pom.xml	(revision 0)
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	
+	<parent>
+		<groupId>org.apache.tika</groupId>
+		<artifactId>tika-parent</artifactId>
+		<version>1.2-SNAPSHOT</version>
+		<relativePath>../tika-parent/pom.xml</relativePath>
+	</parent>
+	  
+	<artifactId>tika-xmp</artifactId>
+	<packaging>bundle</packaging>
+	  
+	<name>Tika to XMP converter</name>
+	<description>Converts Tika metadata to XMP</description>
+  
+	<build>
+	  <plugins>
+			<plugin>
+				<groupId>org.apache.felix</groupId>
+				<artifactId>maven-scr-plugin</artifactId>
+				<version>1.7.4</version>
+			</plugin>
+		  
+			<plugin><!-- builds the bundle -->
+				<groupId>org.apache.felix</groupId>
+				<artifactId>maven-bundle-plugin</artifactId>
+				<extensions>true</extensions>
+				<configuration>
+					<instructions>
+						<Export-Package>
+							org.apache.tika.xmp,
+							org.apache.tika.xmp.convert
+						</Export-Package>
+						<Private-Package/>
+					</instructions> 
+				</configuration>
+			</plugin>
+			
+		</plugins>
+    </build>
+	
+    <dependencies>
+		<dependency>
+			<groupId>${project.groupId}</groupId>
+            <artifactId>tika-core</artifactId>
+            <version>${project.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>${project.groupId}</groupId>
+            <artifactId>tika-parsers</artifactId>
+            <version>${project.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>com.adobe.xmp</groupId>
+			<artifactId>xmpcore</artifactId>
+			<version>5.1.1</version>
+		</dependency>
+		
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<scope>test</scope>
+		</dependency>
+    </dependencies>
+</project>
\ No newline at end of file
Index: tika-app/src/main/java/org/apache/tika/cli/TikaCLI.java
===================================================================
--- tika-app/src/main/java/org/apache/tika/cli/TikaCLI.java	(revision 1356185)
+++ tika-app/src/main/java/org/apache/tika/cli/TikaCLI.java	(working copy)
@@ -16,7 +16,16 @@
  */
 package org.apache.tika.cli;
 
-import java.io.*;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
 import java.lang.reflect.Field;
 import java.net.ServerSocket;
 import java.net.Socket;
@@ -53,6 +62,7 @@
 import org.apache.tika.detect.CompositeDetector;
 import org.apache.tika.detect.DefaultDetector;
 import org.apache.tika.detect.Detector;
+import org.apache.tika.exception.TikaException;
 import org.apache.tika.extractor.EmbeddedDocumentExtractor;
 import org.apache.tika.fork.ForkParser;
 import org.apache.tika.gui.TikaGUI;
@@ -75,6 +85,7 @@
 import org.apache.tika.parser.html.BoilerpipeContentHandler;
 import org.apache.tika.sax.BodyContentHandler;
 import org.apache.tika.sax.XMPContentHandler;
+import org.apache.tika.xmp.XMPMetadata;
 import org.xml.sax.ContentHandler;
 import org.xml.sax.SAXException;
 import org.xml.sax.helpers.DefaultHandler;
@@ -207,18 +218,11 @@
     private final OutputType XMP = new OutputType() {
         @Override
         protected ContentHandler getContentHandler(
-                OutputStream output, final Metadata metadata) throws Exception {
-            final ContentHandler handler =
-                    getTransformerHandler(output, "xml", encoding, prettyPrint);
-            return new DefaultHandler() {
-                @Override
-                public void endDocument() throws SAXException {
-                    XMPContentHandler xmp = new XMPContentHandler(handler);
-                    xmp.startDocument();
-                    xmp.metadata(metadata);
-                    xmp.endDocument();
-                }
-            };
+                OutputStream output, final Metadata metadata) throws Exception 
+        {
+        	final PrintWriter writer = new PrintWriter(getOutputWriter(output, encoding));
+        	
+        	return new NoDocumentXMPMetaHandler(metadata, writer);
         }
     };
 
@@ -833,6 +837,38 @@
     }
 
     /**
+     * Outputs the Tika metadata as XMP using the Tika XMP module
+     */
+    private class NoDocumentXMPMetaHandler extends DefaultHandler
+    {
+    	protected final Metadata metadata;
+    	
+        protected PrintWriter writer;
+        
+        public NoDocumentXMPMetaHandler(Metadata metadata, PrintWriter writer){
+        	this.metadata = metadata;
+            this.writer = writer;
+        }
+        
+        @Override
+        public void endDocument() throws SAXException 
+        {
+        	try 
+        	{
+        		XMPMetadata xmp = new XMPMetadata(metadata);
+        		String result;
+        		result = xmp.toString();
+        		writer.write(result);
+        		writer.flush();
+        	} 
+        	catch (TikaException e) 
+        	{
+        		throw new SAXException(e);
+        	}
+        }
+    }
+    
+    /**
      * Uses GSON to do the JSON escaping, but does
      *  the general JSON glueing ourselves.
      */
Index: tika-app/pom.xml
===================================================================
--- tika-app/pom.xml	(revision 1356185)
+++ tika-app/pom.xml	(working copy)
@@ -44,6 +44,11 @@
       <version>${project.version}</version>
     </dependency>
     <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>tika-xmp</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-log4j12</artifactId>
       <version>1.5.6</version>
Index: tika-core/src/main/java/org/apache/tika/metadata/PropertyTypeException.java
===================================================================
--- tika-core/src/main/java/org/apache/tika/metadata/PropertyTypeException.java	(revision 1356185)
+++ tika-core/src/main/java/org/apache/tika/metadata/PropertyTypeException.java	(working copy)
@@ -28,6 +28,9 @@
  * @since Apache Tika 0.8
  */
 public final class PropertyTypeException extends IllegalArgumentException {
+	public PropertyTypeException(String msg) {
+		super(msg);
+	}
     public PropertyTypeException(PropertyType expected, PropertyType found) {
         super("Expected a property of type " + expected + ", but received " + found);
     }
Index: tika-core/src/main/java/org/apache/tika/metadata/Property.java
===================================================================
--- tika-core/src/main/java/org/apache/tika/metadata/Property.java	(revision 1356185)
+++ tika-core/src/main/java/org/apache/tika/metadata/Property.java	(working copy)
@@ -151,6 +151,32 @@
         return false;
     }
 
+    /**
+     * Get the type of a property
+     * @param key name of the property
+     * @return the type of the property
+     */
+    public static PropertyType getPropertyType(String key)
+    {
+    	PropertyType type = null;
+    	Property prop = properties.get(key);
+    	if( prop != null )
+    	{
+    		type = prop.getPropertyType();
+    	}
+    	return type;
+    }
+    
+    /**
+     * Retrieve the property object that corresponds to the given key
+     * @param key the property key or name
+     * @return the Property object
+     */
+    public static Property get(String key)
+    {
+    	return properties.get(key);
+    }
+    
     public PropertyType getPropertyType() {
         return propertyType;
     }
