/src/exiv2/xmpsdk/src/XMPMeta-Serialize.cpp
Line | Count | Source |
1 | | // ================================================================================================= // Copyright 2002-2008 Adobe Systems Incorporated |
2 | | // All Rights Reserved. |
3 | | // |
4 | | // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms |
5 | | // of the Adobe license agreement accompanying it. |
6 | | // |
7 | | // Adobe patent application tracking #P435, entitled 'Unique markers to simplify embedding data of |
8 | | // one format in a file with a different format', inventors: Sean Parent, Greg Gilley. |
9 | | // ================================================================================================= |
10 | | |
11 | | #include "XMP_Environment.h" // ! This must be the first include! |
12 | | #include "XMPCore_Impl.hpp" |
13 | | |
14 | | #include "XMPMeta.hpp" |
15 | | |
16 | | #include "XMP_Version.h" |
17 | | #include "UnicodeInlines.incl_cpp" |
18 | | #include "UnicodeConversions.hpp" |
19 | | |
20 | | #if XMP_DebugBuild |
21 | | #include <iostream> |
22 | | #endif |
23 | | |
24 | | using namespace std; |
25 | | |
26 | | #if XMP_WinBuild |
27 | | #ifdef _MSC_VER |
28 | | #pragma warning ( disable : 4533 ) // initialization of '...' is skipped by 'goto ...' |
29 | | #pragma warning ( disable : 4702 ) // unreachable code |
30 | | #pragma warning ( disable : 4800 ) // forcing value to bool 'true' or 'false' (performance warning) |
31 | | #endif |
32 | | #endif |
33 | | |
34 | | // *** Use the XMP_PropIsXyz (Schema, Simple, Struct, Array, ...) macros |
35 | | // *** Add debug codegen checks, e.g. that typical masking operations really work |
36 | | // *** Change all uses of strcmp and strncmp to XMP_LitMatch and XMP_LitNMatch |
37 | | |
38 | | |
39 | | // ================================================================================================= |
40 | | // Local Types and Constants |
41 | | // ========================= |
42 | | |
43 | | static const char * kPacketHeader = "<?xpacket begin=\"\xEF\xBB\xBF\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>"; |
44 | | static const char * kPacketTrailer = "<?xpacket end=\"w\"?>"; // ! The w/r is at [size-4]. |
45 | | |
46 | | static const char * kRDF_XMPMetaStart = "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\""; |
47 | | static const char * kRDF_XMPMetaEnd = "</x:xmpmeta>"; |
48 | | |
49 | | static const char * kRDF_RDFStart = "<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">"; |
50 | | static const char * kRDF_RDFEnd = "</rdf:RDF>"; |
51 | | |
52 | | static const char * kRDF_SchemaStart = "<rdf:Description rdf:about="; |
53 | | static const char * kRDF_SchemaEnd = "</rdf:Description>"; |
54 | | |
55 | | static const char * kRDF_StructStart = "<rdf:Description>"; |
56 | | static const char * kRDF_StructEnd = "</rdf:Description>"; |
57 | | |
58 | | static const char * kRDF_BagStart = "<rdf:Bag>"; |
59 | | |
60 | | static const char * kRDF_ItemStart = "<rdf:li>"; |
61 | | |
62 | | static const char * kRDF_ValueStart = "<rdf:value>"; |
63 | | |
64 | | |
65 | | // ================================================================================================= |
66 | | // Static Variables |
67 | | // ================ |
68 | | |
69 | | |
70 | | // ================================================================================================= |
71 | | // Local Utilities |
72 | | // =============== |
73 | | |
74 | | |
75 | | // ------------------------------------------------------------------------------------------------- |
76 | | // EstimateRDFSize |
77 | | // --------------- |
78 | | |
79 | | // *** Pull the strlen(kXyz) calls into constants. |
80 | | |
81 | | static size_t |
82 | | EstimateRDFSize ( const XMP_Node * currNode, XMP_Index indent, size_t indentLen ) |
83 | 6.24k | { |
84 | 6.24k | size_t outputLen = 2 * (indent*indentLen + currNode->name.size() + 4); // The property element tags. |
85 | | |
86 | 6.24k | if ( ! currNode->qualifiers.empty() ) { |
87 | | // This node has qualifiers, assume it is written using rdf:value and estimate the qualifiers. |
88 | | |
89 | 144 | indent += 2; // Everything else is indented inside the rdf:Description element. |
90 | 144 | outputLen += 2 * ((indent-1)*indentLen + strlen(kRDF_StructStart) + 2); // The rdf:Description tags. |
91 | 144 | outputLen += 2 * (indent*indentLen + strlen(kRDF_ValueStart) + 2); // The rdf:value tags. |
92 | | |
93 | 332 | for ( size_t qualNum = 0, qualLim = currNode->qualifiers.size(); qualNum < qualLim; ++qualNum ) { |
94 | 188 | const XMP_Node * currQual = currNode->qualifiers[qualNum]; |
95 | 188 | outputLen += EstimateRDFSize ( currQual, indent, indentLen ); |
96 | 188 | } |
97 | | |
98 | 144 | } |
99 | | |
100 | 6.24k | if ( currNode->options & kXMP_PropValueIsStruct ) { |
101 | 165 | indent += 1; |
102 | 165 | outputLen += 2 * (indent*indentLen + strlen(kRDF_StructStart) + 2); // The rdf:Description tags. |
103 | 6.08k | } else if ( currNode->options & kXMP_PropValueIsArray ) { |
104 | 477 | indent += 2; |
105 | 477 | outputLen += 2 * ((indent-1)*indentLen + strlen(kRDF_BagStart) + 2); // The rdf:Bag/Seq/Alt tags. |
106 | 477 | outputLen += 2 * currNode->children.size() * (strlen(kRDF_ItemStart) + 2); // The rdf:li tags, indent counted in children. |
107 | 5.60k | } else if ( ! (currNode->options & kXMP_SchemaNode) ) { |
108 | 4.70k | outputLen += currNode->value.size(); // This is a leaf value node. |
109 | 4.70k | } |
110 | | |
111 | 11.4k | for ( size_t childNum = 0, childLim = currNode->children.size(); childNum < childLim; ++childNum ) { |
112 | 5.16k | const XMP_Node * currChild = currNode->children[childNum]; |
113 | 5.16k | outputLen += EstimateRDFSize ( currChild, indent+1, indentLen ); |
114 | 5.16k | } |
115 | | |
116 | 6.24k | return outputLen; |
117 | | |
118 | 6.24k | } // EstimateRDFSize |
119 | | |
120 | | |
121 | | // ------------------------------------------------------------------------------------------------- |
122 | | // DeclareOneNamespace |
123 | | // ------------------- |
124 | | |
125 | | static void |
126 | | DeclareOneNamespace ( const XMP_VarString & nsPrefix, |
127 | | const XMP_VarString & nsURI, |
128 | | XMP_VarString & usedNS, // ! A catenation of the prefixes with colons. |
129 | | XMP_VarString & outputStr, |
130 | | XMP_StringPtr newline, |
131 | | XMP_StringPtr indentStr, |
132 | | XMP_Index indent ) |
133 | 1.55k | { |
134 | 1.55k | size_t nsPos = usedNS.find ( nsPrefix ); |
135 | | |
136 | 1.55k | if ( nsPos == XMP_VarString::npos ) { |
137 | | |
138 | 747 | outputStr += newline; |
139 | 3.73k | for ( ; indent > 0; --indent ) outputStr += indentStr; |
140 | 747 | outputStr += "xmlns:"; |
141 | 747 | outputStr += nsPrefix; |
142 | 747 | outputStr[outputStr.size()-1] = '='; // Change the colon to =. |
143 | 747 | outputStr += '"'; |
144 | 747 | outputStr += nsURI; |
145 | 747 | outputStr += '"'; |
146 | | |
147 | 747 | usedNS += nsPrefix; |
148 | | |
149 | 747 | } |
150 | | |
151 | 1.55k | } // DeclareOneNamespace |
152 | | |
153 | | |
154 | | // ------------------------------------------------------------------------------------------------- |
155 | | // DeclareElemNamespace |
156 | | // -------------------- |
157 | | |
158 | | static void |
159 | | DeclareElemNamespace ( const XMP_VarString & elemName, |
160 | | XMP_VarString & usedNS, |
161 | | XMP_VarString & outputStr, |
162 | | XMP_StringPtr newline, |
163 | | XMP_StringPtr indentStr, |
164 | | XMP_Index indent ) |
165 | 655 | { |
166 | 655 | size_t colonPos = elemName.find ( ':' ); |
167 | | |
168 | 655 | if ( colonPos != XMP_VarString::npos ) { |
169 | 655 | XMP_VarString nsPrefix ( elemName.substr ( 0, colonPos+1 ) ); |
170 | 655 | XMP_StringMapPos prefixPos = sNamespacePrefixToURIMap->find ( nsPrefix ); |
171 | 655 | XMP_Enforce ( prefixPos != sNamespacePrefixToURIMap->end() ); |
172 | 655 | DeclareOneNamespace ( nsPrefix, prefixPos->second, usedNS, outputStr, newline, indentStr, indent ); |
173 | 655 | } |
174 | | |
175 | 655 | } // DeclareElemNamespace |
176 | | |
177 | | |
178 | | // ------------------------------------------------------------------------------------------------- |
179 | | // DeclareUsedNamespaces |
180 | | // --------------------- |
181 | | |
182 | | // ??? Should iterators be passed by reference to avoid temp copies? |
183 | | |
184 | | static void |
185 | | DeclareUsedNamespaces ( const XMP_Node * currNode, |
186 | | XMP_VarString & usedNS, |
187 | | XMP_VarString & outputStr, |
188 | | XMP_StringPtr newline, |
189 | | XMP_StringPtr indentStr, |
190 | | XMP_Index indent ) |
191 | 6.24k | { |
192 | | |
193 | 6.24k | if ( currNode->options & kXMP_SchemaNode ) { |
194 | | // The schema node name is the URI, the value is the prefix. |
195 | 895 | DeclareOneNamespace ( currNode->value, currNode->name, usedNS, outputStr, newline, indentStr, indent ); |
196 | 5.35k | } else if ( currNode->options & kXMP_PropValueIsStruct ) { |
197 | 632 | for ( size_t fieldNum = 0, fieldLim = currNode->children.size(); fieldNum < fieldLim; ++fieldNum ) { |
198 | 467 | const XMP_Node * currField = currNode->children[fieldNum]; |
199 | 467 | DeclareElemNamespace ( currField->name, usedNS, outputStr, newline, indentStr, indent ); |
200 | 467 | } |
201 | 165 | } |
202 | | |
203 | 11.4k | for ( size_t childNum = 0, childLim = currNode->children.size(); childNum < childLim; ++childNum ) { |
204 | 5.16k | const XMP_Node * currChild = currNode->children[childNum]; |
205 | 5.16k | DeclareUsedNamespaces ( currChild, usedNS, outputStr, newline, indentStr, indent ); |
206 | 5.16k | } |
207 | | |
208 | 6.43k | for ( size_t qualNum = 0, qualLim = currNode->qualifiers.size(); qualNum < qualLim; ++qualNum ) { |
209 | 188 | const XMP_Node * currQual = currNode->qualifiers[qualNum]; |
210 | 188 | DeclareElemNamespace ( currQual->name, usedNS, outputStr, newline, indentStr, indent ); |
211 | 188 | DeclareUsedNamespaces ( currQual, usedNS, outputStr, newline, indentStr, indent ); |
212 | 188 | } |
213 | | |
214 | 6.24k | } // DeclareUsedNamespaces |
215 | | |
216 | | // ------------------------------------------------------------------------------------------------- |
217 | | // EmitRDFArrayTag |
218 | | // --------------- |
219 | | |
220 | | // ??? Should iterators be passed by reference to avoid temp copies? |
221 | | |
222 | | enum { |
223 | | kIsStartTag = true, |
224 | | kIsEndTag = false |
225 | | }; |
226 | | |
227 | | static void |
228 | | EmitRDFArrayTag ( XMP_OptionBits arrayForm, |
229 | | XMP_VarString & outputStr, |
230 | | XMP_StringPtr newline, |
231 | | XMP_StringPtr indentStr, |
232 | | XMP_Index indent, |
233 | | XMP_Index arraySize, |
234 | | bool isStartTag ) |
235 | 954 | { |
236 | 954 | if ( (! isStartTag) && (arraySize == 0) ) return; |
237 | | |
238 | 4.73k | for ( XMP_Index level = indent; level > 0; --level ) outputStr += indentStr; |
239 | 946 | if ( isStartTag ) { |
240 | 477 | outputStr += "<rdf:"; |
241 | 477 | } else { |
242 | 469 | outputStr += "</rdf:"; |
243 | 469 | } |
244 | | |
245 | 946 | if ( arrayForm & kXMP_PropArrayIsAlternate ) { |
246 | 183 | outputStr += "Alt"; |
247 | 763 | } else if ( arrayForm & kXMP_PropArrayIsOrdered ) { |
248 | 649 | outputStr += "Seq"; |
249 | 649 | } else { |
250 | 114 | outputStr += "Bag"; |
251 | 114 | } |
252 | | |
253 | 946 | if ( isStartTag && (arraySize == 0) ) outputStr += '/'; |
254 | 946 | outputStr += '>'; |
255 | 946 | outputStr += newline; |
256 | | |
257 | 946 | } // EmitRDFArrayTag |
258 | | |
259 | | |
260 | | // ------------------------------------------------------------------------------------------------- |
261 | | // AppendNodeValue |
262 | | // --------------- |
263 | | // |
264 | | // Append a property or qualifier value to the output with appropriate XML escaping. The escaped |
265 | | // characters for elements and attributes are '&', '<', '>', and ASCII controls (tab, LF, CR). In |
266 | | // addition, '"' is escaped for attributes. For efficiency, this is done in a double loop. The outer |
267 | | // loop makes sure the whole value is processed. The inner loop does a contiguous unescaped run |
268 | | // followed by one escaped character (if we're not at the end). |
269 | | // |
270 | | // We depend on parsing and SetProperty logic to make sure there are no invalid ASCII controls in |
271 | | // the XMP values. The XML spec only allows tab, LF, and CR. Others are not even allowed as |
272 | | // numeric escape sequences. |
273 | | |
274 | | enum { |
275 | | kForAttribute = true, |
276 | | kForElement = false |
277 | | }; |
278 | | |
279 | | static void |
280 | | AppendNodeValue ( XMP_VarString & outputStr, const XMP_VarString & value, bool forAttribute ) |
281 | 4.70k | { |
282 | | |
283 | 4.70k | unsigned char * runStart = (unsigned char *) value.c_str(); |
284 | 4.70k | unsigned char * runLimit = runStart + value.size(); |
285 | 4.70k | unsigned char * runEnd; |
286 | 4.70k | unsigned char ch=0; |
287 | | |
288 | 12.3k | while ( runStart < runLimit ) { |
289 | | |
290 | 144k | for ( runEnd = runStart; runEnd < runLimit; ++runEnd ) { |
291 | 140k | ch = *runEnd; |
292 | 140k | if ( forAttribute && (ch == '"') ) break; |
293 | 140k | if ( (ch < 0x20) || (ch == '&') || (ch == '<') || (ch == '>') ) break; |
294 | 140k | } |
295 | | |
296 | 7.62k | outputStr.append ( (char *) runStart, (runEnd - runStart) ); |
297 | | |
298 | 7.62k | if ( runEnd < runLimit ) { |
299 | | |
300 | 3.14k | if ( ch < 0x20 ) { |
301 | | |
302 | 1.39k | XMP_Assert ( (ch == kTab) || (ch == kLF) || (ch == kCR) ); |
303 | | |
304 | 1.39k | char hexBuf[16]; |
305 | 1.39k | memcpy ( hexBuf, "&#xn;", 5 ); |
306 | 1.39k | hexBuf[3] = kHexDigits[ch&0xF]; |
307 | 1.39k | outputStr.append ( hexBuf, 5 ); |
308 | | |
309 | 1.75k | } else { |
310 | | |
311 | 1.75k | if ( ch == '"' ) { |
312 | 172 | outputStr += """; |
313 | 1.57k | } else if ( ch == '<' ) { |
314 | 0 | outputStr += "<"; |
315 | 1.57k | } else if ( ch == '>' ) { |
316 | 1.57k | outputStr += ">"; |
317 | 1.57k | } else { |
318 | 0 | XMP_Assert ( ch == '&' ); |
319 | 0 | outputStr += "&"; |
320 | 0 | } |
321 | | |
322 | 1.75k | } |
323 | | |
324 | 3.14k | ++runEnd; |
325 | | |
326 | 3.14k | } |
327 | | |
328 | 7.62k | runStart = runEnd; |
329 | | |
330 | 7.62k | } |
331 | | |
332 | 4.70k | } // AppendNodeValue |
333 | | |
334 | | |
335 | | // ------------------------------------------------------------------------------------------------- |
336 | | // CanBeRDFAttrProp |
337 | | // ---------------- |
338 | | |
339 | | static bool |
340 | | CanBeRDFAttrProp ( const XMP_Node * propNode ) |
341 | 8.82k | { |
342 | | |
343 | 8.82k | if ( propNode->name[0] == '[' ) return false; |
344 | 7.75k | if ( ! propNode->qualifiers.empty() ) return false; |
345 | 7.66k | if ( propNode->options & kXMP_PropValueIsURI ) return false; |
346 | 7.66k | if ( propNode->options & kXMP_PropCompositeMask ) return false; |
347 | | |
348 | 6.61k | return true; |
349 | | |
350 | 7.66k | } // CanBeRDFAttrProp |
351 | | |
352 | | |
353 | | // ------------------------------------------------------------------------------------------------- |
354 | | // IsRDFAttrQualifier |
355 | | // ------------------ |
356 | | |
357 | | static XMP_StringPtr sAttrQualifiers[] = { "xml:lang", "rdf:resource", "rdf:ID", "rdf:bagID", "rdf:nodeID", "" }; |
358 | | |
359 | | static bool |
360 | | IsRDFAttrQualifier ( XMP_VarString qualName ) |
361 | 248 | { |
362 | | |
363 | 828 | for ( size_t i = 0; *sAttrQualifiers[i] != 0; ++i ) { |
364 | 712 | if ( qualName == sAttrQualifiers[i] ) return true; |
365 | 712 | } |
366 | | |
367 | 116 | return false; |
368 | | |
369 | 248 | } // IsRDFAttrQualifier |
370 | | |
371 | | |
372 | | // ------------------------------------------------------------------------------------------------- |
373 | | // SerializePrettyRDFProperty |
374 | | // -------------------------- |
375 | | // |
376 | | // Recursively handles the "value" for a node. It does not matter if it is a top level property, a |
377 | | // field of a struct, or an item of an array. The indent is that for the property element. An |
378 | | // xml:lang qualifier is written as an attribute of the property start tag, not by itself forcing |
379 | | // the qualified property form. The patterns below mostly ignore attribute qualifiers like xml:lang. |
380 | | // Except for the one struct case, attribute qualifiers don't affect the output form. |
381 | | // |
382 | | // <ns:UnqualifiedSimpleProperty>value</ns:UnqualifiedSimpleProperty> |
383 | | // |
384 | | // <ns:UnqualifiedStructProperty rdf:parseType="Resource"> (If no rdf:resource qualifier) |
385 | | // ... Fields, same forms as top level properties |
386 | | // </ns:UnqualifiedStructProperty> |
387 | | // |
388 | | // <ns:ResourceStructProperty rdf:resource="URI" |
389 | | // ... Fields as attributes |
390 | | // > |
391 | | // |
392 | | // <ns:UnqualifiedArrayProperty> |
393 | | // <rdf:Bag> or Seq or Alt |
394 | | // ... Array items as rdf:li elements, same forms as top level properties |
395 | | // </rdf:Bag> |
396 | | // </ns:UnqualifiedArrayProperty> |
397 | | // |
398 | | // <ns:QualifiedProperty rdf:parseType="Resource"> |
399 | | // <rdf:value> ... Property "value" following the unqualified forms ... </rdf:value> |
400 | | // ... Qualifiers looking like named struct fields |
401 | | // </ns:QualifiedProperty> |
402 | | |
403 | | static void |
404 | | SerializePrettyRDFProperty ( const XMP_Node * propNode, |
405 | | XMP_VarString & outputStr, |
406 | | XMP_StringPtr newline, |
407 | | XMP_StringPtr indentStr, |
408 | | XMP_Index indent, |
409 | | bool emitAsRDFValue = false ) |
410 | 74 | { |
411 | 74 | XMP_Index level; |
412 | 74 | bool emitEndTag = true; |
413 | 74 | bool indentEndTag = true; |
414 | | |
415 | 74 | XMP_OptionBits propForm = propNode->options & kXMP_PropCompositeMask; |
416 | | |
417 | | // ------------------------------------------------------------------------------------------ |
418 | | // Determine the XML element name. Open the start tag with the name and attribute qualifiers. |
419 | | |
420 | 74 | XMP_StringPtr elemName = propNode->name.c_str(); |
421 | 74 | if ( emitAsRDFValue ) { |
422 | 16 | elemName= "rdf:value"; |
423 | 58 | } else if ( *elemName == '[' ) { |
424 | 0 | elemName = "rdf:li"; |
425 | 0 | } |
426 | | |
427 | 518 | for ( level = indent; level > 0; --level ) outputStr += indentStr; |
428 | 74 | outputStr += '<'; |
429 | 74 | outputStr += elemName; |
430 | | |
431 | 74 | #define isCompact false |
432 | 74 | bool hasGeneralQualifiers = isCompact; // Might also become true later. |
433 | 74 | bool hasRDFResourceQual = false; |
434 | | |
435 | 134 | for ( size_t qualNum = 0, qualLim = propNode->qualifiers.size(); qualNum < qualLim; ++qualNum ) { |
436 | 60 | const XMP_Node * currQual = propNode->qualifiers[qualNum]; |
437 | 60 | if ( ! IsRDFAttrQualifier ( currQual->name ) ) { |
438 | 58 | hasGeneralQualifiers = true; |
439 | 58 | } else { |
440 | 2 | if ( currQual->name == "rdf:resource" ) hasRDFResourceQual = true; |
441 | 2 | if ( ! emitAsRDFValue ) { |
442 | 0 | outputStr += ' '; |
443 | 0 | outputStr += currQual->name; |
444 | 0 | outputStr += "=\""; |
445 | 0 | AppendNodeValue ( outputStr, currQual->value, kForAttribute ); |
446 | 0 | outputStr += '"'; |
447 | 0 | } |
448 | 2 | } |
449 | 60 | } |
450 | | |
451 | | // -------------------------------------------------------- |
452 | | // Process the property according to the standard patterns. |
453 | | |
454 | 74 | if ( hasGeneralQualifiers && (! emitAsRDFValue) ) { |
455 | | |
456 | | // ----------------------------------------------------------------------------------------- |
457 | | // This node has general, non-attribute, qualifiers. Emit using the qualified property form. |
458 | | // ! The value is output by a recursive call ON THE SAME NODE with emitAsRDFValue set. |
459 | |
|
460 | 0 | if ( hasRDFResourceQual ) { |
461 | 0 | XMP_Throw ( "Can't mix rdf:resource and general qualifiers", kXMPErr_BadRDF ); |
462 | 0 | } |
463 | | |
464 | 0 | outputStr += " rdf:parseType=\"Resource\">"; |
465 | 0 | outputStr += newline; |
466 | |
|
467 | 0 | SerializePrettyRDFProperty ( propNode, outputStr, newline, indentStr, indent+1, true ); |
468 | | |
469 | 0 | for ( size_t qualNum = 0, qualLim = propNode->qualifiers.size(); qualNum < qualLim; ++qualNum ) { |
470 | 0 | const XMP_Node * currQual = propNode->qualifiers[qualNum]; |
471 | 0 | if ( IsRDFAttrQualifier ( currQual->name ) ) continue; |
472 | 0 | SerializePrettyRDFProperty ( currQual, outputStr, newline, indentStr, indent+1 ); |
473 | 0 | } |
474 | | |
475 | 74 | } else { |
476 | | |
477 | | // -------------------------------------------------------------------- |
478 | | // This node has no general qualifiers. Emit using an unqualified form. |
479 | | |
480 | 74 | if ( propForm == 0 ) { |
481 | | |
482 | | // -------------------------- |
483 | | // This is a simple property. |
484 | | |
485 | 74 | if ( propNode->options & kXMP_PropValueIsURI ) { |
486 | 0 | outputStr += " rdf:resource=\""; |
487 | 0 | AppendNodeValue ( outputStr, propNode->value, kForAttribute ); |
488 | 0 | outputStr += "\"/>"; |
489 | 0 | outputStr += newline; |
490 | 0 | emitEndTag = false; |
491 | 74 | } else if ( propNode->value.empty() ) { |
492 | 3 | outputStr += "/>"; |
493 | 3 | outputStr += newline; |
494 | 3 | emitEndTag = false; |
495 | 71 | } else { |
496 | 71 | outputStr += '>'; |
497 | 71 | AppendNodeValue ( outputStr, propNode->value, kForElement ); |
498 | 71 | indentEndTag = false; |
499 | 71 | } |
500 | | |
501 | 74 | } else if ( propForm & kXMP_PropValueIsArray ) { |
502 | | |
503 | | // This is an array. |
504 | 0 | outputStr += '>'; |
505 | 0 | outputStr += newline; |
506 | 0 | EmitRDFArrayTag ( propForm, outputStr, newline, indentStr, indent+1, propNode->children.size(), kIsStartTag ); |
507 | 0 | if ( XMP_ArrayIsAltText(propNode->options) ) NormalizeLangArray ( (XMP_Node*)propNode ); |
508 | 0 | for ( size_t childNum = 0, childLim = propNode->children.size(); childNum < childLim; ++childNum ) { |
509 | 0 | const XMP_Node * currChild = propNode->children[childNum]; |
510 | 0 | SerializePrettyRDFProperty ( currChild, outputStr, newline, indentStr, indent+2 ); |
511 | 0 | } |
512 | 0 | EmitRDFArrayTag ( propForm, outputStr, newline, indentStr, indent+1, propNode->children.size(), kIsEndTag ); |
513 | | |
514 | | |
515 | 0 | } else if ( ! hasRDFResourceQual ) { |
516 | | |
517 | | // This is a "normal" struct, use the rdf:parseType="Resource" form. |
518 | 0 | XMP_Assert ( propForm & kXMP_PropValueIsStruct ); |
519 | 0 | if ( propNode->children.size() == 0 ) { |
520 | 0 | outputStr += " rdf:parseType=\"Resource\"/>"; |
521 | 0 | outputStr += newline; |
522 | 0 | emitEndTag = false; |
523 | 0 | } else { |
524 | 0 | outputStr += " rdf:parseType=\"Resource\">"; |
525 | 0 | outputStr += newline; |
526 | 0 | for ( size_t childNum = 0, childLim = propNode->children.size(); childNum < childLim; ++childNum ) { |
527 | 0 | const XMP_Node * currChild = propNode->children[childNum]; |
528 | 0 | SerializePrettyRDFProperty ( currChild, outputStr, newline, indentStr, indent+1 ); |
529 | 0 | } |
530 | 0 | } |
531 | | |
532 | 0 | } else { |
533 | | |
534 | | // This is a struct with an rdf:resource attribute, use the "empty property element" form. |
535 | 0 | XMP_Assert ( propForm & kXMP_PropValueIsStruct ); |
536 | 0 | for ( size_t childNum = 0, childLim = propNode->children.size(); childNum < childLim; ++childNum ) { |
537 | 0 | const XMP_Node * currChild = propNode->children[childNum]; |
538 | 0 | if ( ! CanBeRDFAttrProp ( currChild ) ) { |
539 | 0 | XMP_Throw ( "Can't mix rdf:resource and complex fields", kXMPErr_BadRDF ); |
540 | 0 | } |
541 | 0 | outputStr += newline; |
542 | 0 | for ( level = indent+1; level > 0; --level ) outputStr += indentStr; |
543 | 0 | outputStr += ' '; |
544 | 0 | outputStr += currChild->name; |
545 | 0 | outputStr += "=\""; |
546 | 0 | outputStr += currChild->value; |
547 | 0 | outputStr += '"'; |
548 | 0 | } |
549 | 0 | outputStr += "/>"; |
550 | 0 | outputStr += newline; |
551 | 0 | emitEndTag = false; |
552 | | |
553 | 0 | } |
554 | | |
555 | 74 | } |
556 | | |
557 | | // ---------------------------------- |
558 | | // Emit the property element end tag. |
559 | | |
560 | 74 | if ( emitEndTag ) { |
561 | 71 | if ( indentEndTag ) for ( level = indent; level > 0; --level ) outputStr += indentStr; |
562 | 71 | outputStr += "</"; |
563 | 71 | outputStr += elemName; |
564 | 71 | outputStr += '>'; |
565 | 71 | outputStr += newline; |
566 | 71 | } |
567 | | |
568 | 74 | } // SerializePrettyRDFProperty |
569 | | |
570 | | |
571 | | // ------------------------------------------------------------------------------------------------- |
572 | | // SerializePrettyRDFSchema |
573 | | // ------------------------ |
574 | | // |
575 | | // Each schema's properties are written in a separate rdf:Description element. All of the necessary |
576 | | // namespaces are declared in the rdf:Description element. The baseIndent is the base level for the |
577 | | // entire serialization, that of the x:xmpmeta element. An xml:lang qualifier is written as an |
578 | | // attribute of the property start tag, not by itself forcing the qualified property form. |
579 | | // |
580 | | // <rdf:Description rdf:about="TreeName" |
581 | | // xmlns:ns="URI" ... > |
582 | | // |
583 | | // ... The actual properties of the schema, see SerializePrettyRDFProperty |
584 | | // |
585 | | // <!-- ns1:Alias is aliased to ns2:Actual --> ... If alias comments are wanted |
586 | | // |
587 | | // </rdf:Description> |
588 | | |
589 | | static void |
590 | | SerializePrettyRDFSchema ( const XMP_VarString & treeName, |
591 | | const XMP_Node * schemaNode, |
592 | | XMP_VarString & outputStr, |
593 | | XMP_OptionBits options, |
594 | | XMP_StringPtr newline, |
595 | | XMP_StringPtr indentStr, |
596 | | XMP_Index baseIndent ) |
597 | 0 | { |
598 | 0 | XMP_Assert ( schemaNode->options & kXMP_SchemaNode ); |
599 | 0 | XMP_Assert ( schemaNode->qualifiers.empty() ); |
600 | | |
601 | | // Write the rdf:Description start tag with the namespace declarations. |
602 | | |
603 | 0 | XMP_Index level; |
604 | 0 | for ( level = baseIndent+2; level > 0; --level ) outputStr += indentStr; |
605 | 0 | outputStr += kRDF_SchemaStart; |
606 | 0 | outputStr += '"'; |
607 | 0 | outputStr += treeName; |
608 | 0 | outputStr += '"'; |
609 | |
|
610 | 0 | size_t totalLen = 8; // Start at 8 for "xml:rdf:". |
611 | 0 | XMP_cStringMapPos currPos = sNamespacePrefixToURIMap->begin(); |
612 | 0 | XMP_cStringMapPos endPos = sNamespacePrefixToURIMap->end(); |
613 | 0 | for ( ; currPos != endPos; ++currPos ) totalLen += currPos->first.size(); |
614 | |
|
615 | 0 | XMP_VarString usedNS; |
616 | 0 | usedNS.reserve ( totalLen ); |
617 | 0 | usedNS = "xml:rdf:"; |
618 | 0 | DeclareUsedNamespaces ( schemaNode, usedNS, outputStr, newline, indentStr, baseIndent+4 ); |
619 | |
|
620 | 0 | outputStr += ">"; |
621 | 0 | outputStr += newline; |
622 | | |
623 | | // Write alias comments, if wanted. |
624 | |
|
625 | 0 | if ( options & kXMP_WriteAliasComments ) { // *** Hoist into a routine, used for Plain XMP also. |
626 | |
|
627 | | #if 0 // *** Buggy, disable for now. |
628 | | |
629 | | XMP_cAliasMapPos aliasPos = sRegisteredAliasMap->begin(); |
630 | | XMP_cAliasMapPos aliasEnd = sRegisteredAliasMap->end(); |
631 | | |
632 | | for ( ; aliasPos != aliasEnd; ++aliasPos ) { |
633 | | |
634 | | size_t nsPos = aliasPos->first.find ( schemaNode->value ); |
635 | | if ( nsPos == XMP_VarString::npos ) continue; |
636 | | XMP_Assert ( nsPos == 0 ); |
637 | | |
638 | | for ( level = baseIndent+3; level > 0; --level ) outputStr += indentStr; |
639 | | |
640 | | outputStr += "<!-- "; |
641 | | outputStr += aliasPos->first; |
642 | | outputStr += " is aliased to "; |
643 | | for ( size_t step = 1, stepLim = aliasPos->second.size(); step != stepLim; ++step ) { |
644 | | outputStr += aliasPos->second[step].step; |
645 | | } |
646 | | outputStr += " -->"; |
647 | | outputStr += newline; |
648 | | |
649 | | } |
650 | | |
651 | | #endif |
652 | |
|
653 | 0 | } |
654 | | |
655 | | // Write each of the schema's actual properties. |
656 | 0 | for ( size_t propNum = 0, propLim = schemaNode->children.size(); propNum < propLim; ++propNum ) { |
657 | 0 | const XMP_Node * currProp = schemaNode->children[propNum]; |
658 | 0 | SerializePrettyRDFProperty ( currProp, outputStr, newline, indentStr, baseIndent+3 ); |
659 | 0 | } |
660 | | |
661 | | // Write the rdf:Description end tag. |
662 | 0 | for ( level = baseIndent+2; level > 0; --level ) outputStr += indentStr; |
663 | 0 | outputStr += kRDF_SchemaEnd; |
664 | 0 | outputStr += newline; |
665 | |
|
666 | 0 | } // SerializePrettyRDFSchema |
667 | | |
668 | | |
669 | | // ------------------------------------------------------------------------------------------------- |
670 | | // SerializeCompactRDFAttrProps |
671 | | // ---------------------------- |
672 | | // |
673 | | // Write each of the parent's simple unqualified properties as an attribute. Returns true if all |
674 | | // of the properties are written as attributes. |
675 | | |
676 | | static bool |
677 | | SerializeCompactRDFAttrProps ( const XMP_Node * parentNode, |
678 | | XMP_VarString & outputStr, |
679 | | XMP_StringPtr newline, |
680 | | XMP_StringPtr indentStr, |
681 | | XMP_Index indent ) |
682 | 1.06k | { |
683 | 1.06k | size_t prop, propLim; |
684 | 1.06k | bool allAreAttrs = true; |
685 | | |
686 | 5.15k | for ( prop = 0, propLim = parentNode->children.size(); prop != propLim; ++prop ) { |
687 | | |
688 | 4.09k | const XMP_Node * currProp = parentNode->children[prop]; |
689 | 4.09k | if ( ! CanBeRDFAttrProp ( currProp ) ) { |
690 | 573 | allAreAttrs = false; |
691 | 573 | continue; |
692 | 573 | } |
693 | | |
694 | 3.52k | outputStr += newline; |
695 | 15.2k | for ( XMP_Index level = indent; level > 0; --level ) outputStr += indentStr; |
696 | 3.52k | outputStr += currProp->name; |
697 | 3.52k | outputStr += "=\""; |
698 | 3.52k | AppendNodeValue ( outputStr, currProp->value, kForAttribute ); |
699 | 3.52k | outputStr += '"'; |
700 | | |
701 | 3.52k | } |
702 | | |
703 | 1.06k | return allAreAttrs; |
704 | | |
705 | 1.06k | } // SerializeCompactRDFAttrProps |
706 | | |
707 | | |
708 | | // ------------------------------------------------------------------------------------------------- |
709 | | // SerializeCompactRDFElemProps |
710 | | // ---------------------------- |
711 | | // |
712 | | // Recursively handles the "value" for a node that must be written as an RDF property element. It |
713 | | // does not matter if it is a top level property, a field of a struct, or an item of an array. The |
714 | | // indent is that for the property element. The patterns bwlow ignore attribute qualifiers such as |
715 | | // xml:lang, they don't affect the output form. |
716 | | // |
717 | | // <ns:UnqualifiedStructProperty-1 |
718 | | // ... The fields as attributes, if all are simple and unqualified |
719 | | // /> |
720 | | // |
721 | | // <ns:UnqualifiedStructProperty-2 rdf:parseType="Resource"> |
722 | | // ... The fields as elements, if none are simple and unqualified |
723 | | // </ns:UnqualifiedStructProperty-2> |
724 | | // |
725 | | // <ns:UnqualifiedStructProperty-3> |
726 | | // <rdf:Description |
727 | | // ... The simple and unqualified fields as attributes |
728 | | // > |
729 | | // ... The compound or qualified fields as elements |
730 | | // </rdf:Description> |
731 | | // </ns:UnqualifiedStructProperty-3> |
732 | | // |
733 | | // <ns:UnqualifiedArrayProperty> |
734 | | // <rdf:Bag> or Seq or Alt |
735 | | // ... Array items as rdf:li elements, same forms as top level properties |
736 | | // </rdf:Bag> |
737 | | // </ns:UnqualifiedArrayProperty> |
738 | | // |
739 | | // <ns:QualifiedProperty rdf:parseType="Resource"> |
740 | | // <rdf:value> ... Property "value" following the unqualified forms ... </rdf:value> |
741 | | // ... Qualifiers looking like named struct fields |
742 | | // </ns:QualifiedProperty> |
743 | | |
744 | | // *** Consider numbered array items, but has compatibility problems. |
745 | | // *** Consider qualified form with rdf:Description and attributes. |
746 | | |
747 | | static void |
748 | | SerializeCompactRDFElemProps ( const XMP_Node * parentNode, |
749 | | XMP_VarString & outputStr, |
750 | | XMP_StringPtr newline, |
751 | | XMP_StringPtr indentStr, |
752 | | XMP_Index indent ) |
753 | 1.00k | { |
754 | 1.00k | XMP_Index level; |
755 | | |
756 | 5.26k | for ( size_t prop = 0, propLim = parentNode->children.size(); prop != propLim; ++prop ) { |
757 | | |
758 | 4.26k | const XMP_Node * propNode = parentNode->children[prop]; |
759 | 4.26k | if ( CanBeRDFAttrProp ( propNode ) ) continue; |
760 | | |
761 | 1.64k | bool emitEndTag = true; |
762 | 1.64k | bool indentEndTag = true; |
763 | | |
764 | 1.64k | XMP_OptionBits propForm = propNode->options & kXMP_PropCompositeMask; |
765 | | |
766 | | // ----------------------------------------------------------------------------------- |
767 | | // Determine the XML element name, write the name part of the start tag. Look over the |
768 | | // qualifiers to decide on "normal" versus "rdf:value" form. Emit the attribute |
769 | | // qualifiers at the same time. |
770 | | |
771 | 1.64k | XMP_StringPtr elemName = propNode->name.c_str(); |
772 | 1.64k | if ( *elemName == '[' ) elemName = "rdf:li"; |
773 | | |
774 | 8.70k | for ( level = indent; level > 0; --level ) outputStr += indentStr; |
775 | 1.64k | outputStr += '<'; |
776 | 1.64k | outputStr += elemName; |
777 | | |
778 | 1.64k | #define isCompact false |
779 | 1.64k | bool hasGeneralQualifiers = isCompact; // Might also become true later. |
780 | 1.64k | bool hasRDFResourceQual = false; |
781 | | |
782 | 1.82k | for ( size_t qualNum = 0, qualLim = propNode->qualifiers.size(); qualNum < qualLim; ++qualNum ) { |
783 | 188 | const XMP_Node * currQual = propNode->qualifiers[qualNum]; |
784 | 188 | if ( ! IsRDFAttrQualifier ( currQual->name ) ) { |
785 | 58 | hasGeneralQualifiers = true; |
786 | 130 | } else { |
787 | 130 | if ( currQual->name == "rdf:resource" ) hasRDFResourceQual = true; |
788 | 130 | outputStr += ' '; |
789 | 130 | outputStr += currQual->name; |
790 | 130 | outputStr += "=\""; |
791 | 130 | AppendNodeValue ( outputStr, currQual->value, kForAttribute ); |
792 | 130 | outputStr += '"'; |
793 | 130 | } |
794 | 188 | } |
795 | | |
796 | | // -------------------------------------------------------- |
797 | | // Process the property according to the standard patterns. |
798 | | |
799 | 1.64k | if ( hasGeneralQualifiers ) { |
800 | | |
801 | | // ------------------------------------------------------------------------------------- |
802 | | // The node has general qualifiers, ones that can't be attributes on a property element. |
803 | | // Emit using the qualified property pseudo-struct form. The value is output by a call |
804 | | // to SerializePrettyRDFProperty with emitAsRDFValue set. |
805 | | |
806 | | // *** We're losing compactness in the calls to SerializePrettyRDFProperty. |
807 | | // *** Should refactor to have SerializeCompactRDFProperty that does one node. |
808 | | |
809 | 16 | outputStr += " rdf:parseType=\"Resource\">"; |
810 | 16 | outputStr += newline; |
811 | | |
812 | 16 | SerializePrettyRDFProperty ( propNode, outputStr, newline, indentStr, indent+1, true ); |
813 | | |
814 | 16 | size_t qualNum = 0; |
815 | 16 | size_t qualLim = propNode->qualifiers.size(); |
816 | 16 | if ( propNode->options & kXMP_PropHasLang ) ++qualNum; |
817 | | |
818 | 74 | for ( ; qualNum < qualLim; ++qualNum ) { |
819 | 58 | const XMP_Node * currQual = propNode->qualifiers[qualNum]; |
820 | 58 | SerializePrettyRDFProperty ( currQual, outputStr, newline, indentStr, indent+1 ); |
821 | 58 | } |
822 | | |
823 | 1.62k | } else { |
824 | | |
825 | | // -------------------------------------------------------------------- |
826 | | // This node has only attribute qualifiers. Emit as a property element. |
827 | | |
828 | 1.62k | if ( propForm == 0 ) { |
829 | | |
830 | | // -------------------------- |
831 | | // This is a simple property. |
832 | | |
833 | 983 | if ( propNode->options & kXMP_PropValueIsURI ) { |
834 | 0 | outputStr += " rdf:resource=\""; |
835 | 0 | AppendNodeValue ( outputStr, propNode->value, kForAttribute ); |
836 | 0 | outputStr += "\"/>"; |
837 | 0 | outputStr += newline; |
838 | 0 | emitEndTag = false; |
839 | 983 | } else if ( propNode->value.empty() ) { |
840 | 3 | outputStr += "/>"; |
841 | 3 | outputStr += newline; |
842 | 3 | emitEndTag = false; |
843 | 980 | } else { |
844 | 980 | outputStr += '>'; |
845 | 980 | AppendNodeValue ( outputStr, propNode->value, kForElement ); |
846 | 980 | indentEndTag = false; |
847 | 980 | } |
848 | | |
849 | 983 | } else if ( propForm & kXMP_PropValueIsArray ) { |
850 | | |
851 | | // ----------------- |
852 | | // This is an array. |
853 | | |
854 | 477 | outputStr += '>'; |
855 | 477 | outputStr += newline; |
856 | 477 | EmitRDFArrayTag ( propForm, outputStr, newline, indentStr, indent+1, propNode->children.size(), kIsStartTag ); |
857 | | |
858 | 477 | if ( XMP_ArrayIsAltText(propNode->options) ) NormalizeLangArray ( (XMP_Node*)propNode ); |
859 | 477 | SerializeCompactRDFElemProps ( propNode, outputStr, newline, indentStr, indent+2 ); |
860 | | |
861 | 477 | EmitRDFArrayTag ( propForm, outputStr, newline, indentStr, indent+1, propNode->children.size(), kIsEndTag ); |
862 | | |
863 | 477 | } else { |
864 | | |
865 | | // ---------------------- |
866 | | // This must be a struct. |
867 | | |
868 | 165 | XMP_Assert ( propForm & kXMP_PropValueIsStruct ); |
869 | | |
870 | 165 | bool hasAttrFields = false; |
871 | 165 | bool hasElemFields = false; |
872 | | |
873 | 165 | size_t field, fieldLim; |
874 | 632 | for ( field = 0, fieldLim = propNode->children.size(); field != fieldLim; ++field ) { |
875 | 467 | XMP_Node * currField = propNode->children[field]; |
876 | 467 | if ( CanBeRDFAttrProp ( currField ) ) { |
877 | 467 | hasAttrFields = true; |
878 | 467 | if ( hasElemFields ) break; // No sense looking further. |
879 | 467 | } else { |
880 | 0 | hasElemFields = true; |
881 | 0 | if ( hasAttrFields ) break; // No sense looking further. |
882 | 0 | } |
883 | 467 | } |
884 | | |
885 | 165 | if ( hasRDFResourceQual && hasElemFields ) { |
886 | 0 | XMP_Throw ( "Can't mix rdf:resource qualifier and element fields", kXMPErr_BadRDF ); |
887 | 0 | } |
888 | | |
889 | 165 | if ( propNode->children.size() == 0 ) { |
890 | | |
891 | | // Catch an empty struct as a special case. The case below would emit an empty |
892 | | // XML element, which gets reparsed as a simple property with an empty value. |
893 | 0 | outputStr += " rdf:parseType=\"Resource\"/>"; |
894 | 0 | outputStr += newline; |
895 | 0 | emitEndTag = false; |
896 | | |
897 | 165 | } else if ( ! hasElemFields ) { |
898 | | |
899 | | // All fields can be attributes, use the emptyPropertyElt form. |
900 | 165 | SerializeCompactRDFAttrProps ( propNode, outputStr, newline, indentStr, indent+1 ); |
901 | 165 | outputStr += "/>"; |
902 | 165 | outputStr += newline; |
903 | 165 | emitEndTag = false; |
904 | | |
905 | 165 | } else if ( ! hasAttrFields ) { |
906 | | |
907 | | // All fields must be elements, use the parseTypeResourcePropertyElt form. |
908 | 0 | outputStr += " rdf:parseType=\"Resource\">"; |
909 | 0 | outputStr += newline; |
910 | 0 | SerializeCompactRDFElemProps ( propNode, outputStr, newline, indentStr, indent+1 ); |
911 | | |
912 | 0 | } else { |
913 | | |
914 | | // Have a mix of attributes and elements, use an inner rdf:Description. |
915 | 0 | outputStr += '>'; |
916 | 0 | outputStr += newline; |
917 | 0 | for ( level = indent+1; level > 0; --level ) outputStr += indentStr; |
918 | 0 | outputStr += "<rdf:Description"; |
919 | 0 | SerializeCompactRDFAttrProps ( propNode, outputStr, newline, indentStr, indent+2 ); |
920 | 0 | outputStr += ">"; |
921 | 0 | outputStr += newline; |
922 | 0 | SerializeCompactRDFElemProps ( propNode, outputStr, newline, indentStr, indent+1 ); |
923 | 0 | for ( level = indent+1; level > 0; --level ) outputStr += indentStr; |
924 | 0 | outputStr += kRDF_StructEnd; |
925 | 0 | outputStr += newline; |
926 | |
|
927 | 0 | } |
928 | | |
929 | 165 | } |
930 | | |
931 | 1.62k | } |
932 | | |
933 | | // ---------------------------------- |
934 | | // Emit the property element end tag. |
935 | | |
936 | 1.64k | if ( emitEndTag ) { |
937 | 2.00k | if ( indentEndTag ) for ( level = indent; level > 0; --level ) outputStr += indentStr; |
938 | 1.47k | outputStr += "</"; |
939 | 1.47k | outputStr += elemName; |
940 | 1.47k | outputStr += '>'; |
941 | 1.47k | outputStr += newline; |
942 | 1.47k | } |
943 | | |
944 | 1.64k | } |
945 | | |
946 | 1.00k | } // SerializeCompactRDFElemProps |
947 | | |
948 | | |
949 | | // ------------------------------------------------------------------------------------------------- |
950 | | // SerializeCompactRDFSchemas |
951 | | // -------------------------- |
952 | | // |
953 | | // All properties from all schema are written in a single rdf:Description element, as are all of the |
954 | | // necessary namespace declarations. The baseIndent is the base level for the entire serialization, |
955 | | // that of the x:xmpmeta element. The x:xmpmeta and rdf:RDF elements have already been written. |
956 | | // |
957 | | // Top level simple unqualified properties are written as attributes of the (only) rdf:Description |
958 | | // element. Structs, arrays, and qualified properties are written by SerializeCompactRDFElemProp. An |
959 | | // xml:lang qualifier on a simple property prevents the attribute form. |
960 | | // |
961 | | // <rdf:Description rdf:about="TreeName" |
962 | | // xmlns:ns="URI" ... |
963 | | // ns:UnqualifiedSimpleProperty="value" ... > |
964 | | // ... The remaining properties of the schema, see SerializeCompactRDFElemProps |
965 | | // </rdf:Description> |
966 | | |
967 | | static void |
968 | | SerializeCompactRDFSchemas ( const XMP_Node & xmpTree, |
969 | | XMP_VarString & outputStr, |
970 | | XMP_StringPtr newline, |
971 | | XMP_StringPtr indentStr, |
972 | | XMP_Index baseIndent ) |
973 | 298 | { |
974 | 298 | XMP_Index level; |
975 | 298 | size_t schema, schemaLim; |
976 | | |
977 | | // Begin the rdf:Description start tag. |
978 | 894 | for ( level = baseIndent+2; level > 0; --level ) outputStr += indentStr; |
979 | 298 | outputStr += kRDF_SchemaStart; |
980 | 298 | outputStr += '"'; |
981 | 298 | outputStr += xmpTree.name; |
982 | 298 | outputStr += '"'; |
983 | | |
984 | | // Write all necessary xmlns attributes. |
985 | | |
986 | 298 | size_t totalLen = 8; // Start at 8 for "xml:rdf:". |
987 | 298 | XMP_cStringMapPos currPos = sNamespacePrefixToURIMap->begin(); |
988 | 298 | XMP_cStringMapPos endPos = sNamespacePrefixToURIMap->end(); |
989 | 45.7k | for ( ; currPos != endPos; ++currPos ) totalLen += currPos->first.size(); |
990 | | |
991 | 298 | XMP_VarString usedNS; |
992 | 298 | usedNS.reserve ( totalLen ); |
993 | 298 | usedNS = "xml:rdf:"; |
994 | | |
995 | 1.19k | for ( schema = 0, schemaLim = xmpTree.children.size(); schema != schemaLim; ++schema ) { |
996 | 895 | const XMP_Node * currSchema = xmpTree.children[schema]; |
997 | 895 | DeclareUsedNamespaces ( currSchema, usedNS, outputStr, newline, indentStr, baseIndent+4 ); |
998 | 895 | } |
999 | | |
1000 | | // Write the top level "attrProps" and close the rdf:Description start tag. |
1001 | 298 | bool allAreAttrs = true; |
1002 | 1.19k | for ( schema = 0, schemaLim = xmpTree.children.size(); schema != schemaLim; ++schema ) { |
1003 | 895 | const XMP_Node * currSchema = xmpTree.children[schema]; |
1004 | 895 | allAreAttrs &= SerializeCompactRDFAttrProps ( currSchema, outputStr, newline, indentStr, baseIndent+3 ); |
1005 | 895 | } |
1006 | 298 | if ( ! allAreAttrs ) { |
1007 | 111 | outputStr += ">"; |
1008 | 111 | outputStr += newline; |
1009 | 187 | } else { |
1010 | 187 | outputStr += "/>"; |
1011 | 187 | outputStr += newline; |
1012 | 187 | return; // ! Done if all properties in all schema are written as attributes. |
1013 | 187 | } |
1014 | | |
1015 | | // Write the remaining properties for each schema. |
1016 | 634 | for ( schema = 0, schemaLim = xmpTree.children.size(); schema != schemaLim; ++schema ) { |
1017 | 523 | const XMP_Node * currSchema = xmpTree.children[schema]; |
1018 | 523 | SerializeCompactRDFElemProps ( currSchema, outputStr, newline, indentStr, baseIndent+3 ); |
1019 | 523 | } |
1020 | | |
1021 | | // Write the rdf:Description end tag. |
1022 | | // *** Elide the end tag if everything (all props in all schema) is an attr. |
1023 | 333 | for ( level = baseIndent+2; level > 0; --level ) outputStr += indentStr; |
1024 | 111 | outputStr += kRDF_SchemaEnd; |
1025 | 111 | outputStr += newline; |
1026 | | |
1027 | 111 | } // SerializeCompactRDFSchemas |
1028 | | |
1029 | | |
1030 | | // ------------------------------------------------------------------------------------------------- |
1031 | | // SerializeAsRDF |
1032 | | // -------------- |
1033 | | // |
1034 | | // <?xpacket begin... ?> |
1035 | | // <x:xmpmeta xmlns:x=... > |
1036 | | // <rdf:RDF xmlns:rdf=... > |
1037 | | // |
1038 | | // ... The properties, see SerializePrettyRDFSchema or SerializeCompactRDFSchemas |
1039 | | // |
1040 | | // </rdf:RDF> |
1041 | | // </x:xmpmeta> |
1042 | | // <?xpacket end... ?> |
1043 | | |
1044 | | // *** Need to strip empty arrays? |
1045 | | // *** Option to strip/keep empty structs? |
1046 | | // *** Need to verify handling of rdf:type qualifiers in pretty and compact. |
1047 | | // *** Need to verify round tripping of rdf:ID and similar qualifiers, see RDF 7.2.21. |
1048 | | // *** Check cases of rdf:resource plus explicit attr qualifiers (like xml:lang). |
1049 | | |
1050 | | static void |
1051 | | SerializeAsRDF ( const XMPMeta & xmpObj, |
1052 | | XMP_VarString & headStr, // Everything up to the padding. |
1053 | | XMP_VarString & tailStr, // Everything after the padding. |
1054 | | XMP_OptionBits options, |
1055 | | XMP_StringPtr newline, |
1056 | | XMP_StringPtr indentStr, |
1057 | | XMP_Index baseIndent ) |
1058 | 298 | { |
1059 | 298 | const size_t treeNameLen = xmpObj.tree.name.size(); |
1060 | 298 | const size_t indentLen = strlen ( indentStr ); |
1061 | | |
1062 | | // First estimate the worst case space and reserve room in the output string. This optimization |
1063 | | // avoids reallocating and copying the output as it grows. The initial count does not look at |
1064 | | // the values of properties, so it does not account for character entities, e.g. 
 for newline. |
1065 | | // Since there can be a lot of these in things like the base 64 encoding of a large thumbnail, |
1066 | | // inflate the count by 1/4 (easy to do) to accommodate. |
1067 | | |
1068 | | // *** Need to include estimate for alias comments. |
1069 | | |
1070 | 298 | size_t outputLen = 2 * (strlen(kPacketHeader) + strlen(kRDF_XMPMetaStart) + strlen(kRDF_RDFStart) + 3*baseIndent*indentLen); |
1071 | | |
1072 | 1.19k | for ( size_t schemaNum = 0, schemaLim = xmpObj.tree.children.size(); schemaNum < schemaLim; ++schemaNum ) { |
1073 | 895 | const XMP_Node * currSchema = xmpObj.tree.children[schemaNum]; |
1074 | 895 | outputLen += 2*(baseIndent+2)*indentLen + strlen(kRDF_SchemaStart) + treeNameLen + strlen(kRDF_SchemaEnd) + 2; |
1075 | 895 | outputLen += EstimateRDFSize ( currSchema, baseIndent+2, indentLen ); |
1076 | 895 | } |
1077 | | |
1078 | 298 | outputLen += (outputLen >> 2); // Inflate by 1/4, an empirical fudge factor. |
1079 | | |
1080 | | // Now generate the RDF into the head string as UTF-8. |
1081 | | |
1082 | 298 | XMP_Index level; |
1083 | | |
1084 | 298 | headStr.erase(); |
1085 | 298 | headStr.reserve ( outputLen ); |
1086 | | |
1087 | | // Write the packet header PI. |
1088 | 298 | if ( ! (options & kXMP_OmitPacketWrapper) ) { |
1089 | 298 | for ( level = baseIndent; level > 0; --level ) headStr += indentStr; |
1090 | 298 | headStr += kPacketHeader; |
1091 | 298 | headStr += newline; |
1092 | 298 | } |
1093 | | |
1094 | | // Write the xmpmeta element's start tag. |
1095 | 298 | if ( ! (options & kXMP_OmitXMPMetaElement) ) { |
1096 | 298 | for ( level = baseIndent; level > 0; --level ) headStr += indentStr; |
1097 | 298 | headStr += kRDF_XMPMetaStart; |
1098 | 298 | headStr += kXMPCore_VersionMessage "\">"; |
1099 | 298 | headStr += newline; |
1100 | 298 | } |
1101 | | |
1102 | | // Write the rdf:RDF start tag. |
1103 | 596 | for ( level = baseIndent+1; level > 0; --level ) headStr += indentStr; |
1104 | 298 | headStr += kRDF_RDFStart; |
1105 | 298 | headStr += newline; |
1106 | | |
1107 | | // Write all of the properties. |
1108 | 298 | if ( options & kXMP_UseCompactFormat ) { |
1109 | 298 | SerializeCompactRDFSchemas ( xmpObj.tree, headStr, newline, indentStr, baseIndent ); |
1110 | 298 | } else { |
1111 | 0 | if ( xmpObj.tree.children.size() > 0 ) { |
1112 | 0 | for ( size_t schemaNum = 0, schemaLim = xmpObj.tree.children.size(); schemaNum < schemaLim; ++schemaNum ) { |
1113 | 0 | const XMP_Node * currSchema = xmpObj.tree.children[schemaNum]; |
1114 | 0 | SerializePrettyRDFSchema ( xmpObj.tree.name, currSchema, headStr, options, newline, indentStr, baseIndent ); |
1115 | 0 | } |
1116 | 0 | } else { |
1117 | 0 | for ( XMP_Index level = baseIndent+2; level > 0; --level ) headStr += indentStr; |
1118 | 0 | headStr += kRDF_SchemaStart; // Special case an empty XMP object. |
1119 | 0 | headStr += '"'; |
1120 | 0 | headStr += xmpObj.tree.name; |
1121 | 0 | headStr += "\"/>"; |
1122 | 0 | headStr += newline; |
1123 | 0 | } |
1124 | 0 | } |
1125 | | |
1126 | | // Write the rdf:RDF end tag. |
1127 | 596 | for ( level = baseIndent+1; level > 0; --level ) headStr += indentStr; |
1128 | 298 | headStr += kRDF_RDFEnd; |
1129 | 298 | headStr += newline; |
1130 | | |
1131 | | // Write the xmpmeta end tag. |
1132 | 298 | if ( ! (options & kXMP_OmitXMPMetaElement) ) { |
1133 | 298 | for ( level = baseIndent; level > 0; --level ) headStr += indentStr; |
1134 | 298 | headStr += kRDF_XMPMetaEnd; |
1135 | 298 | headStr += newline; |
1136 | 298 | } |
1137 | | |
1138 | | // Write the packet trailer PI into the tail string as UTF-8. |
1139 | 298 | tailStr.erase(); |
1140 | 298 | if ( ! (options & kXMP_OmitPacketWrapper) ) { |
1141 | 298 | tailStr.reserve ( strlen(kPacketTrailer) + (strlen(indentStr) * baseIndent) ); |
1142 | 298 | for ( level = baseIndent; level > 0; --level ) tailStr += indentStr; |
1143 | 298 | tailStr += kPacketTrailer; |
1144 | 298 | if ( options & kXMP_ReadOnlyPacket ) tailStr[tailStr.size()-4] = 'r'; |
1145 | 298 | } |
1146 | | |
1147 | | // ! This assert is just a performance check, to see if the reserve was enough. |
1148 | | // *** XMP_Assert ( headStr.size() <= outputLen ); |
1149 | | // *** Don't use an assert. Think of some way to track this without risk of aborting the client. |
1150 | | |
1151 | 298 | } // SerializeAsRDF |
1152 | | |
1153 | | // ------------------------------------------------------------------------------------------------- |
1154 | | // SerializeToBuffer |
1155 | | // ----------------- |
1156 | | |
1157 | | void |
1158 | | XMPMeta::SerializeToBuffer ( XMP_StringPtr * rdfString, |
1159 | | XMP_StringLen * rdfSize, |
1160 | | XMP_OptionBits options, |
1161 | | XMP_StringLen padding, |
1162 | | XMP_StringPtr newline, |
1163 | | XMP_StringPtr indentStr, |
1164 | | XMP_Index baseIndent ) const |
1165 | 298 | { |
1166 | 298 | XMP_Assert ( (rdfString != 0) && (rdfSize != 0) && (newline != 0) && (indentStr != 0) ); |
1167 | | |
1168 | | // Fix up some default parameters. |
1169 | | |
1170 | 298 | enum { kDefaultPad = 2048 }; |
1171 | 298 | size_t unicodeUnitSize = 1; |
1172 | 298 | XMP_OptionBits charEncoding = options & kXMP_EncodingMask; |
1173 | | |
1174 | 298 | if ( charEncoding != kXMP_EncodeUTF8 ) { |
1175 | 0 | if ( options & _XMP_UTF16_Bit ) { |
1176 | 0 | if ( options & _XMP_UTF32_Bit ) XMP_Throw ( "Can't use both _XMP_UTF16_Bit and _XMP_UTF32_Bit", kXMPErr_BadOptions ); |
1177 | 0 | unicodeUnitSize = 2; |
1178 | 0 | } else if ( options & _XMP_UTF32_Bit ) { |
1179 | 0 | unicodeUnitSize = 4; |
1180 | 0 | } else { |
1181 | 0 | XMP_Throw ( "Can't use _XMP_LittleEndian_Bit by itself", kXMPErr_BadOptions ); |
1182 | 0 | } |
1183 | 0 | } |
1184 | | |
1185 | 298 | if ( options & kXMP_OmitAllFormatting ) { |
1186 | 55 | newline = " "; // ! Yes, a space for "newline". This ensures token separation. |
1187 | 55 | indentStr = ""; |
1188 | 243 | } else { |
1189 | 243 | if ( *newline == 0 ) newline = "\xA"; // Linefeed |
1190 | 243 | if ( *indentStr == 0 ) { |
1191 | 243 | indentStr = " "; |
1192 | 243 | if ( ! (options & kXMP_UseCompactFormat) ) indentStr = " "; |
1193 | 243 | } |
1194 | 243 | } |
1195 | | |
1196 | 298 | if ( options & kXMP_ExactPacketLength ) { |
1197 | 0 | if ( options & (kXMP_OmitPacketWrapper | kXMP_IncludeThumbnailPad) ) { |
1198 | 0 | XMP_Throw ( "Inconsistent options for exact size serialize", kXMPErr_BadOptions ); |
1199 | 0 | } |
1200 | 0 | if ( (padding & (unicodeUnitSize-1)) != 0 ) { |
1201 | 0 | XMP_Throw ( "Exact size must be a multiple of the Unicode element", kXMPErr_BadOptions ); |
1202 | 0 | } |
1203 | 298 | } else if ( options & kXMP_ReadOnlyPacket ) { |
1204 | 0 | if ( options & (kXMP_OmitPacketWrapper | kXMP_IncludeThumbnailPad) ) { |
1205 | 0 | XMP_Throw ( "Inconsistent options for read-only packet", kXMPErr_BadOptions ); |
1206 | 0 | } |
1207 | 0 | padding = 0; |
1208 | 298 | } else if ( options & kXMP_OmitPacketWrapper ) { |
1209 | 0 | if ( options & kXMP_IncludeThumbnailPad ) { |
1210 | 0 | XMP_Throw ( "Inconsistent options for non-packet serialize", kXMPErr_BadOptions ); |
1211 | 0 | } |
1212 | 0 | padding = 0; |
1213 | 298 | } else { |
1214 | 298 | if ( padding == 0 ) padding = kDefaultPad * unicodeUnitSize; |
1215 | 298 | if ( options & kXMP_IncludeThumbnailPad ) { |
1216 | 0 | if ( ! this->DoesPropertyExist ( kXMP_NS_XMP, "Thumbnails" ) ) padding += (10000 * unicodeUnitSize); // *** Need a better estimate. |
1217 | 0 | } |
1218 | 298 | } |
1219 | | |
1220 | | // Serialize as UTF-8, then convert to UTF-16 or UTF-32 if necessary, and assemble with the padding and tail. |
1221 | | |
1222 | 298 | std::string tailStr; |
1223 | | |
1224 | 298 | SerializeAsRDF ( *this, *sOutputStr, tailStr, options, newline, indentStr, baseIndent ); |
1225 | 298 | if ( charEncoding == kXMP_EncodeUTF8 ) { |
1226 | | |
1227 | 298 | if ( options & kXMP_ExactPacketLength ) { |
1228 | 0 | size_t minSize = sOutputStr->size() + tailStr.size(); |
1229 | 0 | if ( minSize > padding ) XMP_Throw ( "Can't fit into specified packet size", kXMPErr_BadSerialize ); |
1230 | 0 | padding -= minSize; // Now the actual amount of padding to add. |
1231 | 0 | } |
1232 | | |
1233 | 298 | size_t newlineLen = strlen ( newline ); |
1234 | | |
1235 | 298 | if ( padding < newlineLen ) { |
1236 | 0 | sOutputStr->append ( padding, ' ' ); |
1237 | 298 | } else { |
1238 | 298 | padding -= newlineLen; // Write this newline last. |
1239 | 6.25k | while ( padding >= (100 + newlineLen) ) { |
1240 | 5.96k | sOutputStr->append ( 100, ' ' ); |
1241 | 5.96k | *sOutputStr += newline; |
1242 | 5.96k | padding -= (100 + newlineLen); |
1243 | 5.96k | } |
1244 | 298 | sOutputStr->append ( padding, ' ' ); |
1245 | 298 | *sOutputStr += newline; |
1246 | 298 | } |
1247 | | |
1248 | 298 | *sOutputStr += tailStr; |
1249 | | |
1250 | 298 | } else { |
1251 | | |
1252 | | // Need to convert the encoding. Swap the UTF-8 into a local string and convert back. Assemble everything. |
1253 | | |
1254 | 0 | XMP_VarString utf8Str, newlineStr; |
1255 | 0 | bool bigEndian = ((charEncoding & _XMP_LittleEndian_Bit) == 0); |
1256 | | |
1257 | 0 | if ( charEncoding & _XMP_UTF16_Bit ) { |
1258 | |
|
1259 | 0 | std::string padStr ( " " ); padStr[0] = 0; // Assume big endian. |
1260 | | |
1261 | 0 | utf8Str.swap ( *sOutputStr ); |
1262 | 0 | ToUTF16 ( (UTF8Unit*)utf8Str.c_str(), utf8Str.size(), sOutputStr, bigEndian ); |
1263 | 0 | utf8Str.swap ( tailStr ); |
1264 | 0 | ToUTF16 ( (UTF8Unit*)utf8Str.c_str(), utf8Str.size(), &tailStr, bigEndian ); |
1265 | |
|
1266 | 0 | if ( options & kXMP_ExactPacketLength ) { |
1267 | 0 | size_t minSize = sOutputStr->size() + tailStr.size(); |
1268 | 0 | if ( minSize > padding ) XMP_Throw ( "Can't fit into specified packet size", kXMPErr_BadSerialize ); |
1269 | 0 | padding -= minSize; // Now the actual amount of padding to add (in bytes). |
1270 | 0 | } |
1271 | | |
1272 | 0 | utf8Str.assign ( newline ); |
1273 | 0 | ToUTF16 ( (UTF8Unit*)utf8Str.c_str(), utf8Str.size(), &newlineStr, bigEndian ); |
1274 | 0 | size_t newlineLen = newlineStr.size(); |
1275 | | |
1276 | 0 | if ( padding < newlineLen ) { |
1277 | 0 | for ( int i = padding/2; i > 0; --i ) *sOutputStr += padStr; |
1278 | 0 | } else { |
1279 | 0 | padding -= newlineLen; // Write this newline last. |
1280 | 0 | while ( padding >= (200 + newlineLen) ) { |
1281 | 0 | for ( int i = 100; i > 0; --i ) *sOutputStr += padStr; |
1282 | 0 | *sOutputStr += newlineStr; |
1283 | 0 | padding -= (200 + newlineLen); |
1284 | 0 | } |
1285 | 0 | for ( int i = padding/2; i > 0; --i ) *sOutputStr += padStr; |
1286 | 0 | *sOutputStr += newlineStr; |
1287 | 0 | } |
1288 | |
|
1289 | 0 | *sOutputStr += tailStr; |
1290 | |
|
1291 | 0 | } else { |
1292 | |
|
1293 | 0 | std::string padStr ( " " ); padStr[0] = padStr[1] = padStr[2] = 0; // Assume big endian. |
1294 | | // UTF8_to_UTF32_Proc Converter = UTF8_to_UTF32BE; |
1295 | |
|
1296 | 0 | if ( charEncoding & _XMP_LittleEndian_Bit ) { |
1297 | 0 | padStr[0] = ' '; padStr[1] = padStr[2] = padStr[3] = 0; |
1298 | | // Converter = UTF8_to_UTF32LE; |
1299 | 0 | } |
1300 | | |
1301 | 0 | utf8Str.swap ( *sOutputStr ); |
1302 | 0 | ToUTF32 ( (UTF8Unit*)utf8Str.c_str(), utf8Str.size(), sOutputStr, bigEndian ); |
1303 | 0 | utf8Str.swap ( tailStr ); |
1304 | 0 | ToUTF32 ( (UTF8Unit*)utf8Str.c_str(), utf8Str.size(), &tailStr, bigEndian ); |
1305 | |
|
1306 | 0 | if ( options & kXMP_ExactPacketLength ) { |
1307 | 0 | size_t minSize = sOutputStr->size() + tailStr.size(); |
1308 | 0 | if ( minSize > padding ) XMP_Throw ( "Can't fit into specified packet size", kXMPErr_BadSerialize ); |
1309 | 0 | padding -= minSize; // Now the actual amount of padding to add (in bytes). |
1310 | 0 | } |
1311 | | |
1312 | 0 | utf8Str.assign ( newline ); |
1313 | 0 | ToUTF32 ( (UTF8Unit*)utf8Str.c_str(), utf8Str.size(), &newlineStr, bigEndian ); |
1314 | 0 | size_t newlineLen = newlineStr.size(); |
1315 | | |
1316 | 0 | if ( padding < newlineLen ) { |
1317 | 0 | for ( int i = padding/4; i > 0; --i ) *sOutputStr += padStr; |
1318 | 0 | } else { |
1319 | 0 | padding -= newlineLen; // Write this newline last. |
1320 | 0 | while ( padding >= (400 + newlineLen) ) { |
1321 | 0 | for ( int i = 100; i > 0; --i ) *sOutputStr += padStr; |
1322 | 0 | *sOutputStr += newlineStr; |
1323 | 0 | padding -= (400 + newlineLen); |
1324 | 0 | } |
1325 | 0 | for ( int i = padding/4; i > 0; --i ) *sOutputStr += padStr; |
1326 | 0 | *sOutputStr += newlineStr; |
1327 | 0 | } |
1328 | |
|
1329 | 0 | *sOutputStr += tailStr; |
1330 | |
|
1331 | 0 | } |
1332 | | |
1333 | 0 | } |
1334 | | |
1335 | | // Return the finished string. |
1336 | | |
1337 | 298 | *rdfString = sOutputStr->c_str(); |
1338 | 298 | *rdfSize = sOutputStr->size(); |
1339 | | |
1340 | 298 | } // SerializeToBuffer |
1341 | | |
1342 | | // ================================================================================================= |