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
10package com.adobe.xmp;
11
12import com.adobe.xmp.impl.Utils;
13import com.adobe.xmp.impl.xpath.XMPPath;
14import 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, &quot;Struct&quot;, fieldNS,
37 *          &quot;Array&quot;);
38 *      String path += XMPPathFactory.composeArrayItemPath (schemaNS, &quot;Array&quot; 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, &quot;Struct&quot;, fieldNS,
47 *          &quot;Array&quot;);
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 &quot;schema&quot; 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 */
61public 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 &quot;ns&quot; is the prefix for schemaNS and
80	 *         &quot;i&quot; 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 &quot;ns&quot; is the prefix for
111	 *         schemaNS and &quot;fNS&quot; 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 &quot;ns&quot; is the prefix for
139	 *         schemaNS and &quot;qNS&quot; 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 &quot;content addressing&quot; 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	 *         &quot;ns&quot; 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 &quot;content addressing&quot; 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, &quot;Downloads&quot;, fieldNS,
202	 *          &quot;City&quot;, chosenCity );
203	 *      XMPProperty prop = xmpObj.getStructField ( schemaNS, path, fieldNS, &quot;URI&quot; );
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 &quot;ns&quot; is the
218	 *         prefix for schemaNS and &quot;fNS&quot; 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}