/* * ORXONOX - the hottest 3D action shooter ever to exist * > www.orxonox.net < * * * License notice: * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: * Fabian 'x3n' Landau * Co-authors: * ... * */ #include "Loader.h" #include #include #include #include #include "util/Output.h" #include "util/Exception.h" #include "util/StringUtils.h" #include "BaseObject.h" #include "LuaState.h" #include "Namespace.h" #include "Resource.h" #include "XMLFile.h" #include "object/Iterator.h" #include "object/ObjectList.h" namespace orxonox { Loader* Loader::singletonPtr_s = nullptr; /** @brief Loads the input file, while conforming to the restrictions given by the input ClassTreeMask. @param file The file to be loaded. @param mask A ClassTreeMask, which defines which types of classes are loaded and which aren't. @param bVerbose Whether the loader is verbose (prints its progress in a low output level) or not. @param bRemoveLuaTags If true lua tags are just ignored and removed. The default is false. @return Returns true if successful. */ bool Loader::load(const XMLFile* file, const ClassTreeMask& mask, bool bVerbose, bool bRemoveLuaTags) { if (!file) return false; this->currentMask_ = file->getMask() * mask; std::string xmlInput; std::shared_ptr>>> lineTrace(std::make_shared>>>()); lineTrace->reserve(1000); //arbitrary number if (file->getLuaSupport() && !bRemoveLuaTags) { // Use the LuaState to replace the XML tags (calls our function) const std::unique_ptr luaState(new LuaState()); luaState->setTraceMap(lineTrace); luaState->setIncludeParser(&Loader::replaceLuaTags); luaState->includeFile(file->getFilename()); xmlInput = luaState->getOutput().str(); } else { std::shared_ptr info = Resource::getInfo(file->getFilename()); if (info == nullptr) { orxout(user_error, context::loader) << "Could not find XML file '" << file->getFilename() << "'." << endl; return false; } xmlInput = Resource::open(file->getFilename())->getAsString(); if (bRemoveLuaTags) { // Remove all Lua code. // Note: we only need this to speed up parsing of level files at the // start of the program. // Assumption: the LevelInfo tag does not use Lua scripting xmlInput = Loader::removeLuaTags(xmlInput); } } try { if(bVerbose) { orxout(user_info) << "Start loading " << file->getFilename() << "..." << endl; orxout(internal_info, context::loader) << "Mask: " << this->currentMask_ << endl; } else { orxout(verbose, context::loader) << "Start loading " << file->getFilename() << "..." << endl; orxout(verbose_more, context::loader) << "Mask: " << this->currentMask_ << endl; } ticpp::Document xmlfile(file->getFilename()); xmlfile.Parse(xmlInput, true); ticpp::Element rootElement; rootElement.SetAttribute("name", "root"); rootElement.SetAttribute("bAutogenerated", true); for (ticpp::Iterator child = xmlfile.FirstChildElement(false); child != child.end(); child++) rootElement.InsertEndChild(*child); orxout(verbose, context::loader) << " creating root-namespace..." << endl; Namespace* rootNamespace = new Namespace(Context::getRootContext()); rootNamespace->setLoaderIndentation(" "); rootNamespace->setFile(file); rootNamespace->setRoot(true); rootNamespace->XMLPort(rootElement, XMLPort::LoadObject); if(bVerbose) orxout(user_info) << "Finished loading " << file->getFilename() << '.' << endl; else orxout(verbose, context::loader) << "Finished loading " << file->getFilename() << '.' << endl; orxout(verbose, context::loader) << "Namespace-tree:" << '\n' << rootNamespace->toString(" ") << endl; return true; } catch (ticpp::Exception& ex) { orxout(user_error, context::loader) << endl; orxout(user_error, context::loader) << "An XML-error occurred in Loader.cc while loading " << file->getFilename() << ':' << endl; OutputLevel ticpplevel = user_error; if (lineTrace->size() > 0) { ticpplevel = internal_error; //Extract the line number from the exception std::string tempstring(ex.what()); std::string::size_type pos = tempstring.find("\nLine: "); if (pos != std::string::npos) { std::istringstream istr(tempstring.substr(pos + 7)); size_t line; istr >> line; if (line <= lineTrace->size()) { std::vector> linesources = lineTrace->at(line - 1); std::ostringstream message; message << "Possible sources of error:" << endl; for (const std::pair& pair : linesources) { message << pair.first << ", Line " << pair.second << endl; } orxout(user_error, context::loader) << message.str() << endl; } } } orxout(ticpplevel, context::loader) << ex.what() << endl; orxout(user_error, context::loader) << "Loading aborted." << endl; } catch (Exception& ex) { orxout(user_error, context::loader) << endl; orxout(user_error, context::loader) << "A loading-error occurred in Loader.cc while loading " << file->getFilename() << ':' << endl; orxout(user_error, context::loader) << ex.what() << endl; orxout(user_error, context::loader) << "Loading aborted." << endl; } catch (...) { orxout(user_error, context::loader) << endl; orxout(user_error, context::loader) << "An error occurred in Loader.cc while loading " << file->getFilename() << ':' << endl; orxout(user_error, context::loader) << Exception::handleMessage() << endl; orxout(user_error, context::loader) << "Loading aborted." << endl; } //The Tardis' version of boost is too old... #if BOOST_VERSION >= 104600 boost::filesystem::path temppath = boost::filesystem::temp_directory_path() / "orxonoxml.xml"; //Need binary mode, because xmlInput already has \r\n for windows boost::filesystem::ofstream outfile(temppath, std::ios_base::binary | std::ios_base::out); outfile << xmlInput; outfile.flush(); outfile.close(); orxout(internal_error, context::loader) << "The complete xml file has been saved to " << temppath << endl; #endif return false; } void Loader::unload(const XMLFile* file, const ClassTreeMask& mask) { if (!file) return; for (ObjectList::iterator it = ObjectList().begin(); it; ) { if ((it->getFile() == file) && mask.isIncluded(it->getIdentifier())) (it++)->destroy(); else ++it; } } bool Loader::getLuaTags(const std::string& text, std::map& luaTags) { // fill map with all Lua tags { size_t pos = 0; while ((pos = text.find("", pos)) != std::string::npos) luaTags[pos++] = false; } // erase all tags from the map that are between two quotes // that means occurrences like "...." would be deleted // however occurrences of lua tags within quotas are retained: ".. .. " { std::map::iterator it = luaTags.begin(); bool bBetweenQuotes = false; size_t pos = 0; while ((pos = getNextQuote(text, pos)) != std::string::npos) { while ((it != luaTags.end()) && (it->first < pos)) { if (bBetweenQuotes) { std::map::iterator it2 = it; it2++; if (it->second && !(it2->second) && it2->first < pos) std::advance(it, 2); else luaTags.erase(it++); } else ++it; } bBetweenQuotes = !bBetweenQuotes; pos++; } } // check whether on every opening tag follows { bool expectedValue = true; for (const auto& mapEntry : luaTags) { if (mapEntry.second == expectedValue) expectedValue = !expectedValue; else { expectedValue = false; break; } } if (!expectedValue) { orxout(internal_error, context::loader) << "Error parsing file: lua tags not matching" << endl; // TODO: error handling return false; } } return true; } std::string Loader::replaceLuaTags(const std::string& text) { // create a map with all lua tags std::map luaTags; if (!getLuaTags(text, luaTags)) return ""; // Use a stringstream object to speed up the parsing std::ostringstream output; // cut the original string into pieces and put them together with print() instead of lua tags { std::map::iterator it = luaTags.begin(); bool bInPrintFunction = true; size_t start = 0; size_t end = 0; do { if (it != luaTags.end()) end = (it++)->first; else end = std::string::npos; unsigned int equalSignCounter = 0; if (bInPrintFunction) { // count ['='[ and ]'='] and replace tags with print([[ and ]]) const std::string& temp = text.substr(start, end - start); { size_t pos = 0; while ((pos = temp.find('[', pos)) != std::string::npos) { unsigned int tempCounter = 1; size_t tempPos = pos++; while (temp[++tempPos] == '=') { tempCounter++; } if (temp[tempPos] != '[') { tempCounter = 0; } else if (tempCounter == 0) { tempCounter = 1; } if (tempCounter > equalSignCounter) equalSignCounter = tempCounter; } } { size_t pos = 0; while ((pos = temp.find(']', pos)) != std::string::npos) { unsigned int tempCounter = 1; size_t tempPos = pos++; while (temp[++tempPos] == '=') { tempCounter++; } if (temp[tempPos] != ']') { tempCounter = 0; } else if (tempCounter == 0) { tempCounter = 1; } if (tempCounter > equalSignCounter) equalSignCounter = tempCounter; } } std::string equalSigns; for (unsigned int i = 0; i < equalSignCounter; i++) { equalSigns += '='; } //A newline directly after square brackets is ignored. To make sure that the string is printed //exactly as it is, including newlines at the beginning, insert a space after the brackets. bool needsExtraSpace = false; if (temp.size() > 0 && (temp[0] == '\n' || temp[0] == '\r')) // begins with \n or \r (a line break) needsExtraSpace = true; output << "print([" + equalSigns + (needsExtraSpace ? "[ " : "[") + temp + ']' + equalSigns +"])"; start = end + 5; } else { output << text.substr(start, end - start); start = end + 2; } bInPrintFunction = !bInPrintFunction; } while (end != std::string::npos); } return output.str(); } std::string Loader::removeLuaTags(const std::string& text) { // create a map with all lua tags std::map luaTags; if (!getLuaTags(text, luaTags)) return ""; // Use a stringstream object to speed up the concatenation std::ostringstream output; // cut the original string into pieces and only write the non Lua parts std::map::iterator it = luaTags.begin(); bool bLuaCode = false; size_t start = 0; size_t end = 0; do { if (it != luaTags.end()) end = (it++)->first; else end = std::string::npos; if (!bLuaCode) { output << text.substr(start, end - start); start = end + 5; } else { //Preserve the amount of lines, otherwise the linenumber from the xml parse error is useless std::string tempstring = text.substr(start, end - start); output << std::string(std::count(tempstring.begin(), tempstring.end(), '\n'), '\n'); start = end + 2; } bLuaCode = !bLuaCode; } while (end != std::string::npos); return output.str(); } }