/**@@@+++@@@@******************************************************************
**
** Microsoft Windows Media
** Copyright (C) Microsoft Corporation. All rights reserved.
**
***@@@---@@@@******************************************************************
*/

#include <drmcommon.h>
#include <drmxmlparser.h>
#include <drmcrt.h>
#include <drmbase64.h>
#include <drmutilities.h>
#include <drmcipher.h>

/*
***********************************************************************
** types used within this file
***********************************************************************
*/

typedef enum {
    eTagNone = 0,
    eOpenTag = 1,     /* <tag ...> */
    eCloseTag,      /* </tag> */
    eEmptyTag,      /* <tag ... /> */
    eCDataSection,  /* <![CDATA[...]]> */
}  _ETAGTYPE;


typedef struct _tag_XMLTAG 
{
    _ETAGTYPE       m_eTagType;  
    DRM_SUBSTRING   m_dasstrTag;
    DRM_SUBSTRING   m_dasstrAttrName;
    DRM_SUBSTRING   m_dasstrAttrValue;
    DRM_SUBSTRING   m_dasstrCData;
    DRM_DWORD       m_ichTagBegin; /* index of '<' */
    DRM_DWORD       m_ichTagEnd;   /* index of '>' */
}  _XMLTAGA;

typedef struct _tag_XMLTAGW
{
    _ETAGTYPE        m_eTagType;  /*fCloseTag; */
    const DRM_WCHAR *m_pwszBase;
    DRM_SUBSTRING    m_dasstrTag;
    DRM_SUBSTRING    m_dasstrAttrName;
    DRM_SUBSTRING    m_dasstrAttrValue;
    DRM_SUBSTRING    m_dasstrCData;
    DRM_DWORD        m_ichTagBegin;     /* index of '<' */
    DRM_DWORD        m_ichTagEnd;  /* index of '>' */
}  _XMLTAGW;

#define SCAN_FOR(condition)  \
{                            \
    while (ichCurr != ichEnd \
       && !(condition))      \
    {                        \
        ichCurr++;           \
    }                        \
    if (ichCurr >= ichEnd)   \
    {                        \
        break;               \
    }                        \
}

#define SCAN_WHILE(condition) \
{                             \
    while(ichCurr != ichEnd   \
       && (condition))        \
    {                         \
        if (++ichCurr >= ichEnd) \
        {                        \
            break;               \
        }                        \
    }                        \
}

#define NEXT_CHAR            \
{                            \
    if (++ichCurr >= ichEnd) \
    {                        \
        break;               \
    }                        \
}    

#define SKIP_SPACEW           \
{                             \
    while (ichCurr < ichEnd   \
       &&  _IsSpaceW(pwszBase[ichCurr]))\
    {                         \
        ichCurr++;            \
    }                         \
    if (ichCurr >= ichEnd)    \
    {                         \
        break;                \
    }                         \
}

#define SKIP_SPACEA           \
{                             \
    while (ichCurr < ichEnd   \
       &&  _IsSpaceA(GET_CHAR(f_pszBase,ichCurr)))\
    {                         \
        ichCurr++;            \
    }                         \
    if (ichCurr >= ichEnd)    \
    {                         \
        break;                \
    }                         \
}

/*
***********************************************************************
** static functions
***********************************************************************
*/

/*
**
*/
#define _IsAlphaNumW(f_wch) (DRM_iswalpha(f_wch) || DRM_iswdigit(f_wch))

#define _IsAlphaNumA(f_ch) ( (f_ch >= '0' && f_ch <= '9')        \
                          || (f_ch >= 'a' && f_ch <= 'z')        \
                          || (f_ch >= 'A' && f_ch <= 'Z'))

#define _IsSymbolW(f_wch) ( _IsAlphaNumW(f_wch)                  \
                         || f_wch == g_wchUnderscore             \
                         || f_wch == g_wchColon                  \
                         || f_wch == g_wchMinus                  \
                         || f_wch == g_wchPeriod )

#define _IsSymbolA(f_ch) ( _IsAlphaNumA(f_ch)                    \
                        || f_ch == '_'                           \
                        || f_ch == ':'                           \
                        || f_ch == '-'                           \
                        || f_ch == '.' )

#define _IsSpaceW(f_wch) ( f_wch > 0           && ( NATIVE_WCHAR(f_wch) < NATIVE_WCHAR (g_wchExclamationMark) ) )
#define _IsSpaceA(f_ch)  ( f_ch > 0            && f_ch < '!' )
#define _IsQuoteW(f_wch) ( f_wch == g_wchQuote || f_wch == g_wchSingleQuote )
#define _IsQuoteA(f_ch)  ( f_ch == '\''        || f_ch == '\"' )



DRMFORCEINLINE DRM_RESULT _TranslateXMLError(DRM_RESULT f_drXML)
{
    DRM_RESULT dr = f_drXML;

    switch (f_drXML)
    {
    case DRM_E_NOXMLOPENTAG:
        dr = DRM_E_XMLNOTFOUND;
        break;

    default:
        dr = f_drXML;
        break;
    }

    return dr;
}


/*
** Trim all leading and trailing blanks in given string 
** return TRUE if resulting string length > 0
*/

static DRM_BOOL _AllTrimW(IN OUT DRM_CONST_STRING *f_pdstr)
{
    DRM_BOOL fOK = FALSE;
    
    if (f_pdstr == NULL)
    {
        goto ErrorExit;
    }
    
    /* trim space in node data, if any */
    while (f_pdstr->cchString > 0
        && _IsSpaceW(f_pdstr->pwszString [0]))
    {
        f_pdstr->pwszString++;
        f_pdstr->cchString--;
    }

    while (f_pdstr->cchString > 0
        && _IsSpaceW(f_pdstr->pwszString [f_pdstr->cchString - 1]))
    {
        f_pdstr->cchString--;
    }

    fOK = f_pdstr->cchString > 0;

ErrorExit:
    return fOK;
}

static DRM_BOOL _AllTrimA(
    IN     const DRM_CHAR *f_pszBase,
    IN OUT DRM_SUBSTRING  *f_pdasstr)
{
    DRM_BOOL fOK = FALSE;

    if (f_pszBase == NULL
     || f_pdasstr == NULL)
    {
        goto ErrorExit;
    }
    
    /* trim space in node data, if any */
    while (_IsSpaceA(GET_CHAR(f_pszBase, f_pdasstr->m_ich)))
    {
        f_pdasstr->m_ich++;
        f_pdasstr->m_cch--;
    }

    while (f_pdasstr->m_cch > 0 
       &&  _IsSpaceA(GET_CHAR(f_pszBase, (f_pdasstr->m_ich + f_pdasstr->m_cch - 1))))
    {
        f_pdasstr->m_cch--;
    }

    fOK = (f_pdasstr->m_cch > 0);

ErrorExit:
    return fOK;
}


/*
**
*/
static DRM_BOOL _ScanTagW(
    IN const DRM_CONST_STRING *f_pdstrXML, 
    IN OUT   DRM_DWORD        *f_pichCurr, 
       OUT   _XMLTAGW         *f_ptagParsed)
{
    const DRM_WCHAR *pwszBase =  f_pdstrXML->pwszString;
    DRM_DWORD        ichCurr  = *f_pichCurr;
    DRM_DWORD        ichEnd   =  f_pdstrXML->cchString;
    DRM_BOOL         fParsedOkay = FALSE;
    DRM_DWORD        ichOpenTag  = 0;
    
#if DBG
    /* init return value */
    ZEROMEM(f_ptagParsed, SIZEOF(_XMLTAGW));
#endif

    f_ptagParsed->m_pwszBase = pwszBase;    
    f_ptagParsed->m_eTagType = eTagNone;
    
    while (ichCurr < ichEnd)
    {
        /* search for next element */

        SCAN_FOR(pwszBase [ichCurr] == g_wchLessThan);   /*Find the next '<' */

        if (pwszBase [ichCurr] != g_wchLessThan)
        {
            continue;
        }
        
        ichOpenTag = ichCurr;
        NEXT_CHAR;

        /* 
        ** ------------------------------------------------------------------ 
        ** parse the CLOSE TAG
        ** ------------------------------------------------------------------ 
        */
        if (pwszBase [ichCurr] == g_wchForwardSlash)    /* '</...' */
        {
            NEXT_CHAR;
            if (_IsSymbolW(pwszBase [ichCurr]))     /* '</#...' */
            {
                f_ptagParsed->m_eTagType = eCloseTag;   /* indicates this is a close tag */
                goto PARSE_TAG;   /* parse the tag name */
            }
            
            /* Bad </...>, ignore the TAG and continue */
            SCAN_FOR(pwszBase [ichCurr] == g_wchGreaterThan);    /* Look for '>' */
            NEXT_CHAR;
            continue;
        }

		/* 
		** ------------------------------------------------------------------ 
		** parse the Process Instruction: ignore the TAG and continue
		** ------------------------------------------------------------------ 
		*/
		else if (pwszBase [ichCurr] == g_wchQuestionMark)    /* '<?...' */
        {
            NEXT_CHAR;
            /* we do not support PI tag, ignore it and continue */
            SCAN_FOR(pwszBase [ichCurr] == g_wchGreaterThan);    /* Look for '>' */
            NEXT_CHAR;
            continue;
		}

		/* 
		** ------------------------------------------------------------------ 
		** parse the Special TAG, ignore and continue
		** ------------------------------------------------------------------ 
		*/
		else if (pwszBase [ichCurr] == g_wchExclamationMark)     /* '<!...' */
        {
            NEXT_CHAR;
            if (pwszBase [ichCurr] == g_wchMinus)     /* '<!-...' */
            {
                NEXT_CHAR;
                /* we do not support comment, ignore it and continue */
                while  (ichCurr < ichEnd)
                {
                    /* Look for terminating '-->' */
                    
                    if (pwszBase [ichCurr]     == g_wchMinus
                    &&  pwszBase [ichCurr + 1] == g_wchMinus
                    &&  pwszBase [ichCurr + 2] == g_wchGreaterThan)
                    {
                        break;
                    }           
        
                    ichCurr++;    
                }

                NEXT_CHAR;
                continue;
            }
            
            /* '<![...' */
            
            else if (pwszBase [ichCurr] == g_wchLeftBracket)    
            {
                NEXT_CHAR;

                /* Parsing CDATA section */

                if ((ichCurr + 6) >= ichEnd  
                ||  (DRM_wcsncmp((DRM_WCHAR *) (&(pwszBase[ichCurr])), 
                             g_dstrTagCDATAOpenBracket.pwszString, 
                             g_dstrTagCDATAOpenBracket.cchString) != 0))
                {   
                    /* not CDATA section, ignore it */
                    while ((ichCurr + 2) < ichEnd)
                    {
                        /*Scan for terminating ']]>' */
                        
                        if (pwszBase [ichCurr]     == g_wchRightBracket
                        &&  pwszBase [ichCurr + 1] == g_wchRightBracket
                        &&  pwszBase [ichCurr + 2] == g_wchGreaterThan)
                        {
                            break;
                        }

                        ichCurr++;
                    }
                }
                else
                {
                    /* CDATA section found */

                    f_ptagParsed->m_dasstrTag.m_ich = ichCurr;
                    f_ptagParsed->m_dasstrTag.m_cch = 5;

                    ichCurr += 6;
                    
                    f_ptagParsed->m_dasstrCData.m_ich = ichCurr;
                    f_ptagParsed->m_dasstrCData.m_cch = 0;
                    
                    while ((ichCurr + 2) < ichEnd)
                    {
                        /*Scan for terminating ']]>' */
                    
                        if (pwszBase [ichCurr]     == g_wchRightBracket
                        &&  pwszBase [ichCurr + 1] == g_wchRightBracket
                        &&  pwszBase [ichCurr + 2] == g_wchGreaterThan)
                        {
                            break;
                        }

                        f_ptagParsed->m_dasstrCData.m_cch++;
                        ichCurr++;  
                    }
                    
                    if ((ichCurr + 2) < ichEnd)
                    {
                        f_ptagParsed->m_eTagType    = eCDataSection;
                        f_ptagParsed->m_ichTagBegin = ichOpenTag;
                        
                        ichCurr += 2;
                        
                        f_ptagParsed->m_ichTagEnd = ichCurr;
                        fParsedOkay               = TRUE;
                        break;
                    }
                    
                    ichCurr += 2;
                }
                NEXT_CHAR;
                continue;
            }
            
            else if (_IsSymbolW(pwszBase [ichCurr]))  /* An inline DTD tag. */
            {
                NEXT_CHAR;
                /* we do not support inline DTD tag, ignore it and continue */
                SCAN_FOR(pwszBase [ichCurr] == g_wchGreaterThan);    /* Look for '>' */
                NEXT_CHAR;
                continue;
			}
		}


		/* 
		** ------------------------------------------------------------------ 
		** parse the contents of a TAG
		** note: we only support this format: <tag attr=value>, attr and value is optional.
		** ------------------------------------------------------------------ 
		*/
		else if (_IsSymbolW(pwszBase [ichCurr]))  /* '<#...' */
		{
PARSE_TAG:   
            /* Scan for tag name. */
            f_ptagParsed->m_dasstrTag.m_ich = ichCurr;
            
            SCAN_WHILE(_IsSymbolW(pwszBase [ichCurr]));    /* Scan for a terminator. */

            f_ptagParsed->m_dasstrTag.m_cch = ichCurr - f_ptagParsed->m_dasstrTag.m_ich;

            /* at this point, we've got the tag name */
            
            SKIP_SPACEW;   /* skip any whitespace and parse attr name. */

            if (_IsSymbolW(pwszBase [ichCurr]))  /* <... #... */
            {
                /* parse the attribute name */

                f_ptagParsed->m_dasstrAttrName.m_ich = ichCurr;
                
                /* Scan for a terminator. */
                
                SCAN_WHILE(_IsSymbolW(pwszBase [ichCurr]));    
                
                f_ptagParsed->m_dasstrAttrName.m_cch = (ichCurr - f_ptagParsed->m_dasstrAttrName.m_ich);

                /* at this point, we've got the attribute name */
                SKIP_SPACEW;   /* skip any whitespace and parse attr value. */

                if (pwszBase [ichCurr] == g_wchEqual)    /* '<... #=...' */
                {
                    /* parse the attribute value */
                    NEXT_CHAR;     /* skip the equal sign */
                    SKIP_SPACEW;    /* skip any whitespace. */
                    
                    if (_IsQuoteW(pwszBase [ichCurr]))     /* '<... #="...' */
                    {
                        DRM_WCHAR wchQuote = pwszBase [ichCurr];    /* Save quote char to avoid breaking on " ' ' " -or- ' " " '. */

                        NEXT_CHAR;    /* Step over the quote. */

                        f_ptagParsed->m_dasstrAttrValue.m_ich = ichCurr;

                        SCAN_FOR((pwszBase [ichCurr] == wchQuote));  /* Scan for the terminating quote, or '>'. */
                        
                        f_ptagParsed->m_dasstrAttrValue.m_cch = ichCurr - f_ptagParsed->m_dasstrAttrValue.m_ich;

                        /* trim space in attribute value, if any */

                        while (f_ptagParsed->m_dasstrAttrValue.m_cch > 0
                           &&  _IsSpaceW(pwszBase [f_ptagParsed->m_dasstrAttrValue.m_ich + f_ptagParsed->m_dasstrAttrValue.m_cch - 1]))
                        {
                            f_ptagParsed->m_dasstrAttrValue.m_cch--;
                        }

                        /* at this point, we've got the attribute value */
                        /* note: we only support parsing the first attribute with value, the rest is ignored */
                        goto PARSE_TAGEND;
                    }
                }
            } /* end if _IsSymbolW(pwszBase [ichCurr] */
            
PARSE_TAGEND:

            if (f_ptagParsed->m_eTagType == eCloseTag)
            {
                SCAN_FOR(pwszBase [ichCurr] == g_wchGreaterThan);   
            }
            else
            {
                SCAN_FOR(pwszBase [ichCurr] == g_wchForwardSlash
                     ||  pwszBase [ichCurr] == g_wchGreaterThan);
                
                if (pwszBase [ichCurr] == g_wchForwardSlash)
                {
                    NEXT_CHAR;
                    SCAN_FOR(pwszBase [ichCurr] == g_wchGreaterThan);   

                    f_ptagParsed->m_eTagType = eEmptyTag;
                }
                else
                {
                    f_ptagParsed->m_eTagType = eOpenTag;  
                }
            }

            if (f_ptagParsed->m_eTagType == eTagNone)
            {
                f_ptagParsed->m_eTagType = eOpenTag;   
            }

            f_ptagParsed->m_ichTagBegin = ichOpenTag;
            f_ptagParsed->m_ichTagEnd   = ichCurr;

            fParsedOkay = TRUE;
            NEXT_CHAR;
            break;  /* that's it, we parsed a tag */
        }
    }

    if (fParsedOkay)
    {
        *f_pichCurr = ichCurr;
    }

    return fParsedOkay;
} /* _ScanTagW */

static DRM_BOOL _ScanTagA(
    IN const DRM_CHAR      *f_pszBase,
    IN const DRM_SUBSTRING *f_pdastrXML, 
    IN OUT   DRM_DWORD     *f_pichCurr, 
       OUT   _XMLTAGA      *f_ptagParsed)
{
    DRM_DWORD       ichEnd  =  f_pdastrXML->m_ich 
                            +  f_pdastrXML->m_cch;
    DRM_DWORD       ichCurr = *f_pichCurr;
    DRM_BOOL        fParsedOkay = FALSE;
    DRM_DWORD       ichOpenTag  = 0;

#if DBG
    /* init return value */
    ZEROMEM(f_ptagParsed, SIZEOF(_XMLTAGA));
#endif

    f_ptagParsed->m_eTagType = eTagNone;
    
    while (ichCurr < ichEnd)
    {
        /* search for next element */

        SCAN_FOR(GET_CHAR(f_pszBase, ichCurr) == '<');   /*Find the next '<' */

        if (GET_CHAR(f_pszBase, ichCurr) != '<')
        {
            continue;
        }
        
        ichOpenTag = ichCurr;
        NEXT_CHAR;

        /* 
        ** ------------------------------------------------------------------ 
        ** parse the CLOSE TAG
        ** ------------------------------------------------------------------ 
        */
        if (GET_CHAR(f_pszBase, ichCurr) == '/')    /* '</...' */
        {
            NEXT_CHAR;
            if (_IsSymbolA(GET_CHAR(f_pszBase, ichCurr)))     /* '</#...' */
            {
                f_ptagParsed->m_eTagType = eCloseTag;   /* indicates this is a close tag */
                goto PARSE_TAG;   /* parse the tag name */
            }
            
            /* Bad </...>, ignore the TAG and continue */
            SCAN_FOR(GET_CHAR(f_pszBase, ichCurr) == '>');    /* Look for '>' */
            NEXT_CHAR;
            continue;
        }

		/* 
		** ------------------------------------------------------------------ 
		** parse the Process Instruction: ignore the TAG and continue
		** ------------------------------------------------------------------ 
		*/
		else if (GET_CHAR(f_pszBase, ichCurr) == '?')    /* '<?...' */
        {
            NEXT_CHAR;
            /* we do not support PI tag, ignore it and continue */
            SCAN_FOR(GET_CHAR(f_pszBase, ichCurr) == '>');    /* Look for '>' */
            NEXT_CHAR;
            continue;
		}

		/* 
		** ------------------------------------------------------------------ 
		** parse the Special TAG, ignore and continue
		** ------------------------------------------------------------------ 
		*/
		else if (GET_CHAR(f_pszBase, ichCurr) == '!')     /* '<!...' */
        {
            NEXT_CHAR;
            if (GET_CHAR(f_pszBase, ichCurr) == '-')     /* '<!-...' */
            {
                NEXT_CHAR;
                /* we do not support comment, ignore it and continue */
                while  (ichCurr < ichEnd)
                {
                    /* Look for terminating '-->' */
                    
                    if (GET_CHAR(f_pszBase, ichCurr)     == '-'
                    &&  GET_CHAR(f_pszBase, ichCurr + 1) == '-'
                    &&  GET_CHAR(f_pszBase, ichCurr + 2) == '>')
                    {
                        break;
                    }           
        
                    ichCurr++;    
                }

                NEXT_CHAR;
                continue;
            }
            
            /* '<![...' */
            
            else if (GET_CHAR(f_pszBase, ichCurr) == '[')    
            {
                NEXT_CHAR;

                /* Parsing CDATA section */

                if ((ichCurr + 6) >= ichEnd  
                ||  (DRM_BYT_CompareBytes(f_pszBase, 
                                          ichCurr, 
                                          g_dastrTagCDATAOpenBracket.pszString, 
                                          0, 
                                          g_dastrTagCDATAOpenBracket.cchString) != 0))
                {   
                    /* not CDATA section, ignore it */
                    while ((ichCurr + 2) < ichEnd)
                    {
                        /*Scan for terminating ']]>' */
                        
                        if (GET_CHAR(f_pszBase, ichCurr)     == ']'
                        &&  GET_CHAR(f_pszBase, ichCurr + 1) == ']'
                        &&  GET_CHAR(f_pszBase, ichCurr + 2) == '>')
                        {
                            break;
                        }

                        NEXT_CHAR;
                   }
                }

                /* CDATA section found */

                else
                {
                    f_ptagParsed->m_dasstrTag.m_ich = ichCurr;
                    f_ptagParsed->m_dasstrTag.m_cch = 5;

                    ichCurr += 6;
                    
                    f_ptagParsed->m_dasstrCData.m_ich = ichCurr;
                    f_ptagParsed->m_dasstrCData.m_cch = 0;
                    
                    while ((ichCurr + 2) < ichEnd)
                    {
                        /*Scan for terminating ']]>' */
                    
                        if (GET_CHAR(f_pszBase, ichCurr)     == ']'
                        &&  GET_CHAR(f_pszBase, ichCurr + 1) == ']'
                        &&  GET_CHAR(f_pszBase, ichCurr + 2) == '>')
                        {
                            break;
                        }

                        f_ptagParsed->m_dasstrCData.m_cch++;
                        NEXT_CHAR;
                    }
                    
                    if ((ichCurr + 2) < ichEnd)
                    {
                        f_ptagParsed->m_eTagType    = eCDataSection;
                        f_ptagParsed->m_ichTagBegin = ichOpenTag;
                        
                        ichCurr += 2;
                        
                        f_ptagParsed->m_ichTagEnd = ichCurr;
                        fParsedOkay               = TRUE;
                        break;
                    }
                    
                    ichCurr += 2;
                }
                NEXT_CHAR;
                continue;
            }
            
            else if (_IsSymbolA(GET_CHAR(f_pszBase, ichCurr)))  /* An inline DTD tag. */
            {
                NEXT_CHAR;
                /* we do not support inline DTD tag, ignore it and continue */
                SCAN_FOR(GET_CHAR(f_pszBase, ichCurr) == '>');    /* Look for '>' */
                NEXT_CHAR;
                continue;
			}
		}


		/* 
		** ------------------------------------------------------------------ 
		** parse the contents of a TAG
		** note: we only support this format: <tag attr=value>, attr and value is optional.
		** ------------------------------------------------------------------ 
		*/
		else if (_IsSymbolA(GET_CHAR(f_pszBase, ichCurr)))  /* '<#...' */
		{
            DRM_BOOL    fParsedAttr = FALSE;
PARSE_TAG:   
            /* Scan for tag name. */
            f_ptagParsed->m_dasstrTag.m_ich = ichCurr;
            
            SCAN_WHILE(_IsSymbolA(GET_CHAR(f_pszBase, ichCurr)));    /* Scan for a terminator. */

            f_ptagParsed->m_dasstrTag.m_cch = ichCurr - f_ptagParsed->m_dasstrTag.m_ich;

            /* at this point, we've got the tag name */
PARSE_ATTR:
            SKIP_SPACEA;   /* skip any whitespace and parse attr name. */

            if (_IsSymbolA(GET_CHAR(f_pszBase, ichCurr)))  /* <... #... */
            {
                /* parse the attribute name */

                if( !fParsedAttr )
                {
                    f_ptagParsed->m_dasstrAttrName.m_ich = ichCurr;
                }
                
                /* Scan for a terminator. */
                
                SCAN_WHILE(_IsSymbolA(GET_CHAR(f_pszBase, ichCurr)));    
                
                if( !fParsedAttr )
                {
                    f_ptagParsed->m_dasstrAttrName.m_cch = (ichCurr - f_ptagParsed->m_dasstrAttrName.m_ich);
                }

                /* at this point, we've got the attribute name */
                SKIP_SPACEA;   /* skip any whitespace and parse attr value. */

                if (GET_CHAR(f_pszBase, ichCurr) == '=')    /* '<... #=...' */
                {
                    /* parse the attribute value */
                    NEXT_CHAR;     /* skip the equal sign */
                    SKIP_SPACEA;    /* skip any whitespace. */
                    
                    if (_IsQuoteA(GET_CHAR(f_pszBase, ichCurr)))     /* '<... #="...' */
                    {
                        DRM_WCHAR wchQuote = GET_CHAR(f_pszBase, ichCurr);    /* Save quote char to avoid breaking on " ' ' " -or- ' " " '. */

                        NEXT_CHAR;    /* Step over the quote. */

                        if( !fParsedAttr )
                        {
                            f_ptagParsed->m_dasstrAttrValue.m_ich = ichCurr;
                        }

                        SCAN_FOR((GET_CHAR(f_pszBase, ichCurr) == wchQuote));  /* Scan for the terminating quote, or '>'. */
                        
                        if( !fParsedAttr )
                        {
                            f_ptagParsed->m_dasstrAttrValue.m_cch = (ichCurr - f_ptagParsed->m_dasstrAttrValue.m_ich);

                            /* trim space in attribute value, if any */

                            while (f_ptagParsed->m_dasstrAttrValue.m_cch > 0
                            &&  _IsSpaceA(GET_CHAR(f_pszBase, f_ptagParsed->m_dasstrAttrValue.m_ich + f_ptagParsed->m_dasstrAttrValue.m_cch - 1)))
                            {
                                f_ptagParsed->m_dasstrAttrValue.m_cch--;
                            }
                        }

                        /* at this point, we've got the attribute value */
                        /* note: we only support parsing the first attribute with value, the rest is ignored */
                        if (_IsQuoteA(GET_CHAR(f_pszBase, ichCurr)))
                        {
                            NEXT_CHAR;
                        }

                        /* We have to parse all the attributes, but we can only return one. 
                        *  To continue the expected behaviour of returning the first attribute
                        *  found (instead of the last), we need to keep track of whether 
                        *  we've already parsed an attrbitue
                        */

                        fParsedAttr = TRUE;
                        goto PARSE_ATTR;
                    }
                }
            } /* end if _IsSymbolA(GET_CHAR(f_pszBase, ichCurr) */

            if (f_ptagParsed->m_eTagType == eCloseTag)
            {
                SCAN_FOR(GET_CHAR(f_pszBase, ichCurr) == '>');   
            }
            else
            {
                SCAN_FOR(GET_CHAR(f_pszBase, ichCurr) == '/'
                     ||  GET_CHAR(f_pszBase, ichCurr) == '>');
                
                if (GET_CHAR(f_pszBase, ichCurr) == '/')
                {
                    NEXT_CHAR;
                    SCAN_FOR(GET_CHAR(f_pszBase, ichCurr) == '>');   

                    f_ptagParsed->m_eTagType = eEmptyTag;
                }
                else
                {
                    f_ptagParsed->m_eTagType = eOpenTag;  
                }
            }

            if (f_ptagParsed->m_eTagType == eTagNone)
            {
                f_ptagParsed->m_eTagType = eOpenTag;   
            }

            f_ptagParsed->m_ichTagBegin = ichOpenTag;
            f_ptagParsed->m_ichTagEnd   = ichCurr;

            fParsedOkay = TRUE;
            NEXT_CHAR;
            break;  /* that's it, we parsed a tag */
        }
    }

    if (fParsedOkay)
    {
        *f_pichCurr = ichCurr;
    }

    return fParsedOkay;
} /* _ScanTagA */


/*
** ----------------------------------------------------------------------------
** 
** ----------------------------------------------------------------------------
*/
static DRM_BOOL _ScanNodeForAttributeW(
    IN const DRM_CONST_STRING *f_pdstrNode,  /* node */
    IN const DRM_CONST_STRING *f_pdstrAttr,  /* Attribute name to search in the topmost node */
    OUT      _XMLTAGW         *f_ptagParsed)
{
    const DRM_WCHAR *pwszBase    = f_pdstrNode->pwszString;
    DRM_DWORD        ichEnd      = f_pdstrNode->cchString;
    DRM_DWORD        ichCurr     = 0;
    DRM_BOOL         fParsedOkay = FALSE;
    DRM_CONST_STRING dstrAttr    = EMPTY_DRM_STRING;

#if DBG
    /* init return value */
    ZEROMEM(f_ptagParsed, SIZEOF(_XMLTAGW));
#endif

    while (TRUE)
    {
        SCAN_FOR(pwszBase [ichCurr] == g_wchLessThan);   /*Find the next '<' */

        if (pwszBase [ichCurr] != g_wchLessThan)
        {
            goto ErrorExit;   /* cannot find  '<'  */
        }
        
        NEXT_CHAR;

        /* 
        ** ------------------------------------------------------------------ 
        ** parse the contents of a TAG
        ** note: we only support this format: <tag attr=value>, attr and value is optional.
        ** ------------------------------------------------------------------ 
        */
        if (! _IsSymbolW(pwszBase [ichCurr]))  /* '<#...' */
        {
            goto ErrorExit;  /* cannot find an element tag */
        }
        
        /* Scan for tag name. */

        f_ptagParsed->m_dasstrTag.m_ich = ichCurr;
        
        /* Scan for a terminator. */
        
        SCAN_WHILE(_IsSymbolW(pwszBase [ichCurr]));    
        
        f_ptagParsed->m_dasstrTag.m_cch = ichCurr - f_ptagParsed->m_dasstrTag.m_ich;

        /* at this point, we've got the tag name */
        SKIP_SPACEW;   /* skip any whitespace and parse attr name. */
        
        if (_IsSymbolW(pwszBase [ichCurr]))  /* <... #... */
        {
PARSE_ATTR:
            /* parse the attribute name */
            f_ptagParsed->m_dasstrAttrName.m_ich = ichCurr;
            
            SCAN_WHILE(_IsSymbolW(pwszBase [ichCurr]));    /* Scan for a terminator. */
            
            f_ptagParsed->m_dasstrAttrName.m_cch = (ichCurr - f_ptagParsed->m_dasstrAttrName.m_ich);

            /* at this point, we've got the attribute name */
            SKIP_SPACEW;   /* skip any whitespace and parse attr value. */
            
            if (pwszBase [ichCurr] == g_wchEqual)    /* '<... #=...' */
            {
                /* parse the attribute value */
                NEXT_CHAR;     /* skip the equal sign */
                SKIP_SPACEW;   /* skip any whitespace. */
                if (_IsQuoteW(pwszBase [ichCurr]))     /* '<... #="...' */
                {
                    DRM_WCHAR wchQuote = pwszBase [ichCurr];    /* Save quote char to avoid breaking on " ' ' " -or- ' " " '. */
                    
                    NEXT_CHAR;    /* Step over the quote. */
                    
                    f_ptagParsed->m_dasstrAttrValue.m_ich = ichCurr;
                    
                    SCAN_FOR(pwszBase [ichCurr] == wchQuote);  /* Scan for the terminating quote, or '>'. */
                    
                    f_ptagParsed->m_dasstrAttrValue.m_cch = (ichCurr - f_ptagParsed->m_dasstrAttrValue.m_ich);

                    NEXT_CHAR;

                    /* trim space in attribute value, if any */
                    while (f_ptagParsed->m_dasstrAttrValue.m_cch > 0 
                       &&  _IsSpaceW(pwszBase [f_ptagParsed->m_dasstrAttrValue.m_ich 
                                             + f_ptagParsed->m_dasstrAttrValue.m_cch 
                                             - 1]))
                    {
                        f_ptagParsed->m_dasstrAttrValue.m_cch--;                       
                    }
                }
            }

            dstrAttr.pwszString = pwszBase + f_ptagParsed->m_dasstrAttrName.m_ich;
            dstrAttr.cchString  =            f_ptagParsed->m_dasstrAttrName.m_cch;
            
            /* check if this is the attr we want */
            if (DRM_UTL_DSTRStringsEqual(&dstrAttr, f_pdstrAttr))
            {
                /* Scan for '>' to make sure this is a legal tag */
                
                SCAN_FOR(pwszBase [ichCurr] == g_wchGreaterThan);   

                fParsedOkay=TRUE;

                f_ptagParsed->m_ichTagBegin = 0;
                f_ptagParsed->m_ichTagEnd   = 0;
                f_ptagParsed->m_eTagType    = eOpenTag;
                goto ErrorExit;		/* we found it */
            }
            else
            {
                SKIP_SPACEW;   /* skip any whitespace and parse attr value. */

                if (_IsAlphaNumW(pwszBase [ichCurr]))
                {
                    goto PARSE_ATTR;	/* keep going */
                }
            }
        } /* end if is alnum */
        
        break;  /* that'pwszBase it */
    }

ErrorExit:
    return fParsedOkay;
}

static DRM_BOOL _ScanNodeForAttributeA(
    IN const DRM_CHAR              *f_pszBase,
    IN const DRM_SUBSTRING         *f_pdastrNode,  /* string that contains a node */
    IN const DRM_ANSI_CONST_STRING *f_pdastrAttrName,  /* Attribute name to search in the topmost node */
    OUT      _XMLTAGA              *f_ptagParsed)
{
    DRM_DWORD ichEnd      = f_pdastrNode->m_ich 
                          + f_pdastrNode->m_cch;
    DRM_DWORD ichCurr     = f_pdastrNode->m_ich;
    DRM_BOOL  fParsedOkay = FALSE;

#if DBG
    /* init return value */
    ZEROMEM(f_ptagParsed, SIZEOF(_XMLTAGA));
#endif
    
    while (TRUE)
    {
        SCAN_FOR(GET_CHAR(f_pszBase, ichCurr) == '<');   /*Find the next '<' */

        if (GET_CHAR(f_pszBase, ichCurr) != '<')
        {
            goto ErrorExit;   /* cannot find  '<'  */
        }
        
        NEXT_CHAR;

        /* 
        ** ------------------------------------------------------------------ 
        ** parse the contents of a TAG
        ** note: we only support this format: <tag attr=value>, attr and value is optional.
        ** ------------------------------------------------------------------ 
        */
        if (! _IsSymbolA(GET_CHAR(f_pszBase, ichCurr)))  /* '<#...' */
        {
            goto ErrorExit;  /* cannot find an element tag */
        }
        
        /* Scan for tag name. */

        f_ptagParsed->m_dasstrTag.m_ich = ichCurr;
        
        /* Scan for a terminator. */
        
        SCAN_WHILE(_IsSymbolA(GET_CHAR(f_pszBase, ichCurr)));    
        
        f_ptagParsed->m_dasstrTag.m_cch = ichCurr - f_ptagParsed->m_dasstrTag.m_ich;

        /* at this point, we've got the tag name */
        SKIP_SPACEA;   /* skip any whitespace and parse attr name. */
        
        if (_IsSymbolA(GET_CHAR(f_pszBase, ichCurr)))  /* <... #... */
        {
PARSE_ATTR:
            /* parse the attribute name */
            f_ptagParsed->m_dasstrAttrName.m_ich = ichCurr;
            
            SCAN_WHILE(_IsSymbolA(GET_CHAR(f_pszBase, ichCurr)));    /* Scan for a terminator. */
            
            f_ptagParsed->m_dasstrAttrName.m_cch = (ichCurr - f_ptagParsed->m_dasstrAttrName.m_ich);

            /* at this point, we've got the attribute name */
            SKIP_SPACEA;   /* skip any whitespace and parse attr value. */
            
            if (GET_CHAR(f_pszBase, ichCurr) == '=')    /* '<... #=...' */
            {
                /* parse the attribute value */
                NEXT_CHAR;     /* skip the equal sign */
                SKIP_SPACEA;   /* skip any whitespace. */
                if (_IsQuoteA(GET_CHAR(f_pszBase, ichCurr)))     /* '<... #="...' */
                {
                    DRM_CHAR chQuote = GET_CHAR(f_pszBase, ichCurr);    /* Save quote char to avoid breaking on " ' ' " -or- ' " " '. */
                    
                    NEXT_CHAR;    /* Step over the quote. */
                    
                    f_ptagParsed->m_dasstrAttrValue.m_ich = ichCurr;
                    
                    SCAN_FOR(GET_CHAR(f_pszBase, ichCurr) == chQuote);  /* Scan for the terminating quote, or '>'. */
                    
                    f_ptagParsed->m_dasstrAttrValue.m_cch = (ichCurr - f_ptagParsed->m_dasstrAttrValue.m_ich);

                    NEXT_CHAR;

                    /* trim space in attribute value, if any */
                    while (f_ptagParsed->m_dasstrAttrValue.m_cch > 0 
                       &&  _IsSpaceA(GET_CHAR(f_pszBase, f_ptagParsed->m_dasstrAttrValue.m_cch - 1)))
                    {
                        f_ptagParsed->m_dasstrAttrValue.m_cch--;                       
                    }
                }
            }

            /* check if this is the attr we want */
            if (DRM_UTL_DASSTRStringsEqual(f_pszBase, &f_ptagParsed->m_dasstrAttrName, f_pdastrAttrName))
            {
                /* Scan for '>' to make sure this is a legal tag */
                
                SCAN_FOR(GET_CHAR(f_pszBase, ichCurr) == '>');   

                fParsedOkay=TRUE;

                f_ptagParsed->m_ichTagBegin = 0;
                f_ptagParsed->m_ichTagEnd   = 0;
                f_ptagParsed->m_eTagType    = eOpenTag;
                goto ErrorExit;		/* we found it */
            }
            else
            {
                SKIP_SPACEA;   /* skip any whitespace and parse attr value. */

                if (_IsAlphaNumA(GET_CHAR(f_pszBase, ichCurr)))
                {
                    goto PARSE_ATTR;	/* keep going */
                }
            }
        } /* end if is alnum */
        
        break;  /* that'f_pszBase it */
    }

ErrorExit:
    return fParsedOkay;
}

/*
** check Tag
*/
static _ETAGTYPE _CheckTagW(
    IN       _XMLTAGW         *f_ptagParsed, 
    IN const DRM_CONST_STRING *f_pdstrTag,
    IN const DRM_CONST_STRING *f_pdstrAttrName,
    IN const DRM_CONST_STRING *f_pdstrAttrValue)
{
    const DRM_WCHAR *pwszBase = f_ptagParsed->m_pwszBase;
    DRM_BOOL   fWork          = FALSE;
    _ETAGTYPE  eStatus        = eTagNone;
    DRM_CONST_STRING dstrTag  = EMPTY_DRM_STRING;

    dstrTag.cchString  =            f_ptagParsed->m_dasstrTag.m_cch;
    dstrTag.pwszString = pwszBase + f_ptagParsed->m_dasstrTag.m_ich;
    
    fWork = f_pdstrTag->cchString  == 0 
         || f_pdstrTag->pwszString == NULL;
         
    fWork = fWork || DRM_UTL_DSTRStringsEqual(f_pdstrTag, &dstrTag);

    if (fWork)
    {
        DRM_CONST_STRING dstrAttrParsed = EMPTY_DRM_STRING;
        
        dstrAttrParsed.cchString  =             f_ptagParsed->m_dasstrAttrName.m_cch;
        dstrAttrParsed.pwszString = pwszBase + f_ptagParsed->m_dasstrAttrName.m_ich;

        if (f_ptagParsed->m_eTagType == eCloseTag)
        {
            eStatus = eCloseTag;
        }

        /* attribute is ignored */
        else if (f_pdstrAttrName            == NULL 
             ||  f_pdstrAttrName->cchString == 0)
        {
            eStatus = f_ptagParsed->m_eTagType;
        }

        /* check the attribute and its value */
        else if (DRM_UTL_DSTRStringsEqual(&dstrAttrParsed, f_pdstrAttrName))
        {
            dstrAttrParsed.cchString  =             f_ptagParsed->m_dasstrAttrValue.m_cch;
            dstrAttrParsed.pwszString = pwszBase + f_ptagParsed->m_dasstrAttrValue.m_ich;

            if (DRM_UTL_DSTRStringsEqual(&dstrAttrParsed, f_pdstrAttrValue))
            {
                eStatus = f_ptagParsed->m_eTagType;
            }
        }
    }
    
    return eStatus;
} /* _CheckTagW */

static _ETAGTYPE _CheckTagA(
    IN const DRM_CHAR              *f_pszBase,
    IN       _XMLTAGA              *f_ptagParsed, 
    IN const DRM_CHAR              *f_pszBaseTag,
    IN const DRM_SUBSTRING         *f_pdasstrTag,
    IN const DRM_ANSI_CONST_STRING *f_pdastrAttrName,
    IN const DRM_ANSI_CONST_STRING *f_pdasstrAttrValue)
{
    DRM_BOOL  fWork    = FALSE;
    _ETAGTYPE eStatus  = eTagNone;
    
    fWork = f_pdasstrTag->m_cch == 0 
         || f_pszBaseTag        == NULL;
         
    fWork = (fWork 
          || DRM_UTL_DASSSTRStringsEqual(f_pszBase, 
                                        &f_ptagParsed->m_dasstrTag, 
                                         f_pszBaseTag,
                                         f_pdasstrTag));

    if (fWork)
    {
        if (f_ptagParsed->m_eTagType == eCloseTag)
        {
            eStatus = eCloseTag;
        }

        /* attribute is ignored */
        else if (f_pdastrAttrName            == NULL 
             ||  f_pdastrAttrName->cchString == 0)
        {
            eStatus = f_ptagParsed->m_eTagType;
        }

        /* check the attribute and its value */
        
        else if (DRM_UTL_DASSTRStringsEqual(f_pszBase, &f_ptagParsed->m_dasstrAttrName,   f_pdastrAttrName)
              && DRM_UTL_DASSTRStringsEqual(f_pszBase, &f_ptagParsed->m_dasstrAttrValue,  f_pdasstrAttrValue))
        {
                eStatus = f_ptagParsed->m_eTagType;
        }
    }
    
    return eStatus;
}

/**********************************************************************
** Function:    _GetXMLSubNodeW
** Synopsis:    
** Arguments:   
** Returns:     DRM_SUCCESS on success
** Notes:       
***********************************************************************
*/
static DRM_RESULT _GetXMLSubNodeW(
    IN const DRM_CONST_STRING *f_pdstrXML,
    IN const DRM_CONST_STRING *f_pdstrTag,
    IN const DRM_CONST_STRING *f_pdstrAttrName,
    IN const DRM_CONST_STRING *f_pdstrAttrValue,
    IN const DRM_DWORD         f_iNode,             /* nth occurence of the node in the xml stirng, start from 0 */
    OUT      DRM_CONST_STRING *f_pdstrNodeOut,      /* these two parameters can either be NULL but not both */
    OUT      DRM_CONST_STRING *f_pdstrNodeDataOut,
    IN const DRM_DWORD         f_cLayersToSkip,
    IN       DRM_BOOL          f_fSkipValidation)
{
    DRM_RESULT dr      = DRM_E_NOXMLOPENTAG;
    DRM_DWORD  cNodes  = 0;
    DRM_DWORD  ichCurr = 0;
    DRM_DWORD  cTagsUnmatched   = 0;   
    DRM_DWORD  cTagsMatched     = 0; 
    DRM_DWORD  ichNodeBegin     = 0; 
    DRM_DWORD  ichNodeEnd       = 0; 
    DRM_DWORD  ichDataBegin     = 0; 
    DRM_DWORD  ichDataEnd       = 0;
    _ETAGTYPE  eStatus          = eTagNone;
    _XMLTAGW   xmltagW          = { 0 };    

    ChkDRMString(f_pdstrXML);
    ChkDRMString(f_pdstrTag);
    
    ChkArg(f_pdstrNodeOut     != NULL
        || f_pdstrNodeDataOut != NULL);

    while (TRUE)
    {
        /* scan for tag at currIndex */
        if (! _ScanTagW(f_pdstrXML, &ichCurr, &xmltagW))
        {
            if (cTagsMatched == 0)
            {
                ChkDR(DRM_E_NOXMLOPENTAG);
            }
            else
            {
                ChkDR(DRM_E_NOXMLCLOSETAG);
            }
        }

        /* check if we have a match to the node tag */
        if (cTagsUnmatched != f_cLayersToSkip)
        {
            if (xmltagW.m_eTagType == eOpenTag)
            {
                cTagsUnmatched++;
            }
            else if (xmltagW.m_eTagType == eCloseTag)
            {
                if (cTagsUnmatched > 0)
                {
                    cTagsUnmatched--;
                }
                else
                {
                    ChkDR(DRM_E_NOXMLOPENTAG);
                }
            }

            continue;
        }

        eStatus = _CheckTagW(&xmltagW, 
                              f_pdstrTag, 
                              f_pdstrAttrName, 
                              f_pdstrAttrValue);

        switch (eStatus)
        {
        case eOpenTag:     /* open tag found */
            /* this is the first matched open tag found. if cTagsMatched>0, it
            ** means the matched opend tag is nested.
            */
            if (cTagsMatched == 0) 
            {
                ichNodeBegin = xmltagW.m_ichTagBegin;
                ichDataBegin = xmltagW.m_ichTagEnd + 1;
                if (f_fSkipValidation && (cNodes == f_iNode) )
                {
                    ichNodeEnd = f_pdstrXML->cchString - 1;
                    ichDataEnd = f_pdstrXML->cchString - 1;
                    goto PREPARENODE;
                }                
            }
            cTagsMatched++;
            break;

        case eCloseTag:    /* close tag found */
            if (cTagsMatched == 0)
            {
                /* we get </tag> before we get <tag> */
                ChkDR(DRM_E_NOXMLOPENTAG);
            }
            cTagsMatched--;
            
            if (cTagsMatched == 0)  /* make sure it is not nested */
            {
                /* we've got the matching <tag> and </tag>, 
                ** exit the loop if this is the node we want
                */
                if (cNodes == f_iNode)
                {
                    ichNodeEnd = xmltagW.m_ichTagEnd;
                    ichDataEnd = xmltagW.m_ichTagBegin - 1;
                    goto PREPARENODE;
                }
                ichNodeBegin = 0;
                ichDataBegin = 0;
                cNodes++;
            }
            break;

        case eEmptyTag:
            if (cTagsMatched == 0)
            {
                /* we've got the matching <tag/>, 
                ** exit the loop if this is the node we want
                */
                if (cNodes == f_iNode)
                {
                    ichNodeBegin = xmltagW.m_ichTagBegin;
                    ichNodeEnd   = xmltagW.m_ichTagEnd;
                    ichDataBegin = 0;
                    ichDataEnd   = 0;
                    goto PREPARENODE;
                }
                ichNodeBegin = 0;
                cNodes++;
            }
            break;

        default:
            if (xmltagW.m_eTagType == eOpenTag)
            {
                cTagsUnmatched++;
            }
            else if (xmltagW.m_eTagType == eCloseTag)
            {
                if (cTagsUnmatched > f_cLayersToSkip)
                {
                    --cTagsUnmatched;
                }
                else
                {
                    /* end of 'f_cLayersToSkip' layers exits, the target tag is not found */
                    ChkDR(DRM_E_NOXMLOPENTAG);   
                }
            }
            break;
        }
    }

PREPARENODE:
    /* prepare f_pdstrNodeOut & f_pdstrNodeDataOut */
    if (f_pdstrNodeOut != NULL)
    {
        f_pdstrNodeOut->pwszString = f_pdstrXML->pwszString + ichNodeBegin;
        f_pdstrNodeOut->cchString  = (DRM_DWORD) (ichNodeEnd - ichNodeBegin + 1);
    }

    if (f_pdstrNodeDataOut != NULL)
    {
        if (eStatus == eEmptyTag)
        {
            f_pdstrNodeDataOut->pwszString = NULL;
            f_pdstrNodeDataOut->cchString  = 0;
        }
        else
        {
            f_pdstrNodeDataOut->pwszString = f_pdstrXML->pwszString + ichDataBegin;
            f_pdstrNodeDataOut->cchString = (DRM_DWORD) (ichDataEnd - ichDataBegin + 1);

            /* trim space in node data, if any */
            _AllTrimW(f_pdstrNodeDataOut);
        }
    }
    dr = DRM_SUCCESS;

ErrorExit:

    return dr;
} /* _GetXMLSubNodeW */

/**********************************************************************
** Function:    _GetXMLNodeCData
** Synopsis:    
** Arguments:   [pdwStamp] -- 
** Returns:     DRM_SUCCESS on success
** Notes:       
***********************************************************************
*/

static DRM_RESULT _GetXMLNodeCDataW(
    IN const DRM_CONST_STRING *f_pdstrNode,     /* the topmost node in this XML string will be worked on */
    OUT      DRM_CONST_STRING *f_pdstrCData)         /* returned CData */
{
    DRM_RESULT dr = DRM_E_NOXMLCDATA;
    DRM_BOOL      fOpenTagFound=FALSE;
    DRM_DWORD     ichCurr = 0;
    _XMLTAGW      xmltagW = { 0 };

    ChkDRMString(f_pdstrNode);
    
    ChkArg(f_pdstrCData != NULL);
    
    while (TRUE)
    {
        /* scan for tag at currIndex */
        if (!_ScanTagW(f_pdstrNode, &ichCurr, &xmltagW))  /* _ScanTagA failed */
        {
            if (! fOpenTagFound)
            {
                ChkDR(DRM_E_NOXMLOPENTAG);      /* open tag not found yet */
            }
            else
            {
                ChkDR(DRM_E_NOXMLCDATA);        /* could not find CDATA */
            }
        }

        switch (xmltagW.m_eTagType)
        {
            case eTagNone:
            case eCloseTag:
            case eEmptyTag:
                ChkDR(DRM_E_NOXMLCDATA);	/* could not find CDATA */

            case eOpenTag:
                if (fOpenTagFound)
                {
                    ChkDR(DRM_E_INVALIDXMLTAG); /* an open tag found already */
                }

                fOpenTagFound = TRUE;
                break;

            case eCDataSection:
                if (!fOpenTagFound)
                {
                    ChkDR(DRM_E_NOXMLOPENTAG); /* an open tag not found yet */
                }

                f_pdstrCData->cchString  =                           xmltagW.m_dasstrCData.m_cch;
                f_pdstrCData->pwszString = f_pdstrNode->pwszString + xmltagW.m_dasstrCData.m_ich;
                
                dr = (_AllTrimW(f_pdstrCData) 
                      ? DRM_SUCCESS
                      : DRM_E_NOXMLCDATA);

                goto ErrorExit;
        }
    }

ErrorExit:
    return dr;
} /* _GetXMLNodeCDataW */

/**********************************************************************
** Function:    _EnumXMLSubNode
** Synopsis:    Enum the next "sub" node from the given xml string. 
**              Caller can specify how many layer to skip. 
** Arguments:   
** Returns:     DRM_SUCCESS on success
** Notes:       
***********************************************************************
*/
static DRM_RESULT _EnumXMLSubNodeW(
    IN const DRM_CONST_STRING *f_pdstrXML,
       OUT   DRM_CONST_STRING *f_pdstrTag,
       OUT   DRM_CONST_STRING *f_pdstrNode,         /* these two parameters can either be NULL but not both */
       OUT   DRM_CONST_STRING *f_pdstrNodeData,
       OUT   DRM_CONST_STRING *f_pdstr1stAttrName,  /* optional */
       OUT   DRM_CONST_STRING *f_pdstr1stAttrValue, /* optional */
       OUT   DRM_BOOL         *f_pfIsLeaf,          /* set to TRUE of the returned node is a leaf node */
    IN const DRM_DWORD         f_cLayers)
{
    DRM_RESULT dr = DRM_E_NOXMLOPENTAG;
    DRM_DWORD cTagsMatched   = 0; 
    DRM_DWORD     ichNodeBegin   = 0, 
                  ichDataBegin   = 0, 
                  ichNodeEnd     = 0, 
                  ichDataEnd     = 0, 
                  ichCurr        = 0;
    DRM_DWORD     cTagsUnmatched = 0;   /* increment until we find the matching tag */
    _ETAGTYPE     eStatus        = eTagNone;
    _XMLTAGW      xmltag         = { 0 };    
    DRM_CONST_STRING dstr1stAttrName  = EMPTY_DRM_STRING;
    DRM_CONST_STRING dstr1stAttrValue = EMPTY_DRM_STRING;

    ChkDRMString(f_pdstrXML);
    ChkArg(f_pdstrTag != NULL);
    
    ChkArg(f_pfIsLeaf      != NULL);
    ChkArg(f_pdstrNode     != NULL
        || f_pdstrNodeData != NULL);

    *f_pfIsLeaf = TRUE;
    
    xmltag.m_pwszBase = f_pdstrXML->pwszString;
    
    while (TRUE)
    {
        /* scan for tag at currIndex */
        if (!_ScanTagW(f_pdstrXML, &ichCurr, &xmltag))
        {
            if (cTagsMatched == 0)
            {
                ChkDR(DRM_E_NOXMLOPENTAG);
            }
            else
            {
                ChkDR(DRM_E_NOXMLCLOSETAG);
            }
        }

        /* check if we are at the desired layer */
        if (cTagsUnmatched != f_cLayers)
        {
            if (xmltag.m_eTagType == eOpenTag)
            {
                cTagsUnmatched++;
            }
            else if (xmltag.m_eTagType == eCloseTag)
            {
                if (cTagsUnmatched>0)
                {
                    cTagsUnmatched--;
                }
                else
                {
                    ChkDR(DRM_E_NOXMLOPENTAG);
                }
            }

            continue;
        }

        /* we are at the layer that caller specified */
        eStatus = _CheckTagW(&xmltag, f_pdstrTag, NULL, NULL);

        switch (eStatus)
        {
            case eOpenTag:     /* open tag found */
                /* this is the first matched open tag found. if cTagsMatched>0, it
                ** means the matched opend tag is nested.
                */
                if (cTagsMatched == 0) 
                {
                    ichNodeBegin = xmltag.m_ichTagBegin;
                    ichDataBegin = xmltag.m_ichTagEnd+1;

                    /* assign the tag name so that we know what to look for closing tag */
                    f_pdstrTag->cchString  =                          xmltag.m_dasstrTag.m_cch;
                    f_pdstrTag->pwszString = f_pdstrXML->pwszString + xmltag.m_dasstrTag.m_ich;

                    /* save the attr name/value */
                    dstr1stAttrName.cchString  = xmltag.m_dasstrAttrName.m_cch;
                    dstr1stAttrName.pwszString = f_pdstrXML->pwszString 
                                               + xmltag.m_dasstrAttrName.m_ich;
                    dstr1stAttrValue.cchString  = xmltag.m_dasstrAttrValue.m_cch;
                    dstr1stAttrValue.pwszString = f_pdstrXML->pwszString 
                                                + xmltag.m_dasstrAttrValue.m_ich;
                }
                else if (*f_pfIsLeaf )  /* only do it once */
                {
                    *f_pfIsLeaf = FALSE;
                }
                cTagsMatched++;
                break;
                
            case eCloseTag:    /* close tag found */
                if (cTagsMatched == 0)
                {
                    /* we get </tag> before we get <tag> */
                    ChkDR(DRM_E_NOXMLOPENTAG);
                }
                cTagsMatched--;
                if (cTagsMatched == 0)  /* make sure it is not nested */
                {
                    /* we've got the matching <tag> and </tag>, 
                    ** exit the loop if this is the node we want
                    */
                    ichNodeEnd = xmltag.m_ichTagEnd;
                    ichDataEnd = xmltag.m_ichTagBegin - 1;
                    goto PREPARENODE;
                }
                break;

            case eEmptyTag:
                if (cTagsMatched == 0)
                {
                    /* we've got the matching <tag/>, 
                    ** exit the loop, this is the node we want
                    */
                    ichNodeBegin = xmltag.m_ichTagBegin;
                    ichDataBegin = 0;
                    ichNodeEnd   = xmltag.m_ichTagEnd;
                    ichDataEnd   = 0;

                    /* assign the tag name so that we know what to look for closing tag */
                    f_pdstrTag->cchString  =                          xmltag.m_dasstrTag.m_cch;
                    f_pdstrTag->pwszString = f_pdstrXML->pwszString + xmltag.m_dasstrTag.m_ich;
                    goto PREPARENODE;
                }
                break;
            default:
                if (xmltag.m_eTagType == eOpenTag)
                {
                    cTagsUnmatched++;
                    *f_pfIsLeaf = FALSE;
                }
                else if (xmltag.m_eTagType == eCloseTag)
                {
                    if (cTagsUnmatched > f_cLayers)
                    {
                        cTagsUnmatched--;
                    }
                    else
                    {
                        /* end of 'f_cLayers' layers exits, the target tag is not found */
                        ChkDR(DRM_E_NOXMLOPENTAG);   
                    }
                }
                break;
        }
    }

PREPARENODE:
    /* prepare f_pdstrNode & f_pdstrNodeData */
    if (f_pdstrNode != NULL)
    {
        f_pdstrNode->pwszString = f_pdstrXML->pwszString  + ichNodeBegin;
        f_pdstrNode->cchString  = (DRM_DWORD) (ichNodeEnd - ichNodeBegin + 1);
    }

    if (f_pdstrNodeData != NULL)
    {
        if (eStatus == eEmptyTag)
        {
            f_pdstrNodeData->pwszString = NULL;
            f_pdstrNodeData->cchString  = 0;
        }
        else
        {
            f_pdstrNodeData->pwszString = f_pdstrXML->pwszString  + ichDataBegin;
            f_pdstrNodeData->cchString  = (DRM_DWORD) (ichDataEnd - ichDataBegin + 1);

            if (! _AllTrimW(f_pdstrNodeData))
            {
                f_pdstrNodeData->pwszString = NULL;
            }
        }
    }

    if (f_pdstr1stAttrName != NULL)
    {
        *f_pdstr1stAttrName = dstr1stAttrName;
    }

    if (f_pdstr1stAttrValue != NULL)
    {
        *f_pdstr1stAttrValue = dstr1stAttrValue;
    }
    
    dr = DRM_SUCCESS;

ErrorExit:

    return dr;
}

/*
***********************************************************************
** API methods
***********************************************************************
*/

DRM_RESULT DRM_API DRM_XML_GetNode(
    IN  const DRM_CONST_STRING *f_pdstrXML,
    IN  const DRM_CONST_STRING *f_pdstrTag,
    IN  const DRM_CONST_STRING *f_pdstrAttrName,
    IN  const DRM_CONST_STRING *f_pdstrAttrValue,
    IN  const DRM_DWORD         f_iNode,            /* nth occurence of the node in the xml stirng, start from 0 */
    OUT DRM_CONST_STRING       *f_pdstrNode,       /* either of these 2 parameter can be NULL but not both */
    OUT DRM_CONST_STRING       *f_pdstrNodeData)   /* data enclosed by the immediate <tag>...</tag> in the given XML string */
{
    return DRM_XML_GetSubNode(f_pdstrXML,
                              f_pdstrTag,
                              f_pdstrAttrName,
                              f_pdstrAttrValue,
                              f_iNode,
                              f_pdstrNode,
                              f_pdstrNodeData,
                              0);    
}

DRM_RESULT DRM_API DRM_XML_GetNodeA(
    IN  const DRM_CHAR              *f_pszBase,
    IN  const DRM_SUBSTRING         *f_pdasstrXML,
    IN  const DRM_ANSI_CONST_STRING *f_pdastrTag,
    IN  const DRM_ANSI_CONST_STRING *f_pdastrAttrName,
    IN  const DRM_ANSI_CONST_STRING *f_pdasstrAttrValue,
    IN  const DRM_DWORD              f_iNode,              /* nth occurence of the node in the xml stirng, start from 0 */
    OUT DRM_SUBSTRING               *f_pdasstrXmlNode,     /* either of these 2 parameter can be NULL but not both */
    OUT DRM_SUBSTRING               *f_pdasstrXmlNodeData) /* data enclosed by the immediate <tag>...</tag> in the given XML string */
{
    return DRM_XML_GetSubNodeA(f_pszBase,
                               f_pdasstrXML,
                               f_pdastrTag,
                               f_pdastrAttrName,
                               f_pdasstrAttrValue,
                               f_iNode,
                               f_pdasstrXmlNode, 
                               f_pdasstrXmlNodeData,
                               0);
}
    
/******************************************************************************
** 
** Function :   _GetSubNodeOptimized
** 
** Synopsis :   Get the beginning of a subnode (i.e. without parsing till the 
**              endtag
** 
** Arguments :  
** 
** Returns :    
** 
** Notes :      f_pdstrNode and f_pdstrNodeData in this case would contain the 
**              entire XML buffer starting from the required node or node-data
**              respectively
** 
******************************************************************************/
DRM_RESULT DRM_API _GetSubNodeOptimized(
    IN  const DRM_CONST_STRING *f_pdstrXML,
    IN  const DRM_CONST_STRING *f_pdstrTag,
    IN  const DRM_CONST_STRING *f_pdstrAttrName,
    IN  const DRM_CONST_STRING *f_pdstrAttrValue,
    IN  const DRM_DWORD         f_iNode,          /* nth occurence of the node in the xml stirng, start from 0 */
    OUT DRM_CONST_STRING       *f_pdstrNode,     /* these two parameters can either be NULL but not both */
    OUT DRM_CONST_STRING       *f_pdstrNodeData,
    IN  DRM_DWORD               f_iLayer)      /* outermost layer is 0 */
{
    DRM_RESULT dr = DRM_SUCCESS;

    ChkDRMString(f_pdstrXML);
    ChkDRMString(f_pdstrTag);
        
    ChkArg(f_pdstrNode     != NULL 
        || f_pdstrNodeData != NULL);

    ChkDR(_GetXMLSubNodeW( f_pdstrXML, 
                           f_pdstrTag, 
                           f_pdstrAttrName, 
                           f_pdstrAttrValue, 
                           f_iNode,
                           f_pdstrNode, 
                           f_pdstrNodeData, 
                           f_iLayer,
                           TRUE ) );

ErrorExit:

    return dr;
}

DRM_RESULT DRM_API DRM_XML_GetSubNode(
    IN  const DRM_CONST_STRING *f_pdstrXML,
    IN  const DRM_CONST_STRING *f_pdstrTag,
    IN  const DRM_CONST_STRING *f_pdstrAttrName,
    IN  const DRM_CONST_STRING *f_pdstrAttrValue,
    IN  const DRM_DWORD         f_iNode,          /* nth occurence of the node in the xml stirng, start from 0 */
    OUT DRM_CONST_STRING       *f_pdstrNode,     /* these two parameters can either be NULL but not both */
    OUT DRM_CONST_STRING       *f_pdstrNodeData,
    IN  DRM_DWORD               f_iLayer)      /* outermost layer is 0 */
{
    DRM_RESULT dr = DRM_SUCCESS;

    ChkDRMString(f_pdstrXML);
    ChkDRMString(f_pdstrTag);
        
    ChkArg(f_pdstrNode     != NULL 
        || f_pdstrNodeData != NULL);

    ChkDR(_GetXMLSubNodeW(f_pdstrXML, 
                          f_pdstrTag, 
                          f_pdstrAttrName, 
                          f_pdstrAttrValue, 
                          f_iNode,
                          f_pdstrNode, 
                          f_pdstrNodeData, 
                          f_iLayer,
                          FALSE));

ErrorExit:

    return _TranslateXMLError(dr);
}

DRM_RESULT DRM_API DRM_XML_GetSubNodeA(
    IN  const DRM_CHAR              *f_pszBase,
    IN  const DRM_SUBSTRING         *f_pdasstrXML,
    IN  const DRM_ANSI_CONST_STRING *f_pdastrTag,
    IN  const DRM_ANSI_CONST_STRING *f_pdastrAttrName,
    IN  const DRM_ANSI_CONST_STRING *f_pdasstrAttrValue,
    IN  const DRM_DWORD              f_iNode,           /* nth occurence of the node in the xml stirng, start from 0 */
    OUT DRM_SUBSTRING               *f_pdasstrNode,     /* these two parameters can either be NULL but not both */
    OUT DRM_SUBSTRING               *f_pdasstrNodeData,
    IN  DRM_DWORD                    f_iLayer)       /* outermost layer is 0 */
{
    DRM_RESULT dr = DRM_SUCCESS;
    DRM_DWORD     cTagsMatched   = 0;
    DRM_DWORD     cNodes         = 0;
    DRM_DWORD     cTagsUnmatched = 0;   /* increment until we find the matching tag */
    DRM_DWORD     ichNodeBegin   = 0; 
    DRM_DWORD     ichDataBegin   = 0; 
    DRM_DWORD     ichNodeEnd     = 0; 
    DRM_DWORD     ichDataEnd     = 0; 
    DRM_DWORD     ichCurr        = f_pdasstrXML->m_ich;
    DRM_SUBSTRING dasstrTag      = EMPTY_DRM_SUBSTRING;
    _ETAGTYPE     eStatus        = eTagNone;
    _XMLTAGA      xmltag         = { 0 };    

    ChkArg(f_pszBase != NULL);
    
    ChkArg(f_pdasstrXML != NULL
        && f_pdastrTag  != NULL);
        
    ChkArg(f_pdasstrNode     != NULL 
        || f_pdasstrNodeData != NULL);

    while (TRUE)
    {
        /* scan for tag at currIndex */
        if (! _ScanTagA(f_pszBase, f_pdasstrXML, &ichCurr, &xmltag))
        {
            if (cTagsMatched == 0)
            {
                ChkDR(DRM_E_NOXMLOPENTAG);
            }
            else
            {
                ChkDR(DRM_E_NOXMLCLOSETAG);
            }
        }

        /* check if we have a match to the node tag */
        if (cTagsUnmatched != f_iLayer)
        {
            if (xmltag.m_eTagType == eOpenTag)
            {
                cTagsUnmatched++;
            }
            else if (xmltag.m_eTagType == eCloseTag)
            {
                if (cTagsUnmatched > 0)
                {
                    cTagsUnmatched--;
                }
                else
                {
                    ChkDR(DRM_E_NOXMLOPENTAG);
                }
            }
            continue;
        }

        dasstrTag.m_cch = f_pdastrTag->cchString;

        eStatus = _CheckTagA(f_pszBase,
                            &xmltag, 
                             f_pdastrTag->pszString, 
                            &dasstrTag,
                             f_pdastrAttrName, 
                             f_pdasstrAttrValue);
        switch (eStatus)
        {
        case eOpenTag:     /* open tag found */
            /* this is the first matched open tag found. if cTagsMatched>0, it
            ** means the matched opend tag is nested.
            */
            if (cTagsMatched == 0) 
            {
                ichNodeBegin = xmltag.m_ichTagBegin;
                ichDataBegin = xmltag.m_ichTagEnd + 1;
            }
            cTagsMatched++;
            break;

        case eCloseTag:    /* close tag found */
            if (cTagsMatched == 0)
            {
                /* we get </tag> before we get <tag> */
                ChkDR(DRM_E_NOXMLOPENTAG);
            }
            
            cTagsMatched--;
            
            if (cTagsMatched == 0)  /* make sure it is not nested */
            {
                /* we've got the matching <tag> and </tag>, 
                ** exit the loop if this is the node we want
                */
                if (cNodes == f_iNode)
                {
                    ichNodeEnd = xmltag.m_ichTagEnd;
                    ichDataEnd = xmltag.m_ichTagBegin - 1;
                    goto PREPARENODE;
                }
                ichNodeBegin = 0;
                ichDataBegin = 0;
                cNodes++;
            }
            break;

        case eEmptyTag:
            if (cTagsMatched == 0)
            {
                /* we've got the matching <tag/>, 
                ** exit the loop if this is the node we want
                */
                if (cNodes == f_iNode)
                {
                    ichNodeBegin = xmltag.m_ichTagBegin;
                    ichNodeEnd   = xmltag.m_ichTagEnd;
                    ichDataBegin = 0;
                    ichDataEnd   = 0;
                    goto PREPARENODE;
                }
                ichNodeBegin = 0;
                cNodes++;
            }
            break;

        default:
            if (xmltag.m_eTagType == eOpenTag)
            {
                cTagsUnmatched++;
            }
            else if (xmltag.m_eTagType == eCloseTag)
            {
                if (cTagsUnmatched > f_iLayer)
                {
                    cTagsUnmatched--;
                }
                else
                {
                    /* end of 'f_cLayers' layers exits, the target tag is not found */
                    ChkDR(DRM_E_NOXMLOPENTAG);   
                }
            }
            break;
        }
    }

PREPARENODE:
    /* prepare f_pdstrNode & f_pdstrNodeData */
    if (f_pdasstrNode != NULL)
    {
        f_pdasstrNode->m_ich = ichNodeBegin;
        f_pdasstrNode->m_cch = (ichNodeEnd - ichNodeBegin + 1);
    }

    if (f_pdasstrNodeData != NULL)
    {
        if (eStatus == eEmptyTag)
        {
            f_pdasstrNodeData->m_cch = 0;
            f_pdasstrNodeData->m_ich = 0;
        }
        else
        {
            f_pdasstrNodeData->m_ich = ichDataBegin;
            f_pdasstrNodeData->m_cch = (ichDataEnd - ichDataBegin + 1);

            _AllTrimA(f_pszBase, f_pdasstrNodeData);
        }
    }
    dr = DRM_SUCCESS;

ErrorExit:

    return _TranslateXMLError(dr);
} /* DRM_XML_GetSubNodeA */

DRM_RESULT DRM_API DRM_XML_GetNodeAttribute(
    IN const DRM_CONST_STRING *f_pdstrNode,    /* the topmost node in this XML string will be worked on */
    IN const DRM_CONST_STRING *f_pdstrAttrName,   /* attrName to retrieve */
    OUT      DRM_CONST_STRING *f_pdstrAttrValue)	/* returned attrValue */
{
    DRM_RESULT dr = DRM_E_INVALIDXMLTAG;
    _XMLTAGW xmltag;

    ChkDRMString(f_pdstrNode);
    ChkDRMString(f_pdstrAttrName);
    ChkArg(f_pdstrAttrName->cchString  > 0);
    ChkArg(f_pdstrAttrValue != NULL);

    if (_ScanNodeForAttributeW(f_pdstrNode, f_pdstrAttrName, &xmltag))
    {
        f_pdstrAttrValue->pwszString = f_pdstrNode->pwszString + xmltag.m_dasstrAttrValue.m_ich;
        f_pdstrAttrValue->cchString  =                           xmltag.m_dasstrAttrValue.m_cch;

        dr = DRM_SUCCESS;
    }
    else
    {
        goto ErrorExit;
    }
    

ErrorExit:

    return _TranslateXMLError(dr);
}


/**********************************************************************
** Function:    DRM_XML_GetNodeAttributeA
** Synopsis:    
** Arguments:   [pdwStamp] -- 
** Returns:     DRM_SUCCESS on success
** Notes:       
***********************************************************************
*/

DRM_RESULT DRM_API DRM_XML_GetNodeAttributeA(
    IN const DRM_CHAR              *f_pszBase,
    IN const DRM_SUBSTRING         *f_pdasstrNode,
    IN const DRM_ANSI_CONST_STRING *f_pdastrAttrName,  /* attrName to retrieve */
       OUT   DRM_SUBSTRING         *f_pdasstrAttrValue) /* returned attrValue */
{
    DRM_RESULT dr = DRM_E_INVALIDXMLTAG;
    _XMLTAGA   xmltag = { 0 };

    ChkArg(f_pszBase          != NULL);
    ChkArg(f_pdasstrAttrValue != NULL);

    ChkArg(f_pdasstrNode       != NULL
        && f_pdasstrNode->m_cch > 0);
        
    ChkArg(f_pdastrAttrName            != NULL
        && f_pdastrAttrName->pszString != NULL
        && f_pdastrAttrName->cchString  > 0);
        
    if (_ScanNodeForAttributeA(f_pszBase, f_pdasstrNode, f_pdastrAttrName, &xmltag))
    {
        f_pdasstrAttrValue->m_cch = xmltag.m_dasstrAttrValue.m_cch;
        f_pdasstrAttrValue->m_ich = xmltag.m_dasstrAttrValue.m_ich;

        dr = DRM_SUCCESS;
    }
    
ErrorExit:

    return _TranslateXMLError(dr);
}


/**********************************************************************
** Function:    DRM_XML_GetNodeCData
** Synopsis:    
** Arguments:   [pdwStamp] -- 
** Returns:     DRM_SUCCESS on success
** Notes:       
***********************************************************************
*/
DRM_RESULT DRM_API DRM_XML_GetNodeCData(
    IN const DRM_CONST_STRING *f_pdstrNode,     /* the topmost node in this XML string will be worked on */
    OUT      DRM_CONST_STRING *f_pdstrCData)       /* returned CData */
{
    DRM_RESULT dr = DRM_SUCCESS;

    ChkDRMString(f_pdstrNode);
    ChkArg(f_pdstrCData != NULL);

    ChkDR(_GetXMLNodeCDataW(f_pdstrNode, f_pdstrCData));

ErrorExit:

    return _TranslateXMLError(dr);
}

DRM_RESULT DRM_API DRM_XML_GetAndDecryptNode(
    IN  const DRM_CONST_STRING *f_pdstrXML,
    IN  const DRM_CONST_STRING *f_pdstrTag,
    IN  const DRM_CONST_STRING *f_pdstrAttrName,
    IN  const DRM_CONST_STRING *f_pdstrAttrValue,
    IN  const DRM_DWORD         f_iNode,           /* nth occurence of the node in the xml stirng, start from 0 */
    IN  DRM_CRYPTO_CONTEXT     *f_pcontextCRYP,    /* Pointer to DRM_CRYPTO_CONTEXT */
    IN  PRIVKEY                *f_pprivkey,          /* priv key to use for decrypt */
    OUT DRM_CONST_STRING       *f_pdstrNode,      /* either of these 2 parameter can be NULL but not both */
    OUT DRM_CONST_STRING       *f_pdstrNodeData)  /* data enclosed by the immediate <tag>...</tag> in the given XML string */
{
    return DRM_XML_GetAndDecryptSubNode(f_pdstrXML,
                                        f_pdstrTag,
                                        f_pdstrAttrName,
                                        f_pdstrAttrValue,
                                        f_iNode,
                                        f_pcontextCRYP,
                                        f_pprivkey,
                                        f_pdstrNode,
                                        f_pdstrNodeData,
                                        0);
}


/*
** extract license node from given XML string matching <tag AttrName="Value"> ... </tag>
*/
DRM_RESULT DRM_API DRM_XML_GetAndVerifyNode(
    IN  const DRM_CONST_STRING *f_pdstrXML,
    IN  const DRM_CONST_STRING *f_pdstrTag,
    IN  const DRM_CONST_STRING *f_pdstrAttrName,
    IN  const DRM_CONST_STRING *f_pdstrAttrValue,
    IN  const DRM_DWORD         f_iNode,           /* nth occurence of the node in the xml stirng, start from 0 */
    IN  DRM_CRYPTO_CONTEXT     *f_pcontextCRYP,    /* Pointer to DRM_CRYPTO_CONTEXT */
    IN  const PUBKEY           *f_ppubkey,           /* pub key to use for verify */
    IN  DRM_BOOL                f_fIncludeTag,
    IN  DRM_CONST_STRING       *f_pdstrB64Signature,
    OUT DRM_CONST_STRING       *f_pdstrNode,      /* either of these 2 parameter can be NULL but not both */
    OUT DRM_CONST_STRING       *f_pdstrNodeData)  /* data enclosed by the immediate <tag>...</tag> in the given XML string */
{
    return DRM_XML_GetAndVerifySubNode(f_pdstrXML,
                                       f_pdstrTag,
                                       f_pdstrAttrName,
                                       f_pdstrAttrValue,
                                       f_iNode,
                                       f_pcontextCRYP,
                                       f_ppubkey,
                                       f_fIncludeTag,
                                       f_pdstrB64Signature,
                                       f_pdstrNode,
                                       f_pdstrNodeData,
                                       0);
}

/*
** extract license node from given XML string matching <tag AttrName="Value"> ... </tag>
*/
DRM_RESULT DRM_API DRM_XML_GetAndDecryptSubNode(
    IN  const DRM_CONST_STRING *f_pdstrXML,
    IN  const DRM_CONST_STRING *f_pdstrTag,
    IN  const DRM_CONST_STRING *f_pdstrAttrName,
    IN  const DRM_CONST_STRING *f_pdstrAttrValue,
    IN  const DRM_DWORD         f_iNode,        /* nth occurence of the node in the xml stirng, start from 0 */
    IN  DRM_CRYPTO_CONTEXT     *f_pcontextCRYP, /* Pointer to DRM_CRYPTO_CONTEXT */
    IN  PRIVKEY                *f_pprivkey,     /* priv key to use for decrypt */
    OUT DRM_CONST_STRING       *f_pdstrNode,    /* these two parameters can either be NULL but not both */
    OUT DRM_CONST_STRING       *f_pdstrNodeData,
    IN  DRM_DWORD               f_iLayer)       /* outermost layer is 0 */
{
    DRM_CONST_STRING dstrXMLNode     = EMPTY_DRM_STRING;
    DRM_CONST_STRING dstrXMLNodeData = EMPTY_DRM_STRING;
    DRM_RESULT       dr          = DRM_SUCCESS;
    DRM_DWORD        cbDecoded     = 0;
    DRM_DWORD        i           = 0;
    DRM_WCHAR       *_pwszString = NULL;

    ChkArg(f_pcontextCRYP  != NULL
        && f_pprivkey      != NULL);
         
    ChkArg(f_pdstrNode     != NULL 
        || f_pdstrNodeData != NULL);

    /* get the node first */

    ChkDR(DRM_XML_GetSubNode(f_pdstrXML, 
                             f_pdstrTag, 
                             f_pdstrAttrName, 
                             f_pdstrAttrValue, 
                             f_iNode, 
                            &dstrXMLNode,
                            &dstrXMLNodeData, 
                             f_iLayer));

    /* base64 decode in-place */

    ChkDR(DRM_B64_DecodeW(&dstrXMLNodeData, &cbDecoded, NULL, DRM_BASE64_DECODE_IN_PLACE));

    /* decrypt the node data in-place */
                                
    ChkDR (DRM_PK_DecryptLarge (f_pprivkey, 
                                PB_DSTR(&dstrXMLNodeData),
                                cbDecoded,
                                PB_DSTR(&dstrXMLNodeData),
                                f_pcontextCRYP));

    /* fill the "empty space" with blanks */
    cbDecoded = (cbDecoded - PK_ENC_CIPHERTEXT_LEN) / SIZEOF(DRM_WCHAR);

    _pwszString = (DRM_WCHAR*)(dstrXMLNodeData.pwszString);
    for (i = 0; i < (dstrXMLNodeData.cchString - cbDecoded); i++)
    {
        _pwszString [cbDecoded+i] = g_wchSpace;
    }

    dstrXMLNodeData.cchString = cbDecoded;

    /* prepare for return value */
    if (f_pdstrNode != NULL)
    {
        ASSIGN_DRM_STRING(*f_pdstrNode, dstrXMLNode);
    }

    if (f_pdstrNodeData != NULL)
    {
        ASSIGN_DRM_STRING(*f_pdstrNodeData, dstrXMLNodeData);
    }
    
ErrorExit:

    return _TranslateXMLError(dr);
}


/*
** extract license node from given XML string matching <tag AttrName="Value"> ... </tag>
*/
DRM_RESULT DRM_API DRM_XML_GetAndVerifySubNode(
    IN  const DRM_CONST_STRING *f_pdstrXML,
    IN  const DRM_CONST_STRING *f_pdstrTag,
    IN  const DRM_CONST_STRING *f_pdstrAttrName,
    IN  const DRM_CONST_STRING *f_pdstrAttrValue,
    IN  const DRM_DWORD         f_iNode,          /* nth occurence of the node in the xml stirng, start from 0 */
    IN  DRM_CRYPTO_CONTEXT     *f_pcontextCRYP,   /* Pointer to DRM_CRYPTO_CONTEXT */
    IN  const PUBKEY           *f_ppubkey,          /* pub key to use for verify */
    IN  DRM_BOOL                f_fIncludeTag,
    IN  DRM_CONST_STRING       *f_pdstrB64Signature,
    OUT DRM_CONST_STRING       *f_pdstrNode,     /* these two parameters can either be NULL but not both */
    OUT DRM_CONST_STRING       *f_pdstrNodeData,
    IN  DRM_DWORD               f_iLayer)      /* outermost layer is 0 */
{
    DRM_RESULT       dr = DRM_SUCCESS;
    DRM_CONST_STRING dstrXMLNode;
    DRM_CONST_STRING dstrXMLNodeData;
    DRM_DWORD        cbDecoded = PK_ENC_SIGNATURE_LEN;
    DRM_BYTE         rgbSign [__CB_DECL(PK_ENC_SIGNATURE_LEN)];
    
    ChkArg(f_pcontextCRYP      != NULL
        && f_ppubkey           != NULL
        && f_pdstrB64Signature != NULL);
        
    ChkArg(f_pdstrNode    != NULL 
       || f_pdstrNodeData != NULL);
        
    ChkArg(f_pdstrB64Signature->cchString == PK_ENC_SIGNATURE_B64LEN);

    /* get the node first */
    ChkDR(DRM_XML_GetSubNode(f_pdstrXML, 
                             f_pdstrTag, 
                             f_pdstrAttrName, 
                             f_pdstrAttrValue, 
                             f_iNode,
                            &dstrXMLNode,
                            &dstrXMLNodeData, 
                             f_iLayer));

    /* base64 decode the signature */
    ChkDR(DRM_B64_DecodeW(f_pdstrB64Signature, &cbDecoded, rgbSign, 0));

    /* verify the node with the signature */
    if (f_fIncludeTag)
    {
        if (! DRM_PK_Verify(f_pcontextCRYP->rgbCryptoContext, 
                            f_ppubkey, 
                            PB_DSTR(&dstrXMLNode), 
                            CB_DSTR(&dstrXMLNode),
                            rgbSign))
        {
            ChkDR (DRM_E_INVALID_SIGNATURE);
        }
    }
    else
    {
        if (! DRM_PK_Verify(f_pcontextCRYP->rgbCryptoContext, 
                            f_ppubkey, 
                            PB_DSTR(&dstrXMLNodeData), 
                            CB_DSTR(&dstrXMLNodeData),
                            rgbSign))
        {
            ChkDR (DRM_E_INVALID_SIGNATURE);
        }
    }

    /* prepare for return value */
    if (f_pdstrNode != NULL)
    {
        ASSIGN_DRM_STRING(*f_pdstrNode, dstrXMLNode);
    }
    if (f_pdstrNodeData != NULL)
    {
        ASSIGN_DRM_STRING(*f_pdstrNodeData, dstrXMLNodeData);
    }

ErrorExit:

    return _TranslateXMLError(dr);
}

DRM_RESULT DRM_API DRM_XML_GetAndVerifyKeyedHashNode(
    IN  const DRM_CONST_STRING *f_pdstrXML,
    IN  const DRM_CONST_STRING *f_pdstrTag,
    IN  const DRM_CONST_STRING *f_pdstrAttrName,
    IN  const DRM_CONST_STRING *f_pdstrAttrValue,
    IN  const DRM_DWORD         f_iNode,              /* nth occurence of the node in the xml stirng, start from 0 */
    IN  HMAC_CONTEXT           *f_pcontextHMAC,
    IN  DRM_BYTE               *f_pbKeyHash,          /* Hash key for HMAC */
    IN  DRM_DWORD               f_cbKeyHash,          /* byte count of HMAC */
    IN  DRM_BOOL                f_fIncludeTag,
    IN  DRM_CONST_STRING       *f_pdstrB64Signature,
    OUT DRM_CONST_STRING       *f_pdstrNode,       /* either of these 2 parameter can be NULL but not both */
    OUT DRM_CONST_STRING       *f_pdstrNodeData)   /* data enclosed by the immediate <tag>...</tag> in the given XML string */
{
    return DRM_XML_GetAndVerifyKeyedHashSubNode(f_pdstrXML,
                                                f_pdstrTag,
                                                f_pdstrAttrName,
                                                f_pdstrAttrValue,
                                                f_iNode,
                                                f_pcontextHMAC,
                                                f_pbKeyHash,
                                                f_cbKeyHash,
                                                f_fIncludeTag,
                                                f_pdstrB64Signature,
                                                f_pdstrNode,
                                                f_pdstrNodeData,
                                                0);
}

DRM_RESULT DRM_API DRM_XML_GetAndVerifyKeyedHashSubNode(
    IN  const DRM_CONST_STRING *f_pdstrXML,
    IN  const DRM_CONST_STRING *f_pdstrTag,
    IN  const DRM_CONST_STRING *f_pdstrAttrName,
    IN  const DRM_CONST_STRING *f_pdstrAttrValue,
    IN  const DRM_DWORD         f_iNode,          /* nth occurence of the node in the xml stirng, start from 0 */
    IN  HMAC_CONTEXT           *f_pcontextHMAC,     /* HMAC context */
    IN  DRM_BYTE               *f_pbKeyHash,        /* Hash key for HMAC */
    IN  DRM_DWORD               f_cbKeyHash,        /* byte count of HMAC */
    IN  DRM_BOOL                f_fIncludeTag,
    IN  DRM_CONST_STRING       *f_pdstrB64Signature,
    OUT DRM_CONST_STRING       *f_pdstrNode,     /* either of these 2 parameter can be NULL but not both */
    OUT DRM_CONST_STRING       *f_pdstrNodeData, /* data enclosed by the immediate <tag>...</tag> in the given XML string */
    IN  DRM_DWORD               f_iLayer)      /* outermost layer is 0 */
{
    DRM_RESULT       dr = DRM_SUCCESS;
    DRM_CONST_STRING dstrXMLNode;
    DRM_CONST_STRING dstrXMLNodeData;
    DRM_DWORD        cbDecoded = SHA_DIGEST_LEN;
    DRM_BYTE         rgbSign   [__CB_DECL(SHA_DIGEST_LEN)];
    DRM_BYTE         rgbVerify [__CB_DECL(SHA_DIGEST_LEN)];
    
    ChkArg(f_pcontextHMAC      != NULL
        && f_pbKeyHash         != NULL
        && f_cbKeyHash         != NULL
        && f_pdstrB64Signature != NULL);

    ChkArg(f_pdstrNode     != NULL 
        || f_pdstrNodeData != NULL);
        
    ChkArg(f_pdstrB64Signature->cchString == SHA_B64ENC_DIGEST_LEN);

    /* get the node first */
    ChkDR(DRM_XML_GetSubNode(f_pdstrXML,
                             f_pdstrTag,
                             f_pdstrAttrName,
                             f_pdstrAttrValue,
                             f_iNode,
                            &dstrXMLNode,
                            &dstrXMLNodeData, 
                             f_iLayer));

    /* base64 decode the signature */
    ChkDR(DRM_B64_DecodeW(f_pdstrB64Signature, &cbDecoded, rgbSign, 0));

    ChkDR(DRM_HMAC_Init(f_pcontextHMAC, f_pbKeyHash, f_cbKeyHash));

    if (f_fIncludeTag)
    {
        ChkDR(DRM_HMAC_Update(f_pcontextHMAC, 
                              PB_DSTR (&dstrXMLNode), 
                              CB_DSTR (&dstrXMLNode)));
    }
    else
    {
        ChkDR(DRM_HMAC_Update(f_pcontextHMAC,
                              PB_DSTR (&dstrXMLNodeData), 
                              CB_DSTR (&dstrXMLNodeData)));
    }

    ChkDR(DRM_HMAC_Finalize(f_pcontextHMAC, rgbVerify, SHA_DIGEST_LEN));

    if (MEMCMP(rgbVerify, rgbSign, SHA_DIGEST_LEN) != 0)
    {
        ChkDR(DRM_E_HASHMISMATCH);
    }

    /* prepare for return value */
    if (f_pdstrNode != NULL)
    {
        ASSIGN_DRM_STRING(*f_pdstrNode, dstrXMLNode);
    }

    if (f_pdstrNodeData != NULL)
    {
        ASSIGN_DRM_STRING(*f_pdstrNodeData, dstrXMLNodeData);
    }

ErrorExit:

    return _TranslateXMLError(dr);
}

DRM_RESULT DRM_API DRM_XML_GetSubNodeByPath(
    IN const DRM_CONST_STRING* f_pdstrXML, 
    IN const DRM_CONST_STRING* f_pdstrXMLNode, 
    IN const DRM_CONST_STRING* f_pdstrAttrName, 
    IN const DRM_CONST_STRING* f_pdstrAttrValue,
    OUT      DRM_CONST_STRING* f_pdstrXMLNodeOut, 
    OUT      DRM_CONST_STRING* f_pdstrXMLDataOut,
    IN       DRM_WCHAR chSeparator)
{
    DRM_RESULT dr = DRM_E_LOGICERR;
    const DRM_WCHAR* pch = NULL;
    DRM_DWORD        cch = 0;
    DRM_CONST_STRING dstrXml      = EMPTY_DRM_STRING;
    DRM_CONST_STRING dstrNode     = EMPTY_DRM_STRING;
    DRM_CONST_STRING dstrNodeName = EMPTY_DRM_STRING;

    ChkDRMString(f_pdstrXML);
    ChkDRMString(f_pdstrXMLNode);

    pch = f_pdstrXMLNode->pwszString;

    ASSIGN_DRM_STRING(dstrXml, *f_pdstrXML);    
    while(cch < f_pdstrXMLNode->cchString)
    {
        pch = f_pdstrXMLNode->pwszString + cch;
        while(f_pdstrXMLNode->pwszString [cch] != chSeparator 
           &&  cch  < f_pdstrXMLNode->cchString)
        {
            cch++;            
        }

        dstrNodeName.pwszString = pch;
        dstrNodeName.cchString  = cch - ((DRM_DWORD)(pch - f_pdstrXMLNode->pwszString));

        if (*(f_pdstrXMLNode->pwszString + cch) != chSeparator)
        {   /* We are on the last segment.             */
            if (DRM_UTL_DSTRStringsEqual(&g_dstrTagCDATA, &dstrNodeName))
            {   /* Requesting the CDATA section.  Ask for that. */
                ChkDR(DRM_XML_GetNodeCData(&dstrXml, f_pdstrXMLDataOut));
            }
            else
            {   /* Just get the node and use the attributes parameters */
                ChkDR(DRM_XML_GetSubNode(&dstrXml, &dstrNodeName, f_pdstrAttrName, f_pdstrAttrValue, 0, &dstrNode, f_pdstrXMLDataOut, 1));
            }
        }
        else
        {   
#if DBG
            ChkDR(DRM_XML_GetSubNode(&dstrXml, &dstrNodeName, NULL, NULL, 0, &dstrNode, f_pdstrXMLDataOut , 1));            
#else
            /*
            **  Use optimized version - no need to check end tag
            */
            ChkDR(_GetSubNodeOptimized(&dstrXml, &dstrNodeName, NULL, NULL, 0, &dstrNode, f_pdstrXMLDataOut , 1));
#endif            
        }

        ASSIGN_DRM_STRING(dstrXml, dstrNode);
        cch++;    
    }

    if(f_pdstrXMLNodeOut != NULL)
    {
        ASSIGN_DRM_STRING(*f_pdstrXMLNodeOut, dstrXml);
    }

ErrorExit:
    DRMASSERT(dr != DRM_E_LOGICERR);

    return _TranslateXMLError(dr);
} /* DRM_XML_GetSubNodeByPath */

/**********************************************************************
** Function:    DRM_XML_EnumNextNode
** Synopsis:    Enumerate the next node from the given xml string
** Arguments:   [f_pdstrXML] -- 
** Returns:     DRM_SUCCESS on success
** Notes:       
***********************************************************************
*/
DRM_RESULT DRM_API DRM_XML_EnumNextNode(
    IN const DRM_CONST_STRING *f_pdstrXML,
       OUT   DRM_CONST_STRING *f_pdstrTag,
       OUT   DRM_CONST_STRING *f_pdstrNode,         /* either of these 2 parameter can be NULL but not both */
       OUT   DRM_CONST_STRING *f_pdstrNodeData,     /* data enclosed by the immediate <tag>...</tag> in the given XML string */
       OUT   DRM_CONST_STRING *f_pdstr1stAttrName,  /* optional */
       OUT   DRM_CONST_STRING *f_pdstr1stAttrValue, /* optional */
       OUT   DRM_BOOL         *f_pfIsLeaf)          /* set to TRUE of the returned node is a leaf node */
{
    return DRM_XML_EnumNextSubNode(
                f_pdstrXML,
                f_pdstrTag,
                f_pdstrNode,
                f_pdstrNodeData,
                f_pdstr1stAttrName,
                f_pdstr1stAttrValue,
                f_pfIsLeaf,
                0);    
} /* DRM_XML_EnumNextNode */

/**********************************************************************
** Function:    DRM_XML_EnumNextSubNode
** Synopsis:    Enumerate the next subnode from the given xml string,
**              optionaly, from nth layer
** Arguments:   [f_pdstrXML] -- 
** Returns:     DRM_SUCCESS on success
** Notes:       
***********************************************************************
*/
DRM_RESULT DRM_API DRM_XML_EnumNextSubNode(
    IN const DRM_CONST_STRING *f_pdstrXML,
       OUT   DRM_CONST_STRING *f_pdstrTag,
       OUT   DRM_CONST_STRING *f_pdstrNode,         /* these two parameters can either be NULL but not both */
       OUT   DRM_CONST_STRING *f_pdstrNodeData,
       OUT   DRM_CONST_STRING *f_pdstr1stAttrName,  /* optional */
       OUT   DRM_CONST_STRING *f_pdstr1stAttrValue, /* optional */
       OUT   DRM_BOOL         *f_pfIsLeaf,          /* set to TRUE of the returned node is a leaf node */
    IN       DRM_DWORD         f_nAtNthLayer)       /* outermost layer is 0 */
{
    DRM_RESULT dr = DRM_SUCCESS;

    ChkDRMString(f_pdstrXML);
    ChkArg(f_pdstrTag != NULL);
        
    ChkArg(f_pdstrNode     != NULL 
        || f_pdstrNodeData != NULL);

    ChkDR(_EnumXMLSubNodeW(f_pdstrXML, 
                           f_pdstrTag, 
                           f_pdstrNode, 
                           f_pdstrNodeData, 
                           f_pdstr1stAttrName,
                           f_pdstr1stAttrValue,
                           f_pfIsLeaf, 
                           f_nAtNthLayer));

ErrorExit:

    return _TranslateXMLError(dr);
} /* DRM_XML_EnumNextSubNode */


/* Ensure that the XML is valid */
DRM_BOOL DRM_API DRM_XML_Validate( IN const DRM_CONST_STRING *f_pdstrXML )
{
    DRM_RESULT       dr      = DRM_SUCCESS;
    DRM_DWORD        ich     = 0;
    _XMLTAGW         xmltag  = { 0 };
    DRM_CONST_STRING dstrTag = EMPTY_DRM_STRING;
    DRM_CONST_STRING dstrNode= EMPTY_DRM_STRING;
    DRM_CONST_STRING dstrData= EMPTY_DRM_STRING;

    /* Note that all error codes are meaningless in the end.  Only a 
       true of false is returned */
    DRMASSERT( f_pdstrXML             != NULL 
            && f_pdstrXML->pwszString != NULL
            && f_pdstrXML->cchString   > 0 );
    
    ChkDRMString( f_pdstrXML );

    /* ScanTag will skip all the xml preamble stuff and give me the
       first real XML tag */
    if( !_ScanTagW( f_pdstrXML, &ich, &xmltag )
     ||  xmltag.m_eTagType != eOpenTag )
    {
        ChkDR( DRM_E_INVALIDXMLTAG );
    }

    /* The first tag has been found. */
    dstrTag.cchString  = xmltag.m_dasstrTag.m_cch;
    dstrTag.pwszString = xmltag.m_pwszBase 
                       + xmltag.m_dasstrTag.m_ich;

    /* Now do an XML GetNode to parse to the end of 
       this tag and ensure it's valid. */
    
    ChkDR( _GetXMLSubNodeW( f_pdstrXML,
                           &dstrTag,
                            NULL,
                            NULL,
                            0,
                           &dstrNode,
                           &dstrData,
                            0,
                            FALSE ) );

ErrorExit:
    return DRM_SUCCEEDED( dr );
}

/**********************************************************************
** Function:    DRM_XML_EnumNextNode
** Synopsis:    Enumerate the next node from the given xml string
** Arguments:   [f_pdstrXML] -- 
** Returns:     DRM_SUCCESS on success
** Notes:       
***********************************************************************
*/

DRM_RESULT DRM_API DRM_XML_EnumNextNodeA(
    IN const DRM_CHAR         *f_pszBase,
    IN const DRM_SUBSTRING    *f_pdasstrXML,
    IN const DRM_DWORD         f_iNode,
       OUT   DRM_SUBSTRING    *f_pdasstrTag,
       OUT   DRM_SUBSTRING    *f_pdasstrNode,         /* either of these 2 parameter can be NULL but not both */
       OUT   DRM_SUBSTRING    *f_pdasstrNodeData,     /* data enclosed by the immediate <tag>...</tag> in the given XML string */
       OUT   DRM_SUBSTRING    *f_pdasstr1stAttrName,  /* optional */
       OUT   DRM_SUBSTRING    *f_pdasstr1stAttrValue)            /* set to TRUE of the returned node is a leaf node */
{
    DRM_RESULT    dr = DRM_E_NOXMLOPENTAG;
    DRM_DWORD     cRecursionDepth= 0;
    DRM_DWORD     cTagsMatched   = 0; 
    DRM_DWORD     ichNodeBegin   = 0; 
    DRM_DWORD     ichDataBegin   = 0; 
    DRM_DWORD     ichNodeEnd     = 0; 
    DRM_DWORD     ichDataEnd     = 0; 
    DRM_DWORD     iNode          = 0;
    DRM_DWORD     ichCurr        = f_pdasstrXML->m_ich;
    _XMLTAGA      xmltag         = { 0 };    
    DRM_SUBSTRING dasstr1stAttrName  = { 0 };
    DRM_SUBSTRING dasstr1stAttrValue = { 0 };

    ChkArg(f_pszBase    != NULL
        && f_pdasstrXML != NULL
        && f_pdasstrTag != NULL);
        
    ChkArg(f_pdasstrNode     != NULL 
        || f_pdasstrNodeData != NULL);

    for( ; ; )
    {
        /* scan for tag at currIndex */
        if (! _ScanTagA(f_pszBase, f_pdasstrXML, &ichCurr, &xmltag))
        {
            if (cTagsMatched == 0)
            {
                ChkDR(DRM_E_NOXMLOPENTAG);
            }
            else
            {
                ChkDR(DRM_E_NOXMLCLOSETAG);
            }
        }

        switch (xmltag.m_eTagType)
        {
            case eOpenTag:
                if (cRecursionDepth == 0
                &&  ++iNode         == (f_iNode + 1)) 
                {
                    ichNodeBegin = xmltag.m_ichTagBegin;
                    ichDataBegin = xmltag.m_ichTagEnd + 1;

                    /* assign the tag name so that we know what to look for closing tag */

                    f_pdasstrTag->m_cch = xmltag.m_dasstrTag.m_cch;
                    f_pdasstrTag->m_ich = xmltag.m_dasstrTag.m_ich;

                    /* save the attr name/value */
                    dasstr1stAttrName.m_cch  = xmltag.m_dasstrAttrName.m_cch;
                    dasstr1stAttrName.m_ich  = xmltag.m_dasstrAttrName.m_ich;
                    dasstr1stAttrValue.m_cch = xmltag.m_dasstrAttrValue.m_cch;
                    dasstr1stAttrValue.m_ich = xmltag.m_dasstrAttrValue.m_ich;
                }
                else
                {
                    cRecursionDepth++;
                }
                cTagsMatched++;
                break;
                
            case eCloseTag:    /* close tag found */
                if (cTagsMatched == 0)
                {
                    /* we get </tag> before we get <tag> */
                    ChkDR(DRM_E_NOXMLOPENTAG);
                }

                cTagsMatched--;

                if (cTagsMatched    == 0
                &&  cRecursionDepth == 0)
                {
                    /* we've got the matching <tag> and </tag>, 
                    ** exit the loop if this is the node we want
                    */
                    ichNodeEnd   = xmltag.m_ichTagEnd;
                    ichDataEnd   = xmltag.m_ichTagBegin - 1;
                    goto PREPARENODE;
                }
                else
                {
                    cRecursionDepth--;
                }
                break;

            default:
                break;
        }
    }

PREPARENODE:
    /* prepare f_pdasstrNode & f_pdasstrNodeData */
    if (f_pdasstrNode != NULL)
    {
        f_pdasstrNode->m_ich = ichNodeBegin;
        f_pdasstrNode->m_cch = (DRM_DWORD) (ichNodeEnd - ichNodeBegin + 1);
    }

    if (f_pdasstrNodeData != NULL)
    {
        if (xmltag.m_eTagType == eEmptyTag)
        {
            f_pdasstrNodeData->m_ich = 0;
            f_pdasstrNodeData->m_cch = 0;
        }
        else
        {
            f_pdasstrNodeData->m_ich = ichDataBegin;
            f_pdasstrNodeData->m_cch = (DRM_DWORD) (ichDataEnd - ichDataBegin + 1);

            if (! _AllTrimA(f_pszBase, f_pdasstrNodeData))
            {
                f_pdasstrNodeData->m_cch = 0;
            }
        }
    }

    if (f_pdasstr1stAttrName != NULL)
    {
        *f_pdasstr1stAttrName = dasstr1stAttrName;
    }

    if (f_pdasstr1stAttrValue != NULL)
    {
        *f_pdasstr1stAttrValue = dasstr1stAttrValue;
    }
    
    dr = DRM_SUCCESS;

ErrorExit:

    return _TranslateXMLError(dr);
} /* DRM_XML_EnumNextNode */

DRM_RESULT DRM_API DRM_XML_CountMatchingNodesA(
    IN const DRM_CHAR              *f_pszBase,
    IN const DRM_SUBSTRING         *f_pdasstrXML,
    IN const DRM_ANSI_CONST_STRING *f_pdastrTag,          /* optional */
    IN       DRM_ANSI_CONST_STRING *f_pdastr1stAttrName,  /* optional */
    IN       DRM_ANSI_CONST_STRING *f_pdastr1stAttrValue, /* optional */
       OUT   DRM_DWORD             *f_cMatchingNodes)
{
    DRM_RESULT    dr    = DRM_SUCCESS;
    DRM_DWORD     iNode = 0;
    DRM_SUBSTRING dasstrTag          = EMPTY_DRM_SUBSTRING;
    DRM_SUBSTRING dasstrDummy        = EMPTY_DRM_SUBSTRING;
    DRM_SUBSTRING dasstr1stAttrLabel = EMPTY_DRM_SUBSTRING;
    DRM_SUBSTRING dasstr1stAttrValue = EMPTY_DRM_SUBSTRING;

    ChkArg(f_pszBase        != NULL
        && f_pdasstrXML     != NULL
        && f_cMatchingNodes != NULL);

    *f_cMatchingNodes = 0;

    for( ; ; )
    {
        dr = DRM_XML_EnumNextNodeA(f_pszBase,
                                   f_pdasstrXML,
                                   iNode++,
                                  &dasstrTag,
                                  &dasstrDummy,
                                   NULL,
                                  &dasstr1stAttrLabel,
                                  &dasstr1stAttrValue);

        if (dr == DRM_E_XMLNOTFOUND)                                                   
        {
            dr = DRM_SUCCESS;
            break;
        }
        else
        {
            ChkDR(dr);
            
            if ((f_pdastrTag          == NULL || DRM_UTL_DASSTRStringsEqual(f_pszBase, &dasstrTag,          f_pdastrTag))
            &&  (f_pdastr1stAttrName  == NULL || DRM_UTL_DASSTRStringsEqual(f_pszBase, &dasstr1stAttrLabel, f_pdastr1stAttrName))
            &&  (f_pdastr1stAttrValue == NULL || DRM_UTL_DASSTRStringsEqual(f_pszBase, &dasstr1stAttrValue, f_pdastr1stAttrValue)))
            {
                (*f_cMatchingNodes)++;
            }              
        }
    }
    
ErrorExit:

    return dr;
}
