// =================================================================================================
// Copyright 2004 Adobe
// 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. If you have received this file from a source other
// than Adobe, then your use, modification, or distribution of it requires the prior written permission
// of Adobe.
// =================================================================================================

#if AdobePrivate
// =================================================================================================
// Change history
// ==============
//
// Writers:
//  AWL Alan Lillich
//  ADC Amandeep Chawla
//
// mm-dd-yy who Description of changes, most recent on top.
//
// 10-10-12 ADC 5.5-c012 Changed internal implementation of common error notification infrastructure.
// 08-14-12 AWL 5.5-c009 Add XMPCore error notifications for RDF parsing errors.
// 06-06-12 AWL 5.5-c001 Make the RDF parsing recover from errors and deliver as much as possible.
//
// 05-01-12 AWL 5.4-c004 [3145445] Tolerate element use of rdf:_n notation for array items.
//
// 12-12-11 AWL 5.3-c010 [3017939,3060455] Undo erroneous changes from misleading static analysis
//              complaints that are breaking data structures and causing heap corruption.
//
// 05-27-10 AWL 5.1-c005 [0525610,1534508] Fix parsing of RDF Typed Nodes to agree with ISO-ized spec.
//
// 05-04-09 AWL 5.0-c026 Remove compact structs and arrays from TransformXMP.
// 04-03-09 AWL 5.0-c022 Change PlainXMP to TransformXMP.
//
// 07-01-08 AWL 4.3-c060 Hide all uses of PlainXMP.
//
// 12-04-07 AWL 4.2-c029 [1646368] Fix the rdf:about checking to allow a mix of empty and non-empty values.
// 11-30-07 AWL 4.2-c027 Expose XML_Node and ExpatAdapter so that XMPFiles can use them.
//
// 11-28-06 AWL 4.1-c027 [1424359] Fix a mistake in the xml:lang handling for rdf:value nodes.
// 11-17-06 AWL 4.1-c026 [1424359] Fix RDF parser to recognize xml:lang qualifier as an element.
//              [1424264] Fix Plain XMP parser to complain about duplicate properties and qualifiers.
//              [1424292] Fix Plain XMP parser to detect aliases.
//
// 03-24-06 AWL 4.0-c001 Adapt for move to ham-perforce, integrate XMPFiles, bump version to 4.
//
// 10-13-05 AWL 3.3-007 [1247060,1246288,1246286] Fix handling of special rdf: attributes, allow and
//              ignore inner XML processing instructions, add Expat handler for CDATA sections.
// 10-12-05 AWL 3.3-006 [1246290] Fix handling of a qualified property as an emptyPropertyElt.
// 06-07-05 AWL 3.2-113 [1071829] Throw better exception for RDF missing rdf:parseType='Resource'.
// 06-03-05 AWL 3.2-109 [0467370] Fix bugs in in RDF_ResourcePropertyElement: catch CData child
//              where an element is required; catch child element in no namespace.
// 04-11-05 AWL 3.2-016 Add AdobePrivate conditionals where appropriate.
// 04-05-05 AWL 3.2-011 [0532345] Normalize xml:lang values so that compares are in effect case
//              insensitive as required by RFC 3066. Change parsing and serializing to force the
//              x-default item to be first.
// 03-17-05 AWL 3.2-006 Revise Plain XMP parsing and serialization for latest proposal.
// 01-28-05 AWL 3.2-001 Remove BIB.
//
// 11-08-04 AWl 3.1.1-098 [1012513] Throw exception for use of rdf:resource attribute with property value.
// 11-08-04 AWl 3.1.1-097 [1008644] Throw BadRDF for elements that are in no namespace.
//
// 07-14-04 AWL 3.1-062 [1026373] Strip old iX:changes elements during parsing.
// 07-14-04 AWL 3.1-060 [1040363] Allow whitespace and processing instructions between x:xmpmeta and rdf:RDF.
//
// 05-25-04 AWL [1018426] Hide all use of cin/cout streams in #if DEBUG or equivalent.
// 04-30-04 AWL Clean up memory leaks.
// 04-21-04 AWL Improve the handling of XML elements and attributes that are not in any namespace.
//              Allow them in the XML parsing, complain on the RDF side.
// 04-20-04 AWL Complain when adding a child or qualifier that lacks an XML namespace.
// 04-12-04 AWL Fix recognition of RDF emptyPropertyElt, it can be used for a struct with all simple
//              fields written as attrProps.
// 03-17-04 AWL Cleanup error exceptions, make sure all have a reasonable message.
// 02-09-04 AWL Make sure rdf:value is recognized as an attribute also.
// 01-23-04 AWL Add logic to detect language alternatives: 7.2.15 resourcePropertyElt.
// 01-17-04 AWL Move into new Perforce depot, cosmetic cleanup.
//
// =================================================================================================
#endif // AdobePrivate


#include "public/include/XMP_Environment.h" // ! This must be the first include!
#if DEBUG
    #include <iostream>
#endif
#include "XMPCore/source/XMPCore_Impl.hpp"
#include "XMPCore/source/XMPMeta.hpp"
#include "source/ExpatAdapter.hpp"

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

// =================================================================================================
