Home | History | Annotate | Download | only in xmp
      1 // =================================================================================================
      2 // ADOBE SYSTEMS INCORPORATED
      3 // Copyright 2006 Adobe Systems Incorporated
      4 // All Rights Reserved
      5 //
      6 // NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
      7 // of the Adobe license agreement accompanying it.
      8 // =================================================================================================
      9 
     10 package com.adobe.xmp;
     11 
     12 import com.adobe.xmp.impl.Base64;
     13 import com.adobe.xmp.impl.ISO8601Converter;
     14 import com.adobe.xmp.impl.XMPUtilsImpl;
     15 import com.adobe.xmp.options.PropertyOptions;
     16 
     17 
     18 /**
     19  * Utility methods for XMP. I included only those that are different from the
     20  * Java default conversion utilities.
     21  *
     22  * @since 21.02.2006
     23  */
     24 public class XMPUtils
     25 {
     26 	/** Private constructor */
     27 	private XMPUtils()
     28 	{
     29 		// EMPTY
     30 	}
     31 
     32 
     33 	/**
     34 	 * Create a single edit string from an array of strings.
     35 	 *
     36 	 * @param xmp
     37 	 *            The XMP object containing the array to be catenated.
     38 	 * @param schemaNS
     39 	 *            The schema namespace URI for the array. Must not be null or
     40 	 *            the empty string.
     41 	 * @param arrayName
     42 	 *            The name of the array. May be a general path expression, must
     43 	 *            not be null or the empty string. Each item in the array must
     44 	 *            be a simple string value.
     45 	 * @param separator
     46 	 *            The string to be used to separate the items in the catenated
     47 	 *            string. Defaults to "; ", ASCII semicolon and space
     48 	 *            (U+003B, U+0020).
     49 	 * @param quotes
     50 	 *            The characters to be used as quotes around array items that
     51 	 *            contain a separator. Defaults to '"'
     52 	 * @param allowCommas
     53 	 *            Option flag to control the catenation.
     54 	 * @return Returns the string containing the catenated array items.
     55 	 * @throws XMPException Forwards the Exceptions from the metadata processing
     56 	 */
     57 	public static String catenateArrayItems(XMPMeta xmp, String schemaNS, String arrayName,
     58 			String separator, String quotes, boolean allowCommas) throws XMPException
     59 	{
     60 		return XMPUtilsImpl
     61 				.catenateArrayItems(xmp, schemaNS, arrayName, separator, quotes, allowCommas);
     62 	}
     63 
     64 
     65 	/**
     66 	 * Separate a single edit string into an array of strings.
     67 	 *
     68 	 * @param xmp
     69 	 *            The XMP object containing the array to be updated.
     70 	 * @param schemaNS
     71 	 *            The schema namespace URI for the array. Must not be null or
     72 	 *            the empty string.
     73 	 * @param arrayName
     74 	 *            The name of the array. May be a general path expression, must
     75 	 *            not be null or the empty string. Each item in the array must
     76 	 *            be a simple string value.
     77 	 * @param catedStr
     78 	 *            The string to be separated into the array items.
     79 	 * @param arrayOptions Option flags to control the separation.
     80 	 * @param preserveCommas Flag if commas shall be preserved
     81 	 * @throws XMPException Forwards the Exceptions from the metadata processing
     82 	 */
     83 	public static void separateArrayItems(XMPMeta xmp, String schemaNS, String arrayName,
     84 			String catedStr, PropertyOptions arrayOptions, boolean preserveCommas)
     85 				throws XMPException
     86 	{
     87 		XMPUtilsImpl.separateArrayItems(xmp, schemaNS, arrayName, catedStr, arrayOptions,
     88 				preserveCommas);
     89 	}
     90 
     91 
     92 	/**
     93 	 * Remove multiple properties from an XMP object.
     94 	 *
     95 	 * RemoveProperties was created to support the File Info dialog's Delete
     96 	 * button, and has been been generalized somewhat from those specific needs.
     97 	 * It operates in one of three main modes depending on the schemaNS and
     98 	 * propName parameters:
     99 	 *
    100 	 * <ul>
    101 	 * <li> Non-empty <code>schemaNS</code> and <code>propName</code> - The named property is
    102 	 * removed if it is an external property, or if the
    103 	 * flag <code>doAllProperties</code> option is true. It does not matter whether the
    104 	 * named property is an actual property or an alias.
    105 	 *
    106 	 * <li> Non-empty <code>schemaNS</code> and empty <code>propName</code> - The all external
    107 	 * properties in the named schema are removed. Internal properties are also
    108 	 * removed if the flag <code>doAllProperties</code> option is set. In addition,
    109 	 * aliases from the named schema will be removed if the flag <code>includeAliases</code>
    110 	 * option is set.
    111 	 *
    112 	 * <li> Empty <code>schemaNS</code> and empty <code>propName</code> - All external properties in
    113 	 * all schema are removed. Internal properties are also removed if the
    114 	 * flag <code>doAllProperties</code> option is passed. Aliases are implicitly handled
    115 	 * because the associated actuals are internal if the alias is.
    116 	 * </ul>
    117 	 *
    118 	 * It is an error to pass an empty <code>schemaNS</code> and non-empty <code>propName</code>.
    119 	 *
    120 	 * @param xmp
    121 	 *            The XMP object containing the properties to be removed.
    122 	 *
    123 	 * @param schemaNS
    124 	 *            Optional schema namespace URI for the properties to be
    125 	 *            removed.
    126 	 *
    127 	 * @param propName
    128 	 *            Optional path expression for the property to be removed.
    129 	 *
    130 	 * @param doAllProperties Option flag to control the deletion: do internal properties in
    131 	 *          addition to external properties.
    132 	 *
    133 	 * @param includeAliases Option flag to control the deletion:
    134 	 * 			Include aliases in the "named schema" case above.
    135 	 * 			<em>Note:</em> Currently not supported.
    136 	 * @throws XMPException Forwards the Exceptions from the metadata processing
    137 	 */
    138 	public static void removeProperties(XMPMeta xmp, String schemaNS, String propName,
    139 			boolean doAllProperties, boolean includeAliases) throws XMPException
    140 	{
    141 		XMPUtilsImpl.removeProperties(xmp, schemaNS, propName, doAllProperties, includeAliases);
    142 	}
    143 
    144 
    145 
    146 	/**
    147 	 * Alias without the new option <code>deleteEmptyValues</code>.
    148 	 * @param source The source XMP object.
    149 	 * @param dest The destination XMP object.
    150 	 * @param doAllProperties Do internal properties in addition to external properties.
    151 	 * @param replaceOldValues Replace the values of existing properties.
    152 	 * @throws XMPException Forwards the Exceptions from the metadata processing
    153 	 */
    154 	public static void appendProperties(XMPMeta source, XMPMeta dest, boolean doAllProperties,
    155 			boolean replaceOldValues) throws XMPException
    156 	{
    157 		appendProperties(source, dest, doAllProperties, replaceOldValues, false);
    158 	}
    159 
    160 
    161 	/**
    162 	 * <p>Append properties from one XMP object to another.
    163 	 *
    164 	 * <p>XMPUtils#appendProperties was created to support the File Info dialog's Append button, and
    165 	 * has been been generalized somewhat from those specific needs. It appends information from one
    166 	 * XMP object (source) to another (dest). The default operation is to append only external
    167 	 * properties that do not already exist in the destination. The flag
    168 	 * <code>doAllProperties</code> can be used to operate on all properties, external and internal.
    169 	 * The flag <code>replaceOldValues</code> option can be used to replace the values
    170 	 * of existing properties. The notion of external
    171 	 * versus internal applies only to top level properties. The keep-or-replace-old notion applies
    172 	 * within structs and arrays as described below.
    173 	 * <ul>
    174 	 * <li>If <code>replaceOldValues</code> is true then the processing is restricted to the top
    175 	 * level properties. The processed properties from the source (according to
    176 	 * <code>doAllProperties</code>) are propagated to the destination,
    177 	 * replacing any existing values.Properties in the destination that are not in the source
    178 	 * are left alone.
    179 	 *
    180 	 * <li>If <code>replaceOldValues</code> is not passed then the processing is more complicated.
    181 	 * Top level properties are added to the destination if they do not already exist.
    182 	 * If they do exist but differ in form (simple/struct/array) then the destination is left alone.
    183 	 * If the forms match, simple properties are left unchanged while structs and arrays are merged.
    184 	 *
    185 	 * <li>If <code>deleteEmptyValues</code> is passed then an empty value in the source XMP causes
    186 	 * the corresponding destination XMP property to be deleted. The default is to treat empty
    187 	 * values the same as non-empty values. An empty value is any of a simple empty string, an array
    188 	 * with no items, or a struct with no fields. Qualifiers are ignored.
    189 	 * </ul>
    190 	 *
    191 	 * <p>The detailed behavior is defined by the following pseudo-code:
    192 	 * <blockquote>
    193 	 * <pre>
    194      *    appendProperties ( sourceXMP, destXMP, doAllProperties,
    195      *    			replaceOldValues, deleteEmptyValues ):
    196      *       for all source schema (top level namespaces):
    197      *          for all top level properties in sourceSchema:
    198      *             if doAllProperties or prop is external:
    199      *                appendSubtree ( sourceNode, destSchema, replaceOldValues, deleteEmptyValues )
    200      *
    201      *    appendSubtree ( sourceNode, destParent, replaceOldValues, deleteEmptyValues ):
    202      *        if deleteEmptyValues and source value is empty:
    203      *            delete the corresponding child from destParent
    204      *        else if sourceNode not in destParent (by name):
    205      *           copy sourceNode's subtree to destParent
    206      *        else if replaceOld:
    207      *            delete subtree from destParent
    208      *            copy sourceNode's subtree to destParent
    209      *        else:
    210      *            // Already exists in dest and not replacing, merge structs and arrays
    211      *            if sourceNode and destNode forms differ:
    212      *                return, leave the destNode alone
    213      *            else if form is a struct:
    214      *                for each field in sourceNode:
    215      *                    AppendSubtree ( sourceNode.field, destNode, replaceOldValues )
    216      *            else if form is an alt-text array:
    217      *                copy new items by "xml:lang" value into the destination
    218      *            else if form is an array:
    219      *                copy new items by value into the destination, ignoring order and duplicates
    220      * </pre>
    221 	 * </blockquote>
    222 	 *
    223 	 * <p><em>Note:</em> appendProperties can be expensive if replaceOldValues is not passed and
    224 	 * the XMP contains large arrays. The array item checking described above is n-squared.
    225 	 * Each source item is checked to see if it already exists in the destination,
    226 	 * without regard to order or duplicates.
    227 	 * <p>Simple items are compared by value and "xml:lang" qualifier, other qualifiers are ignored.
    228 	 * Structs are recursively compared by field names, without regard to field order. Arrays are
    229 	 * compared by recursively comparing all items.
    230 	 *
    231 	 * @param source The source XMP object.
    232 	 * @param dest The destination XMP object.
    233 	 * @param doAllProperties Do internal properties in addition to external properties.
    234 	 * @param replaceOldValues Replace the values of existing properties.
    235 	 * @param deleteEmptyValues Delete destination values if source property is empty.
    236 	 * @throws XMPException Forwards the Exceptions from the metadata processing
    237 	 */
    238 	public static void appendProperties(XMPMeta source, XMPMeta dest, boolean doAllProperties,
    239 			boolean replaceOldValues, boolean deleteEmptyValues) throws XMPException
    240 	{
    241 		XMPUtilsImpl.appendProperties(source, dest, doAllProperties, replaceOldValues,
    242 			deleteEmptyValues);
    243 	}
    244 
    245 
    246 	/**
    247 	 * Convert from string to Boolean.
    248 	 *
    249 	 * @param value
    250 	 *            The string representation of the Boolean.
    251 	 * @return The appropriate boolean value for the string. The checked values
    252 	 *         for <code>true</code> and <code>false</code> are:
    253 	 *         <ul>
    254 	 *    	    	<li>{@link XMPConst#TRUESTR} and {@link XMPConst#FALSESTR}
    255 	 *    		    <li>&quot;t&quot; and &quot;f&quot;
    256 	 *    		    <li>&quot;on&quot; and &quot;off&quot;
    257 	 *    		    <li>&quot;yes&quot; and &quot;no&quot;
    258 	 *   		  	<li>&quot;value <> 0&quot; and &quot;value == 0&quot;
    259 	 *         </ul>
    260 	 * @throws XMPException If an empty string is passed.
    261 	 */
    262 	public static boolean convertToBoolean(String value) throws XMPException
    263 	{
    264 		if (value == null  ||  value.length() == 0)
    265 		{
    266 			throw new XMPException("Empty convert-string", XMPError.BADVALUE);
    267 		}
    268 		value = value.toLowerCase();
    269 
    270 		try
    271 		{
    272 			// First try interpretation as Integer (anything not 0 is true)
    273 			return Integer.parseInt(value) != 0;
    274 		}
    275 		catch (NumberFormatException e)
    276 		{
    277 			return
    278 				"true".equals(value)  ||
    279 				"t".equals(value)  ||
    280 				"on".equals(value)  ||
    281 				"yes".equals(value);
    282 		}
    283 	}
    284 
    285 
    286 	/**
    287 	 * Convert from boolean to string.
    288 	 *
    289 	 * @param value
    290 	 *            a boolean value
    291 	 * @return The XMP string representation of the boolean. The values used are
    292 	 *         given by the constnts {@link XMPConst#TRUESTR} and
    293 	 *         {@link XMPConst#FALSESTR}.
    294 	 */
    295 	public static String convertFromBoolean(boolean value)
    296 	{
    297 		return value ? XMPConst.TRUESTR : XMPConst.FALSESTR;
    298 	}
    299 
    300 
    301 	/**
    302 	 * Converts a string value to an <code>int</code>.
    303 	 *
    304 	 * @param rawValue
    305 	 *            the string value
    306 	 * @return Returns an int.
    307 	 * @throws XMPException
    308 	 *             If the <code>rawValue</code> is <code>null</code> or empty or the
    309 	 *             conversion fails.
    310 	 */
    311 	public static int convertToInteger(String rawValue) throws XMPException
    312 	{
    313 		try
    314 		{
    315 			if (rawValue == null  ||  rawValue.length() == 0)
    316 			{
    317 				throw new XMPException("Empty convert-string", XMPError.BADVALUE);
    318 			}
    319 			if (rawValue.startsWith("0x"))
    320 			{
    321 				return Integer.parseInt(rawValue.substring(2), 16);
    322 			}
    323 			else
    324 			{
    325 				return Integer.parseInt(rawValue);
    326 			}
    327 		}
    328 		catch (NumberFormatException e)
    329 		{
    330 			throw new XMPException("Invalid integer string", XMPError.BADVALUE);
    331 		}
    332 	}
    333 
    334 
    335 	/**
    336 	 * Convert from int to string.
    337 	 *
    338 	 * @param value
    339 	 *            an int value
    340 	 * @return The string representation of the int.
    341 	 */
    342 	public static String convertFromInteger(int value)
    343 	{
    344 		return String.valueOf(value);
    345 	}
    346 
    347 
    348 	/**
    349 	 * Converts a string value to a <code>long</code>.
    350 	 *
    351 	 * @param rawValue
    352 	 *            the string value
    353 	 * @return Returns a long.
    354 	 * @throws XMPException
    355 	 *             If the <code>rawValue</code> is <code>null</code> or empty or the
    356 	 *             conversion fails.
    357 	 */
    358 	public static long convertToLong(String rawValue) throws XMPException
    359 	{
    360 		try
    361 		{
    362 			if (rawValue == null  ||  rawValue.length() == 0)
    363 			{
    364 				throw new XMPException("Empty convert-string", XMPError.BADVALUE);
    365 			}
    366 			if (rawValue.startsWith("0x"))
    367 			{
    368 				return Long.parseLong(rawValue.substring(2), 16);
    369 			}
    370 			else
    371 			{
    372 				return Long.parseLong(rawValue);
    373 			}
    374 		}
    375 		catch (NumberFormatException e)
    376 		{
    377 			throw new XMPException("Invalid long string", XMPError.BADVALUE);
    378 		}
    379 	}
    380 
    381 
    382 	/**
    383 	 * Convert from long to string.
    384 	 *
    385 	 * @param value
    386 	 *            a long value
    387 	 * @return The string representation of the long.
    388 	 */
    389 	public static String convertFromLong(long value)
    390 	{
    391 		return String.valueOf(value);
    392 	}
    393 
    394 
    395 	/**
    396 	 * Converts a string value to a <code>double</code>.
    397 	 *
    398 	 * @param rawValue
    399 	 *            the string value
    400 	 * @return Returns a double.
    401 	 * @throws XMPException
    402 	 *             If the <code>rawValue</code> is <code>null</code> or empty or the
    403 	 *             conversion fails.
    404 	 */
    405 	public static double convertToDouble(String rawValue) throws XMPException
    406 	{
    407 		try
    408 		{
    409 			if (rawValue == null  ||  rawValue.length() == 0)
    410 			{
    411 				throw new XMPException("Empty convert-string", XMPError.BADVALUE);
    412 			}
    413 			else
    414 			{
    415 				return Double.parseDouble(rawValue);
    416 			}
    417 		}
    418 		catch (NumberFormatException e)
    419 		{
    420 			throw new XMPException("Invalid double string", XMPError.BADVALUE);
    421 		}
    422 	}
    423 
    424 
    425 	/**
    426 	 * Convert from long to string.
    427 	 *
    428 	 * @param value
    429 	 *            a long value
    430 	 * @return The string representation of the long.
    431 	 */
    432 	public static String convertFromDouble(double value)
    433 	{
    434 		return String.valueOf(value);
    435 	}
    436 
    437 
    438 	/**
    439 	 * Converts a string value to an <code>XMPDateTime</code>.
    440 	 *
    441 	 * @param rawValue
    442 	 *            the string value
    443 	 * @return Returns an <code>XMPDateTime</code>-object.
    444 	 * @throws XMPException
    445 	 *             If the <code>rawValue</code> is <code>null</code> or empty or the
    446 	 *             conversion fails.
    447 	 */
    448 	public static XMPDateTime convertToDate(String rawValue) throws XMPException
    449 	{
    450 		if (rawValue == null  ||  rawValue.length() == 0)
    451 		{
    452 			throw new XMPException("Empty convert-string", XMPError.BADVALUE);
    453 		}
    454 		else
    455 		{
    456 			return ISO8601Converter.parse(rawValue);
    457 		}
    458 	}
    459 
    460 
    461 	/**
    462 	 * Convert from <code>XMPDateTime</code> to string.
    463 	 *
    464 	 * @param value
    465 	 *            an <code>XMPDateTime</code>
    466 	 * @return The string representation of the long.
    467 	 */
    468 	public static String convertFromDate(XMPDateTime value)
    469 	{
    470 		return ISO8601Converter.render(value);
    471 	}
    472 
    473 
    474 	 /**
    475 	  * Convert from a byte array to a base64 encoded string.
    476 	  *
    477 	  * @param buffer
    478 	  *            the byte array to be converted
    479 	  * @return Returns the base64 string.
    480 	  */
    481 	public static String encodeBase64(byte[] buffer)
    482 	{
    483 		return new String(Base64.encode(buffer));
    484 	}
    485 
    486 
    487 	/**
    488 	 * Decode from Base64 encoded string to raw data.
    489 	 *
    490 	 * @param base64String
    491 	 *            a base64 encoded string
    492 	 * @return Returns a byte array containg the decoded string.
    493 	 * @throws XMPException Thrown if the given string is not property base64 encoded
    494 	 */
    495 	public static byte[] decodeBase64(String base64String) throws XMPException
    496 	{
    497 		try
    498 		{
    499 			return Base64.decode(base64String.getBytes());
    500 		}
    501 		catch (Throwable e)
    502 		{
    503 			throw new XMPException("Invalid base64 string", XMPError.BADVALUE, e);
    504 		}
    505 	}
    506 }