da23faa6be5a4a7f893c2d39db468877303ded22
[xonotic/netradiant.git] / radiant / eclass_xml.cpp
1 /*
2 Copyright (C) 2001-2006, William Joseph.
3 All Rights Reserved.
4
5 This file is part of GtkRadiant.
6
7 GtkRadiant is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 GtkRadiant is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with GtkRadiant; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20 */
21
22 #include "eclass_xml.h"
23
24 #include "ieclass.h"
25 #include "irender.h"
26 #include "ifilesystem.h"
27 #include "iarchive.h"
28
29 #include "xml/xmlparser.h"
30 #include "generic/object.h"
31 #include "generic/reference.h"
32 #include "stream/stringstream.h"
33 #include "stream/textfilestream.h"
34 #include "os/path.h"
35 #include "eclasslib.h"
36 #include "modulesystem/moduleregistry.h"
37 #include "stringio.h"
38
39 #define PARSE_ERROR(elementName, name) makeQuoted(elementName) << " is not a valid child of " << makeQuoted(name)
40
41 class IgnoreBreaks
42 {
43 public:
44   const char* m_first;
45   const char* m_last;
46   IgnoreBreaks(const char* first, const char* last) : m_first(first), m_last(last)
47   {
48   }
49 };
50
51 template<typename TextOutputStreamType>
52 TextOutputStreamType& ostream_write(TextOutputStreamType& ostream, const IgnoreBreaks& ignoreBreaks)
53 {
54   for(const char* i = ignoreBreaks.m_first; i != ignoreBreaks.m_last; ++i)
55   {
56     if(*i != '\n')
57     {
58       ostream << *i;
59     }
60   }
61   return ostream;
62 }
63
64 namespace
65 {
66
67 class TreeXMLImporter : public TextOutputStream
68 {
69 public:
70   virtual TreeXMLImporter& pushElement(const XMLElement& element) = 0;
71   virtual void popElement(const char* name) = 0;
72 };
73
74 template<typename Type>
75 class Storage
76 {
77   char m_storage[sizeof(Type)];
78 public:
79   Type& get()
80   {
81     return *reinterpret_cast<Type*>(m_storage);
82   }
83   const Type& get() const
84   {
85     return *reinterpret_cast<const Type*>(m_storage);
86   }
87 };
88
89 class BreakImporter : public TreeXMLImporter
90 {
91 public:
92   BreakImporter(StringOutputStream& comment)
93   {
94     comment << '\n';
95   }
96   static const char* name()
97   {
98     return "n";
99   }
100   TreeXMLImporter& pushElement(const XMLElement& element)
101   {
102     ERROR_MESSAGE(PARSE_ERROR(element.name(), name()));
103     return *this;
104   }
105   void popElement(const char* elementName)
106   {
107     ERROR_MESSAGE(PARSE_ERROR(elementName, name()));
108   }
109   std::size_t write(const char* data, std::size_t length)
110   {
111     return length;
112   }
113 };
114
115 class AttributeImporter : public TreeXMLImporter
116 {
117   StringOutputStream& m_comment;
118
119 public:
120   AttributeImporter(StringOutputStream& comment, EntityClass* entityClass, const XMLElement& element) : m_comment(comment)
121   {
122     const char* type = element.name();
123     const char* key = element.attribute("key");
124     const char* name = element.attribute("name");
125     const char* value = element.attribute("value");
126
127     ASSERT_MESSAGE(!string_empty(key), "key attribute not specified");
128     ASSERT_MESSAGE(!string_empty(name), "name attribute not specified");
129
130     if(string_equal(type, "flag"))
131     {
132       std::size_t bit = atoi(element.attribute("bit"));
133       ASSERT_MESSAGE(bit < MAX_FLAGS, "invalid flag bit");
134       ASSERT_MESSAGE(string_empty(entityClass->flagnames[bit]), "non-unique flag bit");
135       strcpy(entityClass->flagnames[bit], key);
136     }
137
138     m_comment << key;
139     m_comment << " : ";
140
141     EntityClass_insertAttribute(*entityClass, key, EntityClassAttribute(type, name, value));
142   }
143   ~AttributeImporter()
144   {
145   }
146   TreeXMLImporter& pushElement(const XMLElement& element)
147   {
148     ERROR_MESSAGE(PARSE_ERROR(element.name(), "attribute"));
149     return *this;
150   }
151   void popElement(const char* elementName)
152   {
153     ERROR_MESSAGE(PARSE_ERROR(elementName, "attribute"));
154   }
155   std::size_t write(const char* data, std::size_t length)
156   {
157     return m_comment.write(data, length);
158   }
159 };
160
161 bool attributeSupported(const char* name)
162 {
163   return string_equal(name, "real")
164     || string_equal(name, "integer")
165     || string_equal(name, "boolean")
166     || string_equal(name, "string")
167     || string_equal(name, "array")
168     || string_equal(name, "flag")
169     || string_equal(name, "real3")
170     || string_equal(name, "integer3")
171     || string_equal(name, "direction")
172     || string_equal(name, "angle")
173     || string_equal(name, "angles")
174     || string_equal(name, "color")
175     || string_equal(name, "target")
176     || string_equal(name, "targetname")
177     || string_equal(name, "sound")
178     || string_equal(name, "texture")
179     || string_equal(name, "model")
180     || string_equal(name, "skin")
181     || string_equal(name, "integer2");
182 }
183
184 typedef std::map<CopiedString, ListAttributeType> ListAttributeTypes;
185
186 bool listAttributeSupported(ListAttributeTypes& listTypes, const char* name)
187 {
188   return listTypes.find(name) != listTypes.end();
189 }
190
191
192 class ClassImporter : public TreeXMLImporter
193 {
194   EntityClassCollector& m_collector;
195   EntityClass* m_eclass;
196   StringOutputStream m_comment;
197   Storage<AttributeImporter> m_attribute;
198   ListAttributeTypes& m_listTypes;
199
200 public:
201   ClassImporter(EntityClassCollector& collector, ListAttributeTypes& listTypes, const XMLElement& element) : m_collector(collector), m_listTypes(listTypes)
202   {
203     m_eclass = Eclass_Alloc();
204     m_eclass->free = &Eclass_Free;
205
206     const char* name = element.attribute("name");
207     ASSERT_MESSAGE(!string_empty(name), "name attribute not specified for class");
208     m_eclass->m_name = name;
209
210     const char* color = element.attribute("color");
211     ASSERT_MESSAGE(!string_empty(name), "color attribute not specified for class " << name);
212     string_parse_vector3(color, m_eclass->color);
213     eclass_capture_state(m_eclass);
214
215     const char* model = element.attribute("model");
216     if(!string_empty(model))
217     {
218       StringOutputStream buffer(256);
219       buffer << PathCleaned(model);
220       m_eclass->m_modelpath = buffer.c_str();
221     }
222
223     const char* type = element.name();
224     if(string_equal(type, "point"))
225     {
226       const char* box = element.attribute("box");
227       ASSERT_MESSAGE(!string_empty(box), "box attribute not found for class " << name);
228       m_eclass->fixedsize = true;
229       string_parse_vector(box, &m_eclass->mins.x(), &m_eclass->mins.x() + 6);
230     }
231   }
232   ~ClassImporter()
233   {
234     m_eclass->m_comments = m_comment.c_str();
235     m_collector.insert(m_eclass);
236
237     for(ListAttributeTypes::iterator i = m_listTypes.begin(); i != m_listTypes.end(); ++i)
238     {
239       m_collector.insert((*i).first.c_str(), (*i).second);
240     }
241   }
242   static const char* name()
243   {
244     return "class";
245   }
246   TreeXMLImporter& pushElement(const XMLElement& element)
247   {
248     if(attributeSupported(element.name()) || listAttributeSupported(m_listTypes, element.name()))
249     {
250       constructor(m_attribute.get(), makeReference(m_comment), m_eclass, element);
251       return m_attribute.get();
252     }
253     else
254     {
255       ERROR_MESSAGE(PARSE_ERROR(element.name(), name()));
256       return *this;
257     }
258   }
259   void popElement(const char* elementName)
260   {
261     if(attributeSupported(elementName) || listAttributeSupported(m_listTypes, elementName))
262     {
263       destructor(m_attribute.get());
264     }
265     else
266     {
267       ERROR_MESSAGE(PARSE_ERROR(elementName, name()));
268     }
269   }
270   std::size_t write(const char* data, std::size_t length)
271   {
272     return m_comment.write(data, length);
273   }
274 };
275
276 class ItemImporter : public TreeXMLImporter
277 {
278 public:
279   ItemImporter(ListAttributeType& list, const XMLElement& element)
280   {
281     const char* name = element.attribute("name");
282     const char* value = element.attribute("value");
283     list.push_back(name, value);
284   }
285   TreeXMLImporter& pushElement(const XMLElement& element)
286   {
287     ERROR_MESSAGE(PARSE_ERROR(element.name(), "item"));
288     return *this;
289   }
290   void popElement(const char* elementName)
291   {
292     ERROR_MESSAGE(PARSE_ERROR(elementName, "item"));
293   }
294   std::size_t write(const char* data, std::size_t length)
295   {
296     return length;
297   }
298 };
299
300 bool isItem(const char* name)
301 {
302   return string_equal(name, "item");
303 }
304
305 class ListAttributeImporter : public TreeXMLImporter
306 {
307   ListAttributeType* m_listType;
308   Storage<ItemImporter> m_item;
309 public:
310   ListAttributeImporter(ListAttributeTypes& listTypes, const XMLElement& element)
311   {
312     const char* name = element.attribute("name");
313     m_listType = &listTypes[name];
314   }
315   TreeXMLImporter& pushElement(const XMLElement& element)
316   {
317     if(isItem(element.name()))
318     {
319       constructor(m_item.get(), makeReference(*m_listType), element);
320       return m_item.get();
321     }
322     else
323     {
324       ERROR_MESSAGE(PARSE_ERROR(element.name(), "list"));
325       return *this;
326     }
327   }
328   void popElement(const char* elementName)
329   {
330     if(isItem(elementName))
331     {
332       destructor(m_item.get());
333     }
334     else
335     {
336       ERROR_MESSAGE(PARSE_ERROR(elementName, "list"));
337     }
338   }
339   std::size_t write(const char* data, std::size_t length)
340   {
341     return length;
342   }
343 };
344
345 bool classSupported(const char* name)
346 {
347   return string_equal(name, "group")
348     || string_equal(name, "point");
349 }
350
351 bool listSupported(const char* name)
352 {
353   return string_equal(name, "list");
354 }
355
356 class ClassesImporter : public TreeXMLImporter
357 {
358   EntityClassCollector& m_collector;
359   Storage<ClassImporter> m_class;
360   Storage<ListAttributeImporter> m_list;
361   ListAttributeTypes m_listTypes;
362
363 public:
364   ClassesImporter(EntityClassCollector& collector) : m_collector(collector)
365   {
366   }
367   static const char* name()
368   {
369     return "classes";
370   }
371   TreeXMLImporter& pushElement(const XMLElement& element)
372   {
373     if(classSupported(element.name()))
374     {
375       constructor(m_class.get(), makeReference(m_collector), makeReference(m_listTypes), element);
376       return m_class.get();
377     }
378     else if(listSupported(element.name()))
379     {
380       constructor(m_list.get(), makeReference(m_listTypes), element);
381       return m_list.get();
382     }
383     else
384     {
385       ERROR_MESSAGE(PARSE_ERROR(element.name(), name()));
386       return *this;
387     }
388   }
389   void popElement(const char* elementName)
390   {
391     if(classSupported(elementName))
392     {
393       destructor(m_class.get());
394     }
395     else if(listSupported(elementName))
396     {
397       destructor(m_list.get());
398     }
399     else
400     {
401       ERROR_MESSAGE(PARSE_ERROR(elementName, name()));
402     }
403   }
404   std::size_t write(const char* data, std::size_t length)
405   {
406     return length;
407   }
408 };
409
410 class EclassXMLImporter : public TreeXMLImporter
411 {
412   EntityClassCollector& m_collector;
413   Storage<ClassesImporter> m_classes;
414
415 public:
416   EclassXMLImporter(EntityClassCollector& collector) : m_collector(collector)
417   {
418   }
419   static const char* name()
420   {
421     return "classes";
422   }
423   TreeXMLImporter& pushElement(const XMLElement& element)
424   {
425     if(string_equal(element.name(), ClassesImporter::name()))
426     {
427       constructor(m_classes.get(), makeReference(m_collector));
428       return m_classes.get();
429     }
430     else
431     {
432       ERROR_MESSAGE(PARSE_ERROR(element.name(), name()));
433       return *this;
434     }
435   }
436   void popElement(const char* elementName)
437   {
438     if(string_equal(elementName, ClassesImporter::name()))
439     {
440       destructor(m_classes.get());
441     }
442     else
443     {
444       ERROR_MESSAGE(PARSE_ERROR(elementName, name()));
445     }
446   }
447   std::size_t write(const char* data, std::size_t length)
448   {
449     return length;
450   }
451 };
452
453 class TreeXMLImporterStack : public XMLImporter
454 {
455   std::vector< Reference<TreeXMLImporter> > m_importers;
456 public:
457   TreeXMLImporterStack(TreeXMLImporter& importer)
458   {
459     m_importers.push_back(makeReference(importer));
460   }
461   void pushElement(const XMLElement& element)
462   {
463     m_importers.push_back(makeReference(m_importers.back().get().pushElement(element)));
464   }
465   void popElement(const char* name)
466   {
467     m_importers.pop_back();
468     m_importers.back().get().popElement(name);
469   }
470   std::size_t write(const char* buffer, std::size_t length)
471   {
472     return m_importers.back().get().write(buffer, length);
473   }
474 };
475
476
477
478 const char* GetExtension()
479 {
480   return "ent";
481 }
482 void ScanFile(EntityClassCollector& collector, const char *filename)
483 {
484   TextFileInputStream inputFile(filename);
485   if(!inputFile.failed())
486   {
487     XMLStreamParser parser(inputFile);
488
489     EclassXMLImporter importer(collector);
490     TreeXMLImporterStack stack(importer);
491     parser.exportXML(stack);
492   }
493 }
494
495
496 }
497
498 #include "modulesystem/singletonmodule.h"
499
500 class EntityClassXMLDependencies : public GlobalFileSystemModuleRef, public GlobalShaderCacheModuleRef
501 {
502 };
503
504 class EclassXMLAPI
505 {
506   EntityClassScanner m_eclassxml;
507 public:
508   typedef EntityClassScanner Type;
509   STRING_CONSTANT(Name, "xml");
510
511   EclassXMLAPI()
512   {
513     m_eclassxml.scanFile = &ScanFile;
514     m_eclassxml.getExtension = &GetExtension;
515   }
516   EntityClassScanner* getTable()
517   {
518     return &m_eclassxml;
519   }
520 };
521
522 typedef SingletonModule<EclassXMLAPI, EntityClassXMLDependencies> EclassXMLModule;
523 typedef Static<EclassXMLModule> StaticEclassXMLModule;
524 StaticRegisterModule staticRegisterEclassXML(StaticEclassXMLModule::instance());