summaryrefslogtreecommitdiff
path: root/gpr/source/lib/xmp_core/ParseRDF.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gpr/source/lib/xmp_core/ParseRDF.cpp')
-rw-r--r--gpr/source/lib/xmp_core/ParseRDF.cpp1459
1 files changed, 1459 insertions, 0 deletions
diff --git a/gpr/source/lib/xmp_core/ParseRDF.cpp b/gpr/source/lib/xmp_core/ParseRDF.cpp
new file mode 100644
index 0000000..0b69e31
--- /dev/null
+++ b/gpr/source/lib/xmp_core/ParseRDF.cpp
@@ -0,0 +1,1459 @@
+// =================================================================================================
+// Copyright 2004 Adobe Systems Incorporated
+// All Rights Reserved.
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! This must be the first include!
+#include "XMPCore_Impl.hpp"
+#include "XMPMeta.hpp"
+#include "ExpatAdapter.hpp"
+
+#include <cstring>
+
+#if DEBUG
+ #include <iostream>
+#endif
+
+using namespace std;
+
+#if XMP_WinBuild
+ #pragma warning ( disable : 4189 ) // local variable is initialized but not referenced
+ #pragma warning ( disable : 4505 ) // unreferenced local function has been removed
+#endif
+
+// =================================================================================================
+
+// *** This might be faster and use less memory as a state machine. A big advantage of building an
+// *** XML tree though is easy lookahead during the recursive descent processing.
+
+// *** It would be nice to give a line number or byte offset in the exception messages.
+
+
+// 7 RDF/XML Grammar (from http://www.w3.org/TR/rdf-syntax-grammar/#section-Infoset-Grammar)
+//
+// 7.1 Grammar summary
+//
+// 7.2.2 coreSyntaxTerms
+// rdf:RDF | rdf:ID | rdf:about | rdf:parseType | rdf:resource | rdf:nodeID | rdf:datatype
+//
+// 7.2.3 syntaxTerms
+// coreSyntaxTerms | rdf:Description | rdf:li
+//
+// 7.2.4 oldTerms
+// rdf:aboutEach | rdf:aboutEachPrefix | rdf:bagID
+//
+// 7.2.5 nodeElementURIs
+// anyURI - ( coreSyntaxTerms | rdf:li | oldTerms )
+//
+// 7.2.6 propertyElementURIs
+// anyURI - ( coreSyntaxTerms | rdf:Description | oldTerms )
+//
+// 7.2.7 propertyAttributeURIs
+// anyURI - ( coreSyntaxTerms | rdf:Description | rdf:li | oldTerms )
+//
+// 7.2.8 doc
+// root ( document-element == RDF, children == list ( RDF ) )
+//
+// 7.2.9 RDF
+// start-element ( URI == rdf:RDF, attributes == set() )
+// nodeElementList
+// end-element()
+//
+// 7.2.10 nodeElementList
+// ws* ( nodeElement ws* )*
+//
+// 7.2.11 nodeElement
+// start-element ( URI == nodeElementURIs,
+// attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) )
+// propertyEltList
+// end-element()
+//
+// 7.2.12 ws
+// A text event matching white space defined by [XML] definition White Space Rule [3] S in section Common Syntactic Constructs.
+//
+// 7.2.13 propertyEltList
+// ws* ( propertyElt ws* )*
+//
+// 7.2.14 propertyElt
+// resourcePropertyElt | literalPropertyElt | parseTypeLiteralPropertyElt |
+// parseTypeResourcePropertyElt | parseTypeCollectionPropertyElt | parseTypeOtherPropertyElt | emptyPropertyElt
+//
+// 7.2.15 resourcePropertyElt
+// start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) )
+// ws* nodeElement ws*
+// end-element()
+//
+// 7.2.16 literalPropertyElt
+// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, datatypeAttr?) )
+// text()
+// end-element()
+//
+// 7.2.17 parseTypeLiteralPropertyElt
+// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseLiteral ) )
+// literal
+// end-element()
+//
+// 7.2.18 parseTypeResourcePropertyElt
+// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseResource ) )
+// propertyEltList
+// end-element()
+//
+// 7.2.19 parseTypeCollectionPropertyElt
+// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseCollection ) )
+// nodeElementList
+// end-element()
+//
+// 7.2.20 parseTypeOtherPropertyElt
+// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseOther ) )
+// propertyEltList
+// end-element()
+//
+// 7.2.21 emptyPropertyElt
+// start-element ( URI == propertyElementURIs,
+// attributes == set ( idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) )
+// end-element()
+//
+// 7.2.22 idAttr
+// attribute ( URI == rdf:ID, string-value == rdf-id )
+//
+// 7.2.23 nodeIdAttr
+// attribute ( URI == rdf:nodeID, string-value == rdf-id )
+//
+// 7.2.24 aboutAttr
+// attribute ( URI == rdf:about, string-value == URI-reference )
+//
+// 7.2.25 propertyAttr
+// attribute ( URI == propertyAttributeURIs, string-value == anyString )
+//
+// 7.2.26 resourceAttr
+// attribute ( URI == rdf:resource, string-value == URI-reference )
+//
+// 7.2.27 datatypeAttr
+// attribute ( URI == rdf:datatype, string-value == URI-reference )
+//
+// 7.2.28 parseLiteral
+// attribute ( URI == rdf:parseType, string-value == "Literal")
+//
+// 7.2.29 parseResource
+// attribute ( URI == rdf:parseType, string-value == "Resource")
+//
+// 7.2.30 parseCollection
+// attribute ( URI == rdf:parseType, string-value == "Collection")
+//
+// 7.2.31 parseOther
+// attribute ( URI == rdf:parseType, string-value == anyString - ("Resource" | "Literal" | "Collection") )
+//
+// 7.2.32 URI-reference
+// An RDF URI Reference.
+//
+// 7.2.33 literal
+// Any XML element content that is allowed according to [XML] definition Content of Elements Rule [43] content
+// in section 3.1 Start-Tags, End-Tags, and Empty-Element Tags.
+//
+// 7.2.34 rdf-id
+// An attribute string-value matching any legal [XML-NS] token NCName.
+
+
+// =================================================================================================
+// Primary Parsing Functions
+// =========================
+//
+// Each of these is responsible for recognizing an RDF syntax production and adding the appropriate
+// structure to the XMP tree. They simply return for success, failures will throw an exception. The
+// class exists only to provide access to the error notification object.
+
+class RDF_Parser {
+public:
+
+ void RDF ( XMP_Node * xmpTree, const XML_Node & xmlNode );
+
+ void NodeElementList ( XMP_Node * xmpParent, const XML_Node & xmlParent, bool isTopLevel );
+
+ void NodeElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel );
+
+ void NodeElementAttrs ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel );
+
+ void PropertyElementList ( XMP_Node * xmpParent, const XML_Node & xmlParent, bool isTopLevel );
+
+ void PropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel );
+
+ void ResourcePropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel );
+
+ void LiteralPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel );
+
+ void ParseTypeLiteralPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel );
+
+ void ParseTypeResourcePropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel );
+
+ void ParseTypeCollectionPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel );
+
+ void ParseTypeOtherPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel );
+
+ void EmptyPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel );
+
+ RDF_Parser ( XMPMeta::ErrorCallbackInfo * ec ) : errorCallback(ec) {};
+
+private:
+
+ RDF_Parser() {
+
+ errorCallback = NULL;
+
+ }; // Hidden on purpose.
+
+ XMPMeta::ErrorCallbackInfo * errorCallback;
+
+ XMP_Node * AddChildNode ( XMP_Node * xmpParent, const XML_Node & xmlNode, const XMP_StringPtr value, bool isTopLevel );
+
+ XMP_Node * AddQualifierNode ( XMP_Node * xmpParent, const XMP_VarString & name, const XMP_VarString & value );
+
+ XMP_Node * AddQualifierNode ( XMP_Node * xmpParent, const XML_Node & attr );
+
+ void FixupQualifiedNode ( XMP_Node * xmpParent );
+
+};
+
+enum { kIsTopLevel = true, kNotTopLevel = false };
+
+// =================================================================================================
+
+typedef XMP_Uns8 RDFTermKind;
+
+// *** Logic might be safer with just masks.
+
+enum {
+ kRDFTerm_Other = 0,
+ kRDFTerm_RDF = 1, // Start of coreSyntaxTerms.
+ kRDFTerm_ID = 2,
+ kRDFTerm_about = 3,
+ kRDFTerm_parseType = 4,
+ kRDFTerm_resource = 5,
+ kRDFTerm_nodeID = 6,
+ kRDFTerm_datatype = 7, // End of coreSyntaxTerms.
+ kRDFTerm_Description = 8, // Start of additions for syntaxTerms.
+ kRDFTerm_li = 9, // End of of additions for syntaxTerms.
+ kRDFTerm_aboutEach = 10, // Start of oldTerms.
+ kRDFTerm_aboutEachPrefix = 11,
+ kRDFTerm_bagID = 12, // End of oldTerms.
+
+ kRDFTerm_FirstCore = kRDFTerm_RDF,
+ kRDFTerm_LastCore = kRDFTerm_datatype,
+ kRDFTerm_FirstSyntax = kRDFTerm_FirstCore, // ! Yes, the syntax terms include the core terms.
+ kRDFTerm_LastSyntax = kRDFTerm_li,
+ kRDFTerm_FirstOld = kRDFTerm_aboutEach,
+ kRDFTerm_LastOld = kRDFTerm_bagID
+};
+
+enum {
+ kRDFMask_Other = 1 << kRDFTerm_Other,
+ kRDFMask_RDF = 1 << kRDFTerm_RDF,
+ kRDFMask_ID = 1 << kRDFTerm_ID,
+ kRDFMask_about = 1 << kRDFTerm_about,
+ kRDFMask_parseType = 1 << kRDFTerm_parseType,
+ kRDFMask_resource = 1 << kRDFTerm_resource,
+ kRDFMask_nodeID = 1 << kRDFTerm_nodeID,
+ kRDFMask_datatype = 1 << kRDFTerm_datatype,
+ kRDFMask_Description = 1 << kRDFTerm_Description,
+ kRDFMask_li = 1 << kRDFTerm_li,
+ kRDFMask_aboutEach = 1 << kRDFTerm_aboutEach,
+ kRDFMask_aboutEachPrefix = 1 << kRDFTerm_aboutEachPrefix,
+ kRDFMask_bagID = 1 << kRDFTerm_bagID
+};
+
+enum {
+ kRDF_HasValueElem = 0x10000000UL // ! Contains rdf:value child. Must fit within kXMP_ImplReservedMask!
+};
+
+// -------------------------------------------------------------------------------------------------
+// GetRDFTermKind
+// --------------
+
+static RDFTermKind
+GetRDFTermKind ( const XMP_VarString & name )
+{
+ RDFTermKind term = kRDFTerm_Other;
+
+ // Arranged to hopefully minimize the parse time for large XMP.
+
+ if ( (name.size() > 4) && (strncmp ( name.c_str(), "rdf:", 4 ) == 0) ) {
+
+ if ( name == "rdf:li" ) {
+ term = kRDFTerm_li;
+ } else if ( name == "rdf:parseType" ) {
+ term = kRDFTerm_parseType;
+ } else if ( name == "rdf:Description" ) {
+ term = kRDFTerm_Description;
+ } else if ( name == "rdf:about" ) {
+ term = kRDFTerm_about;
+ } else if ( name == "rdf:resource" ) {
+ term = kRDFTerm_resource;
+ } else if ( name == "rdf:RDF" ) {
+ term = kRDFTerm_RDF;
+ } else if ( name == "rdf:ID" ) {
+ term = kRDFTerm_ID;
+ } else if ( name == "rdf:nodeID" ) {
+ term = kRDFTerm_nodeID;
+ } else if ( name == "rdf:datatype" ) {
+ term = kRDFTerm_datatype;
+ } else if ( name == "rdf:aboutEach" ) {
+ term = kRDFTerm_aboutEach;
+ } else if ( name == "rdf:aboutEachPrefix" ) {
+ term = kRDFTerm_aboutEachPrefix;
+ } else if ( name == "rdf:bagID" ) {
+ term = kRDFTerm_bagID;
+ }
+
+ }
+
+ return term;
+
+} // GetRDFTermKind
+
+// =================================================================================================
+
+static void
+RemoveChild ( XMP_Node * xmpParent, size_t index )
+{
+ XMP_Node * child = xmpParent->children[index];
+ xmpParent->children.erase ( xmpParent->children.begin() + index );
+ delete child;
+}
+
+// -------------------------------------------------------------------------------------------------
+
+static void
+RemoveQualifier ( XMP_Node * xmpParent, size_t index )
+{
+ XMP_Node * qualifier = xmpParent->qualifiers[index];
+ xmpParent->qualifiers.erase ( xmpParent->qualifiers.begin() + index );
+ delete qualifier;
+}
+
+// -------------------------------------------------------------------------------------------------
+
+static void
+RemoveQualifier ( XMP_Node * xmpParent, XMP_NodePtrPos pos )
+{
+ XMP_Node * qualifier = *pos;
+ xmpParent->qualifiers.erase ( pos );
+ delete qualifier;
+}
+
+// =================================================================================================
+
+// -------------------------------------------------------------------------------------------------
+// IsCoreSyntaxTerm
+// ----------------
+//
+// 7.2.2 coreSyntaxTerms
+// rdf:RDF | rdf:ID | rdf:about | rdf:parseType | rdf:resource | rdf:nodeID | rdf:datatype
+
+static bool
+IsCoreSyntaxTerm ( RDFTermKind term )
+{
+ if ( (kRDFTerm_FirstCore <= term) && (term <= kRDFTerm_LastCore) ) return true;
+ return false;
+}
+
+// -------------------------------------------------------------------------------------------------
+// IsSyntaxTerm
+// ------------
+//
+// 7.2.3 syntaxTerms
+// coreSyntaxTerms | rdf:Description | rdf:li
+
+static bool
+IsSyntaxTerm ( RDFTermKind term )
+{
+ if ( (kRDFTerm_FirstSyntax <= term) && (term <= kRDFTerm_LastSyntax) ) return true;
+ return false;
+}
+
+// -------------------------------------------------------------------------------------------------
+// IsOldTerm
+// ---------
+//
+// 7.2.4 oldTerms
+// rdf:aboutEach | rdf:aboutEachPrefix | rdf:bagID
+
+static bool
+IsOldTerm ( RDFTermKind term )
+{
+ if ( (kRDFTerm_FirstOld <= term) && (term <= kRDFTerm_LastOld) ) return true;
+ return false;
+}
+
+// -------------------------------------------------------------------------------------------------
+// IsNodeElementName
+// -----------------
+//
+// 7.2.5 nodeElementURIs
+// anyURI - ( coreSyntaxTerms | rdf:li | oldTerms )
+
+static bool
+IsNodeElementName ( RDFTermKind term )
+{
+ if ( (term == kRDFTerm_li) || IsOldTerm ( term ) ) return false;
+ return (! IsCoreSyntaxTerm ( term ));
+}
+
+// -------------------------------------------------------------------------------------------------
+// IsPropertyElementName
+// ---------------------
+//
+// 7.2.6 propertyElementURIs
+// anyURI - ( coreSyntaxTerms | rdf:Description | oldTerms )
+
+static bool
+IsPropertyElementName ( RDFTermKind term )
+{
+ if ( (term == kRDFTerm_Description) || IsOldTerm ( term ) ) return false;
+ return (! IsCoreSyntaxTerm ( term ));
+}
+
+// -------------------------------------------------------------------------------------------------
+// IsPropertyAttributeName
+// -----------------------
+//
+// 7.2.7 propertyAttributeURIs
+// anyURI - ( coreSyntaxTerms | rdf:Description | rdf:li | oldTerms )
+
+static bool
+IsPropertyAttributeName ( RDFTermKind term )
+{
+ if ( (term == kRDFTerm_Description) || (term == kRDFTerm_li) || IsOldTerm ( term ) ) return false;
+ return (! IsCoreSyntaxTerm ( term ));
+}
+
+// -------------------------------------------------------------------------------------------------
+// IsNumberedArrayItemName
+// -----------------------
+//
+// Return true for a name of the form "rdf:_n", where n is a decimal integer. We're not strict about
+// the integer part, it just has to be characters in the range '0'..'9'.
+
+static bool
+IsNumberedArrayItemName ( const std::string & name )
+{
+ if ( name.size() <= 5 ) return false;
+ if ( strncmp ( name.c_str(), "rdf:_", 5 ) != 0 ) return false;
+ for ( size_t i = 5; i < name.size(); ++i ) {
+ if ( (name[i] < '0') | (name[i] > '9') ) return false;
+ }
+ return true;
+}
+
+// =================================================================================================
+// RDF_Parser::AddChildNode
+// ========================
+
+XMP_Node * RDF_Parser::AddChildNode ( XMP_Node * xmpParent, const XML_Node & xmlNode, const XMP_StringPtr value, bool isTopLevel )
+{
+
+ if ( xmlNode.ns.empty() ) {
+ XMP_Error error ( kXMPErr_BadRDF, "XML namespace required for all elements and attributes" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+ return 0;
+ }
+
+ bool isArrayParent = (xmpParent->options & kXMP_PropValueIsArray) !=0;
+ bool isArrayItem = (xmlNode.name == "rdf:li");
+ bool isValueNode = (xmlNode.name == "rdf:value");
+ XMP_OptionBits childOptions = 0;
+ XMP_StringPtr childName = xmlNode.name.c_str();
+
+ if ( isTopLevel ) {
+
+ // Lookup the schema node, adjust the XMP parent pointer.
+ XMP_Assert ( xmpParent->parent == 0 ); // Incoming parent must be the tree root.
+ XMP_Node * schemaNode = FindSchemaNode ( xmpParent, xmlNode.ns.c_str(), kXMP_CreateNodes );
+ if ( schemaNode->options & kXMP_NewImplicitNode ) schemaNode->options ^= kXMP_NewImplicitNode; // Clear the implicit node bit.
+ // *** Should use "opt &= ~flag" (no conditional), need runtime check for proper 32 bit code.
+ xmpParent = schemaNode;
+
+ // If this is an alias set the isAlias flag in the node and the hasAliases flag in the tree.
+ if ( sRegisteredAliasMap->find ( xmlNode.name ) != sRegisteredAliasMap->end() ) {
+ childOptions |= kXMP_PropIsAlias;
+ schemaNode->parent->options |= kXMP_PropHasAliases;
+ }
+
+ }
+
+ // Check use of rdf:li and rdf:_n names. Must be done before calling FindChildNode!
+ if ( isArrayItem ) {
+
+ // rdf:li can only be used for array children.
+ if ( ! isArrayParent ) {
+ XMP_Error error ( kXMPErr_BadRDF, "Misplaced rdf:li element" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+ return 0;
+ }
+ childName = kXMP_ArrayItemName;
+
+ } else if ( isArrayParent ) {
+
+ // Tolerate use of rdf:_n, don't verify order.
+ if ( IsNumberedArrayItemName ( xmlNode.name ) ) {
+ childName = kXMP_ArrayItemName;
+ isArrayItem = true;
+ } else {
+ XMP_Error error ( kXMPErr_BadRDF, "Array items cannot have arbitrary child names" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+ return 0;
+ }
+
+ }
+
+ // Make sure that this is not a duplicate of a named node.
+ if ( ! (isArrayItem | isValueNode) ) {
+ if ( FindChildNode ( xmpParent, childName, kXMP_ExistingOnly ) != 0 ) {
+ XMP_Error error ( kXMPErr_BadXMP, "Duplicate property or field node" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+ return 0;
+ }
+ }
+
+ // Make sure an rdf:value node is used properly.
+ if ( isValueNode ) {
+ if ( isTopLevel || (! (xmpParent->options & kXMP_PropValueIsStruct)) ) {
+ XMP_Error error ( kXMPErr_BadRDF, "Misplaced rdf:value element" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+ return 0;
+ }
+ xmpParent->options |= kRDF_HasValueElem;
+ }
+
+ // Add the new child to the XMP parent node.
+ XMP_Node * newChild = new XMP_Node ( xmpParent, childName, value, childOptions );
+ if ( (! isValueNode) || xmpParent->children.empty() ) {
+ xmpParent->children.push_back ( newChild );
+ } else {
+ xmpParent->children.insert ( xmpParent->children.begin(), newChild );
+ }
+
+ return newChild;
+
+} // RDF_Parser::AddChildNode
+
+// =================================================================================================
+// RDF_Parser::AddQualifierNode
+// ============================
+
+XMP_Node * RDF_Parser::AddQualifierNode ( XMP_Node * xmpParent, const XMP_VarString & name, const XMP_VarString & value )
+{
+
+ const bool isLang = (name == "xml:lang");
+ const bool isType = (name == "rdf:type");
+
+ XMP_Node * newQual = 0;
+
+ newQual = new XMP_Node ( xmpParent, name, value, kXMP_PropIsQualifier );
+
+ if ( ! (isLang | isType) ) {
+ xmpParent->qualifiers.push_back ( newQual );
+ } else if ( isLang ) {
+ if ( xmpParent->qualifiers.empty() ) {
+ xmpParent->qualifiers.push_back ( newQual );
+ } else {
+ xmpParent->qualifiers.insert ( xmpParent->qualifiers.begin(), newQual );
+ }
+ xmpParent->options |= kXMP_PropHasLang;
+ } else {
+ XMP_Assert ( isType );
+ if ( xmpParent->qualifiers.empty() ) {
+ xmpParent->qualifiers.push_back ( newQual );
+ } else {
+ size_t offset = 0;
+ if ( XMP_PropHasLang ( xmpParent->options ) ) offset = 1;
+ xmpParent->qualifiers.insert ( xmpParent->qualifiers.begin()+offset, newQual );
+ }
+ xmpParent->options |= kXMP_PropHasType;
+ }
+
+ xmpParent->options |= kXMP_PropHasQualifiers;
+
+ return newQual;
+
+} // RDF_Parser::AddQualifierNode
+
+// =================================================================================================
+// RDF_Parser::AddQualifierNode
+// ============================
+
+XMP_Node * RDF_Parser::AddQualifierNode ( XMP_Node * xmpParent, const XML_Node & attr )
+{
+ if ( attr.ns.empty() ) {
+ XMP_Error error ( kXMPErr_BadRDF, "XML namespace required for all elements and attributes" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+ return 0;
+ }
+
+ return this->AddQualifierNode ( xmpParent, attr.name, attr.value );
+
+} // RDF_Parser::AddQualifierNode
+
+// =================================================================================================
+// RDF_Parser::FixupQualifiedNode
+// ==============================
+//
+// The parent is an RDF pseudo-struct containing an rdf:value field. Fix the XMP data model. The
+// rdf:value node must be the first child, the other children are qualifiers. The form, value, and
+// children of the rdf:value node are the real ones. The rdf:value node's qualifiers must be added
+// to the others.
+
+void RDF_Parser::FixupQualifiedNode ( XMP_Node * xmpParent )
+{
+ size_t qualNum, qualLim;
+ size_t childNum, childLim;
+
+ XMP_Enforce ( (xmpParent->options & kXMP_PropValueIsStruct) && (! xmpParent->children.empty()) );
+
+ XMP_Node * valueNode = xmpParent->children[0];
+ XMP_Enforce ( valueNode->name == "rdf:value" );
+
+ xmpParent->qualifiers.reserve ( xmpParent->qualifiers.size() + xmpParent->children.size() + valueNode->qualifiers.size() );
+
+ // Move the qualifiers on the value node to the parent. Make sure an xml:lang qualifier stays at
+ // the front.
+
+ qualNum = 0;
+ qualLim = valueNode->qualifiers.size();
+
+ if ( valueNode->options & kXMP_PropHasLang ) {
+
+ if ( xmpParent->options & kXMP_PropHasLang ) {
+ XMP_Error error ( kXMPErr_BadXMP, "Duplicate xml:lang for rdf:value element" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+ XMP_Assert ( xmpParent->qualifiers[0]->name == "xml:lang" );
+ RemoveQualifier ( xmpParent, 0 ); // Use the rdf:value node's language.
+ }
+
+ XMP_Node * langQual = valueNode->qualifiers[0];
+
+ XMP_Assert ( langQual->name == "xml:lang" );
+ langQual->parent = xmpParent;
+ xmpParent->options |= kXMP_PropHasLang;
+ XMP_ClearOption ( valueNode->options, kXMP_PropHasLang );
+
+ if ( xmpParent->qualifiers.empty() ) {
+ xmpParent->qualifiers.push_back ( langQual ); // *** Should use utilities to add qual & set parent.
+ } else {
+ xmpParent->qualifiers.insert ( xmpParent->qualifiers.begin(), langQual );
+ }
+ valueNode->qualifiers[0] = 0; // We just moved it to the parent.
+
+ qualNum = 1; // Start the remaining copy after the xml:lang qualifier.
+
+ }
+
+ for ( ; qualNum != qualLim; ++qualNum ) {
+
+ XMP_Node * currQual = valueNode->qualifiers[qualNum];
+ XMP_NodePtrPos existingPos;
+ XMP_Node * existingQual = FindQualifierNode ( xmpParent, currQual->name.c_str(), kXMP_ExistingOnly, &existingPos );
+
+ if ( existingQual != 0 ) {
+ XMP_Error error ( kXMPErr_BadXMP, "Duplicate qualifier node" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+ RemoveQualifier ( xmpParent, existingPos ); // Use the rdf:value node's qualifier.
+ }
+
+ currQual->parent = xmpParent;
+ xmpParent->qualifiers.push_back ( currQual );
+ valueNode->qualifiers[qualNum] = 0; // We just moved it to the parent.
+
+ }
+
+ valueNode->qualifiers.clear(); // ! There should be nothing but null pointers.
+
+ // Change the parent's other children into qualifiers. This loop starts at 1, child 0 is the
+ // rdf:value node. Put xml:lang at the front, append all others.
+
+ for ( childNum = 1, childLim = xmpParent->children.size(); childNum != childLim; ++childNum ) {
+
+ XMP_Node * currQual = xmpParent->children[childNum];
+ bool isLang = (currQual->name == "xml:lang");
+
+ if ( FindQualifierNode ( xmpParent, currQual->name.c_str(), kXMP_ExistingOnly ) != 0 ) {
+ XMP_Error error ( kXMPErr_BadXMP, "Duplicate qualifier" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+ delete currQual;
+
+ } else {
+
+ currQual->options |= kXMP_PropIsQualifier;
+ currQual->parent = xmpParent;
+
+ if ( isLang ) {
+ xmpParent->options |= kXMP_PropHasLang;
+ } else if ( currQual->name == "rdf:type" ) {
+ xmpParent->options |= kXMP_PropHasType;
+ }
+
+ if ( (! isLang) || xmpParent->qualifiers.empty() ) {
+ xmpParent->qualifiers.push_back ( currQual );
+ } else {
+ xmpParent->qualifiers.insert ( xmpParent->qualifiers.begin(), currQual );
+ }
+
+ }
+
+ xmpParent->children[childNum] = 0; // We just moved it to the qualifers, or ignored it.
+
+ }
+
+ if ( ! xmpParent->qualifiers.empty() ) xmpParent->options |= kXMP_PropHasQualifiers;
+
+ // Move the options and value last, other checks need the parent's original options. Move the
+ // value node's children to be the parent's children. Delete the now useless value node.
+
+ XMP_Assert ( xmpParent->options & (kXMP_PropValueIsStruct | kRDF_HasValueElem) );
+ xmpParent->options &= ~ (kXMP_PropValueIsStruct | kRDF_HasValueElem);
+ xmpParent->options |= valueNode->options;
+
+ xmpParent->value.swap ( valueNode->value );
+
+ xmpParent->children[0] = 0; // ! Remove the value node itself before the swap.
+ xmpParent->children.swap ( valueNode->children );
+
+ for ( childNum = 0, childLim = xmpParent->children.size(); childNum != childLim; ++childNum ) {
+ XMP_Node * currChild = xmpParent->children[childNum];
+ currChild->parent = xmpParent;
+ }
+
+ delete valueNode;
+
+} // RDF_Parser::FixupQualifiedNode
+
+// =================================================================================================
+// RDF_Parser::RDF
+// ===============
+//
+// 7.2.9 RDF
+// start-element ( URI == rdf:RDF, attributes == set() )
+// nodeElementList
+// end-element()
+//
+// The top level rdf:RDF node. It can only have xmlns attributes, which have already been removed
+// during construction of the XML tree.
+
+void RDF_Parser::RDF ( XMP_Node * xmpTree, const XML_Node & xmlNode )
+{
+
+ if ( ! xmlNode.attrs.empty() ) {
+ XMP_Error error ( kXMPErr_BadRDF, "Invalid attributes of rdf:RDF element" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+ }
+ this->NodeElementList ( xmpTree, xmlNode, kIsTopLevel ); // ! Attributes are ignored.
+
+} // RDF_Parser::RDF
+
+// =================================================================================================
+// RDF_Parser::NodeElementList
+// ===========================
+//
+// 7.2.10 nodeElementList
+// ws* ( nodeElement ws* )*
+
+void RDF_Parser::NodeElementList ( XMP_Node * xmpParent, const XML_Node & xmlParent, bool isTopLevel )
+{
+ XMP_Assert ( isTopLevel );
+
+ XML_cNodePos currChild = xmlParent.content.begin(); // *** Change these loops to the indexed pattern.
+ XML_cNodePos endChild = xmlParent.content.end();
+
+ for ( ; currChild != endChild; ++currChild ) {
+ if ( (*currChild)->IsWhitespaceNode() ) continue;
+ this->NodeElement ( xmpParent, **currChild, isTopLevel );
+ }
+
+} // RDF_Parser::NodeElementList
+
+// =================================================================================================
+// RDF_Parser::NodeElement
+// =======================
+//
+// 7.2.5 nodeElementURIs
+// anyURI - ( coreSyntaxTerms | rdf:li | oldTerms )
+//
+// 7.2.11 nodeElement
+// start-element ( URI == nodeElementURIs,
+// attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) )
+// propertyEltList
+// end-element()
+//
+// A node element URI is rdf:Description or anything else that is not an RDF term.
+
+void RDF_Parser::NodeElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel )
+{
+ RDFTermKind nodeTerm = GetRDFTermKind ( xmlNode.name );
+ if ( (nodeTerm != kRDFTerm_Description) && (nodeTerm != kRDFTerm_Other) ) {
+ XMP_Error error ( kXMPErr_BadRDF, "Node element must be rdf:Description or typedNode" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+ } else if ( isTopLevel && (nodeTerm == kRDFTerm_Other) ) {
+ XMP_Error error ( kXMPErr_BadXMP, "Top level typedNode not allowed" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+ } else {
+ this->NodeElementAttrs ( xmpParent, xmlNode, isTopLevel );
+ this->PropertyElementList ( xmpParent, xmlNode, isTopLevel );
+ }
+
+} // RDF_Parser::NodeElement
+
+// =================================================================================================
+// RDF_Parser::NodeElementAttrs
+// ============================
+//
+// 7.2.7 propertyAttributeURIs
+// anyURI - ( coreSyntaxTerms | rdf:Description | rdf:li | oldTerms )
+//
+// 7.2.11 nodeElement
+// start-element ( URI == nodeElementURIs,
+// attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) )
+// propertyEltList
+// end-element()
+//
+// Process the attribute list for an RDF node element. A property attribute URI is anything other
+// than an RDF term. The rdf:ID and rdf:nodeID attributes are simply ignored, as are rdf:about
+// attributes on inner nodes.
+
+static const XMP_OptionBits kExclusiveAttrMask = (kRDFMask_ID | kRDFMask_nodeID | kRDFMask_about);
+
+void RDF_Parser::NodeElementAttrs ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel )
+{
+ XMP_OptionBits exclusiveAttrs = 0; // Used to detect attributes that are mutually exclusive.
+
+ XML_cNodePos currAttr = xmlNode.attrs.begin();
+ XML_cNodePos endAttr = xmlNode.attrs.end();
+
+ for ( ; currAttr != endAttr; ++currAttr ) {
+
+ RDFTermKind attrTerm = GetRDFTermKind ( (*currAttr)->name );
+
+ switch ( attrTerm ) {
+
+ case kRDFTerm_ID :
+ case kRDFTerm_nodeID :
+ case kRDFTerm_about :
+
+ if ( exclusiveAttrs & kExclusiveAttrMask ) {
+ XMP_Error error ( kXMPErr_BadRDF, "Mutally exclusive about, ID, nodeID attributes" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+ continue; // Skip the later mutually exclusive attributes.
+ }
+ exclusiveAttrs |= (1 << attrTerm);
+
+ if ( isTopLevel && (attrTerm == kRDFTerm_about) ) {
+ // This is the rdf:about attribute on a top level node. Set the XMP tree name if
+ // it doesn't have a name yet. Make sure this name matches the XMP tree name.
+ XMP_Assert ( xmpParent->parent == 0 ); // Must be the tree root node.
+ if ( xmpParent->name.empty() ) {
+ xmpParent->name = (*currAttr)->value;
+ } else if ( ! (*currAttr)->value.empty() ) {
+ if ( xmpParent->name != (*currAttr)->value ) {
+ XMP_Error error ( kXMPErr_BadXMP, "Mismatched top level rdf:about values" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+ }
+ }
+ }
+
+ break;
+
+ case kRDFTerm_Other :
+ this->AddChildNode ( xmpParent, **currAttr, (*currAttr)->value.c_str(), isTopLevel );
+ break;
+
+ default :
+ {
+ XMP_Error error ( kXMPErr_BadRDF, "Invalid nodeElement attribute" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+ }
+ continue;
+
+ }
+
+ }
+
+} // RDF_Parser::NodeElementAttrs
+
+// =================================================================================================
+// RDF_Parser::PropertyElementList
+// ===============================
+//
+// 7.2.13 propertyEltList
+// ws* ( propertyElt ws* )*
+
+void RDF_Parser::PropertyElementList ( XMP_Node * xmpParent, const XML_Node & xmlParent, bool isTopLevel )
+{
+ XML_cNodePos currChild = xmlParent.content.begin();
+ XML_cNodePos endChild = xmlParent.content.end();
+
+ for ( ; currChild != endChild; ++currChild ) {
+ if ( (*currChild)->IsWhitespaceNode() ) continue;
+ if ( (*currChild)->kind != kElemNode ) {
+ XMP_Error error ( kXMPErr_BadRDF, "Expected property element node not found" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+ continue;
+ }
+ this->PropertyElement ( xmpParent, **currChild, isTopLevel );
+ }
+
+} // RDF_Parser::PropertyElementList
+
+// =================================================================================================
+// RDF_Parser::PropertyElement
+// ===========================
+//
+// 7.2.14 propertyElt
+// resourcePropertyElt | literalPropertyElt | parseTypeLiteralPropertyElt |
+// parseTypeResourcePropertyElt | parseTypeCollectionPropertyElt | parseTypeOtherPropertyElt | emptyPropertyElt
+//
+// 7.2.15 resourcePropertyElt
+// start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) )
+// ws* nodeElement ws*
+// end-element()
+//
+// 7.2.16 literalPropertyElt
+// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, datatypeAttr?) )
+// text()
+// end-element()
+//
+// 7.2.17 parseTypeLiteralPropertyElt
+// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseLiteral ) )
+// literal
+// end-element()
+//
+// 7.2.18 parseTypeResourcePropertyElt
+// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseResource ) )
+// propertyEltList
+// end-element()
+//
+// 7.2.19 parseTypeCollectionPropertyElt
+// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseCollection ) )
+// nodeElementList
+// end-element()
+//
+// 7.2.20 parseTypeOtherPropertyElt
+// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseOther ) )
+// propertyEltList
+// end-element()
+//
+// 7.2.21 emptyPropertyElt
+// start-element ( URI == propertyElementURIs,
+// attributes == set ( idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) )
+// end-element()
+//
+// The various property element forms are not distinguished by the XML element name, but by their
+// attributes for the most part. The exceptions are resourcePropertyElt and literalPropertyElt. They
+// are distinguished by their XML element content.
+//
+// NOTE: The RDF syntax does not explicitly include the xml:lang attribute although it can appear in
+// many of these. We have to allow for it in the attibute counts below.
+
+void RDF_Parser::PropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel )
+{
+ RDFTermKind nodeTerm = GetRDFTermKind ( xmlNode.name );
+ if ( ! IsPropertyElementName ( nodeTerm ) ) {
+ XMP_Error error ( kXMPErr_BadRDF, "Invalid property element name" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+ return;
+ }
+
+ if ( xmlNode.attrs.size() > 3 ) {
+
+ // Only an emptyPropertyElt can have more than 3 attributes.
+ this->EmptyPropertyElement ( xmpParent, xmlNode, isTopLevel );
+
+ } else {
+
+ // Look through the attributes for one that isn't rdf:ID or xml:lang, it will usually tell
+ // what we should be dealing with. The called routines must verify their specific syntax!
+
+ XML_cNodePos currAttr = xmlNode.attrs.begin();
+ XML_cNodePos endAttr = xmlNode.attrs.end();
+ XMP_VarString * attrName = 0;
+
+ for ( ; currAttr != endAttr; ++currAttr ) {
+ attrName = &((*currAttr)->name);
+ if ( (*attrName != "xml:lang") && (*attrName != "rdf:ID") ) break;
+ }
+
+ if ( currAttr != endAttr ) {
+
+ XMP_Assert ( attrName != 0 );
+ XMP_VarString& attrValue = (*currAttr)->value;
+
+ if ( *attrName == "rdf:datatype" ) {
+ this->LiteralPropertyElement ( xmpParent, xmlNode, isTopLevel );
+ } else if ( *attrName != "rdf:parseType" ) {
+ this->EmptyPropertyElement ( xmpParent, xmlNode, isTopLevel );
+ } else if ( attrValue == "Literal" ) {
+ this->ParseTypeLiteralPropertyElement ( xmpParent, xmlNode, isTopLevel );
+ } else if ( attrValue == "Resource" ) {
+ this->ParseTypeResourcePropertyElement ( xmpParent, xmlNode, isTopLevel );
+ } else if ( attrValue == "Collection" ) {
+ this->ParseTypeCollectionPropertyElement ( xmpParent, xmlNode, isTopLevel );
+ } else {
+ this->ParseTypeOtherPropertyElement ( xmpParent, xmlNode, isTopLevel );
+ }
+
+ } else {
+
+ // Only rdf:ID and xml:lang, could be a resourcePropertyElt, a literalPropertyElt, or an.
+ // emptyPropertyElt. Look at the child XML nodes to decide which.
+
+ if ( xmlNode.content.empty() ) {
+
+ this->EmptyPropertyElement ( xmpParent, xmlNode, isTopLevel );
+
+ } else {
+
+ XML_cNodePos currChild = xmlNode.content.begin();
+ XML_cNodePos endChild = xmlNode.content.end();
+
+ for ( ; currChild != endChild; ++currChild ) {
+ if ( (*currChild)->kind != kCDataNode ) break;
+ }
+
+ if ( currChild == endChild ) {
+ this->LiteralPropertyElement ( xmpParent, xmlNode, isTopLevel );
+ } else {
+ this->ResourcePropertyElement ( xmpParent, xmlNode, isTopLevel );
+ }
+
+ }
+
+ }
+
+ }
+
+} // RDF_Parser::PropertyElement
+
+// =================================================================================================
+// RDF_Parser::ResourcePropertyElement
+// ===================================
+//
+// 7.2.15 resourcePropertyElt
+// start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) )
+// ws* nodeElement ws*
+// end-element()
+//
+// This handles structs using an rdf:Description node, arrays using rdf:Bag/Seq/Alt, and Typed Nodes.
+// It also catches and cleans up qualified properties written with rdf:Description and rdf:value.
+
+void RDF_Parser::ResourcePropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel )
+{
+ if ( isTopLevel && (xmlNode.name == "iX:changes") ) return; // Strip old "punchcard" chaff.
+
+ XMP_Node * newCompound = this->AddChildNode ( xmpParent, xmlNode, "", isTopLevel );
+ if ( newCompound == 0 ) return; // Ignore lower level errors.
+
+ XML_cNodePos currAttr = xmlNode.attrs.begin();
+ XML_cNodePos endAttr = xmlNode.attrs.end();
+
+ for ( ; currAttr != endAttr; ++currAttr ) {
+ XMP_VarString & attrName = (*currAttr)->name;
+ if ( attrName == "xml:lang" ) {
+ this->AddQualifierNode ( newCompound, **currAttr );
+ } else if ( attrName == "rdf:ID" ) {
+ continue; // Ignore all rdf:ID attributes.
+ } else {
+ XMP_Error error ( kXMPErr_BadRDF, "Invalid attribute for resource property element" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+ continue;
+ }
+ }
+
+ XML_cNodePos currChild = xmlNode.content.begin();
+ XML_cNodePos endChild = xmlNode.content.end();
+
+ for ( ; currChild != endChild; ++currChild ) {
+ if ( ! (*currChild)->IsWhitespaceNode() ) break;
+ }
+ if ( currChild == endChild ) {
+ XMP_Error error ( kXMPErr_BadRDF, "Missing child of resource property element" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+ return;
+ }
+ if ( (*currChild)->kind != kElemNode ) {
+ XMP_Error error ( kXMPErr_BadRDF, "Children of resource property element must be XML elements" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+ return;
+ }
+
+ if ( (*currChild)->name == "rdf:Bag" ) {
+ newCompound->options |= kXMP_PropValueIsArray;
+ } else if ( (*currChild)->name == "rdf:Seq" ) {
+ newCompound->options |= kXMP_PropValueIsArray | kXMP_PropArrayIsOrdered;
+ } else if ( (*currChild)->name == "rdf:Alt" ) {
+ newCompound->options |= kXMP_PropValueIsArray | kXMP_PropArrayIsOrdered | kXMP_PropArrayIsAlternate;
+ } else {
+ // This is the Typed Node case. Add an rdf:type qualifier with a URI value.
+ if ( (*currChild)->name != "rdf:Description" ) {
+ XMP_VarString typeName ( (*currChild)->ns );
+ size_t colonPos = (*currChild)->name.find_first_of(':');
+ if ( colonPos == XMP_VarString::npos ) {
+ XMP_Error error ( kXMPErr_BadXMP, "All XML elements must be in a namespace" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+ return;
+ }
+ typeName.append ( (*currChild)->name, colonPos+1, XMP_VarString::npos ); // Append just the local name.
+ XMP_Node * typeQual = this->AddQualifierNode ( newCompound, XMP_VarString("rdf:type"), typeName );
+ if ( typeQual != 0 ) typeQual->options |= kXMP_PropValueIsURI;
+ }
+ newCompound->options |= kXMP_PropValueIsStruct;
+ }
+
+ this->NodeElement ( newCompound, **currChild, kNotTopLevel );
+ if ( newCompound->options & kRDF_HasValueElem ) {
+ this->FixupQualifiedNode ( newCompound );
+ } else if ( newCompound->options & kXMP_PropArrayIsAlternate ) {
+ DetectAltText ( newCompound );
+ }
+
+ for ( ++currChild; currChild != endChild; ++currChild ) {
+ if ( ! (*currChild)->IsWhitespaceNode() ) {
+ XMP_Error error ( kXMPErr_BadRDF, "Invalid child of resource property element" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+ break; // Don't bother looking for more trailing errors.
+ }
+ }
+
+} // RDF_Parser::ResourcePropertyElement
+
+// =================================================================================================
+// RDF_Parser::LiteralPropertyElement
+// ==================================
+//
+// 7.2.16 literalPropertyElt
+// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, datatypeAttr?) )
+// text()
+// end-element()
+//
+// Add a leaf node with the text value and qualifiers for the attributes.
+
+void RDF_Parser::LiteralPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel )
+{
+ XMP_Node * newChild = this->AddChildNode ( xmpParent, xmlNode, "", isTopLevel );
+ if ( newChild == 0 ) return; // Ignore lower level errors.
+
+ XML_cNodePos currAttr = xmlNode.attrs.begin();
+ XML_cNodePos endAttr = xmlNode.attrs.end();
+
+ for ( ; currAttr != endAttr; ++currAttr ) {
+ XMP_VarString & attrName = (*currAttr)->name;
+ if ( attrName == "xml:lang" ) {
+ this->AddQualifierNode ( newChild, **currAttr );
+ } else if ( (attrName == "rdf:ID") || (attrName == "rdf:datatype") ) {
+ continue; // Ignore all rdf:ID and rdf:datatype attributes.
+ } else {
+ XMP_Error error ( kXMPErr_BadRDF, "Invalid attribute for literal property element" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+ continue;
+ }
+ }
+
+ XML_cNodePos currChild = xmlNode.content.begin();
+ XML_cNodePos endChild = xmlNode.content.end();
+ size_t textSize = 0;
+
+ for ( ; currChild != endChild; ++currChild ) {
+ if ( (*currChild)->kind == kCDataNode ) {
+ textSize += (*currChild)->value.size();
+ } else {
+ XMP_Error error ( kXMPErr_BadRDF, "Invalid child of literal property element" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+ }
+ }
+
+ newChild->value.reserve ( textSize );
+
+ for ( currChild = xmlNode.content.begin(); currChild != endChild; ++currChild ) {
+ newChild->value += (*currChild)->value;
+ }
+
+} // RDF_Parser::LiteralPropertyElement
+
+// =================================================================================================
+// RDF_Parser::ParseTypeLiteralPropertyElement
+// ===========================================
+//
+// 7.2.17 parseTypeLiteralPropertyElt
+// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseLiteral ) )
+// literal
+// end-element()
+
+void RDF_Parser::ParseTypeLiteralPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel )
+{
+ IgnoreParam(xmpParent); IgnoreParam(xmlNode); IgnoreParam(isTopLevel);
+ XMP_Error error ( kXMPErr_BadXMP, "ParseTypeLiteral property element not allowed" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+
+} // RDF_Parser::ParseTypeLiteralPropertyElement
+
+// =================================================================================================
+// RDF_Parser::ParseTypeResourcePropertyElement
+// ============================================
+//
+// 7.2.18 parseTypeResourcePropertyElt
+// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseResource ) )
+// propertyEltList
+// end-element()
+//
+// Add a new struct node with a qualifier for the possible rdf:ID attribute. Then process the XML
+// child nodes to get the struct fields.
+
+void RDF_Parser::ParseTypeResourcePropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel )
+{
+ XMP_Node * newStruct = this->AddChildNode ( xmpParent, xmlNode, "", isTopLevel );
+ if ( newStruct == 0 ) return; // Ignore lower level errors.
+ newStruct->options |= kXMP_PropValueIsStruct;
+
+ XML_cNodePos currAttr = xmlNode.attrs.begin();
+ XML_cNodePos endAttr = xmlNode.attrs.end();
+
+ for ( ; currAttr != endAttr; ++currAttr ) {
+ XMP_VarString & attrName = (*currAttr)->name;
+ if ( attrName == "rdf:parseType" ) {
+ continue; // ! The caller ensured the value is "Resource".
+ } else if ( attrName == "xml:lang" ) {
+ this->AddQualifierNode ( newStruct, **currAttr );
+ } else if ( attrName == "rdf:ID" ) {
+ continue; // Ignore all rdf:ID attributes.
+ } else {
+ XMP_Error error ( kXMPErr_BadRDF, "Invalid attribute for ParseTypeResource property element" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+ continue;
+ }
+ }
+
+ this->PropertyElementList ( newStruct, xmlNode, kNotTopLevel );
+
+ if ( newStruct->options & kRDF_HasValueElem ) this->FixupQualifiedNode ( newStruct );
+
+ // *** Need to look for arrays using rdf:Description and rdf:type.
+
+} // RDF_Parser::ParseTypeResourcePropertyElement
+
+// =================================================================================================
+// RDF_Parser::ParseTypeCollectionPropertyElement
+// ==============================================
+//
+// 7.2.19 parseTypeCollectionPropertyElt
+// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseCollection ) )
+// nodeElementList
+// end-element()
+
+void RDF_Parser::ParseTypeCollectionPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel )
+{
+ IgnoreParam(xmpParent); IgnoreParam(xmlNode); IgnoreParam(isTopLevel);
+ XMP_Error error ( kXMPErr_BadXMP, "ParseTypeCollection property element not allowed" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+
+} // RDF_Parser::ParseTypeCollectionPropertyElement
+
+// =================================================================================================
+// RDF_Parser::ParseTypeOtherPropertyElement
+// =========================================
+//
+// 7.2.20 parseTypeOtherPropertyElt
+// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseOther ) )
+// propertyEltList
+// end-element()
+
+void RDF_Parser::ParseTypeOtherPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel )
+{
+ IgnoreParam(xmpParent); IgnoreParam(xmlNode); IgnoreParam(isTopLevel);
+ XMP_Error error ( kXMPErr_BadXMP, "ParseTypeOther property element not allowed" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+
+} // RDF_Parser::ParseTypeOtherPropertyElement
+
+// =================================================================================================
+// RDF_Parser::EmptyPropertyElement
+// ================================
+//
+// 7.2.21 emptyPropertyElt
+// start-element ( URI == propertyElementURIs,
+// attributes == set ( idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) )
+// end-element()
+//
+// <ns:Prop1/> <!-- a simple property with an empty value -->
+// <ns:Prop2 rdf:resource="http://www.adobe.com/"/> <!-- a URI value -->
+// <ns:Prop3 rdf:value="..." ns:Qual="..."/> <!-- a simple qualified property -->
+// <ns:Prop4 ns:Field1="..." ns:Field2="..."/> <!-- a struct with simple fields -->
+//
+// An emptyPropertyElt is an element with no contained content, just a possibly empty set of
+// attributes. An emptyPropertyElt can represent three special cases of simple XMP properties: a
+// simple property with an empty value (ns:Prop1), a simple property whose value is a URI
+// (ns:Prop2), or a simple property with simple qualifiers (ns:Prop3). An emptyPropertyElt can also
+// represent an XMP struct whose fields are all simple and unqualified (ns:Prop4).
+//
+// It is an error to use both rdf:value and rdf:resource - that can lead to invalid RDF in the
+// verbose form written using a literalPropertyElt.
+//
+// The XMP mapping for an emptyPropertyElt is a bit different from generic RDF, partly for
+// design reasons and partly for historical reasons. The XMP mapping rules are:
+// 1. If there is an rdf:value attribute then this is a simple property with a text value.
+// All other attributes are qualifiers.
+// 2. If there is an rdf:resource attribute then this is a simple property with a URI value.
+// All other attributes are qualifiers.
+// 3. If there are no attributes other than xml:lang, rdf:ID, or rdf:nodeID then this is a simple
+// property with an empty value.
+// 4. Otherwise this is a struct, the attributes other than xml:lang, rdf:ID, or rdf:nodeID are fields.
+
+void RDF_Parser::EmptyPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel )
+{
+ bool hasPropertyAttrs = false;
+ bool hasResourceAttr = false;
+ bool hasNodeIDAttr = false;
+ bool hasValueAttr = false;
+
+ const XML_Node * valueNode = 0; // ! Can come from rdf:value or rdf:resource.
+
+ if ( ! xmlNode.content.empty() ) {
+ XMP_Error error ( kXMPErr_BadRDF, "Nested content not allowed with rdf:resource or property attributes" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+ return;
+ }
+
+ // First figure out what XMP this maps to and remember the XML node for a simple value.
+
+ XML_cNodePos currAttr = xmlNode.attrs.begin();
+ XML_cNodePos endAttr = xmlNode.attrs.end();
+
+ for ( ; currAttr != endAttr; ++currAttr ) {
+
+ RDFTermKind attrTerm = GetRDFTermKind ( (*currAttr)->name );
+
+ switch ( attrTerm ) {
+
+ case kRDFTerm_ID :
+ // Nothing to do.
+ break;
+
+ case kRDFTerm_resource :
+ if ( hasNodeIDAttr ) {
+ XMP_Error error ( kXMPErr_BadRDF, "Empty property element can't have both rdf:resource and rdf:nodeID" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+ return;
+ }
+ if ( hasValueAttr ) {
+ XMP_Error error ( kXMPErr_BadXMP, "Empty property element can't have both rdf:value and rdf:resource" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+ return;
+ }
+ hasResourceAttr = true;
+ if ( ! hasValueAttr ) valueNode = *currAttr;
+ break;
+
+ case kRDFTerm_nodeID :
+ if ( hasResourceAttr ) {
+ XMP_Error error ( kXMPErr_BadRDF, "Empty property element can't have both rdf:resource and rdf:nodeID" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+ return;
+ }
+ hasNodeIDAttr = true;
+ break;
+
+ case kRDFTerm_Other :
+ if ( (*currAttr)->name == "rdf:value" ) {
+ if ( hasResourceAttr ) {
+ XMP_Error error ( kXMPErr_BadXMP, "Empty property element can't have both rdf:value and rdf:resource" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+ return;
+ }
+ hasValueAttr = true;
+ valueNode = *currAttr;
+ } else if ( (*currAttr)->name != "xml:lang" ) {
+ hasPropertyAttrs = true;
+ }
+ break;
+
+ default :
+ {
+ XMP_Error error ( kXMPErr_BadRDF, "Unrecognized attribute of empty property element" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+ }
+
+ return;
+
+ }
+
+ }
+
+ // Create the right kind of child node and visit the attributes again to add the fields or qualifiers.
+ // ! Because of implementation vagaries, the xmpParent is the tree root for top level properties.
+ // ! The schema is found, created if necessary, by AddChildNode.
+
+ XMP_Node * childNode = this->AddChildNode ( xmpParent, xmlNode, "", isTopLevel );
+ if ( childNode == 0 ) return; // Ignore lower level errors.
+ bool childIsStruct = false;
+
+ if ( hasValueAttr | hasResourceAttr ) {
+ childNode->value = valueNode->value;
+ if ( ! hasValueAttr ) childNode->options |= kXMP_PropValueIsURI; // ! Might have both rdf:value and rdf:resource.
+ } else if ( hasPropertyAttrs ) {
+ childNode->options |= kXMP_PropValueIsStruct;
+ childIsStruct = true;
+ }
+
+ currAttr = xmlNode.attrs.begin();
+ endAttr = xmlNode.attrs.end();
+
+ for ( ; currAttr != endAttr; ++currAttr ) {
+
+ if ( *currAttr == valueNode ) continue; // Skip the rdf:value or rdf:resource attribute holding the value.
+ RDFTermKind attrTerm = GetRDFTermKind ( (*currAttr)->name );
+
+ switch ( attrTerm ) {
+
+ case kRDFTerm_ID :
+ case kRDFTerm_nodeID :
+ break; // Ignore all rdf:ID and rdf:nodeID attributes.
+
+ case kRDFTerm_resource :
+ this->AddQualifierNode ( childNode, **currAttr );
+ break;
+
+ case kRDFTerm_Other :
+ if ( (! childIsStruct) || (*currAttr)->name == "xml:lang" ) {
+ this->AddQualifierNode ( childNode, **currAttr );
+ } else {
+ this->AddChildNode ( childNode, **currAttr, (*currAttr)->value.c_str(), false );
+ }
+ break;
+
+ default :
+ {
+ XMP_Error error ( kXMPErr_BadRDF, "Unrecognized attribute of empty property element" );
+ this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error );
+ }
+ continue;
+
+ }
+
+ }
+
+} // RDF_Parser::EmptyPropertyElement
+
+// =================================================================================================
+// XMPMeta::ProcessRDF
+// ===================
+//
+// Parse the XML tree of the RDF and build the corresponding XMP tree.
+
+void XMPMeta::ProcessRDF ( const XML_Node & rdfNode, XMP_OptionBits options )
+{
+ IgnoreParam(options);
+
+ RDF_Parser parser ( &this->errorCallback );
+
+ parser.RDF ( &this->tree, rdfNode );
+
+} // XMPMeta::ProcessRDF
+
+// =================================================================================================