From 19e7328d78ef929f2e7440b8c37cfeebb8590994 Mon Sep 17 00:00:00 2001 From: Glenn Maynard Date: Mon, 2 Oct 2006 05:53:56 +0000 Subject: [PATCH] move XML loading and saving into XmlFileUtil, reducing XNode to a data structure --- stepmania/src/CatalogXml.cpp | 3 +- stepmania/src/ExportStrings.cpp | 3 +- stepmania/src/Profile.cpp | 2 +- stepmania/src/ScreenGameplay.cpp | 3 +- stepmania/src/Workout.cpp | 2 +- stepmania/src/XmlFile.cpp | 486 ------------------------------- stepmania/src/XmlFile.h | 13 - stepmania/src/XmlFileUtil.cpp | 482 +++++++++++++++++++++++++++++- stepmania/src/XmlFileUtil.h | 7 + 9 files changed, 496 insertions(+), 505 deletions(-) diff --git a/stepmania/src/CatalogXml.cpp b/stepmania/src/CatalogXml.cpp index ec3b83f68b..9bf7805240 100644 --- a/stepmania/src/CatalogXml.cpp +++ b/stepmania/src/CatalogXml.cpp @@ -5,6 +5,7 @@ #include "song.h" #include "Steps.h" #include "XmlFile.h" +#include "XmlFileUtil.h" #include "Course.h" #include "SongUtil.h" #include "StepsUtil.h" @@ -391,7 +392,7 @@ void CatalogXml::Save( LoadingWindow *loading_window ) xml.AppendChild( "FooterText", FOOTER_TEXT ); xml.AppendChild( "FooterLink", FOOTER_LINK ); - xml.SaveToFile( fn, CATALOG_XSL, false ); + XmlFileUtil::SaveToFile( &xml, fn, CATALOG_XSL, false ); LOG->Trace( "Done." ); } diff --git a/stepmania/src/ExportStrings.cpp b/stepmania/src/ExportStrings.cpp index 443b4d619e..fa71cca6de 100644 --- a/stepmania/src/ExportStrings.cpp +++ b/stepmania/src/ExportStrings.cpp @@ -4,6 +4,7 @@ #include "RageUtil.h" #include "IniFile.h" #include "XmlFile.h" +#include "XmlFileUtil.h" #include "LuaManager.h" #include "ProductInfo.h" #include "DateTime.h" @@ -57,7 +58,7 @@ void ExportStrings::LuaInformation() XNode *pDateNode = pNode->AppendChild( "Date" ); pDateNode->m_sValue = DateTime::GetNowDate().GetString(); - pNode->SaveToFile( "Lua.xml", "Lua.xsl" ); + XmlFileUtil::SaveToFile( pNode, "Lua.xml", "Lua.xsl" ); delete pNode; } diff --git a/stepmania/src/Profile.cpp b/stepmania/src/Profile.cpp index c77f526a13..06e9f74ed3 100644 --- a/stepmania/src/Profile.cpp +++ b/stepmania/src/Profile.cpp @@ -939,7 +939,7 @@ bool Profile::SaveStatsXmlToDir( RString sDir, bool bSignData ) const // Save stats.xml RString fn = sDir + STATS_XML; - bool bSaved = xml->SaveToFile( fn, STATS_XSL, false ); + bool bSaved = XmlFileUtil::SaveToFile( xml, fn, STATS_XSL, false ); SAFE_DELETE( xml ); diff --git a/stepmania/src/ScreenGameplay.cpp b/stepmania/src/ScreenGameplay.cpp index 52eebd0f60..e8f86aefc2 100644 --- a/stepmania/src/ScreenGameplay.cpp +++ b/stepmania/src/ScreenGameplay.cpp @@ -55,6 +55,7 @@ #include "AdjustSync.h" #include "SongUtil.h" #include "song.h" +#include "XmlFileUtil.h" // // Defines @@ -2619,7 +2620,7 @@ void ScreenGameplay::SaveReplay() RString sFileName = ssprintf( "replay%05d.xml", iIndex ); - p->SaveToFile( "Save/"+sFileName ); + XmlFileUtil::SaveToFile( p, "Save/"+sFileName ); SAFE_DELETE( p ); return; } diff --git a/stepmania/src/Workout.cpp b/stepmania/src/Workout.cpp index 7221c3f34f..67da2120e6 100644 --- a/stepmania/src/Workout.cpp +++ b/stepmania/src/Workout.cpp @@ -212,7 +212,7 @@ bool Workout::SaveToFile( RString sFile ) songGenres->AppendChild( "SongGenre", *s ); } - return xml.SaveToFile( sFile ); + return XmlFileUtil::SaveToFile( &xml, sFile ); } // lua start diff --git a/stepmania/src/XmlFile.cpp b/stepmania/src/XmlFile.cpp index c45280c39d..70c350d5d1 100644 --- a/stepmania/src/XmlFile.cpp +++ b/stepmania/src/XmlFile.cpp @@ -12,84 +12,6 @@ #include "RageUtil.h" #include "DateTime.h" #include "Foreach.h" -#include "RageFileDriverMemory.h" - -static inline long XStr2Int( const char* str, long default_value = 0 ) -{ - return str ? atol(str) : default_value; -} - -static const char chXMLTagOpen = '<'; -static const char chXMLTagClose = '>'; -static const char chXMLQuestion = '?'; // used in checking for meta tags: "" -static const char chXMLTagPre = '/'; -static const char chXMLExclamation = '!'; -static const char chXMLDash = '-'; - - -static map g_mapEntitiesToChars; -static map g_mapCharsToEntities; - -static void InitEntities() -{ - if( !g_mapEntitiesToChars.empty() ) - return; - - static struct Entity - { - char c; - const char *pEntity; - } - const EntityTable[] = - { - { '&', "amp", }, - { '\"', "quot", }, - { '\'', "apos", }, - { '<', "lt", }, - { '>', "gt", } - }; - - for( unsigned i = 0; i < ARRAYLEN(EntityTable); ++i ) - { - const Entity &ent = EntityTable[i]; - g_mapEntitiesToChars[ent.pEntity] = RString(1, ent.c); - g_mapCharsToEntities[ent.c] = ent.pEntity; - } -} - -static struct RunInitEntities -{ - RunInitEntities() { InitEntities(); } -} g_RunInitEntities; - -// skip spaces -static void tcsskip( const RString &s, unsigned &i ) -{ - i = s.find_first_not_of( " \t\r\n", i ); -} - -static bool XIsEmptyString( const RString &s ) -{ - return s.find_first_not_of( "\r\n\t " ) == s.npos; -} - -// put string of (psz~end) on ps string -static void SetString( const RString &s, int iStart, int iEnd, RString* ps, bool trim = false ) -{ - if( trim ) - { - while( iStart < iEnd && s[iStart] > 0 && isspace(s[iStart]) ) - iStart++; - while( iEnd-1 >= iStart && s[iEnd-1] > 0 && isspace(s[iEnd-1]) ) - iEnd--; - } - - int len = iEnd - iStart; - if( len <= 0 ) - return; - - ps->assign( s, iStart, len ); -} XNode::XNode( const XNode &cpy ): m_sName( cpy.m_sName ), @@ -113,387 +35,6 @@ void XNode::Clear() m_attrs.clear(); } -// attr1="value1" attr2='value2' attr3=value3 /> -// ^- return pointer -// Desc : loading attribute plain xml text -// Param : pszAttrs - xml of attributes -// pi = parser information -// Return : advanced string pointer. (error return npos) -unsigned XNode::LoadAttributes( const RString &xml, RString &sErrorOut, unsigned iOffset ) -{ - while( iOffset < xml.size() ) - { - tcsskip( xml, iOffset ); - if( iOffset >= xml.size() ) - continue; - - // close tag - if( iOffset < xml.size() && - (xml[iOffset] == chXMLTagClose || xml[iOffset] == chXMLTagPre || xml[iOffset] == chXMLQuestion || xml[iOffset] == chXMLDash) ) - return iOffset; // well-formed tag - - // XML Attr Name - unsigned iEnd = xml.find_first_of( " =", iOffset ); - if( iEnd == xml.npos ) - { - // error - if( sErrorOut.empty() ) - sErrorOut = ssprintf( "<%s> attribute has error ", m_sName.c_str() ); - return string::npos; - } - - // XML Attr Name - RString sName; - SetString( xml, iOffset, iEnd, &sName ); - - // add new attribute - DEBUG_ASSERT( sName.size() ); - pair it = m_attrs.insert( make_pair(sName, RString()) ); - RString &sValue = it.first->second; - iOffset = iEnd; - - // XML Attr Value - tcsskip( xml, iOffset ); - - if( iOffset < xml.size() && xml[iOffset] == '=' ) - { - ++iOffset; - - tcsskip( xml, iOffset ); - if( iOffset >= xml.size() ) - continue; - - // if " or ' - // or none quote - char quote = xml[iOffset]; - if( quote == '"' || quote == '\'' ) - { - ++iOffset; - iEnd = xml.find( quote, iOffset ); - } - else - { - //attr= value> - // none quote mode - iEnd = xml.find_first_of( " >", iOffset ); - } - - if( iEnd == xml.npos ) - { - // error - if( sErrorOut.empty() ) - sErrorOut = ssprintf( "<%s> attribute text: couldn't find matching quote", sName.c_str() ); - return string::npos; - } - - SetString( xml, iOffset, iEnd, &sValue, true ); - iOffset = iEnd; - // ATTRVALUE - ReplaceEntityText( sValue, g_mapEntitiesToChars ); - - if( quote == '"' || quote == '\'' ) - ++iOffset; - } - } - - // not well-formed tag - return string::npos; -} - -// -// -// or -// -// ^- return pointer -// Desc : load xml plain text -// Param : pszXml - plain xml text -// pi = parser information -// Return : advanced string pointer (error return npos) -unsigned XNode::Load( const RString &xml, RString &sErrorOut, unsigned iOffset ) -{ - Clear(); - - // < - iOffset = xml.find( chXMLTagOpen, iOffset ); - if( iOffset == string::npos ) - return string::npos; - - // ", iOffset ); - if( iEnd == string::npos ) - { - if( sErrorOut.empty() ) - sErrorOut = "Unterminated comment"; - - return string::npos; - } - - // Skip -->. - iOffset = iEnd + 3; - - return Load( xml, sErrorOut, iOffset ); - } - - // XML Node Tag Name Open - iOffset++; - unsigned iTagEnd = xml.find_first_of( " \t\r\n/>", iOffset ); - SetString( xml, iOffset, iTagEnd, &m_sName ); - iOffset = iTagEnd; - - // Generate XML Attributte List - iOffset = LoadAttributes( xml, sErrorOut, iOffset ); - if( iOffset == string::npos ) - return string::npos; - - // alone tag or or - // current pointer: ^ ^ ^ - - if( iOffset < xml.size() && (xml[iOffset] == chXMLTagPre || xml[iOffset] == chXMLQuestion || xml[iOffset] == chXMLDash) ) - { - iOffset++; - - // skip over 2nd dash - if( iOffset < xml.size() && xml[iOffset] == chXMLDash ) - iOffset++; - - if( iOffset == xml.size() || xml[iOffset] != chXMLTagClose ) - { - // error: - if( sErrorOut.empty() ) - sErrorOut = "Element must be closed."; - - // ill-formed tag - return string::npos; - } - - // well-formed tag - ++iOffset; - - // UGLY: We want to ignore all XML meta tags. So, since the Node we - // just loaded is a meta tag, then Load ourself again using the rest - // of the file until we reach a non-meta tag. - if( !m_sName.empty() && (m_sName[0] == chXMLQuestion || m_sName[0] == chXMLExclamation) ) - iOffset = Load( xml, sErrorOut, iOffset ); - - return iOffset; - } - - // open/close tag ... - // ^- current pointer - if( XIsEmptyString( m_sValue ) ) - { - // Text Value - ++iOffset; - unsigned iEnd = xml.find( chXMLTagOpen, iOffset ); - if( iEnd == string::npos ) - { - if( sErrorOut.empty() ) - sErrorOut = ssprintf( "%s must be closed with ", m_sName.c_str(), m_sName.c_str() ); - // error cos not exist CloseTag - return string::npos; - } - - SetString( xml, iOffset, iEnd, &m_sValue, true ); - - iOffset = iEnd; - // TEXTVALUE reference - ReplaceEntityText( m_sValue, g_mapEntitiesToChars ); - } - - // generate child nodes - while( iOffset < xml.size() ) - { - XNode *node = new XNode; - - iOffset = node->Load( xml, sErrorOut, iOffset ); - if( !node->m_sName.empty() ) - { - DEBUG_ASSERT( node->m_sName.size() ); - m_childs.insert( make_pair(node->m_sName, node) ); - } - else - { - delete node; - } - - // open/close tag ... - // ^- current pointer - // CloseTag case - if( iOffset+1 < xml.size() && xml[iOffset] == chXMLTagOpen && xml[iOffset+1] == chXMLTagPre ) - { - // - iOffset += 2; // C - - tcsskip( xml, iOffset ); - if( iOffset >= xml.size() ) - continue; - - unsigned iEnd = xml.find_first_of( " >", iOffset ); - if( iEnd == string::npos ) - { - if( sErrorOut.empty() ) - sErrorOut = ssprintf( "it must be closed with ", m_sName.c_str() ); - // error - return string::npos; - } - - RString closename; - SetString( xml, iOffset, iEnd, &closename ); - iOffset = iEnd+1; - if( closename == this->m_sName ) - { - // wel-formed open/close - // return '>' or ' ' after pointer - return iOffset; - } - else - { - // not welformed open/close - if( sErrorOut.empty() ) - sErrorOut = ssprintf( "'<%s> ... ' is not well-formed.", m_sName.c_str(), closename.c_str() ); - return string::npos; - } - } - else // Alone child Tag Loaded - { - if( XIsEmptyString( m_sValue ) && iOffset < xml.size() && xml[iOffset] != chXMLTagOpen ) - { - // Text Value - unsigned iEnd = xml.find( chXMLTagOpen, iOffset ); - if( iEnd == string::npos ) - { - // error cos not exist CloseTag - if( sErrorOut.empty() ) - sErrorOut = ssprintf( "it must be closed with ", m_sName.c_str() ); - return string::npos; - } - - SetString( xml, iOffset, iEnd, &m_sValue, true ); - - iOffset = iEnd; - //TEXTVALUE - ReplaceEntityText( m_sValue, g_mapEntitiesToChars ); - } - } - } - - return iOffset; -} - -// Desc : convert plain xml text from parsed xml attirbute -// Return : converted plain string -bool XNode::GetAttrXML( RageFileBasic &f, const RString &sName, const RString &sValue ) const -{ - RString s(sValue); - ReplaceEntityText( s, g_mapCharsToEntities ); - return f.Write(sName + "='" + s + "' ") != -1; -} - -// Desc : convert plain xml text from parsed xml node -// Return : converted plain string -bool XNode::GetXMLInternal( RageFileBasic &f, bool bWriteTabs, int &iTabBase ) const -{ - // tab - if( f.Write("\r\n") == -1 ) - return false; - if( bWriteTabs ) - for( int i = 0 ; i < iTabBase ; i++) - if( f.Write("\t") == -1 ) - return false; - - // first, p->second) ) - return false; - - if( m_childs.empty() && m_sValue.empty() ) - { - // alone tag - if( f.Write("/>") == -1 ) - return false; - } - else - { - // and get child - if( f.Write(">") == -1 ) - return false; - - if( !m_childs.empty() ) - iTabBase++; - - FOREACH_CONST_Child( this, p ) - if( !p->GetXMLInternal( f, bWriteTabs, iTabBase ) ) - return false; - - // Text Value - if( !m_sValue.empty() ) - { - if( !m_childs.empty() ) - { - if( f.Write("\r\n") == -1 ) - return false; - if( bWriteTabs ) - for( int i = 0 ; i < iTabBase ; i++) - if( f.Write("\t") == -1 ) - return false; - } - RString s( m_sValue ); - ReplaceEntityText( s, g_mapCharsToEntities ); - if( f.Write(s) == -1 ) - return false; - } - - // CloseTag - if( !m_childs.empty() ) - { - if( f.Write("\r\n") == -1 ) - return false; - if( bWriteTabs ) - for( int i = 0 ; i < iTabBase-1 ; i++) - if( f.Write("\t") == -1 ) - return false; - } - if( f.Write("") == -1 ) - return false; - - if( !m_childs.empty() ) - iTabBase--; - } - return true; -} - -bool XNode::GetXML( RageFileBasic &f, bool bWriteTabs ) const -{ - int iTabBase = 0; - return GetXMLInternal( f, bWriteTabs, iTabBase ); -} - -// Desc : convert plain xml text from parsed xml node -// Return : converted plain string -RString XNode::GetXML() const -{ - RageFileObjMem f; - int iTabBase = 0; - GetXMLInternal( f, true, iTabBase ); - return f.GetString(); -} - void XNode::GetValue( RString &out ) const { out = m_sValue; } void XNode::GetValue( int &out ) const { out = atoi(m_sValue); } void XNode::GetValue( float &out ) const { out = StringToFloat(m_sValue); } @@ -531,8 +72,6 @@ RString *XNode::GetAttr( const RString &attrname ) return NULL; } -// Desc : Find child with name and return child -// Return : NULL return if no child. XNode *XNode::GetChild( const RString &sName ) { multimap::iterator it = m_childs.find( sName ); @@ -598,28 +137,3 @@ void XNode::AppendAttr( const RString &sName, float value ) { AppendAttr(sName,s void XNode::AppendAttr( const RString &sName, int value ) { AppendAttr(sName,ssprintf("%d",value)); } void XNode::AppendAttr( const RString &sName, unsigned value ) { AppendAttr(sName,ssprintf("%u",value)); } -bool XNode::SaveToFile( RageFileBasic &f, const RString &sStylesheet, bool bWriteTabs ) const -{ - f.PutLine( "" ); - if( !sStylesheet.empty() ) - f.PutLine( "" ); - int iTabBase = 0; - if( !this->GetXMLInternal(f, bWriteTabs, iTabBase) ) - return false; - f.PutLine( "" ); - if( f.Flush() == -1 ) - return false; - return true; -} - -bool XNode::SaveToFile( const RString &sFile, const RString &sStylesheet, bool bWriteTabs ) const -{ - RageFile f; - if( !f.Open(sFile, RageFile::WRITE) ) - { - LOG->Warn( "Couldn't open %s for writing: %s", sFile.c_str(), f.GetError().c_str() ); - return false; - } - - return SaveToFile( f, sStylesheet, bWriteTabs ); -} diff --git a/stepmania/src/XmlFile.h b/stepmania/src/XmlFile.h index 306f249c8e..07cc2fd96c 100644 --- a/stepmania/src/XmlFile.h +++ b/stepmania/src/XmlFile.h @@ -57,16 +57,6 @@ public: void SetValue( unsigned v ); void SetValue( const DateTime &v ); - // Load/Save XML - unsigned Load( const RString &sXml, RString &sErrorOut, unsigned iOffset = 0 ); - unsigned LoadAttributes( const RString &sAttrs, RString &sErrorOut, unsigned iOffset ); - bool GetAttrXML( RageFileBasic &f, const RString &sName, const RString &sValue ) const; - bool GetXML( RageFileBasic &f, bool bWriteTabs = true ) const; - RString GetXML() const; - - bool SaveToFile( const RString &sFile, const RString &sStylesheet = "", bool bWriteTabs = true ) const; - bool SaveToFile( RageFileBasic &f, const RString &sStylesheet = "", bool bWriteTabs = true ) const; - // in own attribute list const RString *GetAttr( const RString &sAttrName ) const; RString *GetAttr( const RString &sAttrName ); @@ -103,9 +93,6 @@ public: ~XNode(); void Clear(); - -private: - bool GetXMLInternal( RageFileBasic &f, bool bWriteTabs, int &iTabBase ) const; }; #endif diff --git a/stepmania/src/XmlFileUtil.cpp b/stepmania/src/XmlFileUtil.cpp index a29ede8ccd..60e3279345 100644 --- a/stepmania/src/XmlFileUtil.cpp +++ b/stepmania/src/XmlFileUtil.cpp @@ -2,6 +2,7 @@ #include "XmlFileUtil.h" #include "XmlFile.h" #include "RageFile.h" +#include "RageFileDriverMemory.h" #include "RageUtil.h" #include "RageLog.h" #include "arch/Dialog/Dialog.h" @@ -13,7 +14,7 @@ bool XmlFileUtil::LoadFromFileShowErrors( XNode &xml, RageFileBasic &f ) if( f.Read( s ) == -1 ) sError = f.GetError(); else - xml.Load( s, sError ); + Load( &xml, s, sError ); if( sError.empty() ) return true; @@ -43,6 +44,485 @@ bool XmlFileUtil::LoadFromFileShowErrors( XNode &xml, const RString &sFile ) return bSuccess; } +static const char chXMLTagOpen = '<'; +static const char chXMLTagClose = '>'; +static const char chXMLQuestion = '?'; // used in checking for meta tags: "" +static const char chXMLTagPre = '/'; +static const char chXMLExclamation = '!'; +static const char chXMLDash = '-'; + +static map g_mapEntitiesToChars; +static map g_mapCharsToEntities; + +// XXX not called +static void InitEntities() +{ + if( !g_mapEntitiesToChars.empty() ) + return; + + static struct Entity + { + char c; + const char *pEntity; + } + const EntityTable[] = + { + { '&', "amp", }, + { '\"', "quot", }, + { '\'', "apos", }, + { '<', "lt", }, + { '>', "gt", } + }; + + for( unsigned i = 0; i < ARRAYLEN(EntityTable); ++i ) + { + const Entity &ent = EntityTable[i]; + g_mapEntitiesToChars[ent.pEntity] = RString(1, ent.c); + g_mapCharsToEntities[ent.c] = ent.pEntity; + } +} + + +// skip spaces +static void tcsskip( const RString &s, unsigned &i ) +{ + i = s.find_first_not_of( " \t\r\n", i ); +} + +static bool XIsEmptyString( const RString &s ) +{ + return s.find_first_not_of( "\r\n\t " ) == s.npos; +} + +// put string of (psz~end) on ps string +static void SetString( const RString &s, int iStart, int iEnd, RString* ps, bool trim = false ) +{ + if( trim ) + { + while( iStart < iEnd && s[iStart] > 0 && isspace(s[iStart]) ) + iStart++; + while( iEnd-1 >= iStart && s[iEnd-1] > 0 && isspace(s[iEnd-1]) ) + iEnd--; + } + + int len = iEnd - iStart; + if( len <= 0 ) + return; + + ps->assign( s, iStart, len ); +} + + +// attr1="value1" attr2='value2' attr3=value3 /> +// ^- return pointer +// Desc : loading attribute plain xml text +// Param : pszAttrs - xml of attributes +// pi = parser information +// Return : advanced string pointer. (error return npos) +namespace +{ +unsigned LoadAttributes( XNode *pNode, const RString &xml, RString &sErrorOut, unsigned iOffset ) +{ + while( iOffset < xml.size() ) + { + tcsskip( xml, iOffset ); + if( iOffset >= xml.size() ) + continue; + + // close tag + if( iOffset < xml.size() && + (xml[iOffset] == chXMLTagClose || xml[iOffset] == chXMLTagPre || xml[iOffset] == chXMLQuestion || xml[iOffset] == chXMLDash) ) + return iOffset; // well-formed tag + + // XML Attr Name + unsigned iEnd = xml.find_first_of( " =", iOffset ); + if( iEnd == xml.npos ) + { + // error + if( sErrorOut.empty() ) + sErrorOut = ssprintf( "<%s> attribute has error ", pNode->m_sName.c_str() ); + return string::npos; + } + + // XML Attr Name + RString sName; + SetString( xml, iOffset, iEnd, &sName ); + + // add new attribute + DEBUG_ASSERT( sName.size() ); + pair it = pNode->m_attrs.insert( make_pair(sName, RString()) ); + RString &sValue = it.first->second; + iOffset = iEnd; + + // XML Attr Value + tcsskip( xml, iOffset ); + + if( iOffset < xml.size() && xml[iOffset] == '=' ) + { + ++iOffset; + + tcsskip( xml, iOffset ); + if( iOffset >= xml.size() ) + continue; + + // if " or ' + // or none quote + char quote = xml[iOffset]; + if( quote == '"' || quote == '\'' ) + { + ++iOffset; + iEnd = xml.find( quote, iOffset ); + } + else + { + //attr= value> + // none quote mode + iEnd = xml.find_first_of( " >", iOffset ); + } + + if( iEnd == xml.npos ) + { + // error + if( sErrorOut.empty() ) + sErrorOut = ssprintf( "<%s> attribute text: couldn't find matching quote", sName.c_str() ); + return string::npos; + } + + SetString( xml, iOffset, iEnd, &sValue, true ); + iOffset = iEnd; + // ATTRVALUE + ReplaceEntityText( sValue, g_mapEntitiesToChars ); + + if( quote == '"' || quote == '\'' ) + ++iOffset; + } + } + + // not well-formed tag + return string::npos; +} + +} + + + +// +// +// or +// +// ^- return pointer +// Desc : load xml plain text +// Param : pszXml - plain xml text +// pi = parser information +// Return : advanced string pointer (error return npos) +unsigned XmlFileUtil::Load( XNode *pNode, const RString &xml, RString &sErrorOut, unsigned iOffset ) +{ + pNode->Clear(); + + // < + iOffset = xml.find( chXMLTagOpen, iOffset ); + if( iOffset == string::npos ) + return string::npos; + + // ", iOffset ); + if( iEnd == string::npos ) + { + if( sErrorOut.empty() ) + sErrorOut = "Unterminated comment"; + + return string::npos; + } + + // Skip -->. + iOffset = iEnd + 3; + + return Load( pNode, xml, sErrorOut, iOffset ); + } + + // XML Node Tag Name Open + iOffset++; + unsigned iTagEnd = xml.find_first_of( " \t\r\n/>", iOffset ); + SetString( xml, iOffset, iTagEnd, &pNode->m_sName ); + iOffset = iTagEnd; + + // Generate XML Attributte List + iOffset = LoadAttributes( pNode, xml, sErrorOut, iOffset ); + if( iOffset == string::npos ) + return string::npos; + + // alone tag or or + // current pointer: ^ ^ ^ + + if( iOffset < xml.size() && (xml[iOffset] == chXMLTagPre || xml[iOffset] == chXMLQuestion || xml[iOffset] == chXMLDash) ) + { + iOffset++; + + // skip over 2nd dash + if( iOffset < xml.size() && xml[iOffset] == chXMLDash ) + iOffset++; + + if( iOffset == xml.size() || xml[iOffset] != chXMLTagClose ) + { + // error: + if( sErrorOut.empty() ) + sErrorOut = "Element must be closed."; + + // ill-formed tag + return string::npos; + } + + // well-formed tag + ++iOffset; + + // UGLY: We want to ignore all XML meta tags. So, since the Node we + // just loaded is a meta tag, then Load ourself again using the rest + // of the file until we reach a non-meta tag. + if( !pNode->m_sName.empty() && (pNode->m_sName[0] == chXMLQuestion || pNode->m_sName[0] == chXMLExclamation) ) + iOffset = Load( pNode, xml, sErrorOut, iOffset ); + + return iOffset; + } + + // open/close tag ... + // ^- current pointer + if( XIsEmptyString(pNode->m_sValue) ) + { + // Text Value + ++iOffset; + unsigned iEnd = xml.find( chXMLTagOpen, iOffset ); + if( iEnd == string::npos ) + { + if( sErrorOut.empty() ) + sErrorOut = ssprintf( "%s must be closed with ", pNode->m_sName.c_str(), pNode->m_sName.c_str() ); + // error cos not exist CloseTag + return string::npos; + } + + SetString( xml, iOffset, iEnd, &pNode->m_sValue, true ); + + iOffset = iEnd; + // TEXTVALUE reference + ReplaceEntityText( pNode->m_sValue, g_mapEntitiesToChars ); + } + + // generate child nodes + while( iOffset < xml.size() ) + { + XNode *node = new XNode; + + iOffset = Load( node, xml, sErrorOut, iOffset ); + if( !node->m_sName.empty() ) + { + DEBUG_ASSERT( node->m_sName.size() ); + pNode->m_childs.insert( make_pair(node->m_sName, node) ); + } + else + { + delete node; + } + + // open/close tag ... + // ^- current pointer + // CloseTag case + if( iOffset+1 < xml.size() && xml[iOffset] == chXMLTagOpen && xml[iOffset+1] == chXMLTagPre ) + { + // + iOffset += 2; // C + + tcsskip( xml, iOffset ); + if( iOffset >= xml.size() ) + continue; + + unsigned iEnd = xml.find_first_of( " >", iOffset ); + if( iEnd == string::npos ) + { + if( sErrorOut.empty() ) + sErrorOut = ssprintf( "it must be closed with ", pNode->m_sName.c_str() ); + // error + return string::npos; + } + + RString closename; + SetString( xml, iOffset, iEnd, &closename ); + iOffset = iEnd+1; + if( closename == pNode->m_sName ) + { + // wel-formed open/close + // return '>' or ' ' after pointer + return iOffset; + } + else + { + // not welformed open/close + if( sErrorOut.empty() ) + sErrorOut = ssprintf( "'<%s> ... ' is not well-formed.", pNode->m_sName.c_str(), closename.c_str() ); + return string::npos; + } + } + else // Alone child Tag Loaded + { + if( XIsEmptyString(pNode->m_sValue) && iOffset < xml.size() && xml[iOffset] != chXMLTagOpen ) + { + // Text Value + unsigned iEnd = xml.find( chXMLTagOpen, iOffset ); + if( iEnd == string::npos ) + { + // error cos not exist CloseTag + if( sErrorOut.empty() ) + sErrorOut = ssprintf( "it must be closed with ", pNode->m_sName.c_str() ); + return string::npos; + } + + SetString( xml, iOffset, iEnd, &pNode->m_sValue, true ); + + iOffset = iEnd; + //TEXTVALUE + ReplaceEntityText( pNode->m_sValue, g_mapEntitiesToChars ); + } + } + } + + return iOffset; +} + +namespace +{ +bool GetAttrXML( RageFileBasic &f, const RString &sName, const RString &sValue ) +{ + RString s(sValue); + ReplaceEntityText( s, g_mapCharsToEntities ); + return f.Write(sName + "='" + s + "' ") != -1; +} + +bool GetXMLInternal( const XNode *pNode, RageFileBasic &f, bool bWriteTabs, int &iTabBase ) +{ + // tab + if( f.Write("\r\n") == -1 ) + return false; + if( bWriteTabs ) + for( int i = 0 ; i < iTabBase ; i++) + if( f.Write("\t") == -1 ) + return false; + + // m_sName) == -1 ) + return false; + + // m_attrs.empty() ) + if( f.Write(" ") == -1 ) + return false; + FOREACH_CONST_Attr( pNode, p ) + if( !GetAttrXML(f, p->first, p->second) ) + return false; + + if( pNode->m_childs.empty() && pNode->m_sValue.empty() ) + { + // alone tag + if( f.Write("/>") == -1 ) + return false; + } + else + { + // and get child + if( f.Write(">") == -1 ) + return false; + + if( !pNode->m_childs.empty() ) + iTabBase++; + + FOREACH_CONST_Child( pNode, p ) + if( !GetXMLInternal( pNode, f, bWriteTabs, iTabBase ) ) + return false; + + // Text Value + if( !pNode->m_sValue.empty() ) + { + if( !pNode->m_childs.empty() ) + { + if( f.Write("\r\n") == -1 ) + return false; + if( bWriteTabs ) + for( int i = 0 ; i < iTabBase ; i++) + if( f.Write("\t") == -1 ) + return false; + } + RString s( pNode->m_sValue ); + ReplaceEntityText( s, g_mapCharsToEntities ); + if( f.Write(s) == -1 ) + return false; + } + + // CloseTag + if( !pNode->m_childs.empty() ) + { + if( f.Write("\r\n") == -1 ) + return false; + if( bWriteTabs ) + for( int i = 0 ; i < iTabBase-1 ; i++) + if( f.Write("\t") == -1 ) + return false; + } + if( f.Write("m_sName + ">") == -1 ) + return false; + + if( !pNode->m_childs.empty() ) + iTabBase--; + } + return true; +} +} + +bool XmlFileUtil::GetXML( const XNode *pNode, RageFileBasic &f, bool bWriteTabs ) +{ + int iTabBase = 0; + return GetXMLInternal( pNode, f, bWriteTabs, iTabBase ); +} + +RString XmlFileUtil::GetXML( const XNode *pNode ) +{ + RageFileObjMem f; + int iTabBase = 0; + GetXMLInternal( pNode, f, true, iTabBase ); + return f.GetString(); +} + +bool XmlFileUtil::SaveToFile( const XNode *pNode, RageFileBasic &f, const RString &sStylesheet, bool bWriteTabs ) +{ + f.PutLine( "" ); + if( !sStylesheet.empty() ) + f.PutLine( "" ); + int iTabBase = 0; + if( !GetXMLInternal(pNode, f, bWriteTabs, iTabBase) ) + return false; + f.PutLine( "" ); + if( f.Flush() == -1 ) + return false; + return true; +} + +bool XmlFileUtil::SaveToFile( const XNode *pNode, const RString &sFile, const RString &sStylesheet, bool bWriteTabs ) +{ + RageFile f; + if( !f.Open(sFile, RageFile::WRITE) ) + { + LOG->Warn( "Couldn't open %s for writing: %s", sFile.c_str(), f.GetError().c_str() ); + return false; + } + + return SaveToFile( pNode, f, sStylesheet, bWriteTabs ); +} + /* * (c) 2001-2004 Chris Danford * All rights reserved. diff --git a/stepmania/src/XmlFileUtil.h b/stepmania/src/XmlFileUtil.h index ac9a260951..622b1836fc 100644 --- a/stepmania/src/XmlFileUtil.h +++ b/stepmania/src/XmlFileUtil.h @@ -10,6 +10,13 @@ namespace XmlFileUtil { bool LoadFromFileShowErrors( XNode &xml, const RString &sFile ); bool LoadFromFileShowErrors( XNode &xml, RageFileBasic &f ); + + // Load/Save XML + unsigned Load( XNode *pNode, const RString &sXml, RString &sErrorOut, unsigned iOffset = 0 ); + bool GetXML( const XNode *pNode, RageFileBasic &f, bool bWriteTabs = true ); + RString GetXML( const XNode *pNode ); + bool SaveToFile( const XNode *pNode, const RString &sFile, const RString &sStylesheet = "", bool bWriteTabs = true ); + bool SaveToFile( const XNode *pNode, RageFileBasic &f, const RString &sStylesheet = "", bool bWriteTabs = true ); } #endif