// NeL - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #include "stdmisc.h" #include "nel/misc/xml_pack.h" #include "nel/misc/file.h" using namespace std; namespace NLMISC { NLMISC_SAFE_SINGLETON_IMPL(CXMLPack); // For a simple parser, we read by line, with a limit to 1Ko const uint32 MaxLineSize = 1*1024; /// Consume space and tab characters (but NOT newlines) void CXMLPack::skipWS(string::iterator &it, string::iterator end) { while (it != end && (*it == ' ' || *it == '\t')) ++it; } /// Try to match the specified text at current position. Return false of no match bool CXMLPack::matchString(string::iterator &it, string::iterator end, const char *text) { string::iterator rewind = it; // skip leading WS skipWS(it, end); while (it != end && *text && *text == *it) { ++it; ++text; } if (*text == 0) { // we have advanced up to the end of text, so the match is OK return true; } // no match ! // rewind it = rewind; return false; } /// Advance up to the beginning of the next line, incrementing the in/out param lineCount void CXMLPack::skipLine(string::iterator &it, string::iterator end, uint32 &lineCount) { // advance up to end of string or newline char while (it != end && *it != '\n') { ++it; } // skip the new line char if (it != end && *it == '\n') { ++it; ++lineCount; } } // Add an xml pack to the manager bool CXMLPack::add (const std::string &xmlPackFileName) { // prepare the container to store this pack file TStringId packId = CStringMapper::map(xmlPackFileName); TPackList::iterator packIt(_XMLPacks.find(packId)); if (packIt != _XMLPacks.end()) { nlwarning("CXMLPack::add : can't add xml_pack file '%s' because already added", xmlPackFileName.c_str()); return false; } TXMLPackInfo &packInfo = _XMLPacks[packId]; // open the xml pack for later access // packInfo.FileHandler = fopen(xmlPackFileName.c_str(), "rb"); // open the xml pack for parsing CIFile packFile; packFile.open(xmlPackFileName); uint32 packSize = packFile.getFileSize(); string buffer; buffer.resize(packSize); // read the file in memory for parsing packFile.serialBuffer((uint8*)buffer.data(), packSize); string::iterator it=buffer.begin(), end(buffer.end()); uint32 lineCount = 0; // check the xml pack header element if (!matchString(it, end, "")) { nlwarning("Error : invalid pack file '%s', invalid header", xmlPackFileName.c_str()); return false; } // advance to next line skipLine(it, end, lineCount); // now enter the sub file loop for(;;) { TXMLFileInfo fileInfo; // match a sub file header if (!matchString(it, end, "' while (it != end && *it != '>') ++it; if (it == end) { nlwarning("Error : invalid pack file sub header at line %u in '%s', can't found element closing '>'", lineCount, xmlPackFileName.c_str()); return false; } // advance to next line (beginning of sub file) skipLine(it, end, lineCount); string::iterator beginOfFile = it; string::iterator endOfFile = it; // now, advance up to the end of file while (it != end && !matchString(it, end, "")) { skipLine(it, end, lineCount); endOfFile = it; } // we must not be at end of file if (it == end) { nlwarning("Error : invalid sub file at line %u in '%s', reach end of file without closing file and pack elements", lineCount, xmlPackFileName.c_str()); return false; } // ok, the file is parsed, store it fileInfo.FileName = CStringMapper::map(subFileName); fileInfo.FileOffset = (uint32)(beginOfFile - buffer.begin()); fileInfo.FileSize = (uint32)(endOfFile - beginOfFile); // fileInfo.FileHandler = fopen(xmlPackFileName.c_str(), "rb"); packInfo._XMLFiles.insert(make_pair(fileInfo.FileName, fileInfo)); // advance to next line skipLine(it, end, lineCount); // check for end of pack if (matchString(it, end, "")) { // ok, the parse is over break; } // continue to next file in pack } nldebug("XMLPack : xml_pack '%s' added to the collection with %u files", xmlPackFileName.c_str(), packInfo._XMLFiles.size()); // ok, parsing ended return true; } // List all files in an xml_pack file void CXMLPack::list (const std::string &xmlPackFileName, std::vector &allFiles) { TStringId key = CStringMapper::map(xmlPackFileName); TPackList::const_iterator it(_XMLPacks.find(key)); if (it != _XMLPacks.end()) { const TXMLPackInfo &packInfo = it->second; // we found it, fill the out vector TXMLPackInfo::TFileList::const_iterator first(packInfo._XMLFiles.begin()), last(packInfo._XMLFiles.end()); for (; first != last; ++first) { const TXMLFileInfo &fileInfo = first->second; allFiles.push_back(CStringMapper::unmap(fileInfo.FileName)); } } } // Used by CIFile to get information about the files within the xml pack FILE* CXMLPack::getFile (const std::string &sFileName, uint32 &rFileSize, uint32 &rFileOffset, bool &rCacheFileOnOpen, bool &rAlwaysOpened) { // split the name appart from the '@@' separator to get the pack file name // and subfile name vector parts; explode(sFileName, string("@@"), parts, true); if (parts.size() != 2) { nlwarning("CXMLPack::getFile : Can't extract pack and filename from '%s', found %u part instead of 2 when spliting apart from '@@'", sFileName.c_str(), parts.size()); return NULL; } TStringId packId = CStringMapper::map(parts[0]); TStringId fileId = CStringMapper::map(parts[1]); TPackList::iterator packIt(_XMLPacks.find(packId)); if (packIt == _XMLPacks.end()) { nlwarning("CXMLPack::getFile : Can't find xml pack file named '%s' to open '%s'", parts[0].c_str(), sFileName.c_str()); return NULL; } TXMLPackInfo &packInfo = packIt->second; TXMLPackInfo::TFileList::iterator fileIt = packInfo._XMLFiles.find(fileId); if (fileIt == packInfo._XMLFiles.end()) { nlwarning("CXMLPack::getFile : Can't find xml file named '%s' in pack '%s'", parts[1].c_str(), parts[0].c_str()); return NULL; } // ok, we have found it ! TXMLFileInfo &fileInfo = fileIt->second; // fill the return value rFileSize = fileInfo.FileSize; rFileOffset = fileInfo.FileOffset; rCacheFileOnOpen = false; rAlwaysOpened = false; FILE *fp = fopen(parts[0].c_str(), "rb"); return fp; } } // namespace NLMISC