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>"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 */ 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 }