Coverage Report

Created: 2026-04-12 06:54

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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 += "&quot;";
313
1.57k
        } else if ( ch == '<' ) {
314
0
          outputStr += "&lt;";
315
1.57k
        } else if ( ch == '>' ) {
316
1.57k
          outputStr += "&gt;";
317
1.57k
        } else {
318
0
          XMP_Assert ( ch == '&' );
319
0
          outputStr += "&amp;";
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. &#xA; 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
// =================================================================================================