Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/trunk/src/libraries/core/Loader.cc @ 10264

Last change on this file since 10264 was 10264, checked in by muemart, 9 years ago

Various fixes and improvements in the Loader (and LuaState)

  • When there's lua code in the xml, the error message now says where exactly the error is coming from (file and line)
  • When there's an error in the loader when reading xml, the full xml file gets dumped to the temporary directory for inspection
  • Fix and simplify detection of lua tags
  • Make sure the reported line in an xml parsing error is useful
  • Property svn:eol-style set to native
File size: 18.4 KB
Line 
1/*
2 *   ORXONOX - the hottest 3D action shooter ever to exist
3 *                    > www.orxonox.net <
4 *
5 *
6 *   License notice:
7 *
8 *   This program is free software; you can redistribute it and/or
9 *   modify it under the terms of the GNU General Public License
10 *   as published by the Free Software Foundation; either version 2
11 *   of the License, or (at your option) any later version.
12 *
13 *   This program is distributed in the hope that it will be useful,
14 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
15 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 *   GNU General Public License for more details.
17 *
18 *   You should have received a copy of the GNU General Public License
19 *   along with this program; if not, write to the Free Software
20 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 *
22 *   Author:
23 *      Fabian 'x3n' Landau
24 *   Co-authors:
25 *      ...
26 *
27 */
28
29#include "Loader.h"
30
31#include <sstream>
32#include <tinyxml/ticpp.h>
33#include <boost/scoped_ptr.hpp>
34#include <boost/filesystem.hpp>
35#include <boost/filesystem/fstream.hpp>
36
37#include "util/Output.h"
38#include "util/Exception.h"
39#include "util/StringUtils.h"
40#include "BaseObject.h"
41#include "LuaState.h"
42#include "Namespace.h"
43#include "Resource.h"
44#include "XMLFile.h"
45#include "object/Iterator.h"
46#include "object/ObjectList.h"
47
48namespace orxonox
49{
50    std::vector<std::pair<const XMLFile*, ClassTreeMask> > Loader::files_s;
51    ClassTreeMask Loader::currentMask_s;
52
53    bool Loader::open(const XMLFile* file, const ClassTreeMask& mask, bool bVerbose)
54    {
55        Loader::add(file, mask);
56        return Loader::load(file, mask, bVerbose);
57    }
58
59    void Loader::close()
60    {
61        Loader::unload();
62        Loader::files_s.clear();
63    }
64
65    void Loader::close(const XMLFile* file)
66    {
67        Loader::unload(file);
68        Loader::remove(file);
69    }
70
71    void Loader::add(const XMLFile* file, const ClassTreeMask& mask)
72    {
73        if (!file)
74            return;
75        Loader::files_s.insert(Loader::files_s.end(), std::pair<const XMLFile*, ClassTreeMask>(file, mask));
76    }
77
78    void Loader::remove(const XMLFile* file)
79    {
80        if (!file)
81            return;
82        for (std::vector<std::pair<const XMLFile*, ClassTreeMask> >::iterator it = Loader::files_s.begin(); it != Loader::files_s.end(); ++it)
83        {
84            if (it->first == file)
85            {
86                Loader::files_s.erase(it);
87                break;
88            }
89        }
90    }
91
92    /**
93    @brief
94        Loads all opened files, while conforming to the restrictions given by the input ClassTreeMask.
95    @param mask
96        A ClassTreeMask, which defines which types of classes are loaded and which aren't.
97    @param bVerbose
98        Whether the loader is verbose (prints its progress in a low output level) or not.
99    @return
100        Returns true if successful.
101    */
102    bool Loader::load(const ClassTreeMask& mask, bool bVerbose)
103    {
104        bool success = true;
105        for (std::vector<std::pair<const XMLFile*, ClassTreeMask> >::iterator it = Loader::files_s.begin(); it != Loader::files_s.end(); ++it)
106            if (!Loader::load(it->first, it->second * mask, bVerbose))
107                success = false;
108
109        return success;
110    }
111
112    void Loader::unload(const ClassTreeMask& mask)
113    {
114        for (ObjectList<BaseObject>::iterator it = ObjectList<BaseObject>::begin(); it != ObjectList<BaseObject>::end(); )
115        {
116            if (mask.isIncluded(it->getIdentifier()))
117                (it++)->destroy();
118            else
119                ++it;
120        }
121    }
122
123    /**
124    @brief
125        Reloads all opened files, while conforming to the restrictions given by the input ClassTreeMask.
126    @param mask
127        A ClassTreeMask, which defines which types of classes are reloaded and which aren't.
128    @param bVerbose
129        Whether the loader is verbose (prints its progress in a low output level) or not.
130    @return
131        Returns true if successful.
132    */
133    bool Loader::reload(const ClassTreeMask& mask, bool bVerbose)
134    {
135        Loader::unload(mask);
136        return Loader::load(mask, bVerbose);
137    }
138
139    /**
140    @brief
141        Loads the input file, while conforming to the restrictions given by the input ClassTreeMask.
142    @param file
143        The file to be loaded.
144    @param mask
145        A ClassTreeMask, which defines which types of classes are loaded and which aren't.
146    @param bVerbose
147        Whether the loader is verbose (prints its progress in a low output level) or not.
148    @param bRemoveLuaTags
149        If true lua tags are just ignored and removed. The default is false.
150    @return
151        Returns true if successful.
152    */
153    bool Loader::load(const XMLFile* file, const ClassTreeMask& mask, bool bVerbose, bool bRemoveLuaTags)
154    {
155        if (!file)
156            return false;
157
158        Loader::currentMask_s = file->getMask() * mask;
159
160        std::string xmlInput;
161
162        shared_ptr<std::vector<std::vector<std::pair<std::string, size_t>>>> lineTrace(new std::vector<std::vector<std::pair<std::string, size_t>>>());
163        lineTrace->reserve(1000); //arbitrary number
164
165
166        if (file->getLuaSupport() && !bRemoveLuaTags)
167        {
168            // Use the LuaState to replace the XML tags (calls our function)
169            scoped_ptr<LuaState> luaState(new LuaState());
170            luaState->setTraceMap(lineTrace);
171            luaState->setIncludeParser(&Loader::replaceLuaTags);
172            luaState->includeFile(file->getFilename());
173            xmlInput = luaState->getOutput().str();
174        }
175        else
176        {
177            shared_ptr<ResourceInfo> info = Resource::getInfo(file->getFilename());
178            if (info == NULL)
179            {
180                orxout(user_error, context::loader) << "Could not find XML file '" << file->getFilename() << "'." << endl;
181                return false;
182            }
183            xmlInput = Resource::open(file->getFilename())->getAsString();
184
185            if (bRemoveLuaTags)
186            {
187                // Remove all Lua code.
188                // Note: we only need this to speed up parsing of level files at the
189                // start of the program.
190                // Assumption: the LevelInfo tag does not use Lua scripting
191                xmlInput = removeLuaTags(xmlInput);
192            }
193        }
194
195        try
196        {
197            if(bVerbose)
198            {
199                orxout(user_info) << "Start loading " << file->getFilename() << "..." << endl;
200                orxout(internal_info, context::loader) << "Mask: " << Loader::currentMask_s << endl;
201            }
202            else
203            {
204                orxout(verbose, context::loader) << "Start loading " << file->getFilename() << "..." << endl;
205                orxout(verbose_more, context::loader) << "Mask: " << Loader::currentMask_s << endl;
206            }
207
208            ticpp::Document xmlfile(file->getFilename());
209            xmlfile.Parse(xmlInput, true);
210
211            ticpp::Element rootElement;
212            rootElement.SetAttribute("name", "root");
213            rootElement.SetAttribute("bAutogenerated", true);
214
215            for (ticpp::Iterator<ticpp::Element> child = xmlfile.FirstChildElement(false); child != child.end(); child++)
216                rootElement.InsertEndChild(*child);
217
218            orxout(verbose, context::loader) << "  creating root-namespace..." << endl;
219            Namespace* rootNamespace = new Namespace(Context::getRootContext());
220            rootNamespace->setLoaderIndentation("    ");
221            rootNamespace->setFile(file);
222            rootNamespace->setNamespace(rootNamespace);
223            rootNamespace->setRoot(true);
224            rootNamespace->XMLPort(rootElement, XMLPort::LoadObject);
225
226            if(bVerbose)
227                orxout(user_info) << "Finished loading " << file->getFilename() << '.' << endl;
228            else
229                orxout(verbose, context::loader) << "Finished loading " << file->getFilename() << '.' << endl;
230
231            orxout(verbose, context::loader) << "Namespace-tree:" << '\n' << rootNamespace->toString("  ") << endl;
232
233            return true;
234        }
235        catch (ticpp::Exception& ex)
236        {
237            orxout(user_error, context::loader) << endl;
238            orxout(user_error, context::loader) << "An XML-error occurred in Loader.cc while loading " << file->getFilename() << ':' << endl;
239            orxout(user_error, context::loader) << ex.what() << endl;
240            orxout(user_error, context::loader) << "Loading aborted." << endl;
241            if (lineTrace->size() > 0)
242            {
243                //Extract the line number from the exception
244                std::string tempstring(ex.what());
245                std::string::size_type pos = tempstring.find("\nLine: ");
246                if (pos != std::string::npos)
247                {
248                    std::istringstream istr(tempstring.substr(pos + 7));
249                    size_t line;
250                    istr >> line;
251                    if (line <= lineTrace->size())
252                    {
253                        std::vector<std::pair<std::string, size_t>> linesources = lineTrace->at(line - 1);
254                        orxout(user_error, context::loader) << "Line contains data from:" << endl;
255                        for (std::vector<std::pair<std::string, size_t>>::iterator it = linesources.begin(); it != linesources.end(); ++it)
256                        {
257                            orxout(user_error, context::loader) << it->first << " , Line " << it->second << endl;
258                        }                       
259                    }
260                }
261            }
262        }
263        catch (Exception& ex)
264        {
265            orxout(user_error, context::loader) << endl;
266            orxout(user_error, context::loader) << "A loading-error occurred in Loader.cc while loading " << file->getFilename() << ':' << endl;
267            orxout(user_error, context::loader) << ex.what() << endl;
268            orxout(user_error, context::loader) << "Loading aborted." << endl;
269        }
270        catch (...)
271        {
272            orxout(user_error, context::loader) << endl;
273            orxout(user_error, context::loader) << "An error occurred in Loader.cc while loading " << file->getFilename() << ':' << endl;
274            orxout(user_error, context::loader) << Exception::handleMessage() << endl;
275            orxout(user_error, context::loader) << "Loading aborted." << endl;
276        }
277        //The Tardis' version of boost is too old...
278#if BOOST_VERSION >= 104600
279        boost::filesystem::path temppath = boost::filesystem::temp_directory_path() / "orxonoxml.xml";
280        //Need binary mode, because xmlInput already has \r\n for windows
281        boost::filesystem::ofstream outfile(temppath, std::ios_base::binary | std::ios_base::out);
282        outfile << xmlInput;
283        outfile.flush();
284        outfile.close();
285        orxout(user_error, context::loader) << "The complete xml file has been saved to " << temppath << endl;
286#endif
287        return false;
288    }
289
290    void Loader::unload(const XMLFile* file, const ClassTreeMask& mask)
291    {
292        if (!file)
293            return;
294        for (ObjectList<BaseObject>::iterator it = ObjectList<BaseObject>::begin(); it; )
295        {
296            if ((it->getFile() == file) && mask.isIncluded(it->getIdentifier()))
297                (it++)->destroy();
298            else
299                ++it;
300        }
301    }
302
303    /**
304    @brief
305        Reloads the input file, while conforming to the restrictions given by the input ClassTreeMask.
306    @param file
307        The file to be reloaded.
308    @param mask
309        A ClassTreeMask, which defines which types of classes are reloaded and which aren't.
310    @param bVerbose
311        Whether the loader is verbose (prints its progress in a low output level) or not.
312    @return
313        Returns true if successful.
314    */
315    bool Loader::reload(const XMLFile* file, const ClassTreeMask& mask, bool bVerbose)
316    {
317        Loader::unload(file, mask);
318        return Loader::load(file, mask, bVerbose);
319    }
320
321    bool Loader::getLuaTags(const std::string& text, std::map<size_t, bool>& luaTags)
322    {
323        // fill map with all Lua tags
324        {
325            size_t pos = 0;
326            while ((pos = text.find("<?lua", pos)) != std::string::npos)
327                luaTags[pos++] = true;
328        }
329        {
330            size_t pos = 0;
331            while ((pos = text.find("?>", pos)) != std::string::npos)
332                luaTags[pos++] = false;
333        }
334
335        // erase all tags from the map that are between two quotes
336        {
337            std::map<size_t, bool>::iterator it = luaTags.begin();
338            while(it != luaTags.end())
339            {
340                if (isBetweenQuotes(text, it->first))
341                {
342                    luaTags.erase(it++);
343                }
344                else
345                {
346                    ++it;
347                }
348            }
349        }
350
351        // check whether on every opening <?lua tag a closing ?> tag follows
352        {
353            bool expectedValue = true;
354            for (std::map<size_t, bool>::iterator it = luaTags.begin(); it != luaTags.end(); ++it)
355            {
356                if (it->second == expectedValue)
357                    expectedValue = !expectedValue;
358                else
359                {
360                    expectedValue = false;
361                    break;
362                }
363            }
364            if (!expectedValue)
365            {
366                orxout(internal_error, context::loader) << "Error parsing file: lua tags not matching" << endl;
367                // TODO: error handling
368                return false;
369            }
370        }
371
372        return true;
373    }
374
375    std::string Loader::replaceLuaTags(const std::string& text)
376    {
377        // create a map with all lua tags
378        std::map<size_t, bool> luaTags;
379        if (!getLuaTags(text, luaTags))
380            return "";
381
382        // Use a stringstream object to speed up the parsing
383        std::ostringstream output;
384
385        // cut the original string into pieces and put them together with print() instead of lua tags
386        {
387            std::map<size_t, bool>::iterator it = luaTags.begin();
388            bool bInPrintFunction = true;
389            size_t start = 0;
390            size_t end = 0;
391
392            do
393            {
394                if (it != luaTags.end())
395                    end = (it++)->first;
396                else
397                    end = std::string::npos;
398
399                unsigned int equalSignCounter = 0;
400
401                if (bInPrintFunction)
402                {
403                    // count ['='[ and ]'='] and replace tags with print([[ and ]])
404                    const std::string& temp = text.substr(start, end - start);
405                    {
406                    size_t pos = 0;
407                    while ((pos = temp.find('[', pos)) != std::string::npos)
408                    {
409                        unsigned int tempCounter = 1;
410                        size_t tempPos = pos++;
411                        while (temp[++tempPos] == '=')
412                        {
413                            tempCounter++;
414                        }
415                        if (temp[tempPos] != '[')
416                        {
417                            tempCounter = 0;
418                        }
419                        else if (tempCounter == 0)
420                        {
421                            tempCounter = 1;
422                        }
423                        if (tempCounter > equalSignCounter)
424                            equalSignCounter = tempCounter;
425                        }
426                    }
427                    {
428                        size_t pos = 0;
429                        while ((pos = temp.find(']', pos)) != std::string::npos)
430                        {
431                            unsigned int tempCounter = 1;
432                            size_t tempPos = pos++;
433                            while (temp[++tempPos] == '=')
434                            {
435                                tempCounter++;
436                            }
437                            if (temp[tempPos] != ']')
438                            {
439                                tempCounter = 0;
440                            }
441                            else if (tempCounter == 0)
442                            {
443                                tempCounter = 1;
444                            }
445                            if (tempCounter > equalSignCounter)
446                                equalSignCounter = tempCounter;
447                        }
448                    }
449                    std::string equalSigns;
450                    for (unsigned int i = 0; i < equalSignCounter; i++)
451                    {
452                        equalSigns += '=';
453                    }
454                    //A newline directly after square brackets is ignored. To make sure that the string is printed
455                    //exactly as it is, including newlines at the beginning, insert a space after the brackets.
456                    output << "print([" + equalSigns + "[ " + temp + ']' + equalSigns +"])";
457                    start = end + 5;
458                }
459                else
460                {
461                    output << text.substr(start, end - start);
462                    start = end + 2;
463                }
464
465                bInPrintFunction = !bInPrintFunction;
466            }
467            while (end != std::string::npos);
468        }
469
470        return output.str();
471    }
472
473    std::string Loader::removeLuaTags(const std::string& text)
474    {
475        // create a map with all lua tags
476        std::map<size_t, bool> luaTags;
477        if (!getLuaTags(text, luaTags))
478            return "";
479
480        // Use a stringstream object to speed up the concatenation
481        std::ostringstream output;
482
483        // cut the original string into pieces and only write the non Lua parts
484        std::map<size_t, bool>::iterator it = luaTags.begin();
485        bool bLuaCode = false;
486        size_t start = 0;
487        size_t end = 0;
488
489        do
490        {
491            if (it != luaTags.end())
492                end = (it++)->first;
493            else
494                end = std::string::npos;
495
496            if (!bLuaCode)
497            {
498                output << text.substr(start, end - start);
499                start = end + 5;
500            }
501            else
502            {
503                //Preserve the amount of lines, otherwise the linenumber from the xml parse error is useless
504                std::string tempstring = text.substr(start, end - start);
505                output << std::string(std::count(tempstring.begin(), tempstring.end(), '\n'), '\n');
506                start = end + 2;
507            }
508
509            bLuaCode = !bLuaCode;
510        }
511        while (end != std::string::npos);
512
513        return output.str();
514    }
515}
Note: See TracBrowser for help on using the repository browser.