// =================================================================== // Class: COXParser (including COXParserObject, COXAttribute, COXParserElement and COXToken) // File: OXParser.cpp // Purpose: Parser for XML like structures // Version: 9.3 // This software along with its related components, documentation and files ("The Libraries") // is © 1994-2007 The Code Project (1612916 Ontario Limited) and use of The Libraries is // governed by a software license agreement ("Agreement"). Copies of the Agreement are // available at The Code Project (www.codeproject.com), as part of the package you downloaded // to obtain this file, or directly from our office. For a copy of the license governing // this software, you may contact us at legalaffairs@codeproject.com, or by calling 416-849-8900. // ////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "OXParser.h" #include "UTBStrop.h" #ifndef CSTR_EQUAL #define CSTR_EQUAL 2 // string 1 equal to string 2 #endif ////////////////////////////////////////////////////////////////////// // Macros for debugging ////////////////////////////////////////////////////////////////////// #ifndef TRACE0 // If this isn't defined, a whole bunch of stuff isn't defined #undef TRACE1 #undef TRACE2 #undef TRACE3 #undef UNUSED_ALWAYS #undef UNUSED #define UNUSED_ALWAYS(x) (x); #ifdef _DEBUG #define UNUSED(x) #include #define TRACE0(text) _RPT0(_CRT_WARN, text) #define TRACE1(format, a1) _RPT1(_CRT_WARN, format, a1) #define TRACE2(format, a1, a2) _RPT2(_CRT_WARN, format, a1, a2) #define TRACE3(format, a1, a2, a3) _RPT3(_CRT_WARN, format, a1, a2, a3) #else #define UNUSED(x) (x); #define TRACE0(text) #define TRACE1(format, a1) #define TRACE2(format, a1, a2) #define TRACE3(format, a1, a2, a3) #endif #endif #ifdef _DEBUG #define TRACE4(format, a1, a2, a3, a4 ) _RPT4(_CRT_WARN, format, a1, a2, a3, a4) #else #define TRACE4(format, a1, a2, a3, a4) #endif ////////////////////////////////////////////////////////////////////// // Static data ////////////////////////////////////////////////////////////////////// // This is the list of default entities. Just add entities to this list // and they will be automatically encoded/decoded during text processing. // Note that when the parser writes out text, it will only convert from // text to entity (eg '>' ==> >) when the literal text is a single character // (so if you add "date == "10 May" and "10 May" appears in text, it won't // be converted to "&date;") ParserEntity COXParser::m_Entity[] = { {TEXT("gt"), TEXT(">")}, {TEXT("lt"), TEXT("<")}, {TEXT("amp"), TEXT("&")}, {TEXT("apos"), TEXT("\'")}, {TEXT("quot"), TEXT("\"")}, {TEXT("quot"), TEXT("\"")}, {TEXT("amp"), TEXT("&")}, {TEXT("apos"), TEXT("\'")}, {TEXT("lt"), TEXT("<")}, {TEXT("gt"), TEXT(">")}, {TEXT("nbsp"), TEXT(" ")}, {TEXT("iexcl"), TEXT("¡")}, {TEXT("cent"), TEXT("¢")}, {TEXT("pound"), TEXT("£")}, {TEXT("curren"), TEXT("¤")}, {TEXT("yen"), TEXT("¥")}, {TEXT("brvbar"), TEXT("¦")}, {TEXT("sect"), TEXT("§")}, {TEXT("uml"), TEXT("¨")}, {TEXT("copy"), TEXT("©")}, {TEXT("ordf"), TEXT("ª")}, {TEXT("laquo"), TEXT("«")}, {TEXT("not"), TEXT("¬")}, {TEXT("shy"), TEXT("­")}, {TEXT("reg"), TEXT("®")}, {TEXT("macr"), TEXT("¯")}, {TEXT("deg"), TEXT("°")}, {TEXT("plusmn"), TEXT("±")}, {TEXT("sup2"), TEXT("²")}, {TEXT("sup3"), TEXT("³")}, {TEXT("acute"), TEXT("´")}, {TEXT("micro"), TEXT("µ")}, {TEXT("para"), TEXT("¶")}, {TEXT("middot"), TEXT("·")}, {TEXT("cedil"), TEXT("¸")}, {TEXT("sup1"), TEXT("¹")}, {TEXT("ordm"), TEXT("º")}, {TEXT("raquo"), TEXT("»")}, {TEXT("frac14"), TEXT("¼")}, {TEXT("frac12"), TEXT("½")}, {TEXT("frac34"), TEXT("¾")}, {TEXT("iquest"), TEXT("¿")}, {TEXT("Agrave"), TEXT("À")}, {TEXT("Aacute"), TEXT("Á")}, {TEXT("Acirc"), TEXT("Â")}, {TEXT("Atilde"), TEXT("Ã")}, {TEXT("Auml"), TEXT("Ä")}, {TEXT("Aring"), TEXT("Å")}, {TEXT("AElig"), TEXT("Æ")}, {TEXT("Ccedil"), TEXT("Ç")}, {TEXT("Egrave"), TEXT("È")}, {TEXT("Eacute"), TEXT("É")}, {TEXT("Ecirc"), TEXT("Ê")}, {TEXT("Euml"), TEXT("Ë")}, {TEXT("Igrave"), TEXT("Ì")}, {TEXT("Iacute"), TEXT("Í")}, {TEXT("Icirc"), TEXT("Î")}, {TEXT("Iuml"), TEXT("Ï")}, {TEXT("ETH"), TEXT("Ð")}, {TEXT("Ntilde"), TEXT("Ñ")}, {TEXT("Ograve"), TEXT("Ò")}, {TEXT("Oacute"), TEXT("Ó")}, {TEXT("Ocirc"), TEXT("Ô")}, {TEXT("Otilde"), TEXT("Õ")}, {TEXT("Ouml"), TEXT("Ö")}, {TEXT("times"), TEXT("×")}, {TEXT("Oslash"), TEXT("Ø")}, {TEXT("Ugrave"), TEXT("Ù")}, {TEXT("Uacute"), TEXT("Ú")}, {TEXT("Ucirc"), TEXT("Û")}, {TEXT("Uuml"), TEXT("Ü")}, {TEXT("Yacute"), TEXT("Ý")}, {TEXT("THORN"), TEXT("Þ")}, {TEXT("szlig"), TEXT("ß")}, {TEXT("agrave"), TEXT("à")}, {TEXT("aacute"), TEXT("á")}, {TEXT("acirc"), TEXT("â")}, {TEXT("atilde"), TEXT("ã")}, {TEXT("auml"), TEXT("ä")}, {TEXT("aring"), TEXT("å")}, {TEXT("aelig"), TEXT("æ")}, {TEXT("ccedil"), TEXT("ç")}, {TEXT("egrave"), TEXT("è")}, {TEXT("eacute"), TEXT("é")}, {TEXT("ecirc"), TEXT("ê")}, {TEXT("euml"), TEXT("ë")}, {TEXT("igrave"), TEXT("ì")}, {TEXT("iacute"), TEXT("í")}, {TEXT("icirc"), TEXT("î")}, {TEXT("iuml"), TEXT("ï")}, {TEXT("eth"), TEXT("ð")}, {TEXT("ntilde"), TEXT("ñ")}, {TEXT("ograve"), TEXT("ò")}, {TEXT("oacute"), TEXT("ó")}, {TEXT("ocirc"), TEXT("ô")}, {TEXT("otilde"), TEXT("õ")}, {TEXT("ouml"), TEXT("ö")}, {TEXT("divide"), TEXT("÷")}, {TEXT("oslash"), TEXT("ø")}, {TEXT("ugrave"), TEXT("ù")}, {TEXT("uacute"), TEXT("ú")}, {TEXT("ucirc"), TEXT("û")}, {TEXT("uuml"), TEXT("ü")}, {TEXT("yacute"), TEXT("ý")}, {TEXT("thorn"), TEXT("þ")}, {TEXT("yuml"), TEXT("ÿ")}, { NULL, NULL } // must be last }; ////////////////////////////////////////////////////////////////////// // COXToken ////////////////////////////////////////////////////////////////////// COXToken::COXToken() { Clear(); } COXToken::~COXToken() { Clear(); } // --- In  : // --- Out : // --- Returns : // --- Effect : Resets the token to an empty state void COXToken::Clear() { m_nType = UNKNOWN; } ////////////////////////////////////////////////////////////////////// // COXParserObject ////////////////////////////////////////////////////////////////////// COXParserObject::COXParserObject() { m_nType = UNKNOWN; m_pParent = NULL; m_nFlags = 0; } COXParserObject::COXParserObject(COXParserElement* pParent, int nType, LPCTSTR szText) { m_pParent = pParent; m_nType = nType; m_nFlags = 0; SetText(szText); } COXParserObject::~COXParserObject() { } COXParserElement* COXParserObject::GetParent() const { if (m_pParent && m_pParent->GetType() == ELEMENT) return m_pParent; else return NULL; } BOOL COXParserObject::SetText(LPCTSTR szText) { return m_Text.SetString(szText); } BOOL COXParserObject::IsText(LPCTSTR szText, BOOL bCaseSensitive /*=FALSE*/) { return m_Text.Compare(szText, bCaseSensitive); } ////////////////////////////////////////////////////////////////////// // COXAttribute ////////////////////////////////////////////////////////////////////// COXAttribute::COXAttribute() { m_nValue = 0; SetType(ATTRIBUTE); SetAttributeType(ATTR_UNKNOWN); } COXAttribute::COXAttribute(LPCTSTR szName, LPCTSTR szValue) { m_nValue = 0; SetName(szName); SetValue(szValue); } COXAttribute::COXAttribute(LPCTSTR szName, int nValue) { SetName(szName); SetValue(nValue); } COXAttribute::~COXAttribute() { m_szValue.Empty(); } LPCTSTR COXAttribute::GetStringValue() { static TCHAR strValue[20]; if (GetAttributeType() == ATTR_INTEGER) { UTBStr::stprintf(strValue, 20, TEXT("%d"), m_nValue); return strValue; } else if (m_szValue.IsEmpty()) { strValue[0] = TEXT('\0'); return strValue; } else return m_szValue.GetString(); } int COXAttribute::GetIntValue() const { if (GetAttributeType() == ATTR_STRING) return 0; else return m_nValue; } void COXAttribute::SetValue(LPCTSTR szValue) { m_szValue.SetString(szValue); SetAttributeType(ATTR_STRING); } void COXAttribute::SetValue(int nValue) { if (GetAttributeType() == ATTR_STRING) m_szValue.Empty(); m_nValue = nValue; SetAttributeType(ATTR_INTEGER); } BOOL COXAttribute::IsValue(LPCTSTR szValue, BOOL bCaseSensitive /*=FALSE*/) { if (!szValue || GetAttributeType() != ATTR_STRING || !m_szValue) return FALSE; BOOL bFound = FALSE; if (!bCaseSensitive) bFound = (_tcsicmp(m_szValue, szValue) == 0); else bFound = (_tcscmp(m_szValue, szValue) == 0); return bFound; } void COXAttribute::operator=(COXAttribute& Attr) { SetType(Attr.GetType()); SetName(Attr.GetName()); if (Attr.GetAttributeType() == ATTR_STRING) SetValue(Attr.GetStringValue()); else SetValue(Attr.GetIntValue()); } ////////////////////////////////////////////////////////////////////// // COXParserElement ////////////////////////////////////////////////////////////////////// COXParserElement::COXParserElement() { SetType(ELEMENT); } COXParserElement::COXParserElement(COXParserElement* pParent, LPCTSTR szName) { SetParent(pParent); SetType(ELEMENT); SetName(szName); } COXParserElement::~COXParserElement() { Clear(); } COXParserObject* COXParserElement::AddObject(COXParserObject* pObject) { pObject->SetParent(this); m_Objects.push_back(pObject); return pObject; } COXParserElement* COXParserElement::AddElement(LPCTSTR szName) { COXParserElement *pElement = new COXParserElement(this, szName); if (!pElement) return NULL; return (COXParserElement*) AddObject(pElement); } COXParserObject* COXParserElement::InsertObject(int nIndex, COXParserObject* pObject) { pObject->SetParent(this); m_Objects.insert(m_Objects.begin() + nIndex, pObject); return pObject; } COXParserObject* COXParserElement::MoveObject(int nIndex, COXParserObject* pObject) { if (!pObject) return FALSE; // Find object and remove it from the list for (int i = 0; i < NumObjects(); i++) { if (Object(i) == pObject) { m_Objects.erase(m_Objects.begin() + i); break; } } return InsertObject(nIndex, pObject); } COXParserElement* COXParserElement::InsertElement(int nIndex, LPCTSTR szName) { COXParserElement *pElement = new COXParserElement(this, szName); if (!pElement) return NULL; return (COXParserElement*) InsertObject(nIndex, pElement); } BOOL COXParserElement::DelObject(int nIndex) { if (nIndex >= NumObjects()) return FALSE; COXParserObject *pObject = Object(nIndex); if (pObject) delete pObject; m_Objects.erase(m_Objects.begin() + nIndex); return TRUE; } BOOL COXParserElement::DelObject(COXParserObject* pObject) { if (!pObject) return FALSE; for (int i = 0; i < NumObjects(); i++) { if (Object(i) == pObject) return DelObject(i); } return FALSE; } void COXParserElement::ClearObjects() { for (int i = 0; i < NumObjects(); i++) { COXParserObject* pObject = Object(i); if (pObject) delete pObject; } m_Objects.clear(); } #if _MSC_VER >=1200 #pragma warning(push) #endif #pragma warning(disable:4239) COXParserElement* COXParserElement::FindElement(LPCTSTR szName, BOOL bCaseSensitive /*=FALSE*/) { COXQuickString str = szName; UINT nLevel = 0; COXQuickString strFolderName = str.GetToken(nLevel++, TEXT('\\')); if (strFolderName.IsEmpty()) return NULL; COXParserElement* pElement = this; COXParserElement* pTargetElement = NULL; while (!strFolderName.IsEmpty()) { pTargetElement = NULL; for (int i = 0; i < pElement->NumObjects(); i++) { COXParserElement* pSearchElement = (COXParserElement*) pElement->Object(i); if (!pSearchElement || pSearchElement->GetType() != ELEMENT) continue; // Found folder if (pSearchElement->IsName(strFolderName, bCaseSensitive)) { pTargetElement = pSearchElement; pElement = pTargetElement; break; } } strFolderName = str.GetToken(nLevel++, TEXT('\\')); } return pTargetElement; } #if _MSC_VER >=1200 #pragma warning(pop) #else #pragma warning(default:4239) #endif COXAttribute* COXParserElement::AddAttribute(COXAttribute* pAttribute) { pAttribute->SetParent(this); m_Attributes.push_back(pAttribute); return pAttribute; } COXAttribute* COXParserElement::AddAttribute(LPCTSTR szName, LPCTSTR szValue) { COXAttribute *pAttribute = new COXAttribute; if (!pAttribute) return NULL; pAttribute->SetName(szName); pAttribute->SetValue(szValue); pAttribute->SetParent(this); return AddAttribute(pAttribute); } COXAttribute* COXParserElement::AddAttribute(LPCTSTR szName, int nValue) { COXAttribute *pAttribute = new COXAttribute; if (!pAttribute) return NULL; pAttribute->SetName(szName); pAttribute->SetValue(nValue); pAttribute->SetParent(this); return AddAttribute(pAttribute); } BOOL COXParserElement::DelAttribute(int nIndex) { if (nIndex >= NumAttributes()) return FALSE; COXAttribute *pAttribute = Attribute(nIndex); delete pAttribute; m_Attributes.erase(m_Attributes.begin() + nIndex); return TRUE; } BOOL COXParserElement::DelAttribute(COXAttribute* pAttribute) { if (!pAttribute) return FALSE; for (int i = 0; i < NumAttributes(); i++) { if (Attribute(i) == pAttribute) return DelAttribute(i); } return FALSE; } void COXParserElement::ClearAttributes() { for (int i = 0; i < NumAttributes(); i++) { COXAttribute *pAttribute = Attribute(i); delete pAttribute; } m_Attributes.clear(); } COXAttribute* COXParserElement::FindAttribute(LPCTSTR szName, bool bCaseSensitive /*=false*/) { if (!szName) return NULL; for (int i = 0; i < NumAttributes(); i++) { COXAttribute *pAttribute = Attribute(i); if (pAttribute->IsName(szName, (BOOL) bCaseSensitive)) return pAttribute; } return NULL; } COXAttribute* COXParserElement::FindAttribute(LPCTSTR szName, LPCTSTR szValue, bool bCaseSensitive /*=false*/) { if (!szName || !szValue) return NULL; for (int i = 0; i < NumAttributes(); i++) { COXAttribute* pAttribute = Attribute(i); if (!pAttribute || pAttribute->GetAttributeType() != COXAttribute::ATTR_STRING) continue; if (pAttribute->IsName(szName, (BOOL) bCaseSensitive) && pAttribute->IsValue(szValue, (BOOL) bCaseSensitive)) return pAttribute; } return NULL; } COXAttribute* COXParserElement::FindAttribute(LPCTSTR szName, int nValue, bool bCaseSensitive /*=false*/) { if (!szName) return NULL; for (int i = 0; i < NumAttributes(); i++) { COXAttribute* pAttribute = Attribute(i); if (!pAttribute || pAttribute->GetAttributeType() != COXAttribute::ATTR_INTEGER) continue; if (pAttribute->IsName(szName, (BOOL) bCaseSensitive) && pAttribute->GetIntValue() == nValue) return pAttribute; } return NULL; } ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// COXParser::COXParser() { m_chStartDelim = TEXT('<'); m_chEndDelim = TEXT('>'); m_chTagEnd = TEXT('/'); m_chProcInstr = TEXT('?'); m_chMarkup = TEXT('!'); m_chDash = TEXT('-'); m_chCR = TEXT('\r'); m_chLF = TEXT('\n'); m_chNULL = TEXT('\0'); m_chEsc = TEXT('&'); m_szTab = TEXT(" "); // indent tab when writing XML files m_nLineLength = 80; // Max length of a line of text when writing files m_bErrorOnMissingTag = TRUE; m_bCaseSensitive = TRUE; m_nLine = 0; m_nColumn = 0; m_pfnErrorFn = NULL; m_dwErrData = 0; // Fill hash table with special characters for (int i = 0; m_Entity[i].szName; i++) m_EntityTable.Add(m_Entity[i].szName, (DWORD)(INT_PTR) m_Entity[i].szLiteral); } COXParser::~COXParser() { Clear(); } ////////////////////////////////////////////////////////////////////// // Parsing routines ////////////////////////////////////////////////////////////////////// void COXParser::Clear() { m_Token.Clear(); m_Root.ClearObjects(); m_Root.ClearAttributes(); m_nLine = 0; m_nColumn = 0; m_pBuf = m_pBufStart = NULL; } BOOL COXParser::Initialize() { m_pBuf = m_pBufStart; m_nLine = 1; m_nColumn = 1; m_Root.SetName(TEXT("[Root]")); m_Root.SetParent(NULL); return TRUE; } BOOL COXParser::Cleanup() { return TRUE; } BOOL COXParser::IsEmpty() const { return (!m_Root.NumAttributes() && !m_Root.NumObjects()); } void COXParser::SetErrorRptFunction(ParserErrorFn pFn, DWORD dwData) { m_pfnErrorFn = pFn; m_dwErrData = dwData; } BOOL COXParser::Parse(LPTSTR pBuf) { if (!pBuf) return FALSE; Clear(); m_pBufStart = pBuf; Initialize(); BOOL bResult = ParseElement(&m_Root, 0); Cleanup(); return bResult; } TCHAR COXParser::GetNextChar(int nSteps /*=1*/, BOOL bWarnOnError /*=FALSE*/) { ASSERT(m_pBuf); if (!m_pBuf) { if (bWarnOnError) ReportError(ERROR_NULL_BUFFER, TEXT("NULL buffer supplied.")); return m_chNULL; } TCHAR ch = *m_pBuf; for (int i = 0; i < nSteps && ch; i++) { ch = *m_pBuf; if (!ch) break; m_pBuf++; // Convert CRLF's to LF's, and CR's to LF's if (ch == m_chCR) { ch = m_chLF; if (*m_pBuf == m_chLF) m_pBuf++; } m_nColumn++; if (ch == m_chLF) { m_nLine++; m_nColumn = 0; } } if (ch == m_chNULL) { if (bWarnOnError) ReportError(ERROR_END_OF_BUFFER, TEXT("Unexpected end of buffer.")); return m_chNULL; } return ch; } void COXParser::UngetChar(int nSteps /*=1*/) { for (int i = 0; i < nSteps; i++) { if (m_pBuf <= m_pBufStart) break; m_pBuf--; if (*m_pBuf == m_chLF || (*m_pBuf == m_chCR && *(m_pBuf+1) != m_chLF) ) { m_nLine--; m_nColumn = 0; for (LPTSTR ptr = m_pBuf; ptr >= m_pBufStart && *ptr != m_chLF && *ptr != m_chCR; ptr--) m_nColumn++; } else m_nColumn--; } } BOOL COXParser::RemoveWhiteSpace() { TCHAR ch = GetNextChar(); while (ch && _istspace(ch)) ch = GetNextChar(); if (ch) { UngetChar(); return TRUE; } else { return FALSE; } } void COXParser::SaveBufferPos(SAVEPOS& pos) { pos.pBuf = m_pBuf; pos.nLine = m_nLine; pos.nCol = m_nColumn; } void COXParser::RestoreBufferPos(SAVEPOS& pos) { m_pBuf = pos.pBuf; m_nLine = pos.nLine; m_nColumn = pos.nCol; } BOOL COXParser::GetNameToken(COXQuickString& str) { str.Empty(); str.SetLength(100); TCHAR ch = GetNextChar(); while (ch && !_istspace(ch) && (_istalnum(ch) || ch == TEXT(':') || ch == TEXT('_') || ch == TEXT('-')) ) { str.Append(ch); ch = GetNextChar(); } BOOL bResult = TRUE; if (ch == m_chNULL) { ReportError(ERROR_END_OF_BUFFER, TEXT("Unexpected end of buffer while name.")); bResult = FALSE; } else if (ch != m_chEndDelim && ch != m_chTagEnd && ch != TEXT('=') && !_istspace(ch)) { ReportError(ERROR_ILLEGAL_CHAR, TEXT("Illegal character (%c) encountered while parsing name."), ch); bResult = FALSE; } if (bResult) UngetChar(); else str.Empty(); return bResult; } BOOL COXParser::GetNumberToken(int& nNumber) { COXQuickString str; str.SetLength(20); int nMultiplier = 1; TCHAR ch = GetNextChar(); if (ch == TEXT('-')) { nMultiplier = -1; ch = GetNextChar(); } else if (ch == TEXT('+')) ch = GetNextChar(); while (_istdigit(ch)) { str.Append(ch); ch = GetNextChar(); } if (ch == m_chNULL) { ReportError(ERROR_END_OF_BUFFER, TEXT("Unexpected end of buffer while parsing integer.")); str.Empty(); return FALSE; } UngetChar(); nNumber = _ttoi((LPCTSTR) str) * nMultiplier; return TRUE; } BOOL COXParser::GetStringToken(COXQuickString& str, TCHAR chQuoteChar) { str.Empty(); str.SetLength(100); TCHAR ch = GetNextChar(); while (ch && ch != chQuoteChar) { #ifdef OXPARSER_CONVERT_ENTITY if (ch!=m_chEsc) { #endif str.Append(ch); ch = GetNextChar(); #ifdef OXPARSER_CONVERT_ENTITY } else { BOOL bContinue=TRUE; COXQuickString sEsc; do { ch=GetNextChar(); if (!ch || ch==chQuoteChar) bContinue=FALSE; else if (ch!=TEXT(';')) sEsc.Append(ch); } while(bContinue && ch!=TEXT(';')); if (bContinue) { LPCTSTR lpSymb=GetLiteralString((LPCTSTR) sEsc); if (lpSymb) { str.Append(*lpSymb); } else { str.Append(TEXT('&')); str.AddString((LPCTSTR) sEsc); str.Append(TEXT(';')); } ch=GetNextChar(); } } #endif } if (ch != chQuoteChar) { ReportError(ERROR_END_OF_BUFFER, TEXT("Unexpected end of buffer while parsing string.")); str.Empty(); return FALSE; } TRACE1("%s\n",(LPCTSTR) str); return TRUE; } BOOL COXParser::GetToken(COXToken& token, BOOL bWarnOnEOF /*=FALSE*/) { if (!RemoveWhiteSpace()) return FALSE; token.SetType(COXToken::UNKNOWN); TCHAR ch = GetNextChar(1, bWarnOnEOF); if (!ch) return FALSE; // "<" if (ch == m_chStartDelim) { ch = GetNextChar(1, bWarnOnEOF); if (!ch) return FALSE; // "" else if (ch == m_chEndDelim) { token.SetType(COXToken::CLOSE_TAG_BRACKET); } // "/>" else if (ch == m_chTagEnd) { ch = GetNextChar(1, bWarnOnEOF); if (!ch) return FALSE; if (ch == m_chEndDelim) token.SetType(COXToken::CLOSE_EMPTYTAG_BRACKET); else { UngetChar(2); token.SetType(COXToken::STRING); } } // "=" else if (ch == TEXT('=')) { token.SetType(COXToken::EQUAL_SIGN); } // """ else if (ch == TEXT('"')) { token.SetType(COXToken::QUOTE); } // "'" else if (ch == TEXT('\'')) { token.SetType(COXToken::APOSTROPHE); } // "-->" else if (ch == m_chDash) { ch = GetNextChar(1, bWarnOnEOF); if (ch != m_chDash) { UngetChar(2); token.SetType(COXToken::STRING); } else { ch = GetNextChar(1, bWarnOnEOF); if (ch != m_chEndDelim) { UngetChar(3); token.SetType(COXToken::STRING); } else token.SetType(COXToken::CLOSE_COMMENT_BRACKET); } } // Plain ol' text else { UngetChar(); token.SetType(COXToken::STRING); } #if defined(_DEBUG) && 0 USES_CONVERSION; LPCSTR strToken = NULL; switch (token.GetType()) { case COXToken::OPEN_PROCINSTR_BRACKET: strToken = "OPEN_PROCINSTR_BRACKET"; break; case COXToken::OPEN_ENDTAG_BRACKET: strToken = "OPEN_ENDTAG_BRACKET"; break; case COXToken::OPEN_COMMENT_BRACKET: strToken = "OPEN_COMMENT_BRACKET"; break; case COXToken::OPEN_MARKUP_BRACKET: strToken = "OPEN_MARKUP_BRACKET"; break; case COXToken::OPEN_TAG_BRACKET: strToken = "OPEN_TAG_BRACKET"; break; case COXToken::CLOSE_TAG_BRACKET: strToken = "CLOSE_TAG_BRACKET"; break; case COXToken::CLOSE_EMPTYTAG_BRACKET: strToken = "CLOSE_EMPTYTAG_BRACKET"; break; case COXToken::EQUAL_SIGN: strToken = "EQUAL_SIGN"; break; case COXToken::QUOTE: strToken = "QUOTE"; break; case COXToken::APOSTROPHE: strToken = "APOSTROPHE"; break; case COXToken::CLOSE_COMMENT_BRACKET: strToken = "CLOSE_COMMENT_BRACKET"; break; case COXToken::STRING: strToken = "STRING"; break; case COXToken::UNKNOWN: default: ASSERT(FALSE); } TRACE3("COXParser: Found %s token at Line %d, Col %d.\n", (LPCSTR) strToken, m_nLine, m_nColumn); #endif return TRUE; } void COXParser::AddObjectToElement(COXParserElement* pElement, COXParserObject* pObject) { if (pObject) pElement->AddObject(pObject); } BOOL COXParser::ParseAttributes(COXParserElement* pElement) { BOOL bResult = FALSE; COXAttribute* pAttribute = NULL; do { // What is next in line? If it's not a string value, //it's not a name/value pair pAttribute = NULL; bResult = GetToken(m_Token, TRUE); if (!bResult || m_Token.GetType() != COXToken::STRING) break; // Create a new name Attribute pAttribute = new COXAttribute; if (!pAttribute) { ReportError(ERROR_OUT_OF_MEMORY, TEXT("Unable to create new attribute (element %s)"), pElement->GetName()); bResult = FALSE; break; } // Get the name of the name/value pair COXQuickString str; if (!GetNameToken(str)) { bResult = FALSE; break; } pAttribute->SetName(str); // Search for a "=" sign next if (!GetToken(m_Token)) { ReportError(ERROR_BAD_TOKEN, TEXT("Error while parsing attribute (element %s, name %s)."), pElement->GetName(), pAttribute->GetName()); bResult = FALSE; break; } if (m_Token.GetType() != COXToken::EQUAL_SIGN) { ReportError(ERROR_UNEXPECTED_TOKEN, TEXT("Unexpected token while parsing attribute (element %s, name %s)."), pElement->GetName(), pAttribute->GetName()); bResult = FALSE; break; } // Should have a number, "string" or 'string' value next. if (!GetToken(m_Token)) { ReportError(ERROR_BAD_TOKEN, TEXT("Error while parsing attribute (element %s, name %s)."), pElement->GetName(), pAttribute->GetName()); bResult = FALSE; break; } if (m_Token.GetType() == COXToken::STRING) { int nValue; if (!GetNumberToken(nValue)) { bResult = FALSE; break; } pAttribute->SetValue(nValue); } else if (m_Token.GetType() == COXToken::QUOTE) { if ( !GetStringToken(str, TEXT('"')) ) { bResult = FALSE; break; } pAttribute->SetValue(str); } else if (m_Token.GetType() == COXToken::APOSTROPHE) { if ( !GetStringToken(str, TEXT('\'')) ) { bResult = FALSE; break; } pAttribute->SetValue(str); } else { ReportError(ERROR_UNEXPECTED_TOKEN, TEXT("Unexpected token while parsing attribute (element %s, name %s)."), pElement->GetName(), pAttribute->GetName()); bResult = FALSE; break; } if (bResult) pElement->AddAttribute(pAttribute); } while (bResult); if (bResult) return TRUE; else { delete pAttribute; return FALSE; } } COXParserElement* COXParser::ParseStartTag(COXParserElement* pParent, BOOL& bEmptyTag) { // Get tag name if (!GetToken(m_Token)) { ReportError(ERROR_END_OF_BUFFER, TEXT("Unexpected end of buffer while searching for tag name.")); return NULL; } if (m_Token.GetType() != COXToken::STRING) { ReportError(ERROR_UNEXPECTED_TOKEN, TEXT("Expecting tag name - none found.")); return NULL; } // Get the name of the name/value pair COXQuickString str; if (!GetNameToken(str)) return NULL; COXParserElement* pElement = new COXParserElement(pParent, str); if (!pElement) { ReportError(ERROR_OUT_OF_MEMORY, TEXT("Unable to create new parser Element")); return NULL; } // Search through for attributes BOOL bResult = ParseAttributes(pElement); if (bResult) { bEmptyTag = FALSE; if (m_Token.GetType() == COXToken::CLOSE_TAG_BRACKET) /* do nothing */; else if (m_Token.GetType() == COXToken::CLOSE_EMPTYTAG_BRACKET) bEmptyTag = TRUE; else { bResult = FALSE; ReportError(ERROR_MISSING_END_BRACKET, TEXT("Closing bracket for start tag '%s' not found."), pElement->GetName()); } } if (!bResult) { delete pElement; return NULL; } else return pElement; } BOOL COXParser::IsEndTagMissing(LPCTSTR szCurrentTag, LPCTSTR szNewTag, BOOL NewTagIsEndTag) { UNUSED_ALWAYS(szCurrentTag); UNUSED_ALWAYS(szNewTag); UNUSED_ALWAYS(NewTagIsEndTag); return FALSE; } BOOL COXParser::IgnoreStartTag(COXParserElement* pElement, BOOL bEmptyTag) { UNUSED_ALWAYS(pElement); UNUSED_ALWAYS(bEmptyTag); return FALSE; } BOOL COXParser::IgnoreEndTag(LPCTSTR szEndTag) { UNUSED_ALWAYS(szEndTag); return FALSE; } BOOL COXParser::ParseEndTag(COXParserElement* pElement, COXQuickString& strEndTag) { UNUSED_ALWAYS(pElement); if (!GetToken(m_Token, TRUE)) { ReportError(ERROR_BAD_TOKEN, TEXT("Unexpected end of buffer while parsing endtag.")); return FALSE; } if (m_Token.GetType() != COXToken::STRING) { ReportError(ERROR_UNEXPECTED_TOKEN, TEXT("Missing end tag name.")); return FALSE; } // Get the end tag name if (!GetNameToken(strEndTag)) return FALSE; // peel off the final ">" if (!GetToken(m_Token) || m_Token.GetType() != COXToken::CLOSE_TAG_BRACKET) { ReportError(ERROR_BAD_TOKEN, TEXT("Missing end bracket while parsing end tag '%s'."), (LPCTSTR) strEndTag); return FALSE; } return TRUE; } COXParserObject* COXParser::ParseProcessingInstruction( COXParserElement* pParent) { COXQuickString str; str.SetLength(100); TCHAR ch = GetNextChar(); while (ch) { TCHAR chNextChar = GetNextChar(); if (!chNextChar) { ReportError(ERROR_END_OF_BUFFER, TEXT("Unexpected end of buffer while parsing processing instruction.")); return NULL; } if (ch == m_chProcInstr && chNextChar == m_chEndDelim) ch = m_chNULL; else { str.Append(ch); ch = chNextChar; } } return new COXParserObject(pParent, COXParserObject::PROCINSTR, str); } COXParserObject* COXParser::ParseMarkup(COXParserElement* pParent) { int nMarkupType = COXParserObject::MARKUP; COXQuickString str; str.SetLength(100); if (!_tcsncmp(m_pBuf, TEXT("DOCTYPE"), 7)) { TCHAR ch = GetNextChar(); // Parse initial bit before DTD info while (ch && ch != m_chEndDelim && ch != TEXT('[')) { str.Append(ch); ch = GetNextChar(); } // Parse DTD info if (ch == TEXT('[')) { BOOL bOpened=FALSE; CString sEntity; LPTSTR pData=m_pBuf; TCHAR chSpace=TEXT(' '); TCHAR chTab=TEXT('\t'); while (ch && ch != TEXT(']')) { if (!bOpened) { if (ch==TCHAR(0x3c)) { bOpened=TRUE; pData=m_pBuf; } } else { if (ch==TCHAR(0x3e)) { if (sEntity.GetLength()>8) { int nRet=CompareString(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,(LPCTSTR) sEntity,8, _T("!ENTITY "),8); if (CSTR_EQUAL==nRet) { //found entity sEntity=sEntity.Right(sEntity.GetLength()-8); CString sLiteral=sEntity; int nSpace=sEntity.Find(_T(" ")); if (nSpace!=-1) { pData=pData+nSpace+8; while (*pData==chSpace || *pData==chTab) pData++; sEntity=sEntity.Left(nSpace); sLiteral=sLiteral.Right(sLiteral.GetLength()-nSpace); sLiteral.TrimLeft(); m_EntityTable.Add((LPCTSTR) sEntity,(DWORD)(INT_PTR) pData); } } } sEntity=""; bOpened=FALSE; } else { sEntity+=ch; } } ch = GetNextChar(); } } // Parse any remaining left over stuff while (ch && ch != m_chEndDelim) { str.Append(ch); ch = GetNextChar(); } if (!ch) { ReportError(ERROR_END_OF_BUFFER, TEXT("Unexpected end of buffer while parsing DOCTYPE entry.")); return NULL; } } else if (!_tcsncmp(m_pBuf, TEXT("[CDATA["), 7)) { TCHAR ch = GetNextChar(7+1); while (ch) { TCHAR chNextChar1 = GetNextChar(); TCHAR chNextChar2 = GetNextChar(); if (!chNextChar1 || !chNextChar2) { ReportError(ERROR_END_OF_BUFFER, TEXT("Unexpected end of buffer while CDATA.")); return NULL; } if (ch == TEXT(']') && chNextChar1 == TEXT(']') && chNextChar2 == m_chEndDelim) ch = m_chNULL; else { str.Append(ch); ch = chNextChar1; UngetChar(); } } nMarkupType = COXParserObject::CDATA; } else { TCHAR ch = GetNextChar(); while (ch && ch != m_chEndDelim) { str.Append(ch); ch = GetNextChar(); } if (!ch) { ReportError(ERROR_END_OF_BUFFER, TEXT("Unexpected end of buffer while parsing markup.")); return NULL; } } return new COXParserObject(pParent, nMarkupType, str); } COXParserObject* COXParser::ParseComment(COXParserElement* pParent) { COXQuickString str; str.SetLength(100); TCHAR ch = GetNextChar(); while (ch) { TCHAR chNextChar1 = GetNextChar(); TCHAR chNextChar2 = GetNextChar(); if (!chNextChar1 || !chNextChar2) { ReportError(ERROR_END_OF_BUFFER, TEXT("Unexpected end of buffer while parsing comment.")); return NULL; } if (ch == m_chDash && chNextChar1 == m_chDash && chNextChar2 == m_chEndDelim) ch = m_chNULL; else { str.Append(ch); ch = chNextChar1; UngetChar(); } } return new COXParserObject(pParent, COXParserObject::COMMENT, str); } LPCTSTR COXParser::GetLiteralString(LPCTSTR szStr) { static TCHAR szLiteral[2] = { 0,0 }; szLiteral[0] = TCHAR(0); // Decode numeric character references if (szStr[0] == TEXT('#')) { BOOL bHexValue = ( szStr[1] == TEXT('x') || szStr[1] == TEXT('X') ); LPCTSTR szNumber = (bHexValue)? szStr+2 : szStr+1; // Check it's valid for (LPCTSTR ptr = szNumber; *ptr; ptr++) { if (! ( (bHexValue)? _istxdigit(*ptr) : _istdigit(*ptr) ) ) return NULL; } TCHAR ch; #if _MSC_VER >= 1400 if (!_stscanf_s(szNumber, (bHexValue)? TEXT("%x") : TEXT("%d"), &ch)) #else if (!_stscanf(szNumber, (bHexValue)? TEXT("%x") : TEXT("%d"), &ch)) #endif return NULL; szLiteral[0] = ch; return szLiteral; } // Decode character entity references DWORD dw; if (!m_EntityTable.Lookup(szStr, dw)) return NULL; return (LPCTSTR) (INT_PTR)dw; } LPTSTR COXParser::GetCharEntity(TCHAR ch) { static TCHAR szEntity[256]; HASH_POS pos = m_EntityTable.GetStartPosition(); while (pos) { LPCTSTR szName = NULL; DWORD dwData = 0; m_EntityTable.GetNextAssoc(pos, szName, dwData); LPCTSTR szLiteral = (LPCTSTR)(INT_PTR) dwData; if (ch == szLiteral[0] && szLiteral[1] == m_chNULL) { UTBStr::stprintf(szEntity, 256, TEXT("&%s;"), szName); return szEntity; } } // unable to find - so just return the character as a string szEntity[0] = ch; szEntity[1] = TEXT('\0'); return szEntity; } const COXQuickString COXParser::EncodeText(LPCTSTR szStr) { COXQuickString str; if (!szStr || !szStr[0]) return str; // An initial guess of the string size str.SetLength(PtrToUint(_tcslen(szStr)+1)); while (*szStr) str.AddString(GetCharEntity(*szStr++)); return str; } COXParserObject* COXParser::ParseText(COXParserElement* pParent) { COXQuickString str; str.SetLength(100); TCHAR ch = GetNextChar(); while (ch && ch != m_chStartDelim) { // Process any entities found if (ch == m_chEsc) { // Move past the '&' ch = GetNextChar(); COXQuickString strEntity; while (ch && ch != m_chStartDelim && ch != TEXT(';') && !_istspace(ch)) { strEntity.Append(ch); ch = GetNextChar(); } // Final char should be the ';' if (ch != TEXT(';')) { ReportError(ERROR_BAD_ENTITY, TEXT("Missing ';' on character entity '&%s'."), (LPCTSTR) strEntity); str.Append(TEXT('&')); str += strEntity; } else { LPCTSTR szEntity = GetLiteralString(strEntity); if (szEntity) InsertEntityValue(str,szEntity); else { ReportError(ERROR_BAD_ENTITY, TEXT("Bad character entity '&%s;' encountered."), (LPCTSTR) strEntity); str.Append(TEXT('&')); str += strEntity; str.Append(TEXT(';')); } ch = GetNextChar(); } // finished processing entity - ch will be the char directly // after the entity } else { str.Append(ch); ch = GetNextChar(); } } if (!ch) { //ReportError(ERROR_END_OF_BUFFER, // TEXT("Unexpected end of buffer while parsing text.")); //return NULL; } else UngetChar(); str.Trim(); return new COXParserObject(pParent, COXParserObject::PLAINTEXT, str.GetString()); } BOOL COXParser::ParseElement(COXParserElement *pElement, int nLevel) { SAVEPOS pos; SaveBufferPos(pos); COXParserObject* pObject; BOOL bFoundMatchingEndTag; BOOL bContinue = TRUE; BOOL bResult = FALSE; do { // The new object that will be added to this element pObject = NULL; bFoundMatchingEndTag = FALSE; // Get the first token of the next object. Only report errors for // elements that are at least a level above the root element bResult = GetToken(m_Token, FALSE); if (!bResult) { bContinue = FALSE; bResult = TRUE; break; } switch (m_Token.GetType()) { case COXToken::OPEN_PROCINSTR_BRACKET: // Processing instruction pObject = ParseProcessingInstruction(pElement); bResult = bContinue = (pObject != NULL); break; case COXToken::OPEN_MARKUP_BRACKET: // Markup pObject = ParseMarkup(pElement); bResult = bContinue = (pObject != NULL); break; case COXToken::OPEN_COMMENT_BRACKET: // Comment pObject = ParseComment(pElement); bResult = bContinue = (pObject != NULL); break; case COXToken::QUOTE: case COXToken::APOSTROPHE: UngetChar(); // Fall through case COXToken::STRING: // Plain ol' text case COXToken::EQUAL_SIGN: pObject = ParseText(pElement); bResult = bContinue = (pObject != NULL); break; case COXToken::OPEN_TAG_BRACKET: // New element { BOOL bEmptyTag = FALSE; pObject = ParseStartTag(pElement, bEmptyTag); if (!pObject) { bResult = bContinue = FALSE; break; } // Allow derived classes to simply ignore this element. This is // useful if you wish to treat tags as "toggles" (eg in // HTML), instead of actual XML nodes. if (IgnoreStartTag((COXParserElement*) pObject, bEmptyTag)) { delete pObject; pObject = NULL; break; } // Allow derived classes to close off this element and start a // new one if there is a missing end tag. // If we have a missing end tag, then we end processing of the // current element, restore the buffer to the point just before // this open tag, and return from this function if (nLevel > 0 && IsEndTagMissing(pElement->GetName(), pObject->GetText(), FALSE)) { // Pretend we never saw this tag, and just quit as if // we had reached an end tag RestoreBufferPos(pos); delete pObject; pObject = NULL; bFoundMatchingEndTag = TRUE; bResult = TRUE; bContinue = FALSE; break; } // If not an empty element, then try and parse the element. if ( !bEmptyTag && !ParseElement((COXParserElement*) pObject, nLevel+1) ) { delete pObject; pObject = NULL; bResult = bContinue = FALSE; break; } bResult = TRUE; } break; // An end tag should halt processing - but we allow some leniency here. // We allow derived classes to decide when to cease processing this // element, by either return FALSE in ParseEndTag, or setting // bFoundMatchingEndTag to TRUE (useful for HTML...) case COXToken::OPEN_ENDTAG_BRACKET: // in // HTML), instead of actual XML nodes. if (IgnoreEndTag(strEndTag)) break; // If we are at top level, then issue a warning but continue if (nLevel == 0) { ReportError(WARNING_UNEXPECTED_END_TAG, TEXT("Unexpected end tag '%s'."), (LPCTSTR) strEndTag); break; } // If it matches the current element, then we finish processing here if (strEndTag.Compare(pElement->GetName(), FALSE)) { bFoundMatchingEndTag = TRUE; bContinue = FALSE; break; } if (IsEndTagMissing(pElement->GetName(), strEndTag, TRUE)) { // Pretend we never saw this tag, and just quit as if // we had reached an end tag RestoreBufferPos(pos); bFoundMatchingEndTag = TRUE; bContinue = FALSE; break; } ReportError(ERROR_MISSING_END_TAG, TEXT("Expecting end tag '%s'. Found end tag '%s' instead."), pElement->GetName(), (LPCTSTR) strEndTag); if (m_bErrorOnMissingTag) bContinue = bResult = FALSE; } break; default: ReportError(ERROR_UNEXPECTED_TOKEN, TEXT("Unexpected token.")); //ReportError(ERROR_UNEXPECTED_TOKEN, TEXT("Unexpected token '%s'."), // m_Token.GetText()); bResult = bContinue = FALSE; } if (bResult) AddObjectToElement(pElement, pObject); SaveBufferPos(pos); } while (bContinue); // Error? if (!bResult) { delete pObject; return FALSE; } // Will only get this far if all tags for all elements have been closed, or // if the trailing end tags for elements are missing // If this is the root element, then there is no need to check for missing end tags if (nLevel == 0) return TRUE; else { // Derived classes may wish to allow missing end tags if (!bFoundMatchingEndTag && !IsEndTagMissing(pElement->GetName(), NULL, TRUE)) { ReportError(ERROR_MISSING_END_TAG, TEXT("Missing end tag '%s'."), pElement->GetText()); return FALSE; } else return TRUE; } } BOOL COXParser::ParseFile(LPCTSTR szFile) { LPTSTR ptr = LoadFile(szFile); if (!ptr) return FALSE; BOOL bResult = Parse(ptr); GlobalFree(ptr); return bResult; } ////////////////////////////////////////////////////////////////////// // File handling routines ////////////////////////////////////////////////////////////////////// BOOL COXParser::WriteFile(LPCTSTR szFile) { if (!Root() || !Root()->NumObjects()) return FALSE; HANDLE hFile = CreateFile(szFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (INVALID_HANDLE_VALUE == hFile) return FALSE; for (int i = 0; i < Root()->NumObjects(); i++) WriteObject(hFile, Root()->Object(i), 0); CloseHandle(hFile); return TRUE; } BOOL COXParser::WriteTabs(HANDLE hFile, int nLevel) { USES_CONVERSION; DWORD nCount; for (int i = 0; i < nLevel; i++) { if (!::WriteFile(hFile, T2A((LPTSTR) m_szTab), PtrToLong(_tcslen(m_szTab)), &nCount, NULL)) return FALSE; } return TRUE; } BOOL COXParser::WriteAttributes(HANDLE hFile, COXParserElement* pElement) { USES_CONVERSION; static char buffer[512]; static DWORD nCount; for (int i = 0; i < pElement->NumAttributes(); i++) { COXAttribute* pAttribute = pElement->Attribute(i); if (!pAttribute) continue; UTBStr::sprintf(buffer, 512, " %s=", T2A((LPTSTR) pAttribute->GetName())); if (!::WriteFile(hFile, buffer, PtrToLong(strlen(buffer)), &nCount, NULL)) return FALSE; if (pAttribute->GetAttributeType() == COXAttribute::ATTR_STRING) UTBStr::sprintf(buffer, 512, "\"%s\"", T2A((LPTSTR) pAttribute->GetStringValue())); else if (pAttribute->GetAttributeType() == COXAttribute::ATTR_INTEGER) UTBStr::sprintf(buffer, 512, "%d", pAttribute->GetIntValue()); else UTBStr::sprintf(buffer, 512, "\"\""); if (!::WriteFile(hFile, buffer, PtrToLong(strlen(buffer)), &nCount, NULL)) return FALSE; } return TRUE; } BOOL COXParser::WriteCData(HANDLE hFile, COXParserObject* pObject, int nLevel) { USES_CONVERSION; if (pObject->GetType() != COXParserObject::CDATA) return FALSE; if (!WriteTabs(hFile, nLevel)) return FALSE; DWORD nCount; if (!::WriteFile(hFile, "GetText()), PtrToLong(_tcslen(pObject->GetText())), &nCount, NULL)) return FALSE; if (!::WriteFile(hFile, "]]>\r\n", 5, &nCount, NULL)) return FALSE; return TRUE; } BOOL COXParser::WriteComment(HANDLE hFile, COXParserObject* pObject, int nLevel) { USES_CONVERSION; if (pObject->GetType() != COXParserObject::COMMENT) return FALSE; if (!WriteTabs(hFile, nLevel)) return FALSE; DWORD nCount; if (!::WriteFile(hFile, "\r\n", 5, &nCount, NULL)) return FALSE; return TRUE; } BOOL COXParser::WriteElement(HANDLE hFile, COXParserElement* pElement, int nLevel) { USES_CONVERSION; static char buffer[512]; static DWORD nCount; if (pElement->GetType() != COXParserObject::ELEMENT) return FALSE; if (!WriteTabs(hFile, nLevel)) return FALSE; UTBStr::sprintf(buffer, 512, "<%s", T2A((LPTSTR)pElement->GetName())); if (!::WriteFile(hFile, buffer, PtrToLong(strlen(buffer)), &nCount, NULL)) return FALSE; if (!WriteAttributes(hFile, pElement)) return FALSE; if (pElement->NumObjects() == 0) // empty tag { if (!::WriteFile(hFile, "/>\r\n", 4, &nCount, NULL)) return FALSE; } else { if (!::WriteFile(hFile, ">\r\n", 3, &nCount, NULL)) return FALSE; for (int i = 0; i < pElement->NumObjects(); i++) WriteObject(hFile, pElement->Object(i), nLevel+1); if (!WriteTabs(hFile, nLevel)) return FALSE; UTBStr::sprintf(buffer, 512, "\r\n", T2A((LPTSTR)pElement->GetName())); if (!::WriteFile(hFile, buffer, PtrToLong(strlen(buffer)), &nCount, NULL)) return FALSE; } return TRUE; } BOOL COXParser::WriteMarkup(HANDLE hFile, COXParserObject* pObject, int nLevel) { USES_CONVERSION; if (pObject->GetType() != COXParserObject::MARKUP) return FALSE; if (!WriteTabs(hFile, nLevel)) return FALSE; DWORD nCount; if (!::WriteFile(hFile, "GetText()), PtrToLong(_tcslen(pObject->GetText())), &nCount, NULL)) return FALSE; if (!::WriteFile(hFile, ">\r\n", 3, &nCount, NULL)) return FALSE; return TRUE; } BOOL COXParser::WriteProcessingInstruction(HANDLE hFile, COXParserObject* pObject, int nLevel) { USES_CONVERSION; if (pObject->GetType() != COXParserObject::PROCINSTR) return FALSE; if (!WriteTabs(hFile, nLevel)) return FALSE; DWORD nCount; if (!::WriteFile(hFile, "GetText()), PtrToLong(_tcslen(pObject->GetText())), &nCount, NULL)) return FALSE; if (!::WriteFile(hFile, "?>\r\n", 4, &nCount, NULL)) return FALSE; return TRUE; } BOOL COXParser::WriteText(HANDLE hFile, COXParserObject* pObject, int nLevel) { USES_CONVERSION; if (pObject->GetType() != COXParserObject::PLAINTEXT && pObject->GetType() != COXParserObject::RAWTEXT ) return FALSE; COXQuickString str; if ( pObject->GetType() == COXParserObject::RAWTEXT ) str = pObject->GetText(); else str = EncodeText(pObject->GetText()); str.Trim(); if (str.IsEmpty()) return FALSE; LPCTSTR ptr = str.GetString(); COXQuickString strLine; LPCTSTR pStart; while (*ptr) { strLine.Empty(); BOOL bContinue = TRUE; while (bContinue) { pStart = ptr; // Search for the next white space while (*ptr && !_istspace(*ptr)) ptr++; //if (*ptr) ptr++; int nCharsStepped = PtrToInt(ptr - pStart + 1); // If we have less than a lines worth, or if we have more than a lines // worth and have not added anything previously, add to the current line if ( strLine.GetLength() + nCharsStepped < m_nLineLength || strLine.IsEmpty() ) { strLine.AddString(pStart, nCharsStepped); // If we've still got room on the line, and we are not at EOF or EOL // then continue on bContinue = (strLine.GetLength() < m_nLineLength && *ptr && *ptr != TEXT('\n')); if (*ptr) ptr++; bContinue = (bContinue && *ptr); } else { // We could not add this section - so go back to the start of it // and print out what we already have. ptr = pStart; bContinue = FALSE; } } if (!WriteTabs(hFile, nLevel)) return FALSE; DWORD nCount; if (!::WriteFile(hFile, T2A((LPTSTR)strLine.GetString()), strLine.GetLength(), &nCount, NULL)) return FALSE; if (!::WriteFile(hFile, "\r\n", 2, &nCount, NULL)) return FALSE; } return TRUE; } BOOL COXParser::WriteObject(HANDLE hFile, COXParserObject* pObject, int nLevel) { BOOL bResult = FALSE; switch (pObject->GetType()) { case COXParserObject::CDATA: bResult = WriteCData(hFile, pObject, nLevel); break; case COXParserObject::COMMENT: bResult = WriteComment(hFile, pObject, nLevel); break; case COXParserObject::ELEMENT: bResult = WriteElement(hFile, (COXParserElement*) pObject, nLevel); break; case COXParserObject::MARKUP: bResult = WriteMarkup(hFile, pObject, nLevel); break; case COXParserObject::PLAINTEXT: case COXParserObject::RAWTEXT: bResult = WriteText(hFile, pObject, nLevel); break; case COXParserObject::PROCINSTR: bResult = WriteProcessingInstruction(hFile, pObject, nLevel); break; default: /*nothing*/; } return bResult; } LPTSTR COXParser::LoadFile(LPCTSTR szFile) { HANDLE hFile = CreateFile(szFile, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS, NULL); if (INVALID_HANDLE_VALUE == hFile) return NULL; DWORD size = GetFileSize(hFile, NULL); if (-1 == size) { CloseHandle(hFile); return NULL; } LPSTR ptr = (LPSTR) ::GlobalAlloc(GMEM_FIXED, size + 1); DWORD nCount; if (!ReadFile(hFile, ptr, size, &nCount, NULL)) { CloseHandle(hFile); return NULL; } CloseHandle(hFile); ptr[nCount] = '\0'; #ifdef _UNICODE LPTSTR tptr = (LPTSTR) ::GlobalAlloc(GMEM_FIXED, (nCount + 1) * sizeof(TCHAR)); UTBStr::mbstowcs(tptr, nCount+1, ptr, nCount); ::GlobalFree(ptr); tptr[nCount] = m_chNULL; return tptr; #else return ptr; #endif } ////////////////////////////////////////////////////////////////////// // Error handling routines ////////////////////////////////////////////////////////////////////// BOOL COXParser::ReportError(UINT nErrorCode, LPCTSTR fmt, ...) { va_list args; TCHAR buffer[512]; va_start(args, fmt); #if _MSC_VER >= 1400 _vstprintf_s_l(buffer, 512, fmt, NULL, args); #else _vstprintf(buffer, fmt, args); # endif va_end(args); if (m_pfnErrorFn) return (*m_pfnErrorFn)(nErrorCode, buffer, m_nLine, m_nColumn, m_dwErrData); else return DefErrorHandler(nErrorCode, buffer, m_nLine, m_nColumn, m_dwErrData); } BOOL COXParser::DefErrorHandler(UINT nErrorCode, LPCTSTR szError, int nLine, int nColumn, DWORD dwData) { UNUSED_ALWAYS(dwData); UNUSED(nErrorCode); UNUSED(szError); UNUSED(nLine); UNUSED(nColumn); #ifdef _DEBUG #if _MSC_VER >= 1200 #pragma warning(push) #endif #pragma warning(disable:4127) // conditional expression is constant if (ERROR_FIRST < nErrorCode && nErrorCode < ERROR_LAST) { _RPT4(_CRT_WARN, "COXParser: Error (%d) at line %d, col %d: %s\n", nErrorCode, nLine, nColumn, szError); } else if (WARNING_FIRST < nErrorCode && nErrorCode < WARNING_LAST) { _RPT4(_CRT_WARN, "COXParser: Warning (%d) at line %d, col %d: %s\n", nErrorCode, nLine, nColumn, szError); } else _RPT4(_CRT_WARN, "COXParser: line %d, col %d: %s\n", nErrorCode, nLine, nColumn, szError); #if _MSC_VER >= 1200 #pragma warning(pop) #else #pragma warning(default:4127) #endif #endif return FALSE; } LPCTSTR COXParser::TranslateErrorCode(int nErrorCode) { switch (nErrorCode) { case ERROR_NULL_BUFFER: return TEXT("NULL buffer passed in"); case ERROR_END_OF_BUFFER: return TEXT("Unexpected end of buffer"); case ERROR_OUT_OF_MEMORY: return TEXT("Out of memory"); case ERROR_BAD_TOKEN: return TEXT("Unable to retrieve a token"); case ERROR_ILLEGAL_CHAR: return TEXT("Illegal characters encountered in token"); case ERROR_UNEXPECTED_TOKEN: return TEXT("Unexpected token type"); case ERROR_MISSING_END_TAG: return TEXT("Missing end tag"); case ERROR_BAD_ENTITY: return TEXT("Bad entity string"); case ERROR_BAD_INTEGER: return TEXT("Bad integer value"); case ERROR_MISSING_END_BRACKET: return TEXT("Missing end bracket in tag"); case WARNING_UNEXPECTED_END_TAG: return TEXT("Unexpected end tag"); default: return TEXT("Unknown Error"); } } void COXParser::InsertEntityValue(COXQuickString& str, LPCTSTR lpszEntity) { LPCTSTR lpszSeek=lpszEntity; TCHAR chSpace=TEXT(' '); while(*(lpszSeek) && *(lpszSeek)!=m_chEndDelim && *(lpszSeek)!=chSpace) { str.Append(*lpszSeek); lpszSeek++; } }