#include "precomp.h" #include "..\mcinc.h" #include "parser.h" //////////////////////////////////////////// // CMarsXMLFactory //////////////////////////////////////////// CMarsXMLFactory::CMarsXMLFactory() : m_elemStack(10) { ATLASSERT(! m_ptiaTags); } CMarsXMLFactory::~CMarsXMLFactory() { if (!m_elemStack.IsEmpty()) { do { m_elemStack.Top()->Release_Ref(); m_elemStack.Pop(); } while (! m_elemStack.IsEmpty()); } } // IUnknown IMPLEMENT_ADDREF_RELEASE(CMarsXMLFactory); STDMETHODIMP CMarsXMLFactory::QueryInterface(REFIID iid, void **ppvObject) { HRESULT hr; if (iid == IID_IXMLNodeFactory || iid == IID_IUnknown ) { AddRef(); *ppvObject = (IXMLNodeFactory *) this; hr = S_OK; } else { *ppvObject = NULL; hr = E_NOINTERFACE; } return hr; } // IXMLNodeFactory HRESULT CMarsXMLFactory::NotifyEvent(IXMLNodeSource *pSource, XML_NODEFACTORY_EVENT iEvt) { return S_OK; } HRESULT CMarsXMLFactory::BeginChildren(IXMLNodeSource *pSource, XML_NODE_INFO *pNodeInfo) { return S_OK; } HRESULT CMarsXMLFactory::EndChildren(IXMLNodeSource *pSource, BOOL fEmpty, XML_NODE_INFO *pNodeInfo) { // This call means that the node is completed; all the children and innertext // have been processed, and the has been reached. This is the time to // close up the element. // Note: any return value other than S_OK indicates failure HRESULT hr = S_OK; // It is assumed that the top of the stack is the node whose children are // ending if (! m_elemStack.IsEmpty()) { HRESULT hrTemp; CXMLElement *pxElem = m_elemStack.Top(); m_elemStack.Pop(); // OnNodeComplete should return S_OK for need to be added to parent, // S_FALSE for do not need to be added to parent, and // any failure codes for critical problems hrTemp = pxElem->OnNodeComplete(); if ((hrTemp == S_OK) && !m_elemStack.IsEmpty()) { // Addchild takes ownership of the element on S_OK. // Otherwise, we delete the element here. // we must delete the element here. // NOTE: Parent should Add_Ref the child hrTemp = m_elemStack.Top()->AddChild(pxElem); } else { if(FAILED(hrTemp)) { hr = hrTemp; } } pxElem->Release_Ref(); } return hr; } HRESULT CMarsXMLFactory::Error(IXMLNodeSource *pSource, HRESULT hrErrorCode, USHORT cNumRecs, XML_NODE_INFO **apNodeInfo) { // break out of the xmlparser->Run(-1) with error message E_INVALIDARG // on any xml syntax erros return E_INVALIDARG; } HRESULT CMarsXMLFactory::CreateNode(IXMLNodeSource *pSource, PVOID pNodeParent, USHORT cNumRecs, XML_NODE_INFO **apNodeInfo) { // This call is made when a opening tag is encoutered, ie // ... or (an empty tag) apNodeInfo is an array of // node infos; the first is the name of the tag, the rest are a // series of attributes and other such things which were bundled // in the tag, ie // We only recognize attributes; // SetElementAttributes handles those HRESULT hr = S_OK; ATLASSERT(cNumRecs > 0); switch (apNodeInfo[0]->dwType) { case XML_ELEMENT: { CXMLElement *pxElem; hr = CreateElement(apNodeInfo[0]->pwcText, apNodeInfo[0]->ulLen, &pxElem); if (hr == S_OK) { hr = SetElementAttributes(pxElem, apNodeInfo + 1, cNumRecs - 1); if (SUCCEEDED(hr)) { m_elemStack.Push(pxElem); // the stack holds our Ref } else { pxElem->Release_Ref(); } } break; } case XML_PCDATA: case XML_CDATA: { // since this is the first node in the node-info, this must be the // inner text in a tag (Johhny - we're talking about the // string "Johhny" for example). if (! m_elemStack.IsEmpty()) { hr = m_elemStack.Top()->SetInnerXMLText(apNodeInfo[0]->pwcText, apNodeInfo[0]->ulLen); } break; } default: { // ignore all other types of nodes (whitespace, comment, and unknown) break; } } return hr; } HRESULT CMarsXMLFactory::SetElementAttributes(CXMLElement *pxElem, XML_NODE_INFO **apNodeInfo, ULONG cInfoLen) // apNodeInfo is the beginning of XML_NODE_INFO attributes: the first node is an // XML_ATTRIBUTE with the name of an attribute, the second node is an XML_PCDATA with // the value, and then the 3rd and 4th are similar, if they exist, and so on. // pElement's SetAttribute method is called for all the attribute name/value pairs, // the return result is S_OK, E_OUTOFMEMORY, or S_FALSE (xml symantic err) { ULONG i; HRESULT hr = S_OK; i = 0; while (SUCCEEDED(hr) && (i < cInfoLen)) { if (apNodeInfo[i]->dwType == XML_ATTRIBUTE) { // move to the next ap node to get the value of the attribute i++; if ((i < cInfoLen) && (apNodeInfo[i]->dwType == XML_PCDATA)) { // Set attribute should return S_OK for success, S_FALSE for // unexpected attribute, and may return critical errors such as // E_OUTOFMEMORY hr = pxElem->SetAttribute(apNodeInfo[i-1]->pwcText, apNodeInfo[i-1]->ulLen, apNodeInfo[i]->pwcText, apNodeInfo[i]->ulLen); } else { continue; } } i++; } return hr; } HRESULT CMarsXMLFactory::Run(IStream *pisDoc) { if (!m_ptiaTags) return E_UNEXPECTED; // EPC if (!pisDoc) return E_INVALIDARG; // EPC HRESULT hr; CComPtr spParser; hr = spParser.CoCreateInstance(CLSID_XMLParser); if (SUCCEEDED(hr)) { CComPtr spUnk; hr = pisDoc->QueryInterface(IID_IUnknown, (void **)&spUnk); if (SUCCEEDED(hr)) { hr = spParser->SetInput(spUnk); if (SUCCEEDED(hr)) { hr = spParser->SetFactory(this); if (SUCCEEDED(hr)) { hr = spParser->Run(-1); ATLASSERT(hr != E_PENDING); if (FAILED(hr)) hr = spParser->GetLastError(); } } } else hr = E_UNEXPECTED; } return hr; } void CMarsXMLFactory::SetTagInformation(TagInformation **ptiaTags) { m_ptiaTags = ptiaTags; } void CMarsXMLFactory::SetLParam(LONG lParamNew) { m_lParamArgument = lParamNew; } HRESULT CMarsXMLFactory::CreateElement(LPCWSTR wzTagName, ULONG cLen, CXMLElement **ppxElem) { // Look in m_ptiaTags for a name that matches wzTagName, and call upon the corresponding // creation function once found // REturns S_FALSE if the tag is not found ATLASSERT(ppxElem); HRESULT hr = S_FALSE; int i; *ppxElem = NULL; if (m_ptiaTags) { for (i = 0; m_ptiaTags[i]; i++) { // if m_ptiaTags[i]->wzTagName is ever NULL, we will consider this // the "default" action and call "Create" #ifdef DEBUG // TODO: for now, we assert that the generic element is VT_BSTR, but // it would be cool to have a "Generic number" element, "Generic Time", etc... if (m_ptiaTags[i]->wzTagName == NULL) ATLASSERT(m_ptiaTags[i]->vt == VT_BSTR); #endif if ((m_ptiaTags[i]->wzTagName == NULL) || (StrEqlNToSZ(wzTagName, cLen, m_ptiaTags[i]->wzTagName))) { *ppxElem = m_ptiaTags[i]->funcCreate(m_ptiaTags[i]->wzTagName, m_ptiaTags[i]->vt, m_ptiaTags[i]->ptiaChildren, m_ptiaTags[i]->paiaAttributes, m_lParamArgument); hr = *ppxElem ? S_OK : E_OUTOFMEMORY; break; } } } if (hr == S_FALSE) { SpewUnrecognizedString(wzTagName, cLen, L"Tag name not recognized and being ignored, "); } return hr; } ///////////////////////////////////////////// // CXMLElement ///////////////////////////////////////////// HRESULT CXMLElement::OnNodeComplete() { return S_FALSE; } HRESULT CXMLElement::AddChild(CXMLElement *pxeChild) { ATLASSERT(pxeChild); return S_FALSE; } HRESULT CXMLElement::SetAttribute(LPCWSTR wzName, ULONG cLenName, LPCWSTR pwzValue, ULONG cLenValue) { SpewUnrecognizedString(wzName, cLenName, L"Base class SetAttribute called, attribute name "); return S_FALSE; } HRESULT CXMLElement::SetInnerXMLText(LPCWSTR pwzText, ULONG cLen) { return S_FALSE; } HRESULT CXMLElement::GetAttribute(LPCWSTR wzName, VARIANT *pvarOut) { if (pvarOut) { VariantInit(pvarOut); } return E_INVALIDARG; } HRESULT CXMLElement::GetContent(VARIANT *pvarOut) { if (pvarOut) { VariantInit(pvarOut); } return E_INVALIDARG; } void CXMLElement::FirstChild() { } void CXMLElement::NextChild() { } CXMLElement *CXMLElement::CurrentChild() { return NULL; } CXMLElement *CXMLElement::DetachCurrentChild() { return NULL; } BOOL CXMLElement::IsDoneChild() { return TRUE; } LPCWSTR CXMLElement::GetName() { return NULL; } struct CAttributeStruct { int m_iArrayIndex; CComVariant m_Variant; // NOTE: This is only set when m_iArrayIndex is "-1" CComBSTR m_bstrAttribName; CAttributeStruct(int iArrayIndex, VARTYPE vt, LPCWSTR wzVal, ULONG cLen, LPCWSTR pszAttribName = NULL) { // must set iArrayIndex to -1 in order to pass in bstrAttribName ATLASSERT( ((iArrayIndex >= 0) && !pszAttribName) || pszAttribName); m_iArrayIndex = iArrayIndex; if (pszAttribName) // copy m_bstrAttribName = pszAttribName; HRESULT hr; // We don't call VariantChangeType 'cause wzVal is not null-terminated switch (vt) { case VT_I4: hr = StrToLongNW(wzVal, cLen, &m_Variant.lVal); if (SUCCEEDED(hr)) m_Variant.vt = VT_I4; else m_Variant.vt = VT_NULL; break; case VT_BSTR: m_Variant = SysAllocStringLen(wzVal, cLen); break; case VT_BOOL: m_Variant = StrToIsTrueNW(wzVal, cLen); ATLASSERT(m_Variant.vt == VT_BOOL); break; default: ATLASSERT(FALSE); m_Variant.vt = VT_NULL; break; } } }; ///////////////////////////////////////////// // CXMLGenericElement ///////////////////////////////////////////// CXMLGenericElement::CXMLGenericElement(LPCWSTR wzName, VARTYPE vt, TagInformation **ptiaChildren, AttributeInformation **paiaAttributes) { m_paiaAttributes = paiaAttributes; m_ptiaChildren = ptiaChildren; m_vtData = vt; m_varData.vt = VT_EMPTY; m_bstrName = wzName; ATLASSERT(! m_psnodeAttributes); // The header node is a member variable. m_psnodeChildrenFirst = &m_snodeChildrenFirst; m_psnodeChildrenFirst->m_pvData = NULL; m_psnodeChildrenFirst->m_psnodeNext = NULL; m_psnodeChildrenEnd = m_psnodeChildrenFirst; ATLASSERT(! m_psnodeChildrenIter); } CXMLGenericElement::~CXMLGenericElement() { CAttributeStruct *pattStruct; CXMLElement *pxElem; CSimpleNode *psnodeTemp; while (m_psnodeAttributes) { pattStruct = (CAttributeStruct *) m_psnodeAttributes->m_pvData; psnodeTemp = m_psnodeAttributes; m_psnodeAttributes = m_psnodeAttributes->m_psnodeNext; delete pattStruct; delete psnodeTemp; } // Don't delete the header - a statically allocated member m_psnodeChildrenFirst = m_psnodeChildrenFirst->m_psnodeNext; while (m_psnodeChildrenFirst) { pxElem = (CXMLElement *) m_psnodeChildrenFirst->m_pvData; psnodeTemp = m_psnodeChildrenFirst; m_psnodeChildrenFirst = m_psnodeChildrenFirst->m_psnodeNext; pxElem->Release_Ref(); delete psnodeTemp; } } HRESULT CXMLGenericElement::SetInnerXMLText(LPCWSTR pwzText, ULONG cLen) { HRESULT hr = S_OK; BOOL fAppend = FALSE; if ((m_varData.vt != VT_EMPTY) && (m_varData.vt != VT_NULL)) { fAppend = TRUE; } m_varData.vt = m_vtData; switch (m_vtData) { case VT_I4: { ATLASSERT(!fAppend); LONG lVal; hr = StrToLongNW(pwzText, cLen, &lVal); if (SUCCEEDED(hr)) { m_varData = lVal; // this assignment should never fail; any possible errors are unexpected ATLASSERT(m_varData.vt != VT_ERROR); } else { SpewUnrecognizedString(pwzText, cLen, L"StrToLongNW failed in SetInnerXMLText, "); hr = S_FALSE; } break; } case VT_BSTR: { if (fAppend) { BSTR bstrOld = m_varData.bstrVal; UINT cb_bstrOld = SysStringLen(m_varData.bstrVal); m_varData.bstrVal = SysAllocStringLen(bstrOld, cb_bstrOld + cLen); if (m_varData.bstrVal) { StrCpyN(m_varData.bstrVal + cb_bstrOld, pwzText, cLen + 1); hr = S_OK; } else { hr = E_OUTOFMEMORY; } SysFreeString(bstrOld); } else { m_varData.bstrVal = SysAllocStringLen(pwzText, cLen); if (!m_varData.bstrVal) hr = E_OUTOFMEMORY; } break; } case VT_NULL: case VT_EMPTY: break; default: m_varData = VT_EMPTY; hr = S_FALSE; break; } return hr; } HRESULT CXMLGenericElement::SetAttribute(const WCHAR *pwzName, ULONG cNameLen, const WCHAR *pwzText, ULONG cTextLen) { // It is expected that the number of attributes is small // Hence we use a simple linked list with O(1) insertions and O(n)queries // No syntax checking (ie, dups) is done HRESULT hr = S_FALSE; int i; LPCWSTR pszAttribName = NULL; if (m_paiaAttributes) { for (i = 0; m_paiaAttributes[i]; i++) { // wzAttName NULL is the "default" attribute if (m_paiaAttributes[i]->wzAttName == NULL) { pszAttribName = pwzName; break; } else if (StrEqlNToSZ(pwzName, cNameLen, m_paiaAttributes[i]->wzAttName)) { break; } } if (m_paiaAttributes[i]) { CAttributeStruct *pattStruct; if (pszAttribName) { pattStruct = new CAttributeStruct(-1, m_paiaAttributes[i]->vt, pwzText, cTextLen, pszAttribName); } else { pattStruct = new CAttributeStruct(i, m_paiaAttributes[i]->vt, pwzText, cTextLen, pszAttribName); } if (pattStruct) { CSimpleNode *psnode = new CSimpleNode(); if (psnode) { // add to front of list psnode->m_psnodeNext = m_psnodeAttributes; psnode->m_pvData = pattStruct; m_psnodeAttributes = psnode; hr = S_OK; } else { hr = E_OUTOFMEMORY; delete pattStruct; } } else { hr = E_OUTOFMEMORY; } } } if (hr == S_FALSE) { SpewUnrecognizedString(pwzName, cNameLen, L"Trying to set unrecognized attribute in SetAttribute, "); } return hr; } HRESULT CXMLGenericElement::GetContent(VARIANT *pvarOut) { // Returns S_OK on attribute found, E_INVALIDARG on not. // ppvarOut can be NULL; if it is not, then it is a pointer to the content VARIANT HRESULT hr = E_INVALIDARG; if ((m_varData.vt != VT_EMPTY) && (m_varData.vt != VT_NULL)) { if (pvarOut) { VariantInit(pvarOut); hr = VariantCopy(pvarOut, &m_varData); } } else { if (pvarOut) { VariantInit(pvarOut); } } return hr; } HRESULT CXMLGenericElement::GetAttribute(LPCWSTR wzName, VARIANT *pvarOut) { // Returns S_OK on attribute found, E_INVALIDARG if not. // ppvarOut can be NULL; if it is not, then it is a pointer to the attribute value VARIANT // It is expected that the number of attributes is small // Hence we use a simple linked list with O(1) insertions and O(n)queries // No syntax checking (ie, dups) is done HRESULT hr = E_INVALIDARG; CSimpleNode *psnode = m_psnodeAttributes; CAttributeStruct *pattStruct; while (psnode) { pattStruct = (CAttributeStruct *) psnode->m_pvData; if (((pattStruct->m_iArrayIndex < 0) && StrEql(wzName, pattStruct->m_bstrAttribName)) || (StrEql(m_paiaAttributes[pattStruct->m_iArrayIndex]->wzAttName, wzName))) { hr = S_OK; if (pvarOut) { VariantInit(pvarOut); VariantCopy(pvarOut, &(pattStruct->m_Variant)); } break; } psnode = psnode->m_psnodeNext; } if (!psnode) { // don't fire a trace message if our caller is just pinging us // to see if we have the attribute if (pvarOut) { VariantInit(pvarOut); } } return hr; } void CXMLGenericElement::FirstChild() { m_psnodeChildrenIter = m_psnodeChildrenFirst->m_psnodeNext; } void CXMLGenericElement::NextChild() { if (m_psnodeChildrenIter) { m_psnodeChildrenIter = m_psnodeChildrenIter->m_psnodeNext; } else { ATLASSERT(FALSE); } } CXMLElement *CXMLGenericElement::CurrentChild() { CXMLElement *pxeReturn; if (m_psnodeChildrenIter && (m_psnodeChildrenIter != m_psnodeChildrenFirst)) { pxeReturn = (CXMLElement *) m_psnodeChildrenIter->m_pvData; } else { ATLASSERT(FALSE); pxeReturn = NULL; } return pxeReturn; } // caller gets our ref to the CXMLElement CXMLElement *CXMLGenericElement::DetachCurrentChild() { CXMLElement *pxeReturn; if (m_psnodeChildrenIter) { pxeReturn = (CXMLElement *) m_psnodeChildrenIter->m_pvData; CSimpleNode *psnodeHack = m_psnodeChildrenFirst; while (psnodeHack->m_psnodeNext != m_psnodeChildrenIter) { psnodeHack = psnodeHack->m_psnodeNext; // If this assertion is broken, then m_psnodeChildrenIter is not // in the list ATLASSERT(psnodeHack); } // here psnodeHack=>m_psnodeChildrenIter=>...=>m_psnodeChildrenEnd // update the end of the list if (psnodeHack->m_psnodeNext == m_psnodeChildrenEnd) { m_psnodeChildrenEnd = psnodeHack; } // delete the current NODE (but not the data in the node) and set the iterator // to the previous node (psnodeHack) psnodeHack->m_psnodeNext = psnodeHack->m_psnodeNext->m_psnodeNext; delete m_psnodeChildrenIter; m_psnodeChildrenIter = psnodeHack; } else { ATLASSERT(FALSE); pxeReturn = NULL; } return pxeReturn; } HRESULT CXMLGenericElement::AddChild(CXMLElement *pxeChild) { HRESULT hr = S_FALSE; LPCWSTR pwzName = pxeChild->GetName(); int i; if (m_ptiaChildren) { for (i = 0; m_ptiaChildren[i]; i++) { if (StrEql(m_ptiaChildren[i]->wzTagName, pwzName)) { break; } } if (m_ptiaChildren[i]) { CSimpleNode *psnode; psnode = new CSimpleNode; if (psnode) { pxeChild->Add_Ref(); psnode->m_pvData = (void *) pxeChild; psnode->m_psnodeNext = NULL; m_psnodeChildrenEnd->m_psnodeNext = psnode; m_psnodeChildrenEnd = psnode; // S_OK to indicate we're taking ownership of the child hr = S_OK; } else { hr = E_OUTOFMEMORY; } } } return hr; } BOOL CXMLGenericElement::IsDoneChild() { return !(m_psnodeChildrenIter && (m_psnodeChildrenIter != m_psnodeChildrenFirst)); } CXMLElement *CXMLGenericElement::CreateInstance(LPCWSTR wzName, VARTYPE vt, TagInformation **ptiaChildren, AttributeInformation **paiaAttributes, LONG) { return new CXMLGenericElement(wzName, vt, ptiaChildren, paiaAttributes); } LPCWSTR CXMLGenericElement::GetName() { return m_bstrName; } HRESULT CXMLGenericElement::OnNodeComplete() { return S_OK; } ///////////////////////////////////////////// // Global Helper functions ///////////////////////////////////////////// BOOL StrEqlNToSZ(const WCHAR *wzN, int n, const WCHAR *wzSZ) { int i; for (i = 0; i < n; i++) { if (wzN[i] != wzSZ[i]) { return FALSE; } } // make sure the zero terminated string is ending here return (wzSZ[n] == L'\0'); } bool StrToIsTrueNW(const WCHAR *wz, ULONG cLen) { if (cLen == 4 && (StrEqlNToSZ(wz, cLen, L"true") || StrEqlNToSZ(wz, cLen, L"TRUE"))) { return true; } else { return false; } } #ifndef IS_DIGITW #ifndef InRange #define InRange(id, idFirst, idLast) ((UINT)((id)-(idFirst)) <= (UINT)((idLast)-(idFirst))) #endif #define IS_DIGITW(ch) InRange(ch, L'0', L'9') #endif // convert a wide string to a long, assuming the string has a max of cLen characters. // L'\0' is recognized to stop earlier, but any other non-digit will cause a return // of E_INVALIDARG HRESULT StrToLongNW(const WCHAR *wzString, ULONG cLen, LONG *plong) { HRESULT hr = S_OK; ATLASSERT(plong); *plong = 0; UINT i = 0; BOOL bNeg = FALSE; if ((i < cLen) && wzString[i] == L'-') { bNeg = TRUE; i++; } while ((i < cLen) && IS_DIGITW(wzString[i])) { *plong *= 10; *plong += wzString[i] - L'0'; i++; } if ((i < cLen) && (wzString[i] != L'\0')) { *plong = 0; hr = E_INVALIDARG; } else { if (bNeg) *plong = -(*plong); } return hr; }