1f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling// =================================================================================================
2f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling// ADOBE SYSTEMS INCORPORATED
3f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling// Copyright 2006 Adobe Systems Incorporated
4f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling// All Rights Reserved
5f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling//
6f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
7f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling// of the Adobe license agreement accompanying it.
8f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling// =================================================================================================
9f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
10f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
11f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
12f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberlingpackage com.adobe.xmp.impl;
13f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
14f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberlingimport java.util.Iterator;
15f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
16f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberlingimport com.adobe.xmp.XMPConst;
17f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberlingimport com.adobe.xmp.XMPError;
18f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberlingimport com.adobe.xmp.XMPException;
19f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberlingimport com.adobe.xmp.XMPMeta;
20f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberlingimport com.adobe.xmp.XMPMetaFactory;
21f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberlingimport com.adobe.xmp.XMPUtils;
22f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberlingimport com.adobe.xmp.impl.xpath.XMPPath;
23f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberlingimport com.adobe.xmp.impl.xpath.XMPPathParser;
24f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberlingimport com.adobe.xmp.options.PropertyOptions;
25f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberlingimport com.adobe.xmp.properties.XMPAliasInfo;
26f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
27f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
28f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
29f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling/**
30f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling * @since 11.08.2006
31f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling */
32f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberlingpublic class XMPUtilsImpl implements XMPConst
33f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling{
34f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/** */
35f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private static final int UCK_NORMAL = 0;
36f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/** */
37f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private static final int UCK_SPACE = 1;
38f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/** */
39f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private static final int UCK_COMMA = 2;
40f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/** */
41f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private static final int UCK_SEMICOLON = 3;
42f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/** */
43f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private static final int UCK_QUOTE = 4;
44f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/** */
45f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private static final int UCK_CONTROL = 5;
46f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
47f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
48f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
49f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * Private constructor, as
50f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
51f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private XMPUtilsImpl()
52f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
53f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// EMPTY
54f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
55f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
56f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
57f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
58f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @see XMPUtils#catenateArrayItems(XMPMeta, String, String, String, String,
59f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *      boolean)
60f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
61f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param xmp
62f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            The XMP object containing the array to be catenated.
63f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param schemaNS
64f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            The schema namespace URI for the array. Must not be null or
65f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            the empty string.
66f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param arrayName
67f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            The name of the array. May be a general path expression, must
68f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            not be null or the empty string. Each item in the array must
69f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            be a simple string value.
70f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param separator
71f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            The string to be used to separate the items in the catenated
72f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            string. Defaults to "; ", ASCII semicolon and space
73f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            (U+003B, U+0020).
74f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param quotes
75f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            The characters to be used as quotes around array items that
76f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            contain a separator. Defaults to '"'
77f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param allowCommas
78f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            Option flag to control the catenation.
79f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @return Returns the string containing the catenated array items.
80f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws XMPException
81f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *             Forwards the Exceptions from the metadata processing
82f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
83f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	public static String catenateArrayItems(XMPMeta xmp, String schemaNS, String arrayName,
84f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			String separator, String quotes, boolean allowCommas) throws XMPException
85f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
86f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		ParameterAsserts.assertSchemaNS(schemaNS);
87f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		ParameterAsserts.assertArrayName(arrayName);
88f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		ParameterAsserts.assertImplementation(xmp);
89f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (separator == null  ||  separator.length() == 0)
90f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
91f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			separator = "; ";
92f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
93f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (quotes == null  ||  quotes.length() == 0)
94f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
95f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			quotes = "\"";
96f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
97f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
98f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		XMPMetaImpl xmpImpl = (XMPMetaImpl) xmp;
99f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		XMPNode arrayNode = null;
100f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		XMPNode currItem = null;
101f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
102f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// Return an empty result if the array does not exist,
103f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// hurl if it isn't the right form.
104f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		XMPPath arrayPath = XMPPathParser.expandXPath(schemaNS, arrayName);
105f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		arrayNode = XMPNodeUtils.findNode(xmpImpl.getRoot(), arrayPath, false, null);
106f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (arrayNode == null)
107f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
108f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			return "";
109f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
110f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		else if (!arrayNode.getOptions().isArray() || arrayNode.getOptions().isArrayAlternate())
111f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
112f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			throw new XMPException("Named property must be non-alternate array", XMPError.BADPARAM);
113f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
114f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
115f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// Make sure the separator is OK.
116f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		checkSeparator(separator);
117f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// Make sure the open and close quotes are a legitimate pair.
118f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		char openQuote = quotes.charAt(0);
119f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		char closeQuote = checkQuotes(quotes, openQuote);
120f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
121f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// Build the result, quoting the array items, adding separators.
122f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// Hurl if any item isn't simple.
123f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
124f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		StringBuffer catinatedString = new StringBuffer();
125f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
126f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		for (Iterator it = arrayNode.iterateChildren(); it.hasNext();)
127f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
128f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			currItem = (XMPNode) it.next();
129f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (currItem.getOptions().isCompositeProperty())
130f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
131f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				throw new XMPException("Array items must be simple", XMPError.BADPARAM);
132f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
133f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			String str = applyQuotes(currItem.getValue(), openQuote, closeQuote, allowCommas);
134f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
135f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			catinatedString.append(str);
136f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (it.hasNext())
137f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
138f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				catinatedString.append(separator);
139f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
140f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
141f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
142f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		return catinatedString.toString();
143f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
144f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
145f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
146f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
147f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * see {@link XMPUtils#separateArrayItems(XMPMeta, String, String, String,
148f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * PropertyOptions, boolean)}
149f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
150f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param xmp
151f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            The XMP object containing the array to be updated.
152f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param schemaNS
153f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            The schema namespace URI for the array. Must not be null or
154f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            the empty string.
155f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param arrayName
156f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            The name of the array. May be a general path expression, must
157f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            not be null or the empty string. Each item in the array must
158f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            be a simple string value.
159f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param catedStr
160f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            The string to be separated into the array items.
161f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param arrayOptions
162f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            Option flags to control the separation.
163f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param preserveCommas
164f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            Flag if commas shall be preserved
165f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
166f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws XMPException
167f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *             Forwards the Exceptions from the metadata processing
168f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
169f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	public static void separateArrayItems(XMPMeta xmp, String schemaNS, String arrayName,
170f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			String catedStr, PropertyOptions arrayOptions, boolean preserveCommas)
171f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			throws XMPException
172f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
173f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		ParameterAsserts.assertSchemaNS(schemaNS);
174f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		ParameterAsserts.assertArrayName(arrayName);
175f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (catedStr == null)
176f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
177f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			throw new XMPException("Parameter must not be null", XMPError.BADPARAM);
178f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
179f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		ParameterAsserts.assertImplementation(xmp);
180f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		XMPMetaImpl xmpImpl = (XMPMetaImpl) xmp;
181f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
182f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// Keep a zero value, has special meaning below.
183f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		XMPNode arrayNode = separateFindCreateArray(schemaNS, arrayName, arrayOptions, xmpImpl);
184f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
185f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// Extract the item values one at a time, until the whole input string is done.
186f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		String itemValue;
187f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		int itemStart, itemEnd;
188f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		int nextKind = UCK_NORMAL, charKind = UCK_NORMAL;
189f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		char ch = 0, nextChar = 0;
190f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
191f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		itemEnd = 0;
192f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		int endPos = catedStr.length();
193f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		while (itemEnd < endPos)
194f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
195f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// Skip any leading spaces and separation characters. Always skip commas here.
196f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// They can be kept when within a value, but not when alone between values.
197f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			for (itemStart = itemEnd; itemStart < endPos; itemStart++)
198f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
199f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				ch = catedStr.charAt(itemStart);
200f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				charKind = classifyCharacter(ch);
201f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				if (charKind == UCK_NORMAL || charKind == UCK_QUOTE)
202f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
203f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					break;
204f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
205f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
206f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (itemStart >= endPos)
207f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
208f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				break;
209f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
210f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
211f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (charKind != UCK_QUOTE)
212f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
213f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				// This is not a quoted value. Scan for the end, create an array
214f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				// item from the substring.
215f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				for (itemEnd = itemStart; itemEnd < endPos; itemEnd++)
216f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
217f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					ch = catedStr.charAt(itemEnd);
218f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					charKind = classifyCharacter(ch);
219f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
220f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					if (charKind == UCK_NORMAL || charKind == UCK_QUOTE  ||
221f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						(charKind == UCK_COMMA && preserveCommas))
222f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					{
223f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						continue;
224f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					}
225f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					else if (charKind != UCK_SPACE)
226f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					{
227f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						break;
228f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					}
229f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					else if ((itemEnd + 1) < endPos)
230f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					{
231f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						ch = catedStr.charAt(itemEnd + 1);
232f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						nextKind = classifyCharacter(ch);
233f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						if (nextKind == UCK_NORMAL  ||  nextKind == UCK_QUOTE  ||
234f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling							(nextKind == UCK_COMMA && preserveCommas))
235f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						{
236f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling							continue;
237f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						}
238f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					}
239f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
240f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					// Anything left?
241f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					break; // Have multiple spaces, or a space followed by a
242f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling							// separator.
243f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
244f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				itemValue = catedStr.substring(itemStart, itemEnd);
245f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
246f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			else
247f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
248f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				// Accumulate quoted values into a local string, undoubling
249f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				// internal quotes that
250f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				// match the surrounding quotes. Do not undouble "unmatching"
251f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				// quotes.
252f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
253f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				char openQuote = ch;
254f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				char closeQuote = getClosingQuote(openQuote);
255f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
256f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				itemStart++; // Skip the opening quote;
257f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				itemValue = "";
258f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
259f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				for (itemEnd = itemStart; itemEnd < endPos; itemEnd++)
260f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
261f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					ch = catedStr.charAt(itemEnd);
262f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					charKind = classifyCharacter(ch);
263f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
264f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					if (charKind != UCK_QUOTE || !isSurroundingQuote(ch, openQuote, closeQuote))
265f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					{
266f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						// This is not a matching quote, just append it to the
267f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						// item value.
268f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						itemValue += ch;
269f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					}
270f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					else
271f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					{
272f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						// This is a "matching" quote. Is it doubled, or the
273f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						// final closing quote?
274f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						// Tolerate various edge cases like undoubled opening
275f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						// (non-closing) quotes,
276f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						// or end of input.
277f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
278f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						if ((itemEnd + 1) < endPos)
279f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						{
280f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling							nextChar = catedStr.charAt(itemEnd + 1);
281f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling							nextKind = classifyCharacter(nextChar);
282f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						}
283f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						else
284f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						{
285f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling							nextKind = UCK_SEMICOLON;
286f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling							nextChar = 0x3B;
287f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						}
288f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
289f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						if (ch == nextChar)
290f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						{
291f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling							// This is doubled, copy it and skip the double.
292f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling							itemValue += ch;
293f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling							// Loop will add in charSize.
294f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling							itemEnd++;
295f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						}
296f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						else if (!isClosingingQuote(ch, openQuote, closeQuote))
297f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						{
298f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling							// This is an undoubled, non-closing quote, copy it.
299f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling							itemValue += ch;
300f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						}
301f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						else
302f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						{
303f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling							// This is an undoubled closing quote, skip it and
304f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling							// exit the loop.
305f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling							itemEnd++;
306f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling							break;
307f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						}
308f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					}
309f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
310f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
311f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
312f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// Add the separated item to the array.
313f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// Keep a matching old value in case it had separators.
314f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			int foundIndex = -1;
315f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			for (int oldChild = 1; oldChild <= arrayNode.getChildrenLength(); oldChild++)
316f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
317f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				if (itemValue.equals(arrayNode.getChild(oldChild).getValue()))
318f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
319f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					foundIndex = oldChild;
320f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					break;
321f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
322f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
323f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
324f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			XMPNode newItem = null;
325f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (foundIndex < 0)
326f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
327f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				newItem = new XMPNode(ARRAY_ITEM_NAME, itemValue, null);
328f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				arrayNode.addChild(newItem);
329f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
330f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
331f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
332f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
333f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
334f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
335f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * Utility to find or create the array used by <code>separateArrayItems()</code>.
336f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param schemaNS a the namespace fo the array
337f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param arrayName the name of the array
338f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param arrayOptions the options for the array if newly created
339f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param xmp the xmp object
340f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @return Returns the array node.
341f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws XMPException Forwards exceptions
342f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
343f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private static XMPNode separateFindCreateArray(String schemaNS, String arrayName,
344f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			PropertyOptions arrayOptions, XMPMetaImpl xmp) throws XMPException
345f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
346f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		arrayOptions = XMPNodeUtils.verifySetOptions(arrayOptions, null);
347f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (!arrayOptions.isOnlyArrayOptions())
348f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
349f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			throw new XMPException("Options can only provide array form", XMPError.BADOPTIONS);
350f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
351f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
352f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// Find the array node, make sure it is OK. Move the current children
353f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// aside, to be readded later if kept.
354f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		XMPPath arrayPath = XMPPathParser.expandXPath(schemaNS, arrayName);
355f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		XMPNode arrayNode = XMPNodeUtils.findNode(xmp.getRoot(), arrayPath, false, null);
356f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (arrayNode != null)
357f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
358f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// The array exists, make sure the form is compatible. Zero
359f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// arrayForm means take what exists.
360f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			PropertyOptions arrayForm = arrayNode.getOptions();
361f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (!arrayForm.isArray() || arrayForm.isArrayAlternate())
362f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
363f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				throw new XMPException("Named property must be non-alternate array",
364f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					XMPError.BADXPATH);
365f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
366f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (arrayOptions.equalArrayTypes(arrayForm))
367f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
368f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				throw new XMPException("Mismatch of specified and existing array form",
369f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						XMPError.BADXPATH); // *** Right error?
370f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
371f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
372f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		else
373f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
374f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// The array does not exist, try to create it.
375f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// don't modify the options handed into the method
376f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			arrayNode = XMPNodeUtils.findNode(xmp.getRoot(), arrayPath, true, arrayOptions
377f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					.setArray(true));
378f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (arrayNode == null)
379f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
380f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				throw new XMPException("Failed to create named array", XMPError.BADXPATH);
381f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
382f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
383f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		return arrayNode;
384f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
385f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
386f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
387f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
388f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @see XMPUtils#removeProperties(XMPMeta, String, String, boolean, boolean)
389f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
390f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param xmp
391f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            The XMP object containing the properties to be removed.
392f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
393f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param schemaNS
394f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            Optional schema namespace URI for the properties to be
395f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            removed.
396f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
397f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param propName
398f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            Optional path expression for the property to be removed.
399f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
400f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param doAllProperties
401f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            Option flag to control the deletion: do internal properties in
402f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            addition to external properties.
403f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param includeAliases
404f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            Option flag to control the deletion: Include aliases in the
405f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            "named schema" case above.
406f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws XMPException If metadata processing fails
407f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
408f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	public static void removeProperties(XMPMeta xmp, String schemaNS, String propName,
409f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			boolean doAllProperties, boolean includeAliases) throws XMPException
410f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
411f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		ParameterAsserts.assertImplementation(xmp);
412f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		XMPMetaImpl xmpImpl = (XMPMetaImpl) xmp;
413f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
414f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (propName != null && propName.length() > 0)
415f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
416f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// Remove just the one indicated property. This might be an alias,
417f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// the named schema might not actually exist. So don't lookup the
418f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// schema node.
419f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
420f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (schemaNS == null || schemaNS.length() == 0)
421f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
422f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				throw new XMPException("Property name requires schema namespace",
423f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					XMPError.BADPARAM);
424f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
425f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
426f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			XMPPath expPath = XMPPathParser.expandXPath(schemaNS, propName);
427f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
428f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			XMPNode propNode = XMPNodeUtils.findNode(xmpImpl.getRoot(), expPath, false, null);
429f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (propNode != null)
430f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
431f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				if (doAllProperties
432f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						|| !Utils.isInternalProperty(expPath.getSegment(XMPPath.STEP_SCHEMA)
433f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling								.getName(), expPath.getSegment(XMPPath.STEP_ROOT_PROP).getName()))
434f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
435f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					XMPNode parent = propNode.getParent();
436f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					parent.removeChild(propNode);
437f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					if (parent.getOptions().isSchemaNode()  &&  !parent.hasChildren())
438f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					{
439f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						// remove empty schema node
440f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						parent.getParent().removeChild(parent);
441f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					}
442f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
443f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
444f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
445f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
446f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		else if (schemaNS != null && schemaNS.length() > 0)
447f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
448f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
449f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// Remove all properties from the named schema. Optionally include
450f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// aliases, in which case
451f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// there might not be an actual schema node.
452f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
453f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// XMP_NodePtrPos schemaPos;
454f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			XMPNode schemaNode = XMPNodeUtils.findSchemaNode(xmpImpl.getRoot(), schemaNS, false);
455f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (schemaNode != null)
456f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
457f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				if (removeSchemaChildren(schemaNode, doAllProperties))
458f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
459f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					xmpImpl.getRoot().removeChild(schemaNode);
460f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
461f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
462f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
463f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (includeAliases)
464f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
465f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				// We're removing the aliases also. Look them up by their
466f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				// namespace prefix.
467f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				// But that takes more code and the extra speed isn't worth it.
468f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				// Lookup the XMP node
469f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				// from the alias, to make sure the actual exists.
470f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
471f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				XMPAliasInfo[] aliases = XMPMetaFactory.getSchemaRegistry().findAliases(schemaNS);
472f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				for (int i = 0; i < aliases.length; i++)
473f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
474f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					XMPAliasInfo info = aliases[i];
475f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					XMPPath path = XMPPathParser.expandXPath(info.getNamespace(), info
476f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling							.getPropName());
477f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					XMPNode actualProp = XMPNodeUtils
478f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling							.findNode(xmpImpl.getRoot(), path, false, null);
479f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					if (actualProp != null)
480f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					{
481f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						XMPNode parent = actualProp.getParent();
482f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						parent.removeChild(actualProp);
483f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					}
484f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
485f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
486f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
487f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		else
488f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
489f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// Remove all appropriate properties from all schema. In this case
490f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// we don't have to be
491f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// concerned with aliases, they are handled implicitly from the
492f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// actual properties.
493f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			for (Iterator it = xmpImpl.getRoot().iterateChildren(); it.hasNext();)
494f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
495f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				XMPNode schema = (XMPNode) it.next();
496f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				if (removeSchemaChildren(schema, doAllProperties))
497f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
498f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					it.remove();
499f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
500f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
501f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
502f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
503f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
504f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
505f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
506f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @see XMPUtils#appendProperties(XMPMeta, XMPMeta, boolean, boolean)
507f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param source The source XMP object.
508f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param destination The destination XMP object.
509f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param doAllProperties Do internal properties in addition to external properties.
510f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param replaceOldValues Replace the values of existing properties.
511f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param deleteEmptyValues Delete destination values if source property is empty.
512f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws XMPException Forwards the Exceptions from the metadata processing
513f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
514f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	public static void appendProperties(XMPMeta source, XMPMeta destination,
515f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			boolean doAllProperties, boolean replaceOldValues, boolean deleteEmptyValues)
516f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		throws XMPException
517f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
518f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		ParameterAsserts.assertImplementation(source);
519f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		ParameterAsserts.assertImplementation(destination);
520f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
521f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		XMPMetaImpl src = (XMPMetaImpl) source;
522f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		XMPMetaImpl dest = (XMPMetaImpl) destination;
523f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
524f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		for (Iterator it = src.getRoot().iterateChildren(); it.hasNext();)
525f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
526f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			XMPNode sourceSchema = (XMPNode) it.next();
527f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
528f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// Make sure we have a destination schema node
529f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			XMPNode destSchema = XMPNodeUtils.findSchemaNode(dest.getRoot(),
530f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					sourceSchema.getName(), false);
531f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			boolean createdSchema = false;
532f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (destSchema == null)
533f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
534f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				destSchema = new XMPNode(sourceSchema.getName(), sourceSchema.getValue(),
535f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						new PropertyOptions().setSchemaNode(true));
536f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				dest.getRoot().addChild(destSchema);
537f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				createdSchema = true;
538f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
539f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
540f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// Process the source schema's children.
541f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			for (Iterator ic = sourceSchema.iterateChildren(); ic.hasNext();)
542f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
543f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				XMPNode sourceProp = (XMPNode) ic.next();
544f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				if (doAllProperties
545f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						|| !Utils.isInternalProperty(sourceSchema.getName(), sourceProp.getName()))
546f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
547f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					appendSubtree(
548f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						dest, sourceProp, destSchema, replaceOldValues, deleteEmptyValues);
549f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
550f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
551f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
552f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (!destSchema.hasChildren()  &&  (createdSchema  ||  deleteEmptyValues))
553f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
554f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				// Don't create an empty schema / remove empty schema.
555f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				dest.getRoot().removeChild(destSchema);
556f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
557f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
558f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
559f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
560f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
561f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
562f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * Remove all schema children according to the flag
563f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * <code>doAllProperties</code>. Empty schemas are automatically remove
564f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * by <code>XMPNode</code>
565f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
566f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param schemaNode
567f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            a schema node
568f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param doAllProperties
569f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            flag if all properties or only externals shall be removed.
570f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @return Returns true if the schema is empty after the operation.
571f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
572f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private static boolean removeSchemaChildren(XMPNode schemaNode, boolean doAllProperties)
573f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
574f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		for (Iterator it = schemaNode.iterateChildren(); it.hasNext();)
575f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
576f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			XMPNode currProp = (XMPNode) it.next();
577f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (doAllProperties
578f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					|| !Utils.isInternalProperty(schemaNode.getName(), currProp.getName()))
579f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
580f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				it.remove();
581f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
582f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
583f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
584f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		return !schemaNode.hasChildren();
585f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
586f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
587f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
588f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
589f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @see XMPUtilsImpl#appendProperties(XMPMeta, XMPMeta, boolean, boolean, boolean)
590f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param destXMP The destination XMP object.
591f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param sourceNode the source node
592f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param destParent the parent of the destination node
593f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param replaceOldValues Replace the values of existing properties.
594f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param deleteEmptyValues flag if properties with empty values should be deleted
595f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * 		   in the destination object.
596f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws XMPException
597f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
598f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private static void appendSubtree(XMPMetaImpl destXMP, XMPNode sourceNode, XMPNode destParent,
599f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			boolean replaceOldValues, boolean deleteEmptyValues) throws XMPException
600f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
601f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		XMPNode destNode = XMPNodeUtils.findChildNode(destParent, sourceNode.getName(), false);
602f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
603f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		boolean valueIsEmpty = false;
604f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (deleteEmptyValues)
605f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
606f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			valueIsEmpty = sourceNode.getOptions().isSimple() ?
607f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				sourceNode.getValue() == null  ||  sourceNode.getValue().length() == 0 :
608f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				!sourceNode.hasChildren();
609f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
610f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
611f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (deleteEmptyValues  &&  valueIsEmpty)
612f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
613f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (destNode != null)
614f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
615f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				destParent.removeChild(destNode);
616f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
617f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
618f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		else if (destNode == null)
619f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
620f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// The one easy case, the destination does not exist.
621f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			destParent.addChild((XMPNode) sourceNode.clone());
622f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
623f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		else if (replaceOldValues)
624f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
625f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// The destination exists and should be replaced.
626f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			destXMP.setNode(destNode, sourceNode.getValue(), sourceNode.getOptions(), true);
627f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			destParent.removeChild(destNode);
628f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			destNode = (XMPNode) sourceNode.clone();
629f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			destParent.addChild(destNode);
630f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
631f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		else
632f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
633f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// The destination exists and is not totally replaced. Structs and
634f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// arrays are merged.
635f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
636f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			PropertyOptions sourceForm = sourceNode.getOptions();
637f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			PropertyOptions destForm = destNode.getOptions();
638f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (sourceForm != destForm)
639f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
640f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				return;
641f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
642f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (sourceForm.isStruct())
643f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
644f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				// To merge a struct process the fields recursively. E.g. add simple missing fields.
645f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				// The recursive call to AppendSubtree will handle deletion for fields with empty
646f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				// values.
647f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				for (Iterator it = sourceNode.iterateChildren(); it.hasNext();)
648f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
649f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					XMPNode sourceField = (XMPNode) it.next();
650f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					appendSubtree(destXMP, sourceField, destNode,
651f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						replaceOldValues, deleteEmptyValues);
652f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					if (deleteEmptyValues  &&  !destNode.hasChildren())
653f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					{
654f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						destParent.removeChild(destNode);
655f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					}
656f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
657f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
658f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			else if (sourceForm.isArrayAltText())
659f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
660f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				// Merge AltText arrays by the "xml:lang" qualifiers. Make sure x-default is first.
661f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				// Make a special check for deletion of empty values. Meaningful in AltText arrays
662f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				// because the "xml:lang" qualifier provides unambiguous source/dest correspondence.
663f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				for (Iterator it = sourceNode.iterateChildren(); it.hasNext();)
664f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
665f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					XMPNode sourceItem = (XMPNode) it.next();
666f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					if (!sourceItem.hasQualifier()
667f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling							|| !XMPConst.XML_LANG.equals(sourceItem.getQualifier(1).getName()))
668f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					{
669f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						continue;
670f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					}
671f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
672f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					int destIndex = XMPNodeUtils.lookupLanguageItem(destNode,
673f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling							sourceItem.getQualifier(1).getValue());
674f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					if (deleteEmptyValues  &&
675f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling							(sourceItem.getValue() == null  ||
676f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling							 sourceItem.getValue().length() == 0))
677f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					{
678f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						if (destIndex != -1)
679f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						{
680f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling							destNode.removeChild(destIndex);
681f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling							if (!destNode.hasChildren())
682f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling							{
683f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling								destParent.removeChild(destNode);
684f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling							}
685f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						}
686f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					}
687f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					else if (destIndex == -1)
688f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					{
689f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						// Not replacing, keep the existing item.
690f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						if (!XMPConst.X_DEFAULT.equals(sourceItem.getQualifier(1).getValue())
691f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling								|| !destNode.hasChildren())
692f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						{
693f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling							sourceItem.cloneSubtree(destNode);
694f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						}
695f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						else
696f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						{
697f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling							XMPNode destItem = new XMPNode(
698f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling								sourceItem.getName(),
699f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling								sourceItem.getValue(),
700f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling								sourceItem.getOptions());
701f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling							sourceItem.cloneSubtree(destItem);
702f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling							destNode.addChild(1, destItem);
703f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						}
704f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					}
705f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
706f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
707f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			else if (sourceForm.isArray())
708f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
709f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				// Merge other arrays by item values. Don't worry about order or duplicates. Source
710f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				// items with empty values do not cause deletion, that conflicts horribly with
711f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				// merging.
712f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
713f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				for (Iterator is = sourceNode.iterateChildren(); is.hasNext();)
714f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
715f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					XMPNode sourceItem = (XMPNode) is.next();
716f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
717f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					boolean match = false;
718f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					for (Iterator id = destNode.iterateChildren(); id.hasNext();)
719f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					{
720f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						XMPNode destItem = (XMPNode) id.next();
721f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						if (itemValuesMatch(sourceItem, destItem))
722f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						{
723f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling							match = true;
724f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						}
725f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					}
726f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					if (!match)
727f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					{
728f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						destNode = (XMPNode) sourceItem.clone();
729f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						destParent.addChild(destNode);
730f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					}
731f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
732f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
733f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
734f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
735f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
736f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
737f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
738f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * Compares two nodes including its children and qualifier.
739f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param leftNode an <code>XMPNode</code>
740f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param rightNode an <code>XMPNode</code>
741f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @return Returns true if the nodes are equal, false otherwise.
742f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws XMPException Forwards exceptions to the calling method.
743f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
744f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private static boolean itemValuesMatch(XMPNode leftNode, XMPNode rightNode) throws XMPException
745f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
746f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		PropertyOptions leftForm = leftNode.getOptions();
747f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		PropertyOptions rightForm = rightNode.getOptions();
748f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
749f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (leftForm.equals(rightForm))
750f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
751f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			return false;
752f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
753f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
754f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (leftForm.getOptions() == 0)
755f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
756f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// Simple nodes, check the values and xml:lang qualifiers.
757f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (!leftNode.getValue().equals(rightNode.getValue()))
758f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
759f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				return false;
760f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
761f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (leftNode.getOptions().getHasLanguage() != rightNode.getOptions().getHasLanguage())
762f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
763f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				return false;
764f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
765f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (leftNode.getOptions().getHasLanguage()
766f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					&& !leftNode.getQualifier(1).getValue().equals(
767f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling							rightNode.getQualifier(1).getValue()))
768f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
769f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				return false;
770f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
771f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
772f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		else if (leftForm.isStruct())
773f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
774f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// Struct nodes, see if all fields match, ignoring order.
775f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
776f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (leftNode.getChildrenLength() != rightNode.getChildrenLength())
777f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
778f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				return false;
779f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
780f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
781f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			for (Iterator it = leftNode.iterateChildren(); it.hasNext();)
782f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
783f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				XMPNode leftField = (XMPNode) it.next();
784f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				XMPNode rightField = XMPNodeUtils.findChildNode(rightNode, leftField.getName(),
785f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						false);
786f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				if (rightField == null || !itemValuesMatch(leftField, rightField))
787f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
788f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					return false;
789f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
790f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
791f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
792f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		else
793f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
794f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// Array nodes, see if the "leftNode" values are present in the
795f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// "rightNode", ignoring order, duplicates,
796f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// and extra values in the rightNode-> The rightNode is the
797f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// destination for AppendProperties.
798f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
799f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			assert leftForm.isArray();
800f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
801f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			for (Iterator il = leftNode.iterateChildren(); il.hasNext();)
802f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
803f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				XMPNode leftItem = (XMPNode) il.next();
804f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
805f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				boolean match = false;
806f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				for (Iterator ir = rightNode.iterateChildren(); ir.hasNext();)
807f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
808f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					XMPNode rightItem = (XMPNode) ir.next();
809f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					if (itemValuesMatch(leftItem, rightItem))
810f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					{
811f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						match = true;
812f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						break;
813f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					}
814f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
815f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				if (!match)
816f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
817f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					return false;
818f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
819f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
820f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
821f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		return true; // All of the checks passed.
822f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
823f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
824f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
825f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
826f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * Make sure the separator is OK. It must be one semicolon surrounded by
827f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * zero or more spaces. Any of the recognized semicolons or spaces are
828f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * allowed.
829f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
830f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param separator
831f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws XMPException
832f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
833f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private static void checkSeparator(String separator) throws XMPException
834f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
835f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		boolean haveSemicolon = false;
836f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		for (int i = 0; i < separator.length(); i++)
837f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
838f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			int charKind = classifyCharacter(separator.charAt(i));
839f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (charKind == UCK_SEMICOLON)
840f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
841f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				if (haveSemicolon)
842f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
843f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					throw new XMPException("Separator can have only one semicolon",
844f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						XMPError.BADPARAM);
845f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
846f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				haveSemicolon = true;
847f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
848f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			else if (charKind != UCK_SPACE)
849f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
850f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				throw new XMPException("Separator can have only spaces and one semicolon",
851f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						XMPError.BADPARAM);
852f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
853f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
854f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (!haveSemicolon)
855f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
856f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			throw new XMPException("Separator must have one semicolon", XMPError.BADPARAM);
857f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
858f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
859f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
860f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
861f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
862f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * Make sure the open and close quotes are a legitimate pair and return the
863f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * correct closing quote or an exception.
864f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
865f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param quotes
866f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            opened and closing quote in a string
867f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param openQuote
868f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            the open quote
869f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @return Returns a corresponding closing quote.
870f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws XMPException
871f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
872f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private static char checkQuotes(String quotes, char openQuote) throws XMPException
873f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
874f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		char closeQuote;
875f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
876f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		int charKind = classifyCharacter(openQuote);
877f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (charKind != UCK_QUOTE)
878f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
879f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			throw new XMPException("Invalid quoting character", XMPError.BADPARAM);
880f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
881f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
882f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (quotes.length() == 1)
883f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
884f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			closeQuote = openQuote;
885f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
886f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		else
887f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
888f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			closeQuote = quotes.charAt(1);
889f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			charKind = classifyCharacter(closeQuote);
890f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (charKind != UCK_QUOTE)
891f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
892f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				throw new XMPException("Invalid quoting character", XMPError.BADPARAM);
893f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
894f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
895f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
896f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (closeQuote != getClosingQuote(openQuote))
897f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
898f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			throw new XMPException("Mismatched quote pair", XMPError.BADPARAM);
899f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
900f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		return closeQuote;
901f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
902f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
903f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
904f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
905f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * Classifies the character into normal chars, spaces, semicola, quotes,
906f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * control chars.
907f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
908f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param ch
909f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            a char
910f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @return Return the character kind.
911f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
912f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private static int classifyCharacter(char ch)
913f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
914f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (SPACES.indexOf(ch) >= 0 || (0x2000 <= ch && ch <= 0x200B))
915f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
916f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			return UCK_SPACE;
917f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
918f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		else if (COMMAS.indexOf(ch) >= 0)
919f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
920f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			return UCK_COMMA;
921f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
922f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		else if (SEMICOLA.indexOf(ch) >= 0)
923f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
924f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			return UCK_SEMICOLON;
925f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
926f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		else if (QUOTES.indexOf(ch) >= 0 || (0x3008 <= ch && ch <= 0x300F)
927f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				|| (0x2018 <= ch && ch <= 0x201F))
928f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
929f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			return UCK_QUOTE;
930f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
931f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		else if (ch < 0x0020 || CONTROLS.indexOf(ch) >= 0)
932f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
933f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			return UCK_CONTROL;
934f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
935f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		else
936f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
937f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// Assume typical case.
938f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			return UCK_NORMAL;
939f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
940f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
941f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
942f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
943f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
944f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param openQuote
945f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            the open quote char
946f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @return Returns the matching closing quote for an open quote.
947f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
948f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private static char getClosingQuote(char openQuote)
949f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
950f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		switch (openQuote)
951f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
952f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		case 0x0022:
953f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			return 0x0022; // ! U+0022 is both opening and closing.
954f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		case 0x005B:
955f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			return 0x005D;
956f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		case 0x00AB:
957f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			return 0x00BB; // ! U+00AB and U+00BB are reversible.
958f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		case 0x00BB:
959f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			return 0x00AB;
960f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		case 0x2015:
961f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			return 0x2015; // ! U+2015 is both opening and closing.
962f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		case 0x2018:
963f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			return 0x2019;
964f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		case 0x201A:
965f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			return 0x201B;
966f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		case 0x201C:
967f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			return 0x201D;
968f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		case 0x201E:
969f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			return 0x201F;
970f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		case 0x2039:
971f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			return 0x203A; // ! U+2039 and U+203A are reversible.
972f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		case 0x203A:
973f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			return 0x2039;
974f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		case 0x3008:
975f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			return 0x3009;
976f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		case 0x300A:
977f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			return 0x300B;
978f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		case 0x300C:
979f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			return 0x300D;
980f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		case 0x300E:
981f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			return 0x300F;
982f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		case 0x301D:
983f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			return 0x301F; // ! U+301E also closes U+301D.
984f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		default:
985f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			return 0;
986f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
987f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
988f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
989f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
990f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
991f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * Add quotes to the item.
992f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
993f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param item
994f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            the array item
995f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param openQuote
996f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            the open quote character
997f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param closeQuote
998f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            the closing quote character
999f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param allowCommas
1000f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *            flag if commas are allowed
1001f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @return Returns the value in quotes.
1002f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
1003f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private static String applyQuotes(String item, char openQuote, char closeQuote,
1004f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			boolean allowCommas)
1005f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
1006f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (item == null)
1007f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
1008f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			item = "";
1009f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
1010f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1011f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		boolean prevSpace = false;
1012f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		int charOffset;
1013f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		int charKind;
1014f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1015f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// See if there are any separators in the value. Stop at the first
1016f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// occurrance. This is a bit
1017f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// tricky in order to make typical typing work conveniently. The purpose
1018f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// of applying quotes
1019f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// is to preserve the values when splitting them back apart. That is
1020f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// CatenateContainerItems
1021f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// and SeparateContainerItems must round trip properly. For the most
1022f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// part we only look for
1023f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// separators here. Internal quotes, as in -- Irving "Bud" Jones --
1024f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// won't cause problems in
1025f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// the separation. An initial quote will though, it will make the value
1026f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// look quoted.
1027f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1028f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		int i;
1029f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		for (i = 0; i < item.length(); i++)
1030f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
1031f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			char ch = item.charAt(i);
1032f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			charKind = classifyCharacter(ch);
1033f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (i == 0 && charKind == UCK_QUOTE)
1034f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
1035f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				break;
1036f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
1037f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1038f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (charKind == UCK_SPACE)
1039f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
1040f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				// Multiple spaces are a separator.
1041f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				if (prevSpace)
1042f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
1043f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					break;
1044f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
1045f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				prevSpace = true;
1046f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
1047f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			else
1048f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
1049f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				prevSpace = false;
1050f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				if ((charKind == UCK_SEMICOLON || charKind == UCK_CONTROL)
1051f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						|| (charKind == UCK_COMMA && !allowCommas))
1052f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
1053f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					break;
1054f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
1055f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
1056f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
1057f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1058f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1059f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (i < item.length())
1060f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
1061f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// Create a quoted copy, doubling any internal quotes that match the
1062f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// outer ones. Internal quotes did not stop the "needs quoting"
1063f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// search, but they do need
1064f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// doubling. So we have to rescan the front of the string for
1065f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// quotes. Handle the special
1066f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// case of U+301D being closed by either U+301E or U+301F.
1067f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1068f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			StringBuffer newItem = new StringBuffer(item.length() + 2);
1069f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			int splitPoint;
1070f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			for (splitPoint = 0; splitPoint <= i; splitPoint++)
1071f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
1072f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				if (classifyCharacter(item.charAt(i)) == UCK_QUOTE)
1073f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
1074f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					break;
1075f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
1076f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
1077f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1078f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// Copy the leading "normal" portion.
1079f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			newItem.append(openQuote).append(item.substring(0, splitPoint));
1080f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1081f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			for (charOffset = splitPoint; charOffset < item.length(); charOffset++)
1082f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
1083f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				newItem.append(item.charAt(charOffset));
1084f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				if (classifyCharacter(item.charAt(charOffset)) == UCK_QUOTE
1085f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						&& isSurroundingQuote(item.charAt(charOffset), openQuote, closeQuote))
1086f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
1087f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					newItem.append(item.charAt(charOffset));
1088f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
1089f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
1090f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1091f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			newItem.append(closeQuote);
1092f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1093f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			item = newItem.toString();
1094f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
1095f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1096f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		return item;
1097f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
1098f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1099f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1100f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
1101f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param ch a character
1102f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param openQuote the opening quote char
1103f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param closeQuote the closing quote char
1104f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @return Return it the character is a surrounding quote.
1105f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
1106f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private static boolean isSurroundingQuote(char ch, char openQuote, char closeQuote)
1107f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
1108f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		return ch == openQuote || isClosingingQuote(ch, openQuote, closeQuote);
1109f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
1110f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1111f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1112f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
1113f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param ch a character
1114f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param openQuote the opening quote char
1115f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param closeQuote the closing quote char
1116f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @return Returns true if the character is a closing quote.
1117f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
1118f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private static boolean isClosingingQuote(char ch, char openQuote, char closeQuote)
1119f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
1120f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		return ch == closeQuote || (openQuote == 0x301D && ch == 0x301E || ch == 0x301F);
1121f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
1122f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1123f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1124f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1125f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
1126f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * U+0022 ASCII space<br>
1127f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * U+3000, ideographic space<br>
1128f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * U+303F, ideographic half fill space<br>
1129f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * U+2000..U+200B, en quad through zero width space
1130f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
1131f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private static final String SPACES = "\u0020\u3000\u303F";
1132f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
1133f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * U+002C, ASCII comma<br>
1134f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * U+FF0C, full width comma<br>
1135f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * U+FF64, half width ideographic comma<br>
1136f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * U+FE50, small comma<br>
1137f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * U+FE51, small ideographic comma<br>
1138f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * U+3001, ideographic comma<br>
1139f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * U+060C, Arabic comma<br>
1140f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * U+055D, Armenian comma
1141f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
1142f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private static final String COMMAS = "\u002C\uFF0C\uFF64\uFE50\uFE51\u3001\u060C\u055D";
1143f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
1144f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * U+003B, ASCII semicolon<br>
1145f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * U+FF1B, full width semicolon<br>
1146f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * U+FE54, small semicolon<br>
1147f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * U+061B, Arabic semicolon<br>
1148f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * U+037E, Greek "semicolon" (really a question mark)
1149f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
1150f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private static final String SEMICOLA = "\u003B\uFF1B\uFE54\u061B\u037E";
1151f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
1152f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * U+0022 ASCII quote<br>
1153f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * ASCII '[' (0x5B) and ']' (0x5D) are used as quotes in Chinese and
1154f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * Korean.<br>
1155f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * U+00AB and U+00BB, guillemet quotes<br>
1156f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * U+3008..U+300F, various quotes.<br>
1157f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * U+301D..U+301F, double prime quotes.<br>
1158f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * U+2015, dash quote.<br>
1159f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * U+2018..U+201F, various quotes.<br>
1160f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * U+2039 and U+203A, guillemet quotes.
1161f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
1162f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private static final String QUOTES =
1163f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		"\"\u005B\u005D\u00AB\u00BB\u301D\u301E\u301F\u2015\u2039\u203A";
1164f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
1165f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * U+0000..U+001F ASCII controls<br>
1166f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * U+2028, line separator.<br>
1167f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * U+2029, paragraph separator.
1168f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
1169f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private static final String CONTROLS = "\u2028\u2029";
1170f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling}
1171