content: // =================================================================================================
  // 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.internal.xmp;
  
  import java.security.NoSuchAlgorithmException;
  import com.adobe.internal.xmp.impl.Base64;
  import com.adobe.internal.xmp.impl.ISO8601Converter;
  import com.adobe.internal.xmp.impl.XMPUtilsImpl;
  import com.adobe.internal.xmp.options.PropertyOptions;
  import com.adobe.internal.xmp.options.TemplateOptions;
  
  
  /**
   * Utility methods for XMP. I included only those that are different from the
   * Java default conversion utilities.
   *
   * @author Stefan Makswit
   * @version $Revision$
   * @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 &#39;&quot;&#39;
  	 * @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);
  		}
  	}
  	
  	/**
  	 * creates XMP serializations appropriate for a JPEG file.
  	 *
  	 * The standard XMP in a JPEG file is limited to 64K bytes. This function
  	 * serializes the XMP metadata in an XMP object into a string of RDF . If
  	 * the data does not fit into the 64K byte limit, it creates a second packet
  	 * string with the extended data.
  	 *
  	 * @param origXMP
  	 *            The XMP object containing the metadata.
  	 *
  	 * @param stdStr
  	 *            A string builder object in which to return the full standard XMP
  	 *            packet.
  	 *
  	 * @param extStr
  	 *            A string builder object in which to return the serialized extended
  	 *            XMP, empty if not needed.
  	 *
  	 * @param digestStr
  	 *            A string builder object in which to return an MD5 digest of the
  	 *            serialized extended XMP, empty if not needed.
  	 * 
  	 * @throws NoSuchAlgorithmException if fail to find algorithm for MD5
  	 * @throws XMPException in case of internal error occurs.
  	 *
  	 */
  	
  	public static void packageForJPEG(XMPMeta origXMP,
  			StringBuilder stdStr,
  			StringBuilder extStr,
  			StringBuilder digestStr) throws NoSuchAlgorithmException, XMPException{
  		XMPUtilsImpl.packageForJPEG(origXMP,stdStr,extStr,digestStr);
  	}
  	
  	/**
  	 * merges standard and extended XMP retrieved from a JPEG file.
  	 *
  	 * When an extended partition stores properties that do not fit into the
  	 * JPEG file limitation of 64K bytes, this function integrates those
  	 * properties back into the same XMP object with those from the standard XMP
  	 * packet.
  	 *
  	 * @param fullXMP
  	 *            An XMP object which the caller has initialized from the
  	 *            standard XMP packet in a JPEG file. The extended XMP is added
  	 *            to this object.
  	 *
  	 * @param extendedXMP
  	 *            An XMP object which the caller has initialized from the
  	 *            extended XMP packet in a JPEG file.
  	 *
  	 * @throws XMPException
  	 *             in case of internal error occurs.
  	 */
  	
  	public static void mergeFromJPEG(XMPMeta fullXMP,
  			XMPMeta extendedXMP) throws XMPException{
  		XMPUtilsImpl.mergeFromJPEG(fullXMP,extendedXMP);
  	}
  	
  	/**
  	 * modifies a working XMP object according to a template object.
  	 *<p>
  	 * The XMP template can be used to add, replace or delete properties from
  	 * the working XMP object. The actions that you specify determine how the
  	 * template is applied. Each action can be applied individually or combined;
  	 * if you do not specify any actions, the properties and values in the
  	 * working XMP object do not change.
  	 *<p>
  	 * These actions are available:
  	 * <ul>
  	 * <li>Clear <code> CLEAR_UNNAMED_PROPERTIES </code> : Deletes top-level
  	 * properties. Any top-level property that is present in the template (even
  	 * with empty value) is retained. All other top-level properties in the
  	 * working object are deleted
  	 *
  	 * <li>Add <code> ADD_NEW_PROPERTIES </code>: Adds new properties to the
  	 * working object if the template properties have values. See additional
  	 * detail below.
  	 *
  	 * <li>Replace <code> REPLACE_EXISTING_PROPERTIES </code>: Replaces the
  	 * values of existing top-level properties in the working XMP if the value
  	 * forms match those in the template. Properties with empty values in the
  	 * template are ignored. If combined with Clear or Add actions, those take
  	 * precedence; values are cleared or added, rather than replaced.
  	 *
  	 * <li>Replace/Delete empty <code> REPLACE_WITH_DELETE_EMPTY </code>:
  	 * Replaces values in the same way as the simple Replace action, and also
  	 * deletes properties if the value in the template is empty. If combined
  	 * with Clear or Add actions, those take precedence; values are cleared or
  	 * added, rather than replaced.
  	 *
  	 * <li>Include internal <code> INCLUDE_INTERNAL_PROPERTIES </code>: Performs
  	 * specified action on internal properties as well as external properties.
  	 * By default, internal properties are ignored for all actions.
  	 * </ul><p>
  	 *
  	 * The Add behavior depends on the type of property:
  	 * <ul>
  	 * <li>If a top-level property is not in the working XMP, and has a value in
  	 * the template, the property and value are added. Empty properties are not
  	 * added.
  	 * <li>If a property is in both the working XMP and template, the value
  	 * forms must match, otherwise the template is ignored for that property.
  	 * <li>If a struct is present in both the working XMP and template, the
  	 * individual fields of the template struct are added as appropriate; that
  	 * is, the logic is recursively applied to the fields. Struct values are
  	 * equivalent if they have the same fields with equivalent values.
  	 * <li>If an array is present in both the working XMP and template, items
  	 * from the template are added if the value forms match. Array values match
  	 * if they have sets of equivalent items, regardless of order.
  	 * <li>Alt-text arrays use the \c xml:lang qualifier as a key, adding
  	 * languages that are missing.
  	 * </ul><p>
  	 * Array item checking is n-squared; this can be time-intensive if the
  	 * Replace option is not specified. Each source item is checked to see if it
  	 * already exists in the destination, without regard to order or duplicates.
  	 * Simple items are compared by value and <code> xml:lang </code> 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 workingXMP
  	 *            The destination XMP object.
  	 *
  	 * @param templateXMP
  	 *            The template to apply to the destination XMP object.
  	 *
  	 * @param options
  	 *            Option flags to control the copying. If none are specified,
  	 *            the properties and values in the working XMP do not change. A
  	 *            logical OR of these bit-flag constants:
  	 *            <ul>
  	 *            <li><code> CLEAR_UNNAMED_PROPERTIES </code> Delete anything
  	 *            that is not in the template
  	 *            <li><code> ADD_NEW_PROPERTIES </code> Add properties; see
  	 *            detailed description.
  	 *            <li><code> REPLACE_EXISTING_PROPERTIES </code> Replace the
  	 *            values of existing properties.
  	 *            <li><code> REPLACE_WITH_DELETE_EMPTY </code> Replace the
  	 *            values of existing properties and delete properties if the new
  	 *            value is empty.
  	 *            <li><code> INCLUDE_INTERNAL_PROPERTIES </code> Operate on
  	 *            internal properties as well as external properties.
  	 *            </ul>
  	 *
  	 * @throws XMPException
  	 *             in case of internal error occurs.
  	 */
  	
  	static public void applyTemplate ( XMPMeta workingXMP,
  			  XMPMeta templateXMP, TemplateOptions options)
  					  throws XMPException{
  		XMPUtilsImpl.applyTemplate(workingXMP,templateXMP,options);
  	}
  		
  
  	/**
  	 * 
  	 * Replicate a subtree from one XMP object into another, possibly at a
  	 * different location.
  	 *
  	 *
  	 * @param source
  	 *            The source XMP object.
  	 *
  	 * @param dest
  	 *            The destination XMP object.
  	 *
  	 * @param sourceNS
  	 *            The schema namespace URI for the source subtree.
  	 *
  	 * @param sourceRoot
  	 *            The root location for the source subtree. May be a general
  	 *            path expression, must not be null or the empty string.
  	 *
  	 * @param destNS
  	 *            The schema namespace URI for the destination. Defaults to the
  	 *            source namespace.
  	 *
  	 * @param destRoot
  	 *            The root location for the destination. May be a general path
  	 *            expression. Defaults to the source location.
  	 *
  	 * @param options
  	 *            Option flags to control the separation. (For now, this argument is ignored. 
  	 *            0 should be passed.
  	 * @throws XMPException throws an XMPException
  	 */
  	public static void duplicateSubtree(XMPMeta source, XMPMeta dest, String sourceNS,
  			String sourceRoot, String destNS, String destRoot, PropertyOptions options) throws XMPException
  	{
  		XMPUtilsImpl.duplicateSubtree(source, dest, sourceNS, sourceRoot, destNS, destRoot, options);
  	}
  
  }