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.Utils; 13 import com.adobe.xmp.impl.xpath.XMPPath; 14 import com.adobe.xmp.impl.xpath.XMPPathParser; 15 16 /** 17 * Utility services for the metadata object. It has only public static functions, you cannot create 18 * an object. These are all functions that layer cleanly on top of the core XMP toolkit. 19 * <p> 20 * These functions provide support for composing path expressions to deeply nested properties. The 21 * functions <code>XMPMeta</code> such as <code>getProperty()</code>, 22 * <code>getArrayItem()</code> and <code>getStructField()</code> provide easy access to top 23 * level simple properties, items in top level arrays, and fields of top level structs. They do not 24 * provide convenient access to more complex things like fields several levels deep in a complex 25 * struct, or fields within an array of structs, or items of an array that is a field of a struct. 26 * These functions can also be used to compose paths to top level array items or struct fields so 27 * that you can use the binary accessors like <code>getPropertyAsInteger()</code>. 28 * <p> 29 * You can use these functions is to compose a complete path expression, or all but the last 30 * component. Suppose you have a property that is an array of integers within a struct. You can 31 * access one of the array items like this: 32 * <p> 33 * <blockquote> 34 * 35 * <pre> 36 * String path = XMPPathFactory.composeStructFieldPath (schemaNS, "Struct", fieldNS, 37 * "Array"); 38 * String path += XMPPathFactory.composeArrayItemPath (schemaNS, "Array" index); 39 * PropertyInteger result = xmpObj.getPropertyAsInteger(schemaNS, path); 40 * </pre> 41 * 42 * </blockquote> You could also use this code if you want the string form of the integer: 43 * <blockquote> 44 * 45 * <pre> 46 * String path = XMPPathFactory.composeStructFieldPath (schemaNS, "Struct", fieldNS, 47 * "Array"); 48 * PropertyText xmpObj.getArrayItem (schemaNS, path, index); 49 * </pre> 50 * 51 * </blockquote> 52 * <p> 53 * <em>Note:</em> It might look confusing that the schemaNS is passed in all of the calls above. 54 * This is because the XMP toolkit keeps the top level "schema" namespace separate from 55 * the rest of the path expression. 56 * <em>Note:</em> These methods are much simpler than in the C++-API, they don't check the given 57 * path or array indices. 58 * 59 * @since 25.01.2006 60 */ 61 public final class XMPPathFactory 62 { 63 /** Private constructor */ 64 private XMPPathFactory() 65 { 66 // EMPTY 67 } 68 69 70 /** 71 * Compose the path expression for an item in an array. 72 * 73 * @param arrayName The name of the array. May be a general path expression, must not be 74 * <code>null</code> or the empty string. 75 * @param itemIndex The index of the desired item. Arrays in XMP are indexed from 1. 76 * 0 and below means last array item and renders as <code>[last()]</code>. 77 * 78 * @return Returns the composed path basing on fullPath. This will be of the form 79 * <tt>ns:arrayName[i]</tt>, where "ns" is the prefix for schemaNS and 80 * "i" is the decimal representation of itemIndex. 81 * @throws XMPException Throws exeption if index zero is used. 82 */ 83 public static String composeArrayItemPath(String arrayName, int itemIndex) throws XMPException 84 { 85 if (itemIndex > 0) 86 { 87 return arrayName + '[' + itemIndex + ']'; 88 } 89 else if (itemIndex == XMPConst.ARRAY_LAST_ITEM) 90 { 91 return arrayName + "[last()]"; 92 } 93 else 94 { 95 throw new XMPException("Array index must be larger than zero", XMPError.BADINDEX); 96 } 97 } 98 99 100 /** 101 * Compose the path expression for a field in a struct. The result can be added to the 102 * path of 103 * 104 * 105 * @param fieldNS The namespace URI for the field. Must not be <code>null</code> or the empty 106 * string. 107 * @param fieldName The name of the field. Must be a simple XML name, must not be 108 * <code>null</code> or the empty string. 109 * @return Returns the composed path. This will be of the form 110 * <tt>ns:structName/fNS:fieldName</tt>, where "ns" is the prefix for 111 * schemaNS and "fNS" is the prefix for fieldNS. 112 * @throws XMPException Thrown if the path to create is not valid. 113 */ 114 public static String composeStructFieldPath(String fieldNS, 115 String fieldName) throws XMPException 116 { 117 assertFieldNS(fieldNS); 118 assertFieldName(fieldName); 119 120 XMPPath fieldPath = XMPPathParser.expandXPath(fieldNS, fieldName); 121 if (fieldPath.size() != 2) 122 { 123 throw new XMPException("The field name must be simple", XMPError.BADXPATH); 124 } 125 126 return '/' + fieldPath.getSegment(XMPPath.STEP_ROOT_PROP).getName(); 127 } 128 129 130 /** 131 * Compose the path expression for a qualifier. 132 * 133 * @param qualNS The namespace URI for the qualifier. May be <code>null</code> or the empty 134 * string if the qualifier is in the XML empty namespace. 135 * @param qualName The name of the qualifier. Must be a simple XML name, must not be 136 * <code>null</code> or the empty string. 137 * @return Returns the composed path. This will be of the form 138 * <tt>ns:propName/?qNS:qualName</tt>, where "ns" is the prefix for 139 * schemaNS and "qNS" is the prefix for qualNS. 140 * @throws XMPException Thrown if the path to create is not valid. 141 */ 142 public static String composeQualifierPath( 143 String qualNS, 144 String qualName) throws XMPException 145 { 146 assertQualNS(qualNS); 147 assertQualName(qualName); 148 149 XMPPath qualPath = XMPPathParser.expandXPath(qualNS, qualName); 150 if (qualPath.size() != 2) 151 { 152 throw new XMPException("The qualifier name must be simple", XMPError.BADXPATH); 153 } 154 155 return "/?" + qualPath.getSegment(XMPPath.STEP_ROOT_PROP).getName(); 156 } 157 158 159 /** 160 * Compose the path expression to select an alternate item by language. The 161 * path syntax allows two forms of "content addressing" that may 162 * be used to select an item in an array of alternatives. The form used in 163 * ComposeLangSelector lets you select an item in an alt-text array based on 164 * the value of its <tt>xml:lang</tt> qualifier. The other form of content 165 * addressing is shown in ComposeFieldSelector. \note ComposeLangSelector 166 * does not supplant SetLocalizedText or GetLocalizedText. They should 167 * generally be used, as they provide extra logic to choose the appropriate 168 * language and maintain consistency with the 'x-default' value. 169 * ComposeLangSelector gives you an path expression that is explicitly and 170 * only for the language given in the langName parameter. 171 * 172 * @param arrayName 173 * The name of the array. May be a general path expression, must 174 * not be <code>null</code> or the empty string. 175 * @param langName 176 * The RFC 3066 code for the desired language. 177 * @return Returns the composed path. This will be of the form 178 * <tt>ns:arrayName[@xml:lang='langName']</tt>, where 179 * "ns" is the prefix for schemaNS. 180 */ 181 public static String composeLangSelector(String arrayName, 182 String langName) 183 { 184 return arrayName + "[?xml:lang=\"" + Utils.normalizeLangValue(langName) + "\"]"; 185 } 186 187 188 /** 189 * Compose the path expression to select an alternate item by a field's value. The path syntax 190 * allows two forms of "content addressing" that may be used to select an item in an 191 * array of alternatives. The form used in ComposeFieldSelector lets you select an item in an 192 * array of structs based on the value of one of the fields in the structs. The other form of 193 * content addressing is shown in ComposeLangSelector. For example, consider a simple struct 194 * that has two fields, the name of a city and the URI of an FTP site in that city. Use this to 195 * create an array of download alternatives. You can show the user a popup built from the values 196 * of the city fields. You can then get the corresponding URI as follows: 197 * <p> 198 * <blockquote> 199 * 200 * <pre> 201 * String path = composeFieldSelector ( schemaNS, "Downloads", fieldNS, 202 * "City", chosenCity ); 203 * XMPProperty prop = xmpObj.getStructField ( schemaNS, path, fieldNS, "URI" ); 204 * </pre> 205 * 206 * </blockquote> 207 * 208 * @param arrayName The name of the array. May be a general path expression, must not be 209 * <code>null</code> or the empty string. 210 * @param fieldNS The namespace URI for the field used as the selector. Must not be 211 * <code>null</code> or the empty string. 212 * @param fieldName The name of the field used as the selector. Must be a simple XML name, must 213 * not be <code>null</code> or the empty string. It must be the name of a field that is 214 * itself simple. 215 * @param fieldValue The desired value of the field. 216 * @return Returns the composed path. This will be of the form 217 * <tt>ns:arrayName[fNS:fieldName='fieldValue']</tt>, where "ns" is the 218 * prefix for schemaNS and "fNS" is the prefix for fieldNS. 219 * @throws XMPException Thrown if the path to create is not valid. 220 */ 221 public static String composeFieldSelector(String arrayName, String fieldNS, 222 String fieldName, String fieldValue) throws XMPException 223 { 224 XMPPath fieldPath = XMPPathParser.expandXPath(fieldNS, fieldName); 225 if (fieldPath.size() != 2) 226 { 227 throw new XMPException("The fieldName name must be simple", XMPError.BADXPATH); 228 } 229 230 return arrayName + '[' + fieldPath.getSegment(XMPPath.STEP_ROOT_PROP).getName() + 231 "=\"" + fieldValue + "\"]"; 232 } 233 234 235 /** 236 * ParameterAsserts that a qualifier namespace is set. 237 * @param qualNS a qualifier namespace 238 * @throws XMPException Qualifier schema is null or empty 239 */ 240 private static void assertQualNS(String qualNS) throws XMPException 241 { 242 if (qualNS == null || qualNS.length() == 0) 243 { 244 throw new XMPException("Empty qualifier namespace URI", XMPError.BADSCHEMA); 245 } 246 247 } 248 249 250 /** 251 * ParameterAsserts that a qualifier name is set. 252 * @param qualName a qualifier name or path 253 * @throws XMPException Qualifier name is null or empty 254 */ 255 private static void assertQualName(String qualName) throws XMPException 256 { 257 if (qualName == null || qualName.length() == 0) 258 { 259 throw new XMPException("Empty qualifier name", XMPError.BADXPATH); 260 } 261 } 262 263 264 /** 265 * ParameterAsserts that a struct field namespace is set. 266 * @param fieldNS a struct field namespace 267 * @throws XMPException Struct field schema is null or empty 268 */ 269 private static void assertFieldNS(String fieldNS) throws XMPException 270 { 271 if (fieldNS == null || fieldNS.length() == 0) 272 { 273 throw new XMPException("Empty field namespace URI", XMPError.BADSCHEMA); 274 } 275 276 } 277 278 279 /** 280 * ParameterAsserts that a struct field name is set. 281 * @param fieldName a struct field name or path 282 * @throws XMPException Struct field name is null or empty 283 */ 284 private static void assertFieldName(String fieldName) throws XMPException 285 { 286 if (fieldName == null || fieldName.length() == 0) 287 { 288 throw new XMPException("Empty f name", XMPError.BADXPATH); 289 } 290 } 291 }