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 Haeberlingpackage com.adobe.xmp.impl;
11f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
12f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberlingimport java.io.IOException;
13f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberlingimport java.io.OutputStream;
14f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberlingimport java.io.OutputStreamWriter;
15f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberlingimport java.util.Arrays;
16f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberlingimport java.util.HashSet;
17f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberlingimport java.util.Iterator;
18f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberlingimport java.util.Set;
19f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
20f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberlingimport com.adobe.xmp.XMPConst;
21f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberlingimport com.adobe.xmp.XMPError;
22f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberlingimport com.adobe.xmp.XMPException;
23f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberlingimport com.adobe.xmp.XMPMeta;
24f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberlingimport com.adobe.xmp.XMPMetaFactory;
25f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberlingimport com.adobe.xmp.options.SerializeOptions;
26f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
27f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
28f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling/**
29f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling * Serializes the <code>XMPMeta</code>-object using the standard RDF serialization format.
30f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling * The output is written to an <code>OutputStream</code>
31f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling * according to the <code>SerializeOptions</code>.
32f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling *
33f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling * @since   11.07.2006
34f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling */
35f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberlingpublic class XMPSerializerRDF
36f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling{
37f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/** default padding */
38f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private static final int DEFAULT_PAD = 2048;
39f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/** */
40f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private static final String PACKET_HEADER  =
41f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		"<?xpacket begin=\"\uFEFF\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>";
42f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/** The w/r is missing inbetween */
43f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private static final String PACKET_TRAILER = "<?xpacket end=\"";
44f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/** */
45f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private static final String PACKET_TRAILER2 = "\"?>";
46f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/** */
47f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private static final String RDF_XMPMETA_START =
48f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		"<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"";
49f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/** */
50f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private static final String RDF_XMPMETA_END   = "</x:xmpmeta>";
51f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/** */
52f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private static final String RDF_RDF_START =
53f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		"<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">";
54f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/** */
55f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private static final String RDF_RDF_END       = "</rdf:RDF>";
56f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
57f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/** */
58f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private static final String RDF_SCHEMA_START  = "<rdf:Description rdf:about=";
59f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/** */
60f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private static final String RDF_SCHEMA_END    = "</rdf:Description>";
61f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/** */
62f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private static final String RDF_STRUCT_START  = "<rdf:Description";
63f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/** */
64f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private static final String RDF_STRUCT_END    = "</rdf:Description>";
65f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/** a set of all rdf attribute qualifier */
66f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	static final Set RDF_ATTR_QUALIFIER = new HashSet(Arrays.asList(new String[] {
67f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			XMPConst.XML_LANG, "rdf:resource", "rdf:ID", "rdf:bagID", "rdf:nodeID" }));
68f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
69f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/** the metadata object to be serialized. */
70f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private XMPMetaImpl xmp;
71f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/** the output stream to serialize to */
72f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private CountOutputStream outputStream;
73f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/** this writer is used to do the actual serialisation */
74f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private OutputStreamWriter writer;
75f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/** the stored serialisation options */
76f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private SerializeOptions options;
77f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/** the size of one unicode char, for UTF-8 set to 1
78f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *  (Note: only valid for ASCII chars lower than 0x80),
79f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *  set to 2 in case of UTF-16 */
80f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private int unicodeSize = 1; // UTF-8
81f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/** the padding in the XMP Packet, or the length of the complete packet in
82f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *  case of option <em>exactPacketLength</em>. */
83f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private int padding;
84f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
85f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
86f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
87f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * The actual serialisation.
88f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
89f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param xmp the metadata object to be serialized
90f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param out outputStream the output stream to serialize to
91f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param options the serialization options
92f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
93f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws XMPException If case of wrong options or any other serialisaton error.
94f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
95f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	public void serialize(XMPMeta xmp, OutputStream out,
96f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			SerializeOptions options) throws XMPException
97f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
98f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		try
99f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
100f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			outputStream = new CountOutputStream(out);
101f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			writer = new OutputStreamWriter(outputStream, options.getEncoding());
102f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
103f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			this.xmp = (XMPMetaImpl) xmp;
104f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			this.options = options;
105f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			this.padding = options.getPadding();
106f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
107f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			writer = new OutputStreamWriter(outputStream, options.getEncoding());
108f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
109f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			checkOptionsConsistence();
110f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
111f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// serializes the whole packet, but don't write the tail yet
112f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// and flush to make sure that the written bytes are calculated correctly
113f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			String tailStr = serializeAsRDF();
114f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			writer.flush();
115f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
116f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// adds padding
117f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			addPadding(tailStr.length());
118f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
119f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// writes the tail
120f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			write(tailStr);
121f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			writer.flush();
122f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
123f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			outputStream.close();
124f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
125f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		catch (IOException e)
126f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
127f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			throw new XMPException("Error writing to the OutputStream", XMPError.UNKNOWN);
128f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
129f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
130f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
131f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
132f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
133f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * Calulates the padding according to the options and write it to the stream.
134f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param tailLength the length of the tail string
135f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws XMPException thrown if packet size is to small to fit the padding
136f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws IOException forwards writer errors
137f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
138f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private void addPadding(int tailLength) throws XMPException, IOException
139f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
140f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (options.getExactPacketLength())
141f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
142f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// the string length is equal to the length of the UTF-8 encoding
143f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			int minSize = outputStream.getBytesWritten() + tailLength * unicodeSize;
144f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (minSize > padding)
145f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
146f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				throw new XMPException("Can't fit into specified packet size",
147f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					XMPError.BADSERIALIZE);
148f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
149f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			padding -= minSize;	// Now the actual amount of padding to add.
150f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
151f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
152f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// fix rest of the padding according to Unicode unit size.
153f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		padding /= unicodeSize;
154f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
155f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		int newlineLen = options.getNewline().length();
156f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (padding >= newlineLen)
157f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
158f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			padding -= newlineLen;	// Write this newline last.
159f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			while (padding >= (100 + newlineLen))
160f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
161f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				writeChars(100, ' ');
162f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				writeNewline();
163f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				padding -= (100 + newlineLen);
164f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
165f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			writeChars(padding, ' ');
166f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			writeNewline();
167f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
168f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		else
169f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
170f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			writeChars(padding, ' ');
171f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
172f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
173f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
174f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
175f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
176f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * Checks if the supplied options are consistent.
177f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws XMPException Thrown if options are conflicting
178f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
179f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	protected void checkOptionsConsistence() throws XMPException
180f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
181f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (options.getEncodeUTF16BE() | options.getEncodeUTF16LE())
182f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
183f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			unicodeSize = 2;
184f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
185f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
186f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (options.getExactPacketLength())
187f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
188f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (options.getOmitPacketWrapper() | options.getIncludeThumbnailPad())
189f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
190f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				throw new XMPException("Inconsistent options for exact size serialize",
191f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						XMPError.BADOPTIONS);
192f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
193f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if ((options.getPadding() & (unicodeSize - 1)) != 0)
194f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
195f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				throw new XMPException("Exact size must be a multiple of the Unicode element",
196f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						XMPError.BADOPTIONS);
197f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
198f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
199f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		else if (options.getReadOnlyPacket())
200f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
201f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (options.getOmitPacketWrapper() | options.getIncludeThumbnailPad())
202f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
203f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				throw new XMPException("Inconsistent options for read-only packet",
204f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						XMPError.BADOPTIONS);
205f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
206f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			padding = 0;
207f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
208f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		else if (options.getOmitPacketWrapper())
209f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
210f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (options.getIncludeThumbnailPad())
211f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
212f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				throw new XMPException("Inconsistent options for non-packet serialize",
213f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						XMPError.BADOPTIONS);
214f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
215f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			padding = 0;
216f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
217f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		else
218f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
219f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (padding == 0)
220f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
221f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				padding = DEFAULT_PAD * unicodeSize;
222f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
223f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
224f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (options.getIncludeThumbnailPad())
225f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
226f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				if (!xmp.doesPropertyExist(XMPConst.NS_XMP, "Thumbnails"))
227f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
228f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					padding += 10000 * unicodeSize;
229f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
230f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
231f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
232f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
233f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
234f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
235f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
236f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * Writes the (optional) packet header and the outer rdf-tags.
237f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @return Returns the packet end processing instraction to be written after the padding.
238f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws IOException Forwarded writer exceptions.
239f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws XMPException
240f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
241f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private String serializeAsRDF() throws IOException, XMPException
242f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
243f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// Write the packet header PI.
244f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (!options.getOmitPacketWrapper())
245f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
246f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			writeIndent(0);
247f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			write(PACKET_HEADER);
248f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			writeNewline();
249f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
250f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
251f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// Write the xmpmeta element's start tag.
252f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		writeIndent(0);
253f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		write(RDF_XMPMETA_START);
254f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// Note: this flag can only be set by unit tests
255f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (!options.getOmitVersionAttribute())
256f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
257f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			write(XMPMetaFactory.getVersionInfo().getMessage());
258f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
259f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		write("\">");
260f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		writeNewline();
261f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
262f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// Write the rdf:RDF start tag.
263f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		writeIndent(1);
264f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		write(RDF_RDF_START);
265f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		writeNewline();
266f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
267f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// Write all of the properties.
268f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (options.getUseCompactFormat())
269f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
270f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			serializeCompactRDFSchemas();
271f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
272f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		else
273f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
274f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			serializePrettyRDFSchemas();
275f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
276f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
277f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// Write the rdf:RDF end tag.
278f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		writeIndent(1);
279f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		write(RDF_RDF_END);
280f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		writeNewline();
281f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
282f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// Write the xmpmeta end tag.
283f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		writeIndent(0);
284f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		write(RDF_XMPMETA_END);
285f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		writeNewline();
286f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
287f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// Write the packet trailer PI into the tail string as UTF-8.
288f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		String tailStr = "";
289f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (!options.getOmitPacketWrapper())
290f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
291f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			for (int level = options.getBaseIndent(); level > 0; level--)
292f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
293f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				tailStr += options.getIndent();
294f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
295f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
296f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			tailStr += PACKET_TRAILER;
297f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			tailStr += options.getReadOnlyPacket() ? 'r' : 'w';
298f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			tailStr += PACKET_TRAILER2;
299f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
300f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
301f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		return tailStr;
302f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
303f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
304f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
305f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
306f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * Serializes the metadata in pretty-printed manner.
307f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws IOException Forwarded writer exceptions
308f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws XMPException
309f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
310f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private void serializePrettyRDFSchemas() throws IOException, XMPException
311f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
312f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (xmp.getRoot().getChildrenLength() > 0)
313f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
314f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			for (Iterator it = xmp.getRoot().iterateChildren(); it.hasNext(); )
315f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
316f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				XMPNode currSchema = (XMPNode) it.next();
317f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				serializePrettyRDFSchema(currSchema);
318f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
319f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
320f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		else
321f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
322f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			writeIndent(2);
323f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			write(RDF_SCHEMA_START); // Special case an empty XMP object.
324f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			writeTreeName();
325f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			write("/>");
326f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			writeNewline();
327f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
328f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
329f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
330f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
331f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
332f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws IOException
333f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
334f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private void writeTreeName() throws IOException
335f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
336f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		write('"');
337f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		String name = xmp.getRoot().getName();
338f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (name != null)
339f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
340f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			appendNodeValue(name, true);
341f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
342f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		write('"');
343f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
344f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
345f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
346f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
347f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * Serializes the metadata in compact manner.
348f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws IOException Forwarded writer exceptions
349f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws XMPException
350f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
351f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private void serializeCompactRDFSchemas() throws IOException, XMPException
352f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
353f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// Begin the rdf:Description start tag.
354f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		writeIndent(2);
355f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		write(RDF_SCHEMA_START);
356f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		writeTreeName();
357f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
358f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// Write all necessary xmlns attributes.
359f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		Set usedPrefixes = new HashSet();
360f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		usedPrefixes.add("xml");
361f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		usedPrefixes.add("rdf");
362f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
363f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		for (Iterator it = xmp.getRoot().iterateChildren(); it.hasNext();)
364f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
365f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			XMPNode schema = (XMPNode) it.next();
366f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			declareUsedNamespaces(schema, usedPrefixes, 4);
367f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
368f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
369f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// Write the top level "attrProps" and close the rdf:Description start tag.
370f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		boolean allAreAttrs = true;
371f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		for (Iterator it = xmp.getRoot().iterateChildren(); it.hasNext();)
372f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
373f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			XMPNode schema = (XMPNode) it.next();
374f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			allAreAttrs &= serializeCompactRDFAttrProps (schema, 3);
375f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
376f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
377f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (!allAreAttrs)
378f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
379f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			write('>');
380f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			writeNewline();
381f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
382f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		else
383f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
384f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			write("/>");
385f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			writeNewline();
386f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			return;	// ! Done if all properties in all schema are written as attributes.
387f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
388f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
389f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// Write the remaining properties for each schema.
390f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		for (Iterator it = xmp.getRoot().iterateChildren(); it.hasNext();)
391f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
392f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			XMPNode schema = (XMPNode) it.next();
393f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			serializeCompactRDFElementProps (schema, 3);
394f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
395f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
396f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// Write the rdf:Description end tag.
397f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		writeIndent(2);
398f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		write(RDF_SCHEMA_END);
399f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		writeNewline();
400f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
401f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
402f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
403f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
404f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
405f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * Write each of the parent's simple unqualified properties as an attribute. Returns true if all
406f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * of the properties are written as attributes.
407f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
408f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param parentNode the parent property node
409f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param indent the current indent level
410f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @return Returns true if all properties can be rendered as RDF attribute.
411f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws IOException
412f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
413f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private boolean serializeCompactRDFAttrProps(XMPNode parentNode, int indent) throws IOException
414f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
415f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		boolean allAreAttrs = true;
416f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
417f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		for (Iterator it = parentNode.iterateChildren(); it.hasNext();)
418f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
419f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			XMPNode prop = (XMPNode) it.next();
420f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
421f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (canBeRDFAttrProp(prop))
422f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
423f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				writeNewline();
424f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				writeIndent(indent);
425f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				write(prop.getName());
426f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				write("=\"");
427f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				appendNodeValue(prop.getValue(), true);
428f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				write('"');
429f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
430f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			else
431f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
432f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				allAreAttrs = false;
433f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
434f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
435f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		return allAreAttrs;
436f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
437f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
438f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
439f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
440f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * Recursively handles the "value" for a node that must be written as an RDF
441f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * property element. It does not matter if it is a top level property, a
442f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * field of a struct, or an item of an array. The indent is that for the
443f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * property element. The patterns bwlow ignore attribute qualifiers such as
444f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * xml:lang, they don't affect the output form.
445f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
446f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * <blockquote>
447f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
448f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * <pre>
449f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *  	&lt;ns:UnqualifiedStructProperty-1
450f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *  		... The fields as attributes, if all are simple and unqualified
451f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *  	/&gt;
452f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
453f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *  	&lt;ns:UnqualifiedStructProperty-2 rdf:parseType=&quot;Resource&quot;&gt;
454f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *  		... The fields as elements, if none are simple and unqualified
455f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *  	&lt;/ns:UnqualifiedStructProperty-2&gt;
456f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
457f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *  	&lt;ns:UnqualifiedStructProperty-3&gt;
458f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *  		&lt;rdf:Description
459f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *  			... The simple and unqualified fields as attributes
460f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *  		&gt;
461f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *  			... The compound or qualified fields as elements
462f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *  		&lt;/rdf:Description&gt;
463f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *  	&lt;/ns:UnqualifiedStructProperty-3&gt;
464f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
465f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *  	&lt;ns:UnqualifiedArrayProperty&gt;
466f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *  		&lt;rdf:Bag&gt; or Seq or Alt
467f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *  			... Array items as rdf:li elements, same forms as top level properties
468f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *  		&lt;/rdf:Bag&gt;
469f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *  	&lt;/ns:UnqualifiedArrayProperty&gt;
470f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
471f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *  	&lt;ns:QualifiedProperty rdf:parseType=&quot;Resource&quot;&gt;
472f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *  		&lt;rdf:value&gt; ... Property &quot;value&quot;
473f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *  			following the unqualified forms ... &lt;/rdf:value&gt;
474f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *  		... Qualifiers looking like named struct fields
475f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *  	&lt;/ns:QualifiedProperty&gt;
476f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * </pre>
477f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
478f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * </blockquote>
479f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
480f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * *** Consider numbered array items, but has compatibility problems. ***
481f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * Consider qualified form with rdf:Description and attributes.
482f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
483f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param parentNode the parent node
484f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param indent the current indent level
485f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws IOException Forwards writer exceptions
486f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws XMPException If qualifier and element fields are mixed.
487f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
488f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private void serializeCompactRDFElementProps(XMPNode parentNode, int indent)
489f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			throws IOException, XMPException
490f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
491f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		for (Iterator it = parentNode.iterateChildren(); it.hasNext();)
492f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
493f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			XMPNode node = (XMPNode) it.next();
494f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (canBeRDFAttrProp (node))
495f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
496f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				continue;
497f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
498f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
499f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			boolean emitEndTag = true;
500f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			boolean indentEndTag = true;
501f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
502f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// Determine the XML element name, write the name part of the start tag. Look over the
503f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// qualifiers to decide on "normal" versus "rdf:value" form. Emit the attribute
504f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// qualifiers at the same time.
505f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			String elemName = node.getName();
506f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (XMPConst.ARRAY_ITEM_NAME.equals(elemName))
507f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
508f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				elemName = "rdf:li";
509f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
510f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
511f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			writeIndent(indent);
512f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			write('<');
513f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			write(elemName);
514f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
515f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			boolean hasGeneralQualifiers = false;
516f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			boolean hasRDFResourceQual   = false;
517f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
518f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			for (Iterator iq = 	node.iterateQualifier(); iq.hasNext();)
519f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
520f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				XMPNode qualifier = (XMPNode) iq.next();
521f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				if (!RDF_ATTR_QUALIFIER.contains(qualifier.getName()))
522f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
523f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					hasGeneralQualifiers = true;
524f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
525f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				else
526f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
527f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					hasRDFResourceQual = "rdf:resource".equals(qualifier.getName());
528f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					write(' ');
529f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					write(qualifier.getName());
530f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					write("=\"");
531f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					appendNodeValue(qualifier.getValue(), true);
532f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					write('"');
533f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
534f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
535f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
536f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
537f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// Process the property according to the standard patterns.
538f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (hasGeneralQualifiers)
539f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
540f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				serializeCompactRDFGeneralQualifier(indent, node);
541f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
542f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			else
543f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
544f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				// This node has only attribute qualifiers. Emit as a property element.
545f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				if (!node.getOptions().isCompositeProperty())
546f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
547f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					Object[] result = serializeCompactRDFSimpleProp(node);
548f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					emitEndTag = ((Boolean) result[0]).booleanValue();
549f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					indentEndTag = ((Boolean) result[1]).booleanValue();
550f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
551f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				else if (node.getOptions().isArray())
552f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
553f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					serializeCompactRDFArrayProp(node, indent);
554f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
555f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				else
556f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
557f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					emitEndTag = serializeCompactRDFStructProp(
558f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						node, indent, hasRDFResourceQual);
559f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
560f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
561f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
562f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
563f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// Emit the property element end tag.
564f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (emitEndTag)
565f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
566f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				if (indentEndTag)
567f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
568f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					writeIndent(indent);
569f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
570f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				write("</");
571f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				write(elemName);
572f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				write('>');
573f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				writeNewline();
574f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
575f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
576f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
577f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
578f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
579f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
580f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
581f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * Serializes a simple property.
582f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
583f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param node an XMPNode
584f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @return Returns an array containing the flags emitEndTag and indentEndTag.
585f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws IOException Forwards the writer exceptions.
586f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
587f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private Object[] serializeCompactRDFSimpleProp(XMPNode node) throws IOException
588f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
589f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// This is a simple property.
590f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		Boolean emitEndTag = Boolean.TRUE;
591f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		Boolean indentEndTag = Boolean.TRUE;
592f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
593f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (node.getOptions().isURI())
594f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
595f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			write(" rdf:resource=\"");
596f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			appendNodeValue(node.getValue(), true);
597f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			write("\"/>");
598f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			writeNewline();
599f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			emitEndTag = Boolean.FALSE;
600f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
601f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		else if (node.getValue() == null  ||  node.getValue().length() == 0)
602f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
603f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			write("/>");
604f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			writeNewline();
605f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			emitEndTag = Boolean.FALSE;
606f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
607f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		else
608f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
609f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			write('>');
610f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			appendNodeValue (node.getValue(), false);
611f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			indentEndTag = Boolean.FALSE;
612f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
613f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
614f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		return new Object[] {emitEndTag, indentEndTag};
615f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
616f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
617f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
618f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
619f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * Serializes an array property.
620f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
621f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param node an XMPNode
622f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param indent the current indent level
623f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws IOException Forwards the writer exceptions.
624f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws XMPException If qualifier and element fields are mixed.
625f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
626f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private void serializeCompactRDFArrayProp(XMPNode node, int indent) throws IOException,
627f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			XMPException
628f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
629f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// This is an array.
630f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		write('>');
631f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		writeNewline();
632f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		emitRDFArrayTag (node, true, indent + 1);
633f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
634f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (node.getOptions().isArrayAltText())
635f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
636f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			XMPNodeUtils.normalizeLangArray (node);
637f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
638f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
639f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		serializeCompactRDFElementProps(node, indent + 2);
640f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
641f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		emitRDFArrayTag(node, false, indent + 1);
642f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
643f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
644f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
645f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
646f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * Serializes a struct property.
647f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
648f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param node an XMPNode
649f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param indent the current indent level
650f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param hasRDFResourceQual Flag if the element has resource qualifier
651f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @return Returns true if an end flag shall be emitted.
652f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws IOException Forwards the writer exceptions.
653f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws XMPException If qualifier and element fields are mixed.
654f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
655f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private boolean serializeCompactRDFStructProp(XMPNode node, int indent,
656f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			boolean hasRDFResourceQual) throws XMPException, IOException
657f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
658f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// This must be a struct.
659f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		boolean hasAttrFields = false;
660f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		boolean hasElemFields = false;
661f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		boolean emitEndTag = true;
662f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
663f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		for (Iterator ic = node.iterateChildren(); ic.hasNext(); )
664f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
665f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			XMPNode field = (XMPNode) ic.next();
666f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (canBeRDFAttrProp(field))
667f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
668f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				hasAttrFields = true;
669f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
670f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			else
671f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
672f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				hasElemFields = true;
673f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
674f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
675f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (hasAttrFields  &&  hasElemFields)
676f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
677f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				break;	// No sense looking further.
678f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
679f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
680f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
681f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (hasRDFResourceQual && hasElemFields)
682f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
683f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			throw new XMPException(
684f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					"Can't mix rdf:resource qualifier and element fields",
685f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					XMPError.BADRDF);
686f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
687f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
688f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (!node.hasChildren())
689f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
690f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// Catch an empty struct as a special case. The case
691f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// below would emit an empty
692f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// XML element, which gets reparsed as a simple property
693f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// with an empty value.
694f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			write(" rdf:parseType=\"Resource\"/>");
695f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			writeNewline();
696f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			emitEndTag = false;
697f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
698f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
699f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		else if (!hasElemFields)
700f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
701f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// All fields can be attributes, use the
702f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// emptyPropertyElt form.
703f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			serializeCompactRDFAttrProps(node, indent + 1);
704f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			write("/>");
705f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			writeNewline();
706f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			emitEndTag = false;
707f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
708f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
709f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		else if (!hasAttrFields)
710f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
711f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// All fields must be elements, use the
712f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// parseTypeResourcePropertyElt form.
713f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			write(" rdf:parseType=\"Resource\">");
714f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			writeNewline();
715f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			serializeCompactRDFElementProps(node, indent + 1);
716f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
717f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
718f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		else
719f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
720f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// Have a mix of attributes and elements, use an inner rdf:Description.
721f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			write('>');
722f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			writeNewline();
723f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			writeIndent(indent + 1);
724f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			write(RDF_STRUCT_START);
725f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			serializeCompactRDFAttrProps(node, indent + 2);
726f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			write(">");
727f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			writeNewline();
728f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			serializeCompactRDFElementProps(node, indent + 1);
729f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			writeIndent(indent + 1);
730f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			write(RDF_STRUCT_END);
731f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			writeNewline();
732f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
733f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		return emitEndTag;
734f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
735f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
736f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
737f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
738f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * Serializes the general qualifier.
739f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param node the root node of the subtree
740f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param indent the current indent level
741f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws IOException Forwards all writer exceptions.
742f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws XMPException If qualifier and element fields are mixed.
743f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
744f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private void serializeCompactRDFGeneralQualifier(int indent, XMPNode node)
745f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			throws IOException, XMPException
746f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
747f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// The node has general qualifiers, ones that can't be
748f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// attributes on a property element.
749f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// Emit using the qualified property pseudo-struct form. The
750f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// value is output by a call
751f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// to SerializePrettyRDFProperty with emitAsRDFValue set.
752f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		write(" rdf:parseType=\"Resource\">");
753f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		writeNewline();
754f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
755f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		serializePrettyRDFProperty(node, true, indent + 1);
756f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
757f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		for (Iterator iq = 	node.iterateQualifier(); iq.hasNext();)
758f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
759f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			XMPNode qualifier = (XMPNode) iq.next();
760f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			serializePrettyRDFProperty(qualifier, false, indent + 1);
761f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
762f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
763f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
764f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
765f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
766f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * Serializes one schema with all contained properties in pretty-printed
767f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * manner.<br>
768f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * Each schema's properties are written in a separate
769f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * rdf:Description element. All of the necessary namespaces are declared in
770f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * the rdf:Description element. The baseIndent is the base level for the
771f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * entire serialization, that of the x:xmpmeta element. An xml:lang
772f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * qualifier is written as an attribute of the property start tag, not by
773f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * itself forcing the qualified property form.
774f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
775f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * <blockquote>
776f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
777f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * <pre>
778f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *  	 &lt;rdf:Description rdf:about=&quot;TreeName&quot; xmlns:ns=&quot;URI&quot; ... &gt;
779f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
780f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *  	 	... The actual properties of the schema, see SerializePrettyRDFProperty
781f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
782f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *  	 	&lt;!-- ns1:Alias is aliased to ns2:Actual --&gt;  ... If alias comments are wanted
783f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
784f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *  	 &lt;/rdf:Description&gt;
785f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * </pre>
786f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
787f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * </blockquote>
788f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
789f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param schemaNode a schema node
790f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws IOException Forwarded writer exceptions
791f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws XMPException
792f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
793f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private void serializePrettyRDFSchema(XMPNode schemaNode) throws IOException, XMPException
794f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
795f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		writeIndent(2);
796f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		write(RDF_SCHEMA_START);
797f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		writeTreeName();
798f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
799f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		Set usedPrefixes = new HashSet();
800f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		usedPrefixes.add("xml");
801f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		usedPrefixes.add("rdf");
802f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
803f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		declareUsedNamespaces(schemaNode, usedPrefixes, 4);
804f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
805f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		write('>');
806f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		writeNewline();
807f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
808f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// Write each of the schema's actual properties.
809f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		for (Iterator it = schemaNode.iterateChildren(); it.hasNext();)
810f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
811f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			XMPNode propNode = (XMPNode) it.next();
812f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			serializePrettyRDFProperty(propNode, false, 3);
813f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
814f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
815f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// Write the rdf:Description end tag.
816f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		writeIndent(2);
817f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		write(RDF_SCHEMA_END);
818f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		writeNewline();
819f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
820f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
821f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
822f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
823f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * Writes all used namespaces of the subtree in node to the output.
824f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * The subtree is recursivly traversed.
825f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param node the root node of the subtree
826f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param usedPrefixes a set containing currently used prefixes
827f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param indent the current indent level
828f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws IOException Forwards all writer exceptions.
829f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
830f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private void declareUsedNamespaces(XMPNode node, Set usedPrefixes, int indent)
831f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			throws IOException
832f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
833f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (node.getOptions().isSchemaNode())
834f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
835f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// The schema node name is the URI, the value is the prefix.
836f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			String prefix = node.getValue().substring(0, node.getValue().length() - 1);
837f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			declareNamespace(prefix, node.getName(), usedPrefixes, indent);
838f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
839f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		else if (node.getOptions().isStruct())
840f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
841f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			for (Iterator it = node.iterateChildren(); it.hasNext();)
842f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
843f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				XMPNode field = (XMPNode) it.next();
844f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				declareNamespace(field.getName(), null, usedPrefixes, indent);
845f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
846f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
847f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
848f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		for (Iterator it = node.iterateChildren(); it.hasNext();)
849f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
850f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			XMPNode child = (XMPNode) it.next();
851f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			declareUsedNamespaces(child, usedPrefixes, indent);
852f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
853f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
854f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		for (Iterator it = node.iterateQualifier(); it.hasNext();)
855f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
856f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			XMPNode qualifier = (XMPNode) it.next();
857f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			declareNamespace(qualifier.getName(), null, usedPrefixes, indent);
858f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			declareUsedNamespaces(qualifier, usedPrefixes, indent);
859f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
860f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
861f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
862f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
863f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
864f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * Writes one namespace declaration to the output.
865f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param prefix a namespace prefix (without colon) or a complete qname (when namespace == null)
866f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param namespace the a namespace
867f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param usedPrefixes a set containing currently used prefixes
868f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param indent the current indent level
869f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws IOException Forwards all writer exceptions.
870f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
871f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private void declareNamespace(String prefix, String namespace, Set usedPrefixes, int indent)
872f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			throws IOException
873f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
874f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (namespace == null)
875f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
876f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// prefix contains qname, extract prefix and lookup namespace with prefix
877f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			QName qname = new QName(prefix);
878f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (qname.hasPrefix())
879f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
880f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				prefix = qname.getPrefix();
881f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				// add colon for lookup
882f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				namespace = XMPMetaFactory.getSchemaRegistry().getNamespaceURI(prefix + ":");
883f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				// prefix w/o colon
884f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				declareNamespace(prefix, namespace, usedPrefixes, indent);
885f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
886f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			else
887f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
888f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				return;
889f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
890f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
891f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
892f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (!usedPrefixes.contains(prefix))
893f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
894f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			writeNewline();
895f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			writeIndent(indent);
896f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			write("xmlns:");
897f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			write(prefix);
898f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			write("=\"");
899f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			write(namespace);
900f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			write('"');
901f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			usedPrefixes.add(prefix);
902f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
903f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
904f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
905f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
906f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
907f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * Recursively handles the "value" for a node. It does not matter if it is a
908f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * top level property, a field of a struct, or an item of an array. The
909f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * indent is that for the property element. An xml:lang qualifier is written
910f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * as an attribute of the property start tag, not by itself forcing the
911f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * qualified property form. The patterns below mostly ignore attribute
912f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * qualifiers like xml:lang. Except for the one struct case, attribute
913f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * qualifiers don't affect the output form.
914f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
915f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * <blockquote>
916f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
917f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * <pre>
918f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * 	&lt;ns:UnqualifiedSimpleProperty&gt;value&lt;/ns:UnqualifiedSimpleProperty&gt;
919f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
920f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * 	&lt;ns:UnqualifiedStructProperty rdf:parseType=&quot;Resource&quot;&gt;
921f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * 		(If no rdf:resource qualifier)
922f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * 		... Fields, same forms as top level properties
923f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * 	&lt;/ns:UnqualifiedStructProperty&gt;
924f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
925f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * 	&lt;ns:ResourceStructProperty rdf:resource=&quot;URI&quot;
926f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * 		... Fields as attributes
927f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * 	&gt;
928f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
929f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * 	&lt;ns:UnqualifiedArrayProperty&gt;
930f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * 		&lt;rdf:Bag&gt; or Seq or Alt
931f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * 			... Array items as rdf:li elements, same forms as top level properties
932f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * 		&lt;/rdf:Bag&gt;
933f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * 	&lt;/ns:UnqualifiedArrayProperty&gt;
934f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
935f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * 	&lt;ns:QualifiedProperty rdf:parseType=&quot;Resource&quot;&gt;
936f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * 		&lt;rdf:value&gt; ... Property &quot;value&quot; following the unqualified
937f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * 			forms ... &lt;/rdf:value&gt;
938f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * 		... Qualifiers looking like named struct fields
939f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * 	&lt;/ns:QualifiedProperty&gt;
940f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * </pre>
941f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
942f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * </blockquote>
943f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
944f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param node the property node
945f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param emitAsRDFValue property shall be renderes as attribute rather than tag
946f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param indent the current indent level
947f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws IOException Forwards all writer exceptions.
948f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws XMPException If &quot;rdf:resource&quot; and general qualifiers are mixed.
949f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
950f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private void serializePrettyRDFProperty(XMPNode node, boolean emitAsRDFValue, int indent)
951f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			throws IOException, XMPException
952f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
953f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		boolean emitEndTag   = true;
954f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		boolean indentEndTag = true;
955f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
956f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// Determine the XML element name. Open the start tag with the name and
957f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// attribute qualifiers.
958f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
959f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		String elemName = node.getName();
960f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (emitAsRDFValue)
961f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
962f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			elemName = "rdf:value";
963f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
964f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		else if (XMPConst.ARRAY_ITEM_NAME.equals(elemName))
965f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
966f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			elemName = "rdf:li";
967f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
968f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
969f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		writeIndent(indent);
970f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		write('<');
971f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		write(elemName);
972f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
973f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		boolean hasGeneralQualifiers = false;
974f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		boolean hasRDFResourceQual   = false;
975f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
976f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		for (Iterator it = node.iterateQualifier(); it.hasNext();)
977f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
978f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			XMPNode qualifier = (XMPNode) it.next();
979f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (!RDF_ATTR_QUALIFIER.contains(qualifier.getName()))
980f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
981f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				hasGeneralQualifiers = true;
982f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
983f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			else
984f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
985f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				hasRDFResourceQual = "rdf:resource".equals(qualifier.getName());
986f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				if (!emitAsRDFValue)
987f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
988f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					write(' ');
989f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					write(qualifier.getName());
990f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					write("=\"");
991f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					appendNodeValue(qualifier.getValue(), true);
992f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					write('"');
993f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
994f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
995f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
996f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
997f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// Process the property according to the standard patterns.
998f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
999f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (hasGeneralQualifiers &&  !emitAsRDFValue)
1000f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
1001f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// This node has general, non-attribute, qualifiers. Emit using the
1002f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// qualified property form.
1003f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// ! The value is output by a recursive call ON THE SAME NODE with
1004f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// emitAsRDFValue set.
1005f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1006f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (hasRDFResourceQual)
1007f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
1008f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				throw new XMPException("Can't mix rdf:resource and general qualifiers",
1009f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						XMPError.BADRDF);
1010f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
1011f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1012f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			write(" rdf:parseType=\"Resource\">");
1013f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			writeNewline();
1014f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1015f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			serializePrettyRDFProperty(node, true, indent + 1);
1016f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1017f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			for (Iterator it = node.iterateQualifier(); it.hasNext();)
1018f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
1019f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				XMPNode qualifier = (XMPNode) it.next();
1020f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				if (!RDF_ATTR_QUALIFIER.contains(qualifier.getName()))
1021f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
1022f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					serializePrettyRDFProperty(qualifier, false, indent + 1);
1023f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
1024f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
1025f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
1026f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		else
1027f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
1028f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			// This node has no general qualifiers. Emit using an unqualified form.
1029f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1030f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (!node.getOptions().isCompositeProperty())
1031f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
1032f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				// This is a simple property.
1033f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1034f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				if (node.getOptions().isURI())
1035f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
1036f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					write(" rdf:resource=\"");
1037f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					appendNodeValue(node.getValue(), true);
1038f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					write("\"/>");
1039f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					writeNewline();
1040f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					emitEndTag = false;
1041f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
1042f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				else if (node.getValue() == null ||  "".equals(node.getValue()))
1043f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
1044f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					write("/>");
1045f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					writeNewline();
1046f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					emitEndTag = false;
1047f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
1048f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				else
1049f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
1050f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					write('>');
1051f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					appendNodeValue(node.getValue(), false);
1052f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					indentEndTag = false;
1053f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
1054f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
1055f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			else if (node.getOptions().isArray())
1056f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
1057f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				// This is an array.
1058f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				write('>');
1059f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				writeNewline();
1060f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				emitRDFArrayTag(node, true, indent + 1);
1061f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				if (node.getOptions().isArrayAltText())
1062f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
1063f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					XMPNodeUtils.normalizeLangArray(node);
1064f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
1065f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				for (Iterator it = node.iterateChildren(); it.hasNext();)
1066f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
1067f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					XMPNode child = (XMPNode) it.next();
1068f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					serializePrettyRDFProperty(child, false, indent + 2);
1069f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
1070f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				emitRDFArrayTag(node, false, indent + 1);
1071f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1072f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1073f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
1074f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			else if (!hasRDFResourceQual)
1075f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
1076f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				// This is a "normal" struct, use the rdf:parseType="Resource" form.
1077f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				if (!node.hasChildren())
1078f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
1079f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					write(" rdf:parseType=\"Resource\"/>");
1080f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					writeNewline();
1081f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					emitEndTag = false;
1082f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
1083f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				else
1084f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
1085f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					write(" rdf:parseType=\"Resource\">");
1086f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					writeNewline();
1087f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					for (Iterator it = node.iterateChildren(); it.hasNext();)
1088f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					{
1089f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						XMPNode child = (XMPNode) it.next();
1090f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						serializePrettyRDFProperty(child, false, indent + 1);
1091f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					}
1092f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
1093f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
1094f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			else
1095f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
1096f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				// This is a struct with an rdf:resource attribute, use the
1097f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				// "empty property element" form.
1098f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				for (Iterator it = node.iterateChildren(); it.hasNext();)
1099f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				{
1100f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					XMPNode child = (XMPNode) it.next();
1101f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					if (!canBeRDFAttrProp(child))
1102f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					{
1103f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling						throw new XMPException("Can't mix rdf:resource and complex fields",
1104f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling								XMPError.BADRDF);
1105f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					}
1106f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					writeNewline();
1107f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					writeIndent(indent + 1);
1108f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					write(' ');
1109f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					write(child.getName());
1110f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					write("=\"");
1111f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					appendNodeValue(child.getValue(), true);
1112f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling					write('"');
1113f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				}
1114f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				write("/>");
1115f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				writeNewline();
1116f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				emitEndTag = false;
1117f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
1118f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
1119f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1120f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		// Emit the property element end tag.
1121f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (emitEndTag)
1122f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
1123f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (indentEndTag)
1124f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
1125f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				writeIndent(indent);
1126f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
1127f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			write("</");
1128f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			write(elemName);
1129f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			write('>');
1130f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			writeNewline();
1131f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
1132f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
1133f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1134f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1135f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
1136f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * Writes the array start and end tags.
1137f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
1138f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param arrayNode an array node
1139f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param isStartTag flag if its the start or end tag
1140f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param indent the current indent level
1141f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws IOException forwards writer exceptions
1142f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
1143f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private void emitRDFArrayTag(XMPNode arrayNode, boolean isStartTag, int indent)
1144f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		throws IOException
1145f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
1146f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		if (isStartTag  ||  arrayNode.hasChildren())
1147f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
1148f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			writeIndent(indent);
1149f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			write(isStartTag ? "<rdf:" : "</rdf:");
1150f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1151f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (arrayNode.getOptions().isArrayAlternate())
1152f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
1153f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				write("Alt");
1154f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
1155f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			else if (arrayNode.getOptions().isArrayOrdered())
1156f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
1157f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				write("Seq");
1158f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
1159f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			else
1160f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
1161f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				write("Bag");
1162f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
1163f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1164f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			if (isStartTag && !arrayNode.hasChildren())
1165f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
1166f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				write("/>");
1167f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
1168f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			else
1169f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			{
1170f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling				write(">");
1171f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			}
1172f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1173f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			writeNewline();
1174f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
1175f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
1176f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1177f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1178f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
1179f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * Serializes the node value in XML encoding. Its used for tag bodies and
1180f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * attributes. <em>Note:</em> The attribute is always limited by quotes,
1181f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * thats why <code>&amp;apos;</code> is never serialized. <em>Note:</em>
1182f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * Control chars are written unescaped, but if the user uses others than tab, LF
1183f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * and CR the resulting XML will become invalid.
1184f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
1185f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param value the value of the node
1186f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param forAttribute flag if value is an attribute value
1187f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws IOException
1188f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
1189f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private void appendNodeValue(String value, boolean forAttribute) throws IOException
1190f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
1191f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		write (Utils.escapeXML(value, forAttribute, true));
1192f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
1193f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1194f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1195f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
1196f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * A node can be serialized as RDF-Attribute, if it meets the following conditions:
1197f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * <ul>
1198f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *  	<li>is not array item
1199f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * 		<li>don't has qualifier
1200f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * 		<li>is no URI
1201f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * 		<li>is no composite property
1202f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * </ul>
1203f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 *
1204f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param node an XMPNode
1205f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @return Returns true if the node serialized as RDF-Attribute
1206f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
1207f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private boolean canBeRDFAttrProp(XMPNode node)
1208f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
1209f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		return
1210f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			!node.hasQualifier()  &&
1211f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			!node.getOptions().isURI()  &&
1212f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			!node.getOptions().isCompositeProperty()  &&
1213f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			!XMPConst.ARRAY_ITEM_NAME.equals(node.getName());
1214f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
1215f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1216f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1217f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
1218f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * Writes indents and automatically includes the baseindend from the options.
1219f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param times number of indents to write
1220f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws IOException forwards exception
1221f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
1222f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private void writeIndent(int times) throws IOException
1223f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
1224f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		for (int i = options.getBaseIndent() + times; i > 0; i--)
1225f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
1226f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			writer.write(options.getIndent());
1227f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
1228f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
1229f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1230f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1231f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
1232f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * Writes a char to the output.
1233f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param c a char
1234f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws IOException forwards writer exceptions
1235f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
1236f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private void write(int c) throws IOException
1237f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
1238f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		writer.write(c);
1239f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
1240f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1241f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1242f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
1243f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * Writes a String to the output.
1244f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param str a String
1245f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws IOException forwards writer exceptions
1246f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
1247f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private void write(String str) throws IOException
1248f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
1249f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		writer.write(str);
1250f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
1251f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1252f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1253f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
1254f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * Writes an amount of chars, mostly spaces
1255f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param number number of chars
1256f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @param c a char
1257f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws IOException
1258f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
1259f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private void writeChars(int number, char c) throws IOException
1260f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
1261f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		for (; number > 0; number--)
1262f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		{
1263f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling			writer.write(c);
1264f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		}
1265f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
1266f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1267f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling
1268f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	/**
1269f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * Writes a newline according to the options.
1270f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 * @throws IOException Forwards exception
1271f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	 */
1272f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	private void writeNewline() throws IOException
1273f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	{
1274f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling		writer.write(options.getNewline());
1275f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling	}
1276f12f744843a67c910ec325fc6dfa73988f67b97cSascha Haeberling}