summaryrefslogtreecommitdiff
path: root/XMPCore/src/com/adobe/xmp/XMPPathFactory.java
blob: 9d8f632ca7b27bb51af279a598fd0cf6a577fc70 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
// =================================================================================================
// 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.xmp;

import com.adobe.xmp.impl.Utils;
import com.adobe.xmp.impl.xpath.XMPPath;
import com.adobe.xmp.impl.xpath.XMPPathParser;

/**
 * Utility services for the metadata object. It has only public static functions, you cannot create
 * an object. These are all functions that layer cleanly on top of the core XMP toolkit.
 * <p>
 * These functions provide support for composing path expressions to deeply nested properties. The
 * functions <code>XMPMeta</code> such as <code>getProperty()</code>,
 * <code>getArrayItem()</code> and <code>getStructField()</code> provide easy access to top
 * level simple properties, items in top level arrays, and fields of top level structs. They do not
 * provide convenient access to more complex things like fields several levels deep in a complex
 * struct, or fields within an array of structs, or items of an array that is a field of a struct.
 * These functions can also be used to compose paths to top level array items or struct fields so
 * that you can use the binary accessors like <code>getPropertyAsInteger()</code>.
 * <p>
 * You can use these functions is to compose a complete path expression, or all but the last
 * component. Suppose you have a property that is an array of integers within a struct. You can
 * access one of the array items like this:
 * <p>
 * <blockquote>
 * 
 * <pre>
 *      String path = XMPPathFactory.composeStructFieldPath (schemaNS, &quot;Struct&quot;, fieldNS,
 *          &quot;Array&quot;);
 *      String path += XMPPathFactory.composeArrayItemPath (schemaNS, &quot;Array&quot; index);
 *      PropertyInteger result = xmpObj.getPropertyAsInteger(schemaNS, path);
 * </pre>
 * 
 * </blockquote> You could also use this code if you want the string form of the integer:
 * <blockquote>
 * 
 * <pre>
 *      String path = XMPPathFactory.composeStructFieldPath (schemaNS, &quot;Struct&quot;, fieldNS,
 *          &quot;Array&quot;);
 *      PropertyText xmpObj.getArrayItem (schemaNS, path, index);
 * </pre>
 * 
 * </blockquote>
 * <p>
 * <em>Note:</em> It might look confusing that the schemaNS is passed in all of the calls above.
 * This is because the XMP toolkit keeps the top level &quot;schema&quot; namespace separate from
 * the rest of the path expression.
 * <em>Note:</em> These methods are much simpler than in the C++-API, they don't check the given
 * path or array indices.
 * 
 * @since 25.01.2006
 */
public final class XMPPathFactory
{
	/** Private constructor */
	private XMPPathFactory()
	{
		// EMPTY
	}


	/**
	 * Compose the path expression for an item in an array.
	 * 
	 * @param arrayName The name of the array. May be a general path expression, must not be
	 *        <code>null</code> or the empty string.
	 * @param itemIndex The index of the desired item. Arrays in XMP are indexed from 1.
	 * 		  0 and below means last array item and renders as <code>[last()]</code>.	
	 * 					
	 * @return Returns the composed path basing on fullPath. This will be of the form
	 *         <tt>ns:arrayName[i]</tt>, where &quot;ns&quot; is the prefix for schemaNS and
	 *         &quot;i&quot; is the decimal representation of itemIndex.
	 * @throws XMPException Throws exeption if index zero is used.
	 */
	public static String composeArrayItemPath(String arrayName, int itemIndex) throws XMPException
	{
		if (itemIndex > 0)
		{
			return arrayName + '[' + itemIndex + ']';
		}
		else  if (itemIndex == XMPConst.ARRAY_LAST_ITEM)
		{
			return arrayName + "[last()]";
		}
		else
		{
			throw new XMPException("Array index must be larger than zero", XMPError.BADINDEX);
		}
	}

	
	/**
	 * Compose the path expression for a field in a struct. The result can be added to the
	 * path of 
	 * 
	 * 
	 * @param fieldNS The namespace URI for the field. Must not be <code>null</code> or the empty
	 *        string.
	 * @param fieldName The name of the field. Must be a simple XML name, must not be
	 *        <code>null</code> or the empty string.
	 * @return Returns the composed path. This will be of the form
	 *         <tt>ns:structName/fNS:fieldName</tt>, where &quot;ns&quot; is the prefix for
	 *         schemaNS and &quot;fNS&quot; is the prefix for fieldNS.
	 * @throws XMPException Thrown if the path to create is not valid.
	 */
	public static String composeStructFieldPath(String fieldNS,
			String fieldName) throws XMPException
	{
		assertFieldNS(fieldNS);
		assertFieldName(fieldName);
		
		XMPPath fieldPath = XMPPathParser.expandXPath(fieldNS, fieldName);
		if (fieldPath.size() != 2) 
		{
			throw new XMPException("The field name must be simple", XMPError.BADXPATH);
		}
		
		return '/' + fieldPath.getSegment(XMPPath.STEP_ROOT_PROP).getName(); 
	}


	/**
	 * Compose the path expression for a qualifier.
	 * 
	 * @param qualNS The namespace URI for the qualifier. May be <code>null</code> or the empty
	 *        string if the qualifier is in the XML empty namespace.
	 * @param qualName The name of the qualifier. Must be a simple XML name, must not be
	 *        <code>null</code> or the empty string.
	 * @return Returns the composed path. This will be of the form
	 *         <tt>ns:propName/?qNS:qualName</tt>, where &quot;ns&quot; is the prefix for
	 *         schemaNS and &quot;qNS&quot; is the prefix for qualNS.
	 * @throws XMPException Thrown if the path to create is not valid.
	 */
	public static String composeQualifierPath(
			String qualNS,
			String qualName) throws XMPException
	{
		assertQualNS(qualNS);
		assertQualName(qualName);
		
		XMPPath qualPath = XMPPathParser.expandXPath(qualNS, qualName);
		if (qualPath.size() != 2)
		{
			throw new XMPException("The qualifier name must be simple", XMPError.BADXPATH);
		}

		return "/?" + qualPath.getSegment(XMPPath.STEP_ROOT_PROP).getName();
	}


	/**
	 * Compose the path expression to select an alternate item by language. The
	 * path syntax allows two forms of &quot;content addressing&quot; that may
	 * be used to select an item in an array of alternatives. The form used in
	 * ComposeLangSelector lets you select an item in an alt-text array based on
	 * the value of its <tt>xml:lang</tt> qualifier. The other form of content
	 * addressing is shown in ComposeFieldSelector. \note ComposeLangSelector
	 * does not supplant SetLocalizedText or GetLocalizedText. They should
	 * generally be used, as they provide extra logic to choose the appropriate
	 * language and maintain consistency with the 'x-default' value.
	 * ComposeLangSelector gives you an path expression that is explicitly and
	 * only for the language given in the langName parameter.
	 * 
	 * @param arrayName
	 *            The name of the array. May be a general path expression, must
	 *            not be <code>null</code> or the empty string.
	 * @param langName
	 *            The RFC 3066 code for the desired language.
	 * @return Returns the composed path. This will be of the form
	 *         <tt>ns:arrayName[@xml:lang='langName']</tt>, where
	 *         &quot;ns&quot; is the prefix for schemaNS.
	 */
	public static String composeLangSelector(String arrayName,
			String langName)
	{
		return arrayName + "[?xml:lang=\"" + Utils.normalizeLangValue(langName) + "\"]";
	}


	/**
	 * Compose the path expression to select an alternate item by a field's value. The path syntax
	 * allows two forms of &quot;content addressing&quot; that may be used to select an item in an
	 * array of alternatives. The form used in ComposeFieldSelector lets you select an item in an
	 * array of structs based on the value of one of the fields in the structs. The other form of
	 * content addressing is shown in ComposeLangSelector. For example, consider a simple struct
	 * that has two fields, the name of a city and the URI of an FTP site in that city. Use this to
	 * create an array of download alternatives. You can show the user a popup built from the values
	 * of the city fields. You can then get the corresponding URI as follows:
	 * <p>
	 * <blockquote>
	 * 
	 * <pre>
	 *      String path = composeFieldSelector ( schemaNS, &quot;Downloads&quot;, fieldNS, 
	 *          &quot;City&quot;, chosenCity ); 
	 *      XMPProperty prop = xmpObj.getStructField ( schemaNS, path, fieldNS, &quot;URI&quot; );
	 * </pre>
	 * 
	 * </blockquote>
	 * 
	 * @param arrayName The name of the array. May be a general path expression, must not be
	 *        <code>null</code> or the empty string.
	 * @param fieldNS The namespace URI for the field used as the selector. Must not be
	 *        <code>null</code> or the empty string.
	 * @param fieldName The name of the field used as the selector. Must be a simple XML name, must
	 *        not be <code>null</code> or the empty string. It must be the name of a field that is
	 *        itself simple.
	 * @param fieldValue The desired value of the field.
	 * @return Returns the composed path. This will be of the form
	 *         <tt>ns:arrayName[fNS:fieldName='fieldValue']</tt>, where &quot;ns&quot; is the
	 *         prefix for schemaNS and &quot;fNS&quot; is the prefix for fieldNS.
	 * @throws XMPException Thrown if the path to create is not valid.
	 */
	public static String composeFieldSelector(String arrayName, String fieldNS,
			String fieldName, String fieldValue) throws XMPException
	{
		XMPPath fieldPath = XMPPathParser.expandXPath(fieldNS, fieldName);
		if (fieldPath.size() != 2) 
		{
			throw new XMPException("The fieldName name must be simple", XMPError.BADXPATH);
		}
		
		return arrayName + '[' + fieldPath.getSegment(XMPPath.STEP_ROOT_PROP).getName() +
			"=\"" + fieldValue + "\"]"; 
	}
	
	
	/**
	 * ParameterAsserts that a qualifier namespace is set.
	 * @param qualNS a qualifier namespace
	 * @throws XMPException Qualifier schema is null or empty
	 */
	private static void assertQualNS(String qualNS) throws XMPException
	{
		if (qualNS == null  ||  qualNS.length() == 0)
		{
			throw new XMPException("Empty qualifier namespace URI", XMPError.BADSCHEMA);
		}
		
	}
	
	
	/**
	 * ParameterAsserts that a qualifier name is set.
	 * @param qualName a qualifier name or path
	 * @throws XMPException Qualifier name is null or empty
	 */
	private static void assertQualName(String qualName) throws XMPException
	{
		if (qualName == null  ||  qualName.length() == 0)
		{
			throw new XMPException("Empty qualifier name", XMPError.BADXPATH);
		}
	}

	
	/**
	 * ParameterAsserts that a struct field namespace is set.
	 * @param fieldNS a struct field namespace
	 * @throws XMPException Struct field schema is null or empty
	 */
	private static void assertFieldNS(String fieldNS) throws XMPException
	{
		if (fieldNS == null  ||  fieldNS.length() == 0)
		{
			throw new XMPException("Empty field namespace URI", XMPError.BADSCHEMA);
		}
		
	}
	
	
	/**
	 * ParameterAsserts that a struct field name is set.
	 * @param fieldName a struct field name or path
	 * @throws XMPException Struct field name is null or empty
	 */
	private static void assertFieldName(String fieldName) throws XMPException
	{
		if (fieldName == null  ||  fieldName.length() == 0)
		{
			throw new XMPException("Empty f name", XMPError.BADXPATH);
		}
	}
}