//+--------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1999. // // File: ncxml.cpp // // Contents: helper functions for doing remarkably simple things // with the XML DOM. // //---------------------------------------------------------------------------- #include "pch.h" #pragma hdrstop #include // for SysFreeString() #include "ncbase.h" #include "ncdebug.h" #include "ncxml.h" #include "ncinet2.h" // HrCombineUrl, HrCopyAndValidateUrl struct DTNAME { LPCWSTR m_pszType; SST_DATA_TYPE m_sdt; }; // Lookup table, sorted by string // Note: if you add strings here, you also have to add a // corresponding entry to SST_DATA_TYPE // static CONST DTNAME g_rgdtTypeStrings[] = { {L"bin.base64", SDT_BIN_BASE64}, // base64 as defined in the MIME IETF spec {L"bin.hex", SDT_BIN_HEX}, // Hexadecimal digits representing octets VT_ARRAY safearray or stream {L"boolean", SDT_BOOLEAN}, // boolean, "1" or "0" VT_BOOL int {L"char", SDT_CHAR}, // char, string VT_UI2 wchar {L"date", SDT_DATE_ISO8601}, // date.iso8601, A date in ISO 8601 format. (no time) VT_DATE long {L"dateTime", SDT_DATETIME_ISO8601}, // dateTime.iso8601, A date in ISO 8601 format, with optional time and no optional zone. Fractional seconds may be as precise as nanoseconds. VT_DATE long {L"dateTime.tz", SDT_DATETIME_ISO8601TZ},// dateTime.iso8601tz, A date in ISO 8601 format, with optional time and optional zone. Fractional seconds may be as precise as nanoseconds. VT_DATE long {L"fixed.14.4", SDT_FIXED_14_4}, // fixed.14.4, Same as "number" but no more than 14 digits to the left of the decimal point, and no more than 4 to the right. VT_CY large_integer {L"float", SDT_FLOAT}, // float, Same as for "number." VT_R8 double {L"i1", SDT_I1}, // i1, A number, with optional sign, no fractions, no exponent. VT_I1 char {L"i2", SDT_I2}, // i2, " VT_I2 short {L"i4", SDT_I4}, // i4, " VT_I4 long {L"int", SDT_INT}, // int, A number, with optional sign, no fractions, no exponent. VT_I4 long {L"number", SDT_NUMBER}, // number, A number, with no limit on digits, may potentially have a leading sign, fractional digits, and optionally an exponent. Punctuation as in US English. VT_R8 double {L"r4", SDT_R4}, // r4, Same as "number." VT_FLOAT float {L"r8", SDT_R8}, // r8, " VT_DOUBLE double {L"string", SDT_STRING}, // string, pcdata VT_BSTR BSTR {L"time", SDT_TIME_ISO8601}, // time.iso8601, A time in ISO 8601 format, with no date and no time zone. VT_DATE long {L"time.tz", SDT_TIME_ISO8601TZ}, // time.iso8601.tz, A time in ISO 8601 format, with no date but optional time zone. VT_DATE. long {L"ui1", SDT_UI1}, // ui1, A number, unsigned, no fractions, no exponent. VT_UI1 unsigned char {L"ui2", SDT_UI2}, // ui2, " VT_UI2 unsigned short {L"ui4", SDT_UI4}, // ui4, " VT_UI4 unsigned long {L"uri", SDT_URI}, // uri, Universal Resource Identifier VT_BSTR BSTR {L"uuid", SDT_UUID}, // uuid, Hexadecimal digits representing octets, optional embedded hyphens which should be ignored. VT_BSTR GUID // // note(cmr): ADD NEW VALUES IN ALPHABETICAL ORDER // }; // NOTE: the order of elements in this array must correspond to the order of // elements in the SST_DATA_TYPE enum. static CONST VARTYPE g_rgvtTypes[] = { VT_BSTR, VT_BSTR, VT_I4, VT_CY, VT_BOOL, VT_DATE, VT_DATE, VT_DATE, VT_DATE, VT_DATE, VT_I1, VT_I2, VT_I4, VT_UI1, VT_UI2, VT_UI4, VT_R4, VT_R8, VT_R8, VT_BSTR, VT_ARRAY, VT_ARRAY, VT_UI2, VT_BSTR, // // note(cmr): ADD NEW VALUES IMMEDIATELY BEFORE THIS COMMENT. // If adding new values, see comment above. // VT_EMPTY }; /* * Function: GetStringFromType() * * Purpose: Returns an xml-data type string for the specified * SDT_DATA_TYPE value. * * Arguments: * sdt [in] The data type whose descriptive string is desired. * This must be a valid SST_DATA_TYPE value (less * than SDT_INVALID) * * Returns: * A pointer to a constant string id representing the type. This string * should not be modified or freed. * This method will never return NULL. * */ LPCWSTR GetStringFromType(CONST SST_DATA_TYPE sdt) { // there must be an entry in g_rgdtTypeStrings for every real // value of SST_DATA_TYPE // Assert(SDT_INVALID == celems(g_rgdtTypeStrings)); Assert(sdt < SDT_INVALID); // since comparing SDT_s is somewhat cheaper than comparing whole strings, // we just walk through the whole list sequentually // CONST INT nSize = celems(g_rgdtTypeStrings); LPCWSTR pszResult; INT i; pszResult = NULL; i = 0; for ( ; i < nSize; ++i) { SST_DATA_TYPE sdtCurrent; sdtCurrent = g_rgdtTypeStrings[i].m_sdt; if (sdt == sdtCurrent) { // we have a match pszResult = g_rgdtTypeStrings[i].m_pszType; break; } } AssertSz(pszResult, "GetStringFromType: " "sdt not found in g_rgdtTypeStrings!"); return pszResult; } /* * Function: GetTypeFromString() * * Purpose: Returns the appropriate SDT_DATA_TYPE value for the given * xml-data type string. * * Arguments: * pszTypeString [in] The data type identifier whose SDT_DATA_TYPE value * is desired. * * Returns: * If the string is found, it returns the appropriate value of the * SST_DATA_TYPE enumeration. * If the string is not found, it returns SDT_INVALID. * * Notes: * The source string is compared to known strings in a case-insensitive * comparison. * */ SST_DATA_TYPE GetTypeFromString(LPCWSTR pszTypeString) { // there must be an entry in g_rgdtTypeStrings for every real // value of SST_DATA_TYPE // Assert(SDT_INVALID == celems(g_rgdtTypeStrings)); SST_DATA_TYPE sdtResult; sdtResult = SDT_INVALID; { // now search for the string in the list, using a // standard binary search // INT nLow; INT nMid; INT nHigh; nLow = 0; nHigh = celems(g_rgdtTypeStrings) - 1; while (TRUE) { if (nLow > nHigh) { // not found // break; } nMid = (nLow + nHigh) / 2; { LPCWSTR pszCurrent; int result; pszCurrent = g_rgdtTypeStrings[nMid].m_pszType; result = _wcsicmp(pszTypeString, pszCurrent); if (result < 0) { nHigh = nMid - 1; } else if (result > 0) { nLow = nMid + 1; } else { // found // sdtResult = g_rgdtTypeStrings[nMid].m_sdt; break; } } } } return sdtResult; } VARTYPE GetVarTypeFromString(LPCWSTR pszTypeString) { SST_DATA_TYPE sdt = SDT_INVALID; sdt = GetTypeFromString(pszTypeString); return g_rgvtTypes[sdt]; } // not an exported function, but one that we use internally... HRESULT HrGetChildElement(IXMLDOMNode * pxdn, LPCWSTR pszNodeName, IXMLDOMNode ** ppxdn); CONST WCHAR pszTextType [] = L"string"; //+--------------------------------------------------------------------------- // // Function: HrGetTypedValueFromElement // // Purpose: Given an IXMLDOMNode that should be of type // dt:string, returns a new BSTR containing that // string. // // Arguments: // pxdn IXMLDOMNode to extract the string from. // It is intended that this node be of type // NODE_ELEMENT. // // pszDataType The type of the data encoded in the element. // // pvarOut Address of a VARIANT that will obtain the // data value. This must be freed when // no longer needed. // // Returns: // S_OK if *pvarOut contains the data of the desired type // // Notes: // HRESULT HrGetTypedValueFromElement(IXMLDOMNode * pxdn, CONST LPCWSTR pszDataType, VARIANT * pvarOut) { Assert(pxdn); Assert(pszDataType); Assert(pvarOut); HRESULT hr; BSTR bstrDataType; bstrDataType = ::SysAllocString(pszDataType); if (bstrDataType) { hr = pxdn->put_dataType(bstrDataType); if (SUCCEEDED(hr)) { hr = pxdn->get_nodeTypedValue(pvarOut); if (FAILED(hr)) { TraceError("HrGetTypedValueFromElement: " "get_nodeTypedValue()", hr); // clear pvarOut ::VariantInit(pvarOut); } } else { TraceError("HrGetTypedValueFromElement: " "put_dataType()", hr); } ::SysFreeString(bstrDataType); } else { hr = E_OUTOFMEMORY; TraceError("HrGetTypedValueFromElement: " "SysAllocString()", hr); } TraceError("HrGetTypedValueFromElement", hr); return hr; } //+--------------------------------------------------------------------------- // // Function: HrGetTypedValueFromChildElement // // Purpose: Given an IXMLDOMElement, finds its child element // of the given name, and extracts the data contained // in that child element, decoding it from the specified // format. // // Arguments: // pxdn The element which should contain the specified // children // // arypszTokens A serial list of child element names that uniquely // describe the desired element. // // cTokens The number of element names contained in // arypszTokens. // // pszDataType The type of the data encoded in the element. // // pvarOut Address of a VARIANT that will obtain the // data value. This must be freed when // no longer needed. // // Returns: // S_OK if *pbstrOut has been set to a new BSTR // S_FALSE if the specified element did not exist. // in this case, *pbstrOut is set to NULL // // Notes: // for example, if the document looked like this: // text // and pxdn referred to , arypszTokens = [ "bar" ] // cTokens = 1, and sdtType = "string", this would // return "text". // See the definition of HrGetNestedChildElement() for // further explination. // HRESULT HrGetTypedValueFromChildElement(IXMLDOMNode * pxdn, CONST LPCWSTR * arypszTokens, CONST ULONG cTokens, CONST LPCWSTR pszDataType, VARIANT * pvarOut) { Assert(pxdn); Assert(arypszTokens); Assert(cTokens); Assert(pszDataType); Assert(pvarOut); HRESULT hr; IXMLDOMNode * pxdnTemp; pxdnTemp = NULL; hr = HrGetNestedChildElement(pxdn, arypszTokens, cTokens, &pxdnTemp); if (FAILED(hr)) { pxdnTemp = NULL; goto Cleanup; } if (S_OK == hr) { Assert(pxdn); hr = HrGetTypedValueFromElement(pxdnTemp, pszDataType, pvarOut); if (FAILED(hr)) { goto Cleanup; } } // hr is S_FALSE if bstrResult is NULL, or S_OK if // pvarOut has been retrieved Cleanup: SAFE_RELEASE(pxdnTemp); TraceErrorOptional("HrGetTypedValueFromChildElement", hr, (S_FALSE == hr)); return hr; } //+--------------------------------------------------------------------------- // // Function: HrGetTextValueFromElement // // Purpose: Given an IXMLDOMNode that should be of type // dt:string, returns a new BSTR containing that // string. // // Arguments: // pxdn IXMLDOMNode to extract the string from. // It is intended that this node be of type // NODE_ELEMENT. // // pbstrOut Address of a BSTR that will obtain the // text value. This must be freed when // no longer needed. // // Returns: // S_OK if *pbstrOut has been set to a new BSTR // // Notes: // This function should return an error if the element // contains things other than NODE_TEXT children. // HRESULT HrGetTextValueFromElement(IXMLDOMNode * pxdn, BSTR * pbstrOut) { Assert(pxdn); Assert(pbstrOut); HRESULT hr; VARIANT varOut; *pbstrOut = NULL; hr = HrGetTypedValueFromElement(pxdn, pszTextType, &varOut); if (S_OK == hr) { Assert(VT_BSTR == V_VT(&varOut)); *pbstrOut = V_BSTR(&varOut); // note: Don't clear varOut, since it's the returned BSTR } TraceErrorOptional("HrGetTextValueFromElement", hr, (S_FALSE == hr)); return hr; } //+--------------------------------------------------------------------------- // // Function: HrGetTextValueFromChildElement // // Purpose: Given an IXMLDOMElement, finds its child element // of the given name, and extracts the dt:type="string" // data contained in that child element. // // Arguments: // pxdn The element which should contain the specified // children // // arypszTokens A serial list of child element names that uniquely // describe the desired element. // // cTokens The number of element names contained in // arypszTokens. // // pbstrOut Address of a BSTR that will obtain the // text value. This must be freed when // no longer needed. // // Returns: // S_OK if *pbstrOut has been set to a new BSTR // S_FALSE if the specified element did not exist. // in this case, *pbstrOut is set to NULL // // Notes: // for example, if the document looked like this: // text // and pxdn referred to , arypszTokens = [ "bar" ] // and cTokens = 1, this would return "text". // See the definition of HrGetNestedChildElement() for // further explination. // HRESULT HrGetTextValueFromChildElement(IXMLDOMNode * pxdn, const LPCWSTR * arypszTokens, const ULONG cTokens, BSTR * pbstrOut) { HRESULT hr; VARIANT varOut; *pbstrOut = NULL; hr = HrGetTypedValueFromChildElement(pxdn, arypszTokens, cTokens, pszTextType, &varOut); if (S_OK == hr) { Assert(VT_BSTR == V_VT(&varOut)); *pbstrOut = V_BSTR(&varOut); // note: Don't clear varOut, since it's the returned BSTR } TraceErrorOptional("HrGetTextValueFromChildElement", hr, (S_FALSE == hr)); return hr; } //+--------------------------------------------------------------------------- // // Function: FIsThisTheNodeName // // Purpose: Returns TRUE if the tag name of the specified XML DOM // element is the specified string. // // Arguments: // pxdn Node whose name is being tested // // pszNodeName Proposed name of the node // // Returns: // TRUE if the node name matches, FALSE if not // // Notes: // for the xml node declared as // ... // IsThisTheNodeName() will return TRUE only when // pszNodeName == T"foo"; BOOL FIsThisTheNodeName(IXMLDOMNode * pxdn, LPCWSTR pszNodeName) { Assert(pxdn); Assert(pszNodeName); HRESULT hr; BSTR bstrNodeName; BOOL fResult; int result; bstrNodeName = NULL; hr = pxdn->get_baseName(&bstrNodeName); if (SUCCEEDED(hr)) { Assert(bstrNodeName); result = wcscmp(bstrNodeName, pszNodeName); ::SysFreeString(bstrNodeName); return (result == 0) ? TRUE : FALSE; } TraceError("OBJ: FIsThisTheNodeName - get_baseName", hr); Assert(!bstrNodeName); return FALSE; } //+--------------------------------------------------------------------------- // // Function: FIsThisTheNodeNameWithNamespace // // Purpose: Returns TRUE if the element name of the specified XML DOM // element is the specified string and is in a given namespace. // // Arguments: // pxdn Node whose name is being tested // pszNodeName Proposed base name of the node // pszNamespaceURI Proposed namespace URI of the node // // Returns: // TRUE if the node name matches, FALSE if not // // Notes: // for the xml node declared as // ... // FIsThisTheNodeNameWithNamespace() will return TRUE only when // pszNodeName == L"foo" and pszNamespaceURI == L"urn:..." BOOL FIsThisTheNodeNameWithNamespace(IXMLDOMNode * pxdn, LPCWSTR pszNodeName, LPCWSTR pszNamespaceURI) { Assert(pxdn); Assert(pszNodeName); Assert(pszNamespaceURI); HRESULT hr = S_OK;; BSTR bstrNodeName = NULL; BSTR bstrNamespaceURI = NULL; BOOL fResult = FALSE; hr = pxdn->get_baseName(&bstrNodeName); if (SUCCEEDED(hr)) { if (bstrNodeName) { hr = pxdn->get_namespaceURI(&bstrNamespaceURI); if (SUCCEEDED(hr)) { if (bstrNamespaceURI) { if ((lstrcmpW(bstrNodeName, pszNodeName) == 0) && (lstrcmpW(bstrNamespaceURI, pszNamespaceURI) == 0)) { fResult = TRUE; } ::SysFreeString(bstrNamespaceURI); } } ::SysFreeString(bstrNodeName); } } TraceError("FIsThisTheNodeNameWithNamespace(): " "Exiting", hr); return fResult; } //+--------------------------------------------------------------------------- // // Function: FIsThisTheNodeTextValue // // Purpose: Determines whether the text value of a node matches a specified // value // // Arguments: // pxdn [in] The node to check // cszTextValue [in] The value to compare against // // Returns: // TRUE if the node's text value matches the value specified in cszTextValue // FALSE otherwise // // Author: spather 2000/11/2 // // Notes: // BOOL FIsThisTheNodeTextValue( IN IXMLDOMNode * pxdn, IN LPCWSTR cszTextValue) { HRESULT hr = S_OK; BOOL fResult = FALSE; BSTR bstrNodeText = NULL; Assert(pxdn); Assert(cszTextValue); hr = pxdn->get_text(&bstrNodeText); if (SUCCEEDED(hr)) { Assert(bstrNodeText); if (0 == lstrcmpW(bstrNodeText, cszTextValue)) { fResult = TRUE; } SysFreeString(bstrNodeText); } else { TraceError("FIsThisTheNodeTextValue(): " "Failed to get node text", hr); } TraceError("FIsThisTheNodeTextValue(): " "Exiting", hr); return fResult; } //+--------------------------------------------------------------------------- // // Function: FAreNodeValuesEqual // // Purpose: Compares the text values of two DOM nodes and, if they are // equal, returns TRUE. // // Arguments: // pxdn1 [in] The first node // pxdn2 [in] The second node // // Returns: // TRUE if the nodes' text values are equal, false otherwise. // // Author: spather 2000/11/2 // // Notes: // BOOL FAreNodeValuesEqual( IN IXMLDOMNode * pxdn1, IN IXMLDOMNode * pxdn2) { HRESULT hr = S_OK; BOOL fResult = FALSE; BSTR bstrNode1Text = NULL; BSTR bstrNode2Text = NULL; hr = pxdn1->get_text(&bstrNode1Text); if (SUCCEEDED(hr)) { Assert(bstrNode1Text); hr = pxdn2->get_text(&bstrNode2Text); if (SUCCEEDED(hr)) { Assert(bstrNode2Text); } else { TraceError("FAreNodeValuesEqual(): " "Failed to get node 2 text", hr); } } else { TraceError("FAreNodeValuesEqual(): " "Failed to get node 1 text", hr); } if (SUCCEEDED(hr)) { if (0 == lstrcmpW(bstrNode1Text, bstrNode2Text)) { fResult = TRUE; } } if (bstrNode1Text) { SysFreeString(bstrNode1Text); } if (bstrNode2Text) { SysFreeString(bstrNode2Text); } TraceError("FAreNodeValuesEqual(): " "Exiting", hr); return fResult; } //+--------------------------------------------------------------------------- // // Function: HrAreThereDuplicatesInChildNodeTextValues // // Purpose: Starting from a specified parent node, this function // examines all child nodes that have a given name, and // checks their text values to see whether duplicates // exist (i.e. whether two or more of these child nodes // have the same text value). // // Arguments: // pxdn [in] The parent node // cszXSLPattern [in] XSL Pattern identifying the name of the child // nodes to examine // fCaseSensitive [in] Indicates whether the text values should be // examined with a case-sensitive or case- // insensitive comparison (if TRUE, compare will // be case-sensitive) // pfDuplicatesExist [out] Receives a boolean value indicating whether or // not there were duplicates in the child node // values. (TRUE if there were duplicates // // Returns: // If the function succeeds, the return value is S_OK. Otherwise, the // function returns one of the COM error codes defined in WinError.h. // // Author: spather 2000/11/5 // // Notes: // This function uses a very simple O(n^2) algorithm. Do not use it to // check nodes that have very many (> 100) children. HRESULT HrAreThereDuplicatesInChildNodeTextValues( IN IXMLDOMNode * pxdn, IN LPCWSTR cszXSLPattern, IN BOOL fCaseSensitive, OUT BOOL * pfDuplicatesExist) { HRESULT hr = S_OK; BOOL fDuplicatesExist = FALSE; LONG lNumNodes = 0; BSTR * rgbstrValues = NULL; BSTR bstrXSLPattern; IXMLDOMNodeList * pxdnlNodes = NULL; Assert(pxdn); Assert(cszXSLPattern); Assert(pfDuplicatesExist); // Get the child nodes that match the XSL pattern. bstrXSLPattern = SysAllocString(cszXSLPattern); if (bstrXSLPattern) { hr = pxdn->selectNodes(bstrXSLPattern, &pxdnlNodes); SysFreeString(bstrXSLPattern); } else { hr = E_OUTOFMEMORY; TraceError("HrAreThereDuplicatesInChildNodeTextValues(): " "Failed to allocate BSTR for XSL pattern", hr); } // Construct an array of the child nodes' text values. if (SUCCEEDED(hr)) { Assert(pxdnlNodes); hr = pxdnlNodes->get_length(&lNumNodes); if (SUCCEEDED(hr)) { if (lNumNodes > 0) { rgbstrValues = new BSTR[lNumNodes]; if (rgbstrValues) { ZeroMemory(rgbstrValues, lNumNodes * sizeof(BSTR)); for (LONG i = 0; SUCCEEDED(hr) && (i < lNumNodes); i++) { IXMLDOMNode * pxdnChild = NULL; hr = pxdnlNodes->get_item(i, &pxdnChild); if (SUCCEEDED(hr)) { Assert(pxdnChild); hr = pxdnChild->get_text(&rgbstrValues[i]); if (FAILED(hr)) { TraceError("HrAreThereDuplicatesInChildNodeTextValues(): " "Failed to get node text value", hr); } pxdnChild->Release(); } else { TraceError("HrAreThereDuplicatesInChildNodeTextValues(): " "Failed to get list item", hr); } } } else { hr = E_OUTOFMEMORY; TraceError("HrAreThereDuplicatesInChildNodeTextValues(): " "Failed to allocate text value array", hr); } } } pxdnlNodes->Release(); } else { TraceError("HrAreThereDuplicatesInChildNodeTextValues(): " "Failed to get nodes matching XSL pattern", hr); } // Check the array for duplicates. if (SUCCEEDED(hr) && (lNumNodes > 0)) { Assert(rgbstrValues); for (LONG i = 0; i < lNumNodes; i++) { for (LONG j = i+1; j < lNumNodes; j++) { if (fCaseSensitive) { if (0 == lstrcmpW(rgbstrValues[i], rgbstrValues[j])) { fDuplicatesExist = TRUE; break; } } else { if (0 == lstrcmpiW(rgbstrValues[i], rgbstrValues[j])) { fDuplicatesExist = TRUE; break; } } } } } // Clean up the array. if (rgbstrValues) { for (LONG i = 0; i < lNumNodes; i++) { if (rgbstrValues[i]) { SysFreeString(rgbstrValues[i]); rgbstrValues[i] = NULL; } } delete [] rgbstrValues; rgbstrValues = NULL; } if (SUCCEEDED(hr)) { *pfDuplicatesExist = fDuplicatesExist; } TraceError("HrAreThereDuplicatesInChildNodeTextValues(): " "Exiting", hr); return hr; } HRESULT HrGetFirstChildElement(IXMLDOMNode * pxdn, LPCWSTR pszNodeName, IXMLDOMNode ** ppxdn) { Assert(pxdn); Assert(pszNodeName); Assert(ppxdn); HRESULT hr; IXMLDOMNode * pxdnChild; pxdnChild = NULL; hr = pxdn->get_firstChild(&pxdnChild); if (FAILED(hr)) { TraceError("HrGetFirstChildElement - get_firstChild", hr); pxdnChild = NULL; goto Cleanup; } // maybe there is a child, maybe not. hr = HrGetChildElement(pxdnChild, pszNodeName, ppxdn); Cleanup: SAFE_RELEASE(pxdnChild); TraceErrorOptional("HrGetFirstChildElement", hr, (S_FALSE == hr)); return hr; } HRESULT HrGetNextChildElement(IXMLDOMNode * pxdnLastChild, LPCWSTR pszNodeName, IXMLDOMNode ** ppxdn) { Assert(pxdnLastChild); Assert(pszNodeName); Assert(ppxdn); HRESULT hr; IXMLDOMNode * pxdnChild; IXMLDOMNode * pxdnResult; pxdnChild = NULL; pxdnResult = NULL; // we're searching for another child. hr = pxdnLastChild->get_nextSibling(&pxdnChild); if (FAILED(hr)) { TraceError("HrGetNextChildElement - get_nextSibling", hr); pxdnChild = NULL; // we couldn't get the next child, and we haven't found a result, // so we need to barf. goto Cleanup; } // maybe there was a next node, maybe there wasn't hr = HrGetChildElement(pxdnChild, pszNodeName, &pxdnResult); if (FAILED(hr)) { goto Cleanup; } Cleanup: Assert(FImplies(S_OK == hr, pxdnResult)); Assert(FImplies(S_OK != hr, !pxdnResult)); SAFE_RELEASE(pxdnChild); *ppxdn = pxdnResult; TraceErrorOptional("HrGetNextChildElement", hr, (S_FALSE == hr)); return hr; } //+--------------------------------------------------------------------------- // // Function: HrGetChildElement // // Purpose: Returns the first child element of a given name. // // Arguments: // pxdn Node to start searching at. This node and its // subsequent siblings are searched. // Note that this MAY be null, in which case this // function doesn't do much... // // pszNodeName Name of the desired child node. // // ppxdn On return, contiains an AddRef()'d IXMLDOMNode // pointer to the child element, if one of the // given name exists. // // Returns: // S_OK if a matching child node has been found. *ppxdn contains // a reference to this found node // S_FALSE if no matching child was found. *ppxdn is set to NULL. // // Notes: // To retrieve a child element declared as , pszNodeName // must be "yyyy". // TODO: make sure the elements considered match a specified // namespace uri. HRESULT HrGetChildElement(IXMLDOMNode * pxdn, LPCWSTR pszNodeName, IXMLDOMNode ** ppxdn) { Assert(ppxdn); Assert(pszNodeName); HRESULT hr; IXMLDOMNode * pxdnChild; if (pxdn) { pxdn->AddRef(); } // in case pxdnChild is NULL hr = S_FALSE; pxdnChild = pxdn; while (pxdnChild) { // invaiant: pxdnChild is an addref()'d pointer to the current // child of interest IXMLDOMNode * pxdnTemp; DOMNodeType dnt; pxdnTemp = NULL; // get the DOMNodeType hr = pxdnChild->get_nodeType(&dnt); if (FAILED(hr)) { TraceError("HrGetChildElement - get_nodeType", hr); goto Error; } if (NODE_ELEMENT == dnt) { if (FIsThisTheNodeName(pxdnChild, pszNodeName)) { break; } } hr = pxdnChild->get_nextSibling(&pxdnTemp); if (FAILED(hr)) { TraceError("HrGetChildElement - get_nextSibling", hr); // we couldn't get the next child, and we haven't found a result, // so we need to barf. goto Error; } Assert((S_OK == hr) || (S_FALSE == hr)); Assert(FImplies(S_OK == hr, pxdnTemp)); Assert(FImplies(S_FALSE == hr, !pxdnTemp)); // note: if hr is S_FALSE, we're done pxdnChild->Release(); pxdnChild = pxdnTemp; } Assert((S_OK == hr) || (S_FALSE == hr)); Assert(FImplies(S_OK == hr, pxdnChild)); Assert(FImplies(S_FALSE == hr, !pxdnChild)); // post-loop-condition: pxdnChild is an addref'd pointer to the // matching child, of type NODE_ELEMENT. // if there is no match, pxdnChild is NULL. Cleanup: *ppxdn = pxdnChild; TraceErrorOptional("HrGetChildElement", hr, (S_FALSE == hr)); return hr; Error: hr = E_FAIL; SAFE_RELEASE(pxdnChild); goto Cleanup; } // Used for Validation duplicate tags in Description Document - Guru HRESULT HrIsElementPresentOnce( IXMLDOMNode * pxdnRoot, LPCWSTR pszNodeName ) { HRESULT hr = S_OK ; LONG nNumNodes = 0; IXMLDOMNodeList * pxdnlNodes = NULL; BSTR bstrXSLPattern = NULL; bstrXSLPattern = SysAllocString(pszNodeName); if (bstrXSLPattern) { hr = pxdnRoot->selectNodes(bstrXSLPattern, &pxdnlNodes); if(hr == S_OK) { hr = pxdnlNodes->get_length(&nNumNodes); if(SUCCEEDED(hr)) { if ( nNumNodes == 1 ) hr = S_OK; else { hr = E_FAIL ; //hr = S_FALSE; } } } else hr = E_FAIL; } else { hr = E_OUTOFMEMORY; TraceError("HrIsElementPresentOnce(): " "Failed to allocate BSTR for XSL pattern", hr); } SAFE_RELEASE(pxdnlNodes); SysFreeString(bstrXSLPattern); TraceErrorOptional("HrIsElementPresentOnce", hr, (S_FALSE == hr)); return hr; } // Used for Validation duplicate tags in Description Document - Guru HRESULT HrCheckForDuplicateElement(IXMLDOMNode * pxdnRoot, LPCWSTR pszNodeName ) { HRESULT hr = S_OK ; LONG nNumNodes = 0; IXMLDOMNodeList * pxdnlNodes = NULL; BSTR bstrXSLPattern = NULL; bstrXSLPattern = SysAllocString(pszNodeName); if (bstrXSLPattern) { hr = pxdnRoot->selectNodes(bstrXSLPattern, &pxdnlNodes); if(hr == S_OK) { hr = pxdnlNodes->get_length(&nNumNodes); if(SUCCEEDED(hr)) { if ( nNumNodes > 1 ) { hr = E_FAIL; TraceError("HrCheckForDuplicateElement(): " "Duplicate Tag Present", hr); } else { hr = S_OK; } } } } else { hr = E_OUTOFMEMORY; TraceError("HrCheckForDuplicateElement(): " "Failed to allocate BSTR for XSL pattern", hr); } SAFE_RELEASE(pxdnlNodes); SysFreeString(bstrXSLPattern); TraceErrorOptional("HrCheckForDuplicateElement", hr, (S_FALSE == hr)); return hr; } //+--------------------------------------------------------------------------- // // Function: HrGetNestedChildElement // // Purpose: Returns the nth-level child of a particular element, // given the name of the element and the name of each // intermediate element. // // Arguments: // pxdnRoot The element which should contain the specified // children // // arypszTokens A serial list of child element names that uniquely // describe the desired element. See notes below. // // cTokens The number of element names contained in // arypszTokens. // // ppxdn On return, contiains an AddRef()'d IXMLDOMNode // pointer to the child element, if one of the // given name exists. // // Returns: // S_OK if a matching child node has been found. *ppxdn contains // a reference to this found node // S_FALSE if no matching child was found. *ppxdn is set to NULL. // // Notes: // To retrieve a child element declared as , pszNodeName // must be "yyyy". // TODO: make it ensure that there is only ONE matching node! // also... // TODO: make sure the elements considered match a specified // namespace uri. // // overview of function: // given a list of element names and a root element, it returns the // "n"th child element of the given element. // e.g., if the "root" element passed in was and the tokens // passed in were ["bar","car","dar" and "ear"], and the document // looked like the following, // <--- pxdnRoot // // // // // // // // // this returns the IXMLDOMNode reference to "" // // if more than one of the desired name exists at a given level, // this will chose the first element. // // ... // ... // // // TODO: only return an element if its namespace matches the desired // namespace uri // // pszNameSpaceURI = URI of the namespace that each element in the // token list must be defined in HRESULT HrGetNestedChildElement(IXMLDOMNode * pxdnRoot, const LPCWSTR * arypszTokens, const ULONG cTokens, IXMLDOMNode ** ppxdn) { HRESULT hr; IXMLDOMNode * pxdnCurrent; ULONG i; Assert(pxdnRoot); Assert(arypszTokens); Assert(cTokens); Assert(ppxdn); pxdnCurrent = pxdnRoot; pxdnCurrent->AddRef(); i = 0; for ( ; i < cTokens; ++i) { Assert(arypszTokens[i]); IXMLDOMNode * pxdnChild; pxdnChild = NULL; hr = HrGetFirstChildElement(pxdnCurrent, arypszTokens[i], &pxdnChild); if (FAILED(hr)) { goto Error; } else if (S_FALSE == hr) { // the child of the specified name doesn't exist. well, then... // we return S_FALSE and NULL in this case, which is what // we happen to have in hr and will put in pxdnCurrent goto Error; } Assert(pxdnChild); pxdnCurrent->Release(); pxdnCurrent = pxdnChild; } Assert(pxdnCurrent); // pxdnCurrent is the desired child Cleanup: Assert(FImplies((S_OK == hr), pxdnCurrent)); Assert(FImplies(S_OK != hr, !pxdnCurrent)); *ppxdn = pxdnCurrent; TraceErrorOptional("HrGetNestedChildElement", hr, (S_FALSE == hr)); return hr; Error: // we didn't find a match SAFE_RELEASE(pxdnCurrent); goto Cleanup; } // return values: // S_OK all values have been read // error some element couldn't be read. // the returned array is undefined. HRESULT HrReadElementWithParseData (IXMLDOMNode * pxdn, const ULONG cElems, const DevicePropertiesParsingStruct dppsInfo [], LPCWSTR pszBaseUrl, LPWSTR arypszReadValues []) { Assert(pxdn); Assert(cElems); Assert(dppsInfo); Assert(arypszReadValues); HRESULT hr = S_OK; ULONG i; ::ZeroMemory(arypszReadValues, sizeof(LPWSTR *) * cElems); i = 0; for ( ; i < cElems; ++i ) { BSTR bstrTemp; bstrTemp = NULL; hr = HrGetTextValueFromChildElement(pxdn, &(dppsInfo[i].m_pszTagName), 1, &bstrTemp); Assert(FImplies((S_FALSE == hr), !bstrTemp)); if (S_OK == hr) { Assert(bstrTemp); if (dppsInfo[i].m_fIsUrl) { LPWSTR pszTemp; pszTemp = NULL; if (pszBaseUrl) { // we have to combine base and relative urls hr = HrCombineUrl(pszBaseUrl, bstrTemp, &pszTemp); } else { // just copy and validate the url hr = HrCopyAndValidateUrl(bstrTemp, &pszTemp); if (S_FALSE == hr) { // invalid urls aren't acceptable hr = E_FAIL; } } if (SUCCEEDED(hr)) { arypszReadValues[i] = pszTemp; } } else { // just copy the value arypszReadValues[i] = WszAllocateAndCopyWsz(bstrTemp); if (!(arypszReadValues[i])) { hr = E_OUTOFMEMORY; } } ::SysFreeString(bstrTemp); } // note: hr may have been changed by the above code if (FAILED(hr)) { Assert(!(arypszReadValues[i])); goto Error; } TraceTag(ttidUPnPDescriptionDoc,"XML Tag - %S - %S",dppsInfo[i].m_pszTagName,arypszReadValues[i]); } if (S_FALSE == hr) { hr = S_OK; } Cleanup: TraceError("HrReadElementWithParseData", hr); return hr; Error: // we need to free the strings we've already allocated { ULONG j; // i is the number of elements successfully read above j = 0; for ( ; j < i; ++j ) { Assert(arypszReadValues[j]); delete [] arypszReadValues[j]; arypszReadValues[j] = NULL; } } goto Cleanup; } // Used for Validation duplicate tags in Description Document - Guru // If the element is optional we check if the Tag is present At most Once // IF the element is required we check if its present exactly once HRESULT HrAreElementTagsValid(IXMLDOMNode *pxdnRoot, const ULONG cElems, const DevicePropertiesParsingStruct dppsInfo []) { HRESULT hr = S_OK; ULONG i; i = 0; for ( ; i < cElems; ++i ) { if (dppsInfo[i].m_fOptional) { hr = HrCheckForDuplicateElement(pxdnRoot,dppsInfo[i].m_pszTagName); } else { hr = HrIsElementPresentOnce(pxdnRoot,dppsInfo[i].m_pszTagName); } if(FAILED(hr)) break; } TraceError("HrAreElementTagsValid", hr); return hr; } // return TRUE if all of the non-optional values (as specified by the // m_fOptional in dppsInfo) are non-null in arypszReadValues. // equivalently, it returns FALSE if there exists a value marked as // required (m_fOptional == FALSE) that is NULL in arypszReadValues. BOOL fAreReadValuesComplete (const ULONG cElems, const DevicePropertiesParsingStruct dppsInfo [], const LPCWSTR arypszReadValues []) { ULONG i; BOOL fResult; fResult = TRUE; i = 0; for ( ; i < cElems; ++i ) { if (!dppsInfo[i].m_fOptional) { if (!(arypszReadValues[i])) { fResult = FALSE; break; } } } return fResult; } //+--------------------------------------------------------------------------- // // Function: HrGetTextValueFromAttribute // // Purpose: Retrieves the text value of an attribute on a DOM element node // // Arguments: // pxdn [in] XML DOM Node representing the element whose // attribute is to be read // szAttrName [in] Name of the attribute // pbstrAttrValue [in] Receives the attribute text value. // // Returns: // If the function successfully reads the attribute value, the return value // is S_OK and the attribute value is returned at pbstrAttrValue. If the // attribute does not exist on the element, the function returns S_FALSE, // and NULL at pbstrAttrValue. Otherwise the function returns one of the // error codes defined in WinError.h. // // Author: spather 2000/11/9 // // Notes: // HRESULT HrGetTextValueFromAttribute( IN IXMLDOMNode * pxdn, IN LPCWSTR szAttrName, OUT BSTR * pbstrAttrValue) { HRESULT hr = S_OK; IXMLDOMElement * pxde = NULL; BSTR bstrValue = NULL; hr = pxdn->QueryInterface(IID_IXMLDOMElement, (void **) &pxde); if (SUCCEEDED(hr)) { BSTR bstrAttrName = NULL; Assert(pxde); bstrAttrName = SysAllocString(szAttrName); if (bstrAttrName) { VARIANT varValue; VariantInit(&varValue); hr = pxde->getAttribute(bstrAttrName, &varValue); if (SUCCEEDED(hr)) { if (VT_NULL == varValue.vt) { Assert(S_FALSE == hr); } else if (VT_BSTR == varValue.vt) { bstrValue = V_BSTR(&varValue); } else { Assert(FALSE); // Should never be here. } } else { TraceError("HrGetTextValueFromAttribute(): " "Failed to get attribute value", hr); } SysFreeString(bstrAttrName); } else { hr = E_OUTOFMEMORY; TraceError("HrGetTextValueFromAttribute(): " "Failed to allocate attribute name BSTR", hr); } pxde->Release(); } else { TraceError("HrGetTextValueFromAttribute(): " "Failed to get IXMLDOMElement interface", hr); } if (SUCCEEDED(hr)) { *pbstrAttrValue = bstrValue; } else { if (bstrValue) { SysFreeString(bstrValue); } } TraceHr(ttidError, FAL, hr, (hr == S_FALSE), "HrGetTextValueFromAttribute"); return hr; } /* * Function: HrSetTextAttribute() * * Purpose: Sets an text-valued attribute on an XML element. * * Arguments: * pElement [in] The element on which to set the attribute * pcwszAttrName [in] The attribute name * pcwszValue [in] The value to give the attribute * * Returns: * S_OK if successful, other HRESULT otherwise. */ HRESULT HrSetTextAttribute( IXMLDOMElement * pElement, LPCWSTR pcwszAttrName, LPCWSTR pcwszValue) { HRESULT hr = S_OK; VARIANT vValue; BSTR bstrValue, bstrAttrName; // Allocate BSTRs for the attribute name and value. bstrValue = SysAllocString(pcwszValue); if (bstrValue) { bstrAttrName = SysAllocString(pcwszAttrName); if (!bstrAttrName) { hr = E_OUTOFMEMORY; SysFreeString(bstrValue); } } else { hr = E_OUTOFMEMORY; } if (SUCCEEDED(hr)) { // danielwe: Bug 144883 VariantInit(&vValue); vValue.vt = VT_BSTR; V_BSTR(&vValue) = bstrValue; hr = pElement->setAttribute(bstrAttrName, vValue); SysFreeString(bstrValue); SysFreeString(bstrAttrName); } return hr; } /* * Function: HrCreateElementWithType() * * Purpose: Creates an XML element containing a binary value that is * encoded in bin.base64. * * Arguments: * pDoc [in] Document in which to create the element * pcwszElementName [in] Name for the new element * pszDataType [in] The type of the data encoded in the element. * varData [in] Data to insert as the element's value * ppElement [out] Address at which to place the pointer to the * new element object * * Returns: * S_OK if successful, other HRESULT otherwise. * * Notes: * This function does not actually insert the new element into the document * tree. */ HRESULT HrCreateElementWithType( IN IXMLDOMDocument * pDoc, IN LPCWSTR pcwszElementName, IN LPCWSTR pszDataType, IN VARIANT varData, OUT IXMLDOMElement ** ppElement) { Assert(pDoc); Assert(pcwszElementName); Assert(pszDataType); Assert(ppElement); HRESULT hr; BSTR bstrElementName; hr = S_OK; *ppElement = NULL; bstrElementName = SysAllocString(pcwszElementName); if (bstrElementName) { IXMLDOMElement * pElement = NULL; hr = pDoc->createElement(bstrElementName, &pElement); if (SUCCEEDED(hr)) { BSTR bstrDataType; bstrDataType = ::SysAllocString(pszDataType); if (bstrDataType) { hr = pElement->put_dataType(bstrDataType); if (SUCCEEDED(hr)) { hr = pElement->put_nodeTypedValue(varData); if (SUCCEEDED(hr)) { *ppElement = pElement; pElement->AddRef(); } else { TraceError("HrCreateElementWithType(): " "put_nodeTypedValue", hr); } } else { TraceError("HrCreateElementWithType(): " "put_dataType", hr); } ::SysFreeString(bstrDataType); } else { hr = E_OUTOFMEMORY; TraceError("HrCreateElementWithType(): " "SysAllocString", hr); } pElement->Release(); } else { TraceError("HrCreateElementWithType(): " "Could not create element", hr); } ::SysFreeString(bstrElementName); } else { hr = E_OUTOFMEMORY; TraceError("HrCreateElementWithType(): " "Could not allocate BSTR", hr); } return hr; } /* * Function: HrCreateElementWithTextValue() * * Purpose: Creates an XML element containing a text string as * a value. * * Arguments: * pDoc [in] Document in which to create the element * pcwszElementName [in] Name for the new element * pcwszTextValue [in] Text to insert as the element's value * ppElement [out] Address at which to place the pointer to the * new element object * * Returns: * S_OK if successful, other HRESULT otherwise. * * Notes: * This function does not actually insert the new element into the document * tree. */ HRESULT HrCreateElementWithTextValue( IN IXMLDOMDocument * pDoc, IN LPCWSTR pcwszElementName, IN LPCWSTR pcwszTextValue, OUT IXMLDOMElement ** ppElement) { HRESULT hr = S_OK; IXMLDOMElement * pElement = NULL; BSTR bstrElementName, bstrTextValue; *ppElement = NULL; // Allocate BSTRs for the element name and value. bstrElementName = SysAllocString(pcwszElementName); if (bstrElementName) { bstrTextValue = SysAllocString(pcwszTextValue); if (!bstrTextValue) { hr = E_OUTOFMEMORY; SysFreeString(bstrElementName); } } else { hr = E_OUTOFMEMORY; } if (SUCCEEDED(hr)) { hr = pDoc->createElement(bstrElementName, &pElement); if (SUCCEEDED(hr)) { IXMLDOMText * pText = NULL; hr = pDoc->createTextNode(bstrTextValue, &pText); if (SUCCEEDED(hr)) { VARIANT vAfter; // danielwe: Bug 144882 VariantInit(&vAfter); vAfter.vt = VT_EMPTY; hr = pElement->insertBefore(pText, vAfter, NULL); if (FAILED(hr)) { TraceError("HrCreateElementWithTextValue(): " "Failed to insert text node", hr); } pText->Release(); } else { TraceError("HrCreateElementWithTextValue(): " "Failed to create text node", hr); } if (SUCCEEDED(hr)) { *ppElement = pElement; pElement->AddRef(); } pElement->Release(); } else { TraceError("HrCreateElementWithTextValue(): " "Could not create element", hr); } SysFreeString(bstrElementName); SysFreeString(bstrTextValue); } else { TraceError("HrCreateElementWithTextValue(): " "Could not allocate BSTRs", hr); } return hr; } /* * Function: HrCreateElement() * * Purpose: Create an XML Element DOM object * * Arguments: * pxdd [in] The document object in whose context the * element will be created * pcwszElementName [in] Name of the element to be created * pcwszNamespaceURI [in] URI identifying the namespace to which * the element name belongs * ppxdnNewElt [out] The newly created element object * * Return Value: * S_OK if successful, other HRESULT otherwise. * * Notes: * (none) */ HRESULT HrCreateElement( IN IXMLDOMDocument * pxdd, IN LPCWSTR pcwszElementName, IN LPCWSTR pcwszNamespaceURI, OUT IXMLDOMNode ** ppxdnNewElt) { HRESULT hr = S_OK; IXMLDOMNode * pxdnNewElt = NULL; BSTR bstrElementName = NULL; BSTR bstrNamespaceURI = NULL; Assert(pxdd); Assert(pcwszElementName); Assert(ppxdnNewElt); // Allocate BSTRs bstrElementName = SysAllocString(pcwszElementName); if (bstrElementName) { if (pcwszNamespaceURI) { bstrNamespaceURI = SysAllocString(pcwszNamespaceURI); if (NULL == bstrNamespaceURI) { hr = E_OUTOFMEMORY; TraceError("HrCreateElement(): " "Failed to allocate memory for name string", hr); } } if (SUCCEEDED(hr)) { VARIANT varNodeType; VariantInit(&varNodeType); varNodeType.vt = VT_I4; V_I4(&varNodeType) = (int) NODE_ELEMENT; hr = pxdd->createNode(varNodeType, bstrElementName, bstrNamespaceURI, &pxdnNewElt); if (FAILED(hr)) { TraceError("HrCreateElement(): " "Failed to create element node", hr); } if (bstrNamespaceURI) { SysFreeString(bstrNamespaceURI); } } SysFreeString(bstrElementName); } else { hr = E_OUTOFMEMORY; TraceError("HrCreateElement(): " "Failed to allocate memory for name string", hr); } if (SUCCEEDED(hr)) { *ppxdnNewElt = pxdnNewElt; } TraceError("HrCreateElement(): " "Exiting", hr); return hr; } /* * Function: HrAppendProcessingInstruction() * * Purpose: Append a processing instruction to the DOM document * * Arguments: * pxdd [in] The document object in whose context the * element will be created * pcwszName [in] Name of the processing instruction to be created * pcwszValue [in] The text value of the p.i. * * Return Value: * S_OK if successful, other HRESULT otherwise. * * Notes: * (none) */ HRESULT HrAppendProcessingInstruction( IN IXMLDOMDocument * pxdd, IN LPCWSTR pcwszName, IN LPCWSTR pcwszValue) { HRESULT hr = S_OK; Assert(pxdd); Assert(pcwszName); Assert(pcwszValue); IXMLDOMProcessingInstruction* pxdpi = NULL; BSTR bstrTarget = SysAllocString(pcwszName); BSTR bstrData = SysAllocString(pcwszValue); if (bstrTarget && bstrData) { hr = pxdd->createProcessingInstruction(bstrTarget, bstrData, &pxdpi); } else { hr = E_OUTOFMEMORY; } SysFreeString(bstrTarget); SysFreeString(bstrData); if (SUCCEEDED(hr)) { IXMLDOMNode* pxdn = NULL; hr = pxdpi->QueryInterface(IID_IXMLDOMNode, (void**)&pxdn); if (SUCCEEDED(hr)) { hr = pxdd->appendChild(pxdn, NULL); pxdn->Release(); } } if (pxdpi) { pxdpi->Release(); } return hr; }