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 */ XMPUtils()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 */ catenateArrayItems(XMPMeta xmp, String schemaNS, String arrayName, String separator, String quotes, boolean allowCommas)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 */ separateArrayItems(XMPMeta xmp, String schemaNS, String arrayName, String catedStr, PropertyOptions arrayOptions, boolean preserveCommas)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 */ removeProperties(XMPMeta xmp, String schemaNS, String propName, boolean doAllProperties, boolean includeAliases)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 */ appendProperties(XMPMeta source, XMPMeta dest, boolean doAllProperties, boolean replaceOldValues)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 */ appendProperties(XMPMeta source, XMPMeta dest, boolean doAllProperties, boolean replaceOldValues, boolean deleteEmptyValues)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>"t" and "f" 256 * <li>"on" and "off" 257 * <li>"yes" and "no" 258 * <li>"value <> 0" and "value == 0" 259 * </ul> 260 * @throws XMPException If an empty string is passed. 261 */ convertToBoolean(String value)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 */ convertFromBoolean(boolean value)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 */ convertToInteger(String rawValue)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 */ convertFromInteger(int value)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 */ convertToLong(String rawValue)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 */ convertFromLong(long value)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 */ convertToDouble(String rawValue)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 */ convertFromDouble(double value)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 */ convertToDate(String rawValue)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 */ convertFromDate(XMPDateTime value)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 */ encodeBase64(byte[] buffer)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 */ decodeBase64(String base64String)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 }