1 // =================================================================================================
2 // Copyright 2003 Adobe Systems Incorporated
3 // All Rights Reserved.
4 //
5 // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
6 // of the Adobe license agreement accompanying it.
7 // =================================================================================================
8
9
10 #include "public/include/XMP_Environment.h" // ! This must be the first include!
11 #include "XMPCore/source/XMPCore_Impl.hpp"
12
13 #include "XMPCore/XMPCoreDefines.h"
14 #if ENABLE_CPP_DOM_MODEL
15 #include "XMPCore/source/XMPIterator2.hpp"
16 #include "XMPCore/source/XMPMeta2.hpp"
17 #include "XMPCore/source/XMPUtils.hpp"
18 #include "XMPCore/Interfaces/IMetadata_I.h"
19 #include "XMPCore/Interfaces/INameSpacePrefixMap_I.h"
20 #include "XMPCommon/Interfaces/IUTF8String_I.h"
21 #include "XMPCore/Interfaces/ISimpleNode_I.h"
22 #include <map>
23 #include <string>
24 #include <stdio.h> // For snprintf.
25
26 #if XMP_WinBuild
27 #pragma warning ( disable : 4702 ) // unreachable code
28 #pragma warning ( disable : 4800 ) // forcing value to bool 'true' or 'false' (performance warning)
29 #pragma warning ( disable : 4996 ) // '...' was declared deprecated
30 #endif
31
32 // =================================================================================================
33 // Support Routines
34 // =================================================================================================
35
36
37 #ifndef TraceIterators
38 #define TraceIterators 0
39 #endif
40
41 #if TraceIterators
42 static const char * sStageNames[] = { "before", "self", "qualifiers", "children" };
43 #endif
44
45
GetNameSpace(const AdobeXMPCommon::spcIUTF8String & nameSpace)46 XMP_VarString GetNameSpace( const AdobeXMPCommon::spcIUTF8String & nameSpace )
47 {
48 auto defaultMap = AdobeXMPCore::INameSpacePrefixMap::GetDefaultNameSpacePrefixMap()->GetINameSpacePrefixMap_I();
49 auto prefix = defaultMap->GetPrefix( nameSpace );
50 return prefix->c_str();
51 }
52
53
NodeFullName(const AdobeXMPCore::spcINode & node)54 XMP_VarString NodeFullName( const AdobeXMPCore::spcINode & node )
55 {
56 XMP_VarString name = GetNameSpace(node->GetNameSpace()) + ":" + node->GetName()->c_str();
57 return name;
58 }
59
AddSchemaProperties(IteratorNode & iterSchema,const char * nameSpace)60 void XMPIterator2::AddSchemaProperties(IteratorNode & iterSchema, const char * nameSpace)
61 {
62 using namespace AdobeXMPCore;
63
64 for (auto childIter = mDOM->Iterator(); childIter; childIter = childIter->Next()) {
65
66 spINode childNode = childIter->GetNode();
67 //TODO check name
68 if (!strcmp(childNode->GetNameSpace()->c_str(), nameSpace)) {
69 iterSchema.nodeChildren.push_back(IteratorNode( XMPUtils::GetIXMPOptions(childNode), NodeFullName(childNode), 0 ));
70 }
71
72 }
73
74 }
SetCurrentSchema(XMP_StringPtr schemaName)75 void XMPIterator2::SetCurrentSchema(XMP_StringPtr schemaName)
76 {
77
78 info.currSchema = schemaName;
79
80 } // SetCurrSchema
81
SetCurrentSchema(XMP_VarString & schemaName)82 void XMPIterator2::SetCurrentSchema(XMP_VarString & schemaName)
83 {
84
85 info.currSchema = schemaName;
86
87 } // SetCurrSchema
88
AdvanceIteratorPosition()89 void XMPIterator2::AdvanceIteratorPosition()
90 {
91
92 while (true) {
93
94 if (info.currPos == info.endPos) {
95
96 if (info.ancestors.empty()) break;
97
98 IteratorPosPair & parent = info.ancestors.back();
99 info.currPos = parent.first;
100 info.endPos = parent.second;
101 info.ancestors.pop_back();
102 }
103 else {
104
105 if (info.currPos->visitStage == kIter_BeforeVisit) {
106 if (info.currPos->options & kXMP_SchemaNode) SetCurrentSchema(info.currPos->fullPath);
107 break;
108 }
109
110
111 if (info.currPos->visitStage == kIter_VisitSelf) {
112
113 info.currPos->visitStage = kIter_VisitQualifiers;
114 if (!info.currPos->nodeQualifiers.empty()) {
115
116 info.ancestors.push_back(IteratorPosPair(info.currPos, info.endPos));
117 info.endPos = info.currPos->nodeQualifiers.end();
118 info.currPos = info.currPos->nodeQualifiers.begin();
119 break;
120 }
121 }
122
123 if (info.currPos->visitStage == kIter_VisitQualifiers) { // Just finished visiting the qualifiers.
124 info.currPos->nodeQualifiers.clear();
125 info.currPos->visitStage = kIter_VisitChildren; // Start visiting the children.
126 if (!info.currPos->nodeChildren.empty()) {
127 info.ancestors.push_back(IteratorPosPair(info.currPos, info.endPos));
128 info.endPos = info.currPos->nodeChildren.end(); // ! Set the parent's endPos before changing currPos!
129 info.currPos = info.currPos->nodeChildren.begin();
130 break;
131 }
132 }
133
134 if (info.currPos->visitStage == kIter_VisitChildren) {
135 info.currPos->nodeChildren.clear();
136 ++info.currPos;
137 continue;
138 }
139 }
140 }
141
142 XMP_Assert((info.currPos == info.endPos) || (info.currPos->visitStage == kIter_BeforeVisit));
143
144 }
145
GetNextNode(bool & isSchema)146 AdobeXMPCore::spINode XMPIterator2::GetNextNode(bool & isSchema)
147 {
148 using namespace AdobeXMPCore;
149 spINode xmpNode = spINode();
150
151 if (info.currPos->visitStage != kIter_BeforeVisit) AdvanceIteratorPosition();
152
153 bool isSchemaNode = false;
154 XMP_ExpandedXPath exPath;
155
156 while (info.currPos != info.endPos) {
157
158 isSchemaNode = XMP_NodeIsSchema(info.currPos->options);
159 if (isSchemaNode) {
160
161 SetCurrentSchema(info.currPos->fullPath);
162
163 isSchema = isSchemaNode;
164 }
165 else {
166
167 ExpandXPath(info.currSchema.c_str(), info.currPos->fullPath.c_str(), &exPath);
168 bool found = XMPUtils::FindCnstNode(mDOM, exPath, xmpNode);
169 }
170 if (xmpNode || isSchemaNode) break;
171
172 info.currPos->visitStage = kIter_VisitChildren;
173 info.currPos->nodeChildren.clear();
174 info.currPos->nodeQualifiers.clear();
175
176 AdvanceIteratorPosition();
177 }
178
179 if (info.currPos == info.endPos) return spINode();
180
181 XMP_Assert(info.currPos->visitStage == kIter_BeforeVisit);
182
183 if (info.currPos->visitStage == kIter_BeforeVisit) {
184
185 if (!isSchemaNode && !(info.options & kXMP_IterJustChildren)) {
186
187 AddNodeOffSpring(*info.currPos, xmpNode);
188 }
189 info.currPos->visitStage = kIter_VisitSelf;
190 }
191
192 return xmpNode;
193 }
194
AddNodeOffSpring(IteratorNode & iterParent,const AdobeXMPCore::spINode & xmpParent)195 void XMPIterator2::AddNodeOffSpring(IteratorNode &iterParent, const AdobeXMPCore::spINode & xmpParent)
196 {
197
198 using namespace AdobeXMPCore;
199 XMP_VarString currPath(iterParent.fullPath);
200 size_t leafOffset = currPath.size();
201 if (xmpParent->HasQualifiers() && (!(info.options & kXMP_IterOmitQualifiers))) {
202
203 currPath += "/?"; // All qualifiers are named and use paths like "Prop/?Qual".
204 leafOffset += 2;
205
206 for ( auto qualIter = xmpParent->QualifiersIterator(); qualIter; qualIter = qualIter->Next() ) {
207
208 spINode qualNode = qualIter->GetNode();
209 //TOTO Add prefix too
210 currPath += NodeFullName( qualNode );
211
212 iterParent.nodeQualifiers.push_back( IteratorNode( XMPUtils::GetIXMPOptions( qualNode ), currPath, leafOffset ) );
213 currPath.erase( leafOffset );
214 }
215
216 leafOffset -= 2;
217 currPath.erase(leafOffset);
218
219 }
220 if (XMPUtils::GetNodeChildCount(xmpParent)) {
221
222
223
224 if (xmpParent->GetNodeType() == INode::kNTStructure) {
225 currPath += '/';
226 leafOffset += 1;
227 }
228 size_t childIdx = 0;
229 for (auto childIter = XMPUtils::GetNodeChildIterator(xmpParent); childIter; childIter = childIter->Next(), ++childIdx) {
230
231 spcINode xmpChild = childIter->GetNode();
232 if (xmpParent->GetNodeType() != INode::kNTArray) {
233 //TODO Add prefix as well
234 currPath += NodeFullName(xmpChild);
235 }
236 else {
237
238 char buffer[32]; // AUDIT: Using sizeof(buffer) below for snprintf length is safe.
239 #if XMP_WinBuild
240 snprintf(buffer, sizeof(buffer), "[%Iu]", childIdx + 1); // ! XPath indices are one-based.
241 #else
242 snprintf(buffer, sizeof(buffer), "[%zu]", childIdx + 1);
243 #endif
244 currPath += buffer;
245 }
246
247 iterParent.nodeChildren.push_back(IteratorNode(XMPUtils::GetIXMPOptions(xmpChild), currPath, leafOffset));
248 currPath.erase(leafOffset);
249
250 }
251
252
253 }
254
255
256
257 } // AddNodeOffspring
258
259 // =================================================================================================
260 // Constructors
261 // =================================================================================================
262
263 // -------------------------------------------------------------------------------------------------
264 // XMPIterator
265 // -----------
266 //
267 // Constructor for iterations over the nodes in an XMPMeta object. This builds a tree of iteration
268 // nodes that caches the existing node names of the XMPMeta object. The iteration tree is a partial
269 // replica of the XMPMeta tree. The initial iteration tree normally has just the root node, all of
270 // the schema nodes for a full object iteration. Lower level nodes (children and qualifiers) are
271 // added when the parent is visited. If the kXMP_IterJustChildren option is passed then the initial
272 // iterator includes the children and the parent is marked as done. The iteration tree nodes are
273 // pruned when they are no longer needed.
274
XMPIterator2(const XMPMeta & xmpObjBase,XMP_StringPtr schemaNS,XMP_StringPtr propName,XMP_OptionBits options)275 XMPIterator2::XMPIterator2 ( const XMPMeta & xmpObjBase,
276 XMP_StringPtr schemaNS,
277 XMP_StringPtr propName,
278 XMP_OptionBits options) :XMPIterator(xmpObjBase, schemaNS, propName, options)
279 {
280 using namespace AdobeXMPCore;
281 if ( (options & kXMP_IterClassMask) != kXMP_IterProperties ) {
282 XMP_Throw ( "Unsupported iteration kind", kXMPErr_BadOptions );
283 }
284 iteratorOptions = options;
285 info.options = options;
286
287 if(sUseNewCoreAPIs) {
288
289 const XMPMeta2 & tempPtr = dynamic_cast<const XMPMeta2 &>(xmpObjBase);
290
291 }
292 else {
293 XMP_Throw("Unsupported iteration kind", kXMPErr_BadOptions);
294 }
295
296 const XMPMeta2 & xmpObj = dynamic_cast<const XMPMeta2 &>(xmpObjBase);
297
298 spIMetadata root = xmpObj.mDOM;
299 mDOM = xmpObj.mDOM;
300 // *** Lock the XMPMeta object if we ever stop using a full DLL lock.
301
302 if ( *propName != 0 ) {
303 XMP_ExpandedXPath propPath;
304 ExpandXPath(schemaNS, propName, &propPath);
305 spINode destNode;
306 XMP_OptionBits destOptions = 0;
307 bool nodeFound = XMPUtils::FindCnstNode(root, propPath, destNode,&destOptions);
308
309 if (nodeFound) {
310
311 XMP_VarString rootName(propPath[1].step); // The schema is [0].
312 for (size_t i = 2; i < propPath.size(); ++i) {
313 XMP_OptionBits stepKind = GetStepKind(propPath[i].options);
314 if (stepKind <= kXMP_QualifierStep) rootName += '/';
315 rootName += propPath[i].step;
316 }
317
318 propName = rootName.c_str();
319 size_t leafOffset = rootName.size();
320 while ((leafOffset > 0) && (propName[leafOffset] != '/') && (propName[leafOffset] != '[')) --leafOffset;
321 if (propName[leafOffset] == '/') ++leafOffset;
322
323 info.tree.nodeChildren.push_back( IteratorNode( destOptions, propName, leafOffset ) );
324 SetCurrentSchema(propPath[kSchemaStep].step.c_str());
325 if (options & kXMP_IterJustChildren) {
326
327 AddNodeOffSpring(info.tree.nodeChildren.back(), destNode);
328 }
329
330 }
331
332
333 } else if ( *schemaNS != 0 ) {
334
335 info.tree.nodeChildren.push_back(IteratorNode(kXMP_SchemaNode, schemaNS, 0));
336 IteratorNode & iterSchema = info.tree.nodeChildren.back();
337
338 bool schemaFound = false;
339
340 for (auto childIter = mDOM->Iterator(); childIter; childIter = childIter->Next()) {
341
342 if (!strcmp(childIter->GetNode()->GetNameSpace()->c_str(), schemaNS)) {
343
344 schemaFound = true;
345 break;
346 }
347 }
348
349
350 if (schemaFound) AddSchemaProperties(iterSchema, schemaNS);
351
352 if (iterSchema.nodeChildren.empty()) {
353 info.tree.nodeChildren.pop_back(); // No properties, remove the schema node.
354 }
355 else {
356 SetCurrentSchema(schemaNS);
357 }
358
359
360 } else {
361
362 std::map < XMP_VarString, bool > schemaProperties;
363
364 for (auto childIter = mDOM->Iterator(); childIter; childIter = childIter->Next()) {
365
366 spINode childNode = childIter->GetNode();
367
368 schemaProperties[childNode->GetNameSpace()->c_str()] = true;
369 }
370
371
372 for (auto key : schemaProperties) {
373 //TODO check name
374 info.tree.nodeChildren.push_back(IteratorNode( kXMP_SchemaNode, key.first, 0 ));
375
376 IteratorNode & iterSchema = info.tree.nodeChildren.back();
377
378 if (!(info.options & kXMP_IterJustChildren)) {
379 AddSchemaProperties(iterSchema, key.first.c_str());
380 // if (iterSchema.nodeChildren.empty()) info.tree.nodeChildren.pop_back(); // No properties, remove the schema node.
381 }
382 }
383
384
385
386
387 }
388
389 info.currPos = info.tree.nodeChildren.begin();
390 info.endPos = info.tree.nodeChildren.end();
391
392 if ((info.options & kXMP_IterJustChildren) && (info.currPos != info.endPos) && (*schemaNS != 0)) {
393 info.currPos->visitStage = kIter_VisitSelf;
394 }
395
396
397
398 } // XMPIterator for XMPMeta objects
399
400 // -------------------------------------------------------------------------------------------------
401 // XMPIterator
402 // -----------
403 //
404 // Constructor for iterations over global tables such as registered namespaces or aliases.
405
XMPIterator2(XMP_StringPtr schemaNS,XMP_StringPtr propName,XMP_OptionBits options)406 XMPIterator2::XMPIterator2 ( XMP_StringPtr schemaNS,
407 XMP_StringPtr propName,
408 XMP_OptionBits options) : XMPIterator(schemaNS, propName, options)
409 {
410 void * p; p = &schemaNS; p = &propName; p = &options; // Avoid unused param warnings.
411 XMP_Throw("Unimplemented XMPIterator constructor for global tables", kXMPErr_Unimplemented);
412
413 } // XMPIterator for global tables
414
415 // -------------------------------------------------------------------------------------------------
416 // ~XMPIterator
417 // -----------
418
~XMPIterator2()419 XMPIterator2::~XMPIterator2() RELEASE_NO_THROW
420 {
421
422 // Let everything else default.
423
424 } // ~XMPIterator
425
426 // =================================================================================================
427 // Iteration Methods
428 // =================================================================================================
429
430 // -------------------------------------------------------------------------------------------------
431 // Next
432 // ----
433 //
434 // Do a preorder traversal of the cached nodes.
435
436 // *** Need to document the relationships between currPos, endPos, and visitStage.
437
438 bool
Next(XMP_StringPtr * schemaNS,XMP_StringLen * nsSize,XMP_StringPtr * propPath,XMP_StringLen * pathSize,XMP_StringPtr * propValue,XMP_StringLen * valueSize,XMP_OptionBits * propOptions)439 XMPIterator2::Next ( XMP_StringPtr * schemaNS,
440 XMP_StringLen * nsSize,
441 XMP_StringPtr * propPath,
442 XMP_StringLen * pathSize,
443 XMP_StringPtr * propValue,
444 XMP_StringLen * valueSize,
445 XMP_OptionBits * propOptions )
446 {
447
448 if (info.currPos == info.endPos) return false;
449 using namespace AdobeXMPCore;
450 using namespace AdobeXMPCommon;
451 bool isSchema = false;
452 spcINode xmpNode = GetNextNode(isSchema);
453 if (!xmpNode && !isSchema) return false;
454 bool isSchemaNode = isSchema;
455 if (info.options & kXMP_IterJustLeafNodes) {
456 while (isSchemaNode || ( xmpNode && XMPUtils::GetNodeChildCount(xmpNode))) {
457 info.currPos->visitStage = kIter_VisitQualifiers; // Skip to this node's children.
458 xmpNode = GetNextNode(isSchemaNode);
459 if (xmpNode == 0 && !isSchemaNode) return false;
460 isSchemaNode = XMP_NodeIsSchema(info.currPos->options);
461 }
462 }
463
464 *schemaNS = info.currSchema.c_str();
465 *nsSize = info.currSchema.size();
466
467
468 *propOptions = info.currPos->options;
469
470 *propPath = "";
471 *pathSize = 0;
472 *propValue = "";
473 *valueSize = 0;
474
475 if (!(*propOptions & kXMP_SchemaNode)) {
476
477 *propPath = info.currPos->fullPath.c_str();
478 *pathSize = info.currPos->fullPath.size();
479
480 if (info.options & kXMP_IterJustLeafName) {
481 *propPath += info.currPos->leafOffset;
482 *pathSize -= info.currPos->leafOffset;
483 if (! xmpNode->IsArrayItem()) {
484 *schemaNS = xmpNode->GetNameSpace()->c_str();
485 *nsSize = xmpNode->GetNameSpace()->size();
486 }
487 else {
488 *schemaNS = "";
489 *nsSize = 0;
490 }
491
492 }
493
494 if (!(*propOptions & kXMP_PropCompositeMask)) {
495 spcIUTF8String nodeValue = xmpNode->ConvertToSimpleNode()->GetValue();
496 *propValue = nodeValue->c_str();
497 *valueSize = nodeValue->size();
498 }
499
500 }
501
502 return true;
503 } // Next
504
505 // -------------------------------------------------------------------------------------------------
506 // Skip
507 // ----
508 //
509 // Skip some portion of the traversal related to the last visited node. We skip either that node's
510 // children, or those children and the previous node's siblings. The implementation might look a bit
511 // awkward because info.currNode always points to the next node to be visited. We might already have
512 // moved past the things to skip, e.g. if the previous node was simple and the last of its siblings.
513
514 enum {
515 kXMP_ValidIterSkipOptions = kXMP_IterSkipSubtree | kXMP_IterSkipSiblings
516 };
517
518
519 void
Skip(XMP_OptionBits iterOptions)520 XMPIterator2::Skip ( XMP_OptionBits iterOptions )
521 {
522 // if ( (info.currPos == kIter_NullPos) ) XMP_Throw ( "No prior postion to skip from", kXMPErr_BadIterPosition );
523 if (iterOptions == 0) XMP_Throw("Must specify what to skip", kXMPErr_BadOptions);
524 if ((iterOptions & ~kXMP_ValidIterSkipOptions) != 0) XMP_Throw("Undefined options", kXMPErr_BadOptions);
525
526
527 if (iterOptions & kXMP_IterSkipSubtree) {
528
529 info.currPos->visitStage = kIter_VisitChildren;
530 }
531 else if (iterOptions & kXMP_IterSkipSiblings) {
532
533 info.currPos = info.endPos;
534 AdvanceIteratorPosition();
535 }
536
537 } // Skip
538
539
540
541 // =================================================================================================
542 #endif // ENABLE_CPP_DOM_MODEL