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;
IgnoreBreaks(const char * first,const char * last)46 IgnoreBreaks(const char* first, const char* last) : m_first(first), m_last(last)
47 {
48 }
49 };
50
51 template<typename TextOutputStreamType>
ostream_write(TextOutputStreamType & ostream,const IgnoreBreaks & ignoreBreaks)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:
get()79 Type& get()
80 {
81 return *reinterpret_cast<Type*>(m_storage);
82 }
get() const83 const Type& get() const
84 {
85 return *reinterpret_cast<const Type*>(m_storage);
86 }
87 };
88
89 class BreakImporter : public TreeXMLImporter
90 {
91 public:
BreakImporter(StringOutputStream & comment)92 BreakImporter(StringOutputStream& comment)
93 {
94 comment << '\n';
95 }
name()96 static const char* name()
97 {
98 return "n";
99 }
pushElement(const XMLElement & element)100 TreeXMLImporter& pushElement(const XMLElement& element)
101 {
102 ERROR_MESSAGE(PARSE_ERROR(element.name(), name()));
103 return *this;
104 }
popElement(const char * elementName)105 void popElement(const char* elementName)
106 {
107 ERROR_MESSAGE(PARSE_ERROR(elementName, name()));
108 }
write(const char * data,std::size_t length)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:
AttributeImporter(StringOutputStream & comment,EntityClass * entityClass,const XMLElement & element)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 }
~AttributeImporter()143 ~AttributeImporter()
144 {
145 }
pushElement(const XMLElement & element)146 TreeXMLImporter& pushElement(const XMLElement& element)
147 {
148 ERROR_MESSAGE(PARSE_ERROR(element.name(), "attribute"));
149 return *this;
150 }
popElement(const char * elementName)151 void popElement(const char* elementName)
152 {
153 ERROR_MESSAGE(PARSE_ERROR(elementName, "attribute"));
154 }
write(const char * data,std::size_t length)155 std::size_t write(const char* data, std::size_t length)
156 {
157 return m_comment.write(data, length);
158 }
159 };
160
attributeSupported(const char * name)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
listAttributeSupported(ListAttributeTypes & listTypes,const char * name)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:
ClassImporter(EntityClassCollector & collector,ListAttributeTypes & listTypes,const XMLElement & element)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 }
~ClassImporter()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 }
name()242 static const char* name()
243 {
244 return "class";
245 }
pushElement(const XMLElement & element)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 }
popElement(const char * elementName)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 }
write(const char * data,std::size_t length)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:
ItemImporter(ListAttributeType & list,const XMLElement & element)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 }
pushElement(const XMLElement & element)285 TreeXMLImporter& pushElement(const XMLElement& element)
286 {
287 ERROR_MESSAGE(PARSE_ERROR(element.name(), "item"));
288 return *this;
289 }
popElement(const char * elementName)290 void popElement(const char* elementName)
291 {
292 ERROR_MESSAGE(PARSE_ERROR(elementName, "item"));
293 }
write(const char * data,std::size_t length)294 std::size_t write(const char* data, std::size_t length)
295 {
296 return length;
297 }
298 };
299
isItem(const char * name)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:
ListAttributeImporter(ListAttributeTypes & listTypes,const XMLElement & element)310 ListAttributeImporter(ListAttributeTypes& listTypes, const XMLElement& element)
311 {
312 const char* name = element.attribute("name");
313 m_listType = &listTypes[name];
314 }
pushElement(const XMLElement & element)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 }
popElement(const char * elementName)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 }
write(const char * data,std::size_t length)339 std::size_t write(const char* data, std::size_t length)
340 {
341 return length;
342 }
343 };
344
classSupported(const char * name)345 bool classSupported(const char* name)
346 {
347 return string_equal(name, "group")
348 || string_equal(name, "point");
349 }
350
listSupported(const char * name)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:
ClassesImporter(EntityClassCollector & collector)364 ClassesImporter(EntityClassCollector& collector) : m_collector(collector)
365 {
366 }
name()367 static const char* name()
368 {
369 return "classes";
370 }
pushElement(const XMLElement & element)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 }
popElement(const char * elementName)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 }
write(const char * data,std::size_t length)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:
EclassXMLImporter(EntityClassCollector & collector)416 EclassXMLImporter(EntityClassCollector& collector) : m_collector(collector)
417 {
418 }
name()419 static const char* name()
420 {
421 return "classes";
422 }
pushElement(const XMLElement & element)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 }
popElement(const char * elementName)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 }
write(const char * data,std::size_t length)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:
TreeXMLImporterStack(TreeXMLImporter & importer)457 TreeXMLImporterStack(TreeXMLImporter& importer)
458 {
459 m_importers.push_back(makeReference(importer));
460 }
pushElement(const XMLElement & element)461 void pushElement(const XMLElement& element)
462 {
463 m_importers.push_back(makeReference(m_importers.back().get().pushElement(element)));
464 }
popElement(const char * name)465 void popElement(const char* name)
466 {
467 m_importers.pop_back();
468 m_importers.back().get().popElement(name);
469 }
write(const char * buffer,std::size_t length)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
GetExtension()478 const char* GetExtension()
479 {
480 return "ent";
481 }
ScanFile(EntityClassCollector & collector,const char * filename)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
EclassXMLAPI()511 EclassXMLAPI()
512 {
513 m_eclassxml.scanFile = &ScanFile;
514 m_eclassxml.getExtension = &GetExtension;
515 }
getTable()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());
525