#include "cfg_xml.h" #include #include "exception.h" #include #include #include BEGIN_ICPF_NAMESPACE /// Buffer size for reading xml data from a file #define XML_BUFFER 65536 // definition of line ending - system dependent #if defined(_WIN32) || defined(_WIN64) #define ENDL _t("\r\n") #else #define ENDL _t("\n") #endif // forward declaration class xml_node; /// Xml node storage typedef std::map xml_storage; /// String storage (key(s)=>value(s)) typedef std::multimap attr_storage; /** Class manages a single xml node. */ class xml_node { public: /** \name Construction/destruction */ /**@{*/ /// Standard constructor xml_node() : m_mNodes(), m_mAttr(), m_pParentNode(NULL) { }; /// Constructor defining the parent node xml_node(xml_node* pParentNode) : m_mNodes(), m_mAttr(), m_pParentNode(pParentNode) { }; /**@}*/ /// Clears the node void clear(bool bClearParent = false) { m_mNodes.clear(); m_mAttr.clear(); if(bClearParent) m_pParentNode = NULL; } public: xml_storage m_mNodes; ///< Additional nodes inside of this one attr_storage m_mAttr; ///< String pairs belonging to this node xml_node* m_pParentNode; ///< Parent node }; /** State structure - used by expat notifications. */ struct XMLSTATE { xml_cfg* pCfg; xml_node* pNode; }; /** Xml find handle structure - used for searching. */ struct XMLFINDHANDLE { attr_storage::iterator it; ///< Iterator of currently retrieved string attr_storage::iterator itEnd; ///< Iterator of a last string matching the criteria }; /// Macro for faster access to the xml storage #define m_pMainNode ((xml_node*)m_hMainNode) /** Constructs the xml_cfg object. */ xml_cfg::xml_cfg() : m_hMainNode((ptr_t)new xml_node) { } /** Destructs the xml config object. */ xml_cfg::~xml_cfg() { delete m_pMainNode; } /** Expat start element handler. * * \param[in] userData - pointer to user defined parameters * \param[in] name - name of the tag being processed * \param[in] attrs - array of pointers to strings with attributes and their values */ void xml_cfg::element_start(void *userData, const tchar_t *name, const tchar_t **attrs) { XMLSTATE* pState=(XMLSTATE*)userData; assert(pState); assert(pState->pNode); // temp tchar_t szData[512]; _sntprintf(szData, 512, _t("Opening Name: %s\n"), name); OutputDebugString(szData); // /temp // parse node attributes for(size_t t=0;attrs[t] != NULL;t+=2) { if(_tcscmp(attrs[t], _t("value")) == 0) { // this is the value type tag pState->pNode->m_mAttr.insert(attr_storage::value_type(tstring_t(name), tstring_t(attrs[t+1]))); } } std::pair pr; pr=pState->pNode->m_mNodes.insert(xml_storage::value_type(tstring_t(name), xml_node(pState->pNode))); pState->pNode=&((*pr.first).second); } /** Expat handler for closing tag. * * \param[in] userData - user defined parameter * \param[in] name - name of the tag being closed */ void xml_cfg::element_end(void *userData, const tchar_t* name) { XMLSTATE* pState=(XMLSTATE*)userData; assert(pState); // temp tchar_t szData[512]; _sntprintf(szData, 512, _t("Closing Name: %s\n"), name); OutputDebugString(szData); // /temp // go up one level if(pState->pNode) pState->pNode=pState->pNode->m_pParentNode; else THROW(_t("Trying to close non-existent tag."), 0, 0, 0); } /*void XMLCALL element_content(void *userData, const XML_Char *s, int len) { XMLSTATE* pState=(XMLSTATE*)userData; }*/ /** Function reads the contents of the xml file, parses it using expat parser * and then creates xml nodes in memory that could be read using find functions. * * \param[in] pszPath - path to the file to be read */ void xml_cfg::read(const tchar_t* pszPath) { // clear current contents clear(); // read the data from file in 64kB portions and feed it to the expat xml parser FILE* pFile=_tfopen(pszPath, _t("rb")); if(pFile == NULL) THROW(icpf::exception::format(_t("Cannot open the file ") TSTRFMT _t(" for reading."), pszPath), 0, errno, 0); // create the parser XML_Parser parser=XML_ParserCreate(NULL); XML_SetElementHandler(parser, element_start, element_end); // XML_SetCharacterDataHandler(parser, element_content); XMLSTATE xs = { this, m_pMainNode }; XML_SetUserData(parser, &xs); for(;;) { bool bLast=false; // get xml buffer void* pBuffer=XML_GetBuffer(parser, XML_BUFFER); // read some data to it size_t tSize=fread(pBuffer, 1, XML_BUFFER, pFile); if(tSize < XML_BUFFER) { // check for errors int iErr=0; if( (iErr=ferror(pFile)) != 0) THROW(icpf::exception::format(_t("Error reading from the file ") TSTRFMT _t("."), pszPath), 0, iErr, 0); else bLast=true; } // parse if(!XML_ParseBuffer(parser, (int)tSize, bLast)) { // parser error THROW(icpf::exception::format(_t("Error encountered while parsing the xml file ") STRFMT _t(" - ") STRFMT _t("."), pszPath, XML_ErrorString(XML_GetErrorCode(parser))), 0, 0, 0); } // end of processing ? if(bLast) break; } // free parser XML_ParserFree(parser); // close the file fclose(pFile); } /** Saves the internal xml nodes to the specified xml file. * * \param[in] pszPath - path to the file the data should be written to * * \note Function overwrites the contents of a file */ void xml_cfg::save(const tchar_t* pszPath) { FILE* pFile=_tfopen(pszPath, _t("wb")); if(pFile == NULL) THROW(icpf::exception::format(_t("Cannot open the file ") TSTRFMT _t(" for writing."), pszPath), 0, errno, 0); // put BOM into the file #if(defined(_WIN32) || defined(_WIN64)) // utf-16le const uint_t uiBOM=0x0000feff; const uint_t uiCount=2; #else // utf-8 const uint_t uiBOM=0x00bfbbef; const uint_t uiCount=3; #endif try { // write bom, check if it succeeded if(fwrite(&uiBOM, 1, uiCount, pFile) != uiCount) THROW(_t("Cannot write the BOM to the file '") TSTRFMT _t("'"), 0, errno, 0); // and write save_node(pFile, m_pMainNode); } catch(...) { fclose(pFile); throw; } // close the file fclose(pFile); } void xml_cfg::save_node(FILE* pFile, ptr_t pNodePtr) { xml_node* pNode=(xml_node*)pNodePtr; // attributes first const tchar_t *pszFmt = _t("<") TSTRFMT _t(" value=\"") TSTRFMT _t("\"/>") ENDL; for(attr_storage::iterator it=pNode->m_mAttr.begin();it != pNode->m_mAttr.end();it++) { fprintf_encoded(pFile, pszFmt, (*it).first.c_str(), (*it).second.c_str()); } // sub-nodes for(xml_storage::iterator it=pNode->m_mNodes.begin();it != pNode->m_mNodes.end();it++) { xml_node& rNode = (*it).second; if(!rNode.m_mNodes.empty() || !rNode.m_mAttr.empty()) { // opening tag fprintf_encoded(pFile, _t("<") TSTRFMT _t(">") ENDL, (*it).first.c_str()); save_node(pFile, &(*it).second); // closing tag fprintf_encoded(pFile, _t("") ENDL, (*it).first.c_str()); } } } void xml_cfg::fprintf_encoded(FILE* pFile, const tchar_t* pszFmt, ...) { va_list va; va_start(va, pszFmt); // get count of characters in the string int_t iCount=_vsctprintf(pszFmt, va); tchar_t* pszFormatted=new tchar_t[iCount+1]; // make a formatted string va_start(va, pszFmt); _vsntprintf(pszFormatted, iCount + 1, pszFmt, va); #if(!defined(UNICODE) && (defined(_WIN32) || defined(_WIN64))) // convert to unicode iCount = lstrlen(pszFormatted); int iWideCount = MultiByteToWideChar(CP_ACP, 0, pszFormatted, iCount, NULL, 0); if(iWideCount) { wchar_t* pszWideString = new wchar_t[iWideCount]; iWideCount = MultiByteToWideChar(CP_ACP, 0, pszFormatted, iCount, pszWideString, iWideCount); fwrite(pszWideString, 1, iWideCount*sizeof(wchar_t), pFile); delete [] pszWideString; } else THROW(_t("Cannot convert string to wide characters."), 0, GetLastError(), 0); #else fwrite(pszFormatted, sizeof(tchar_t), iCount, pFile); #endif delete [] pszFormatted; va_end(va); } /** Function starts a search operation. Given the name of the property * to be searched for(ie. "ch/program/startup"), funtion searches for * it and returns a handle that can be used by subsequent calls to the * find_next(). Free the handle using find_close() after finish. * * \param[in] pszName - name of the property to search for(in the form of * "ch/program/startup" for xml such as this: * * * * * * * \return Handle to the search (NULL if not found). */ ptr_t xml_cfg::find(const tchar_t* pszName) { return find(m_pMainNode, pszName); } /** A find() helper function - recursively searches a specific node * for a given name. * * \param[in] pNodePtr - pointer to a node to search in * \param[in] pszName - name of the property to search for * \return Handle to the node or NULL if none. */ ptr_t xml_cfg::find(ptr_t pNodePtr, const tchar_t* pszName) { xml_node* pNode=(xml_node*)pNodePtr; // parse the name const tchar_t* pSign=_tcschr(pszName, _t('/')); if(pSign) { // locate the xml_node associated with the name xml_storage::iterator it=pNode->m_mNodes.find(tstring_t(pszName, pSign-pszName)); if(it != pNode->m_mNodes.end()) return find(&(*it).second, pSign+1); else return NULL; } else { std::pair pr=pNode->m_mAttr.equal_range(pszName); if(pr.first != pNode->m_mAttr.end() && pr.second != pNode->m_mAttr.end()) { XMLFINDHANDLE* pfh=new XMLFINDHANDLE; pfh->it=pr.first; pfh->itEnd=pr.second; return pfh; } else return NULL; } } /** Finds the next string that belong to a specific key (as defined in * a call to find() function. * * \param[in] pFindHandle - handle to the search (as returned from find()) * \return Pointer to a next string found, NULL if none. */ const tchar_t* xml_cfg::find_next(ptr_t pFindHandle) { XMLFINDHANDLE* pfh=(XMLFINDHANDLE*)pFindHandle; if(pfh->it != pfh->itEnd) return (*pfh->it++).second.c_str(); else return NULL; } /** Closes the find handle. * * \param[in] pFindHandle - handle to the search (as returned from find()) */ void xml_cfg::find_close(ptr_t pFindHandle) { delete ((XMLFINDHANDLE*)pFindHandle); } /** Sets the specified value in the given key name. Value can be either added to * the current ones (multi-string support) or replace them completely. * * \param[in] pszName - key name for which the string should be set at * \param[in] pszValue - value to set * \param[in] a - action to take while setting */ void xml_cfg::set_value(const tchar_t* pszName, const tchar_t* pszValue, actions a) { // traverse the current tag tree set_value(m_pMainNode, pszName, pszValue, a); } /** Sets the specified value in the given key name - recursive helper function. * * \param[in] pNodePtr - pointer to the xml node to process * \param[in] pszName - key name for which the string should be set at * \param[in] pszValue - value to set * \param[in] a - action to take while setting */ void xml_cfg::set_value(ptr_t pNodePtr, const tchar_t* pszName, const tchar_t* pszValue, actions a) { xml_node* pNode=(xml_node*)pNodePtr; const tchar_t* pszSign=_tcschr(pszName, _t('/')); if(pszSign != NULL) { xml_storage::iterator it=pNode->m_mNodes.find(tstring_t(pszName, pszSign-pszName)); if(it != pNode->m_mNodes.end()) set_value(&(*it).second, pszSign+1, pszValue, a); else { std::pair pr=pNode->m_mNodes.insert(xml_storage::value_type(tstring_t(pszName, pszSign-pszName), xml_node(pNode))); set_value(&(*pr.first).second, pszSign+1, pszValue, a); } } else { // clear if we're replacing switch(a) { case config_base::action_replace: pNode->m_mAttr.clear(); case config_base::action_add: pNode->m_mAttr.insert(attr_storage::value_type(tstring_t(pszName), tstring_t(pszValue))); break; default: assert(false); } } } /** Clear values for a given property name. * * \param[in] pszName - name of the property to clear the values for */ void xml_cfg::clear(const tchar_t* pszName) { clear(m_pMainNode, pszName); } /** Clears the contents of this class * * \param[in] pszName - name of the property to clear the values for */ void xml_cfg::clear() { m_pMainNode->clear(true); } /** Recursive clear function - searches recursively for a proper node * and finally clears the string map. * * \param[in] pNodePtr - pointer to a node to be processed * \param[in] pszName - name of the property to search for in the given node */ void xml_cfg::clear(ptr_t pNodePtr, const tchar_t* pszName) { xml_node* pNode=(xml_node*)pNodePtr; // parse the name const tchar_t* pSign=_tcschr(pszName, _t('/')); if(pSign) { // locate the xml_node associated with the name xml_storage::iterator it=pNode->m_mNodes.find(tstring_t(pszName, pSign-pszName)); if(it != pNode->m_mNodes.end()) clear(&(*it).second, pSign+1); } else { std::pair pr=pNode->m_mAttr.equal_range(tstring_t(pszName)); pNode->m_mAttr.erase(pr.first, pr.second); } } END_ICPF_NAMESPACE