0
|
1 /**
|
|
2 * Copyright: Copyright (c) 2010 Jacob Carlborg.
|
|
3 * Authors: Jacob Carlborg
|
|
4 * Version: Initial created: Jan 26, 2010
|
|
5 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0)
|
|
6 */
|
|
7 module orange.serialization.archives.XMLArchive;
|
|
8
|
|
9 version (Tango)
|
|
10 {
|
|
11 import tango.text.xml.DocPrinter;
|
|
12 import tango.text.xml.Document;
|
|
13 import tango.util.Convert : to;
|
|
14 }
|
|
15
|
|
16 import orange.serialization.archives._;
|
|
17 import orange.util._;
|
|
18
|
|
19 private enum ArchiveMode
|
|
20 {
|
|
21 archiving,
|
|
22 unarchiving
|
|
23 }
|
|
24
|
|
25 class XMLArchive (U = char) : Archive!(U)
|
|
26 {
|
|
27 static assert (isChar!(U), format!(`The given type "`, U, `" is not a valid type. Valid types are: "char", "wchar" and "dchar".`));
|
|
28
|
|
29 private struct Tags
|
|
30 {
|
|
31 static const DataType structTag = "struct";
|
|
32 static const DataType dataTag = "data";
|
|
33 static const DataType archiveTag = "archive";
|
|
34 static const DataType arrayTag = "array";
|
|
35 static const DataType objectTag = "object";
|
|
36 static const DataType baseTag = "base";
|
|
37 static const DataType stringTag = "string";
|
|
38 static const DataType referenceTag = "reference";
|
|
39 static const DataType pointerTag = "pointer";
|
|
40 static const DataType associativeArrayTag = "associativeArray";
|
|
41 static const DataType typedefTag = "typedef";
|
|
42 static const DataType nullTag = "null";
|
|
43 static const DataType enumTag = "enum";
|
|
44 }
|
|
45
|
|
46 private struct Attributes
|
|
47 {
|
|
48 static const DataType typeAttribute = "type";
|
|
49 static const DataType versionAttribute = "version";
|
|
50 static const DataType lengthAttribute = "length";
|
|
51 static const DataType keyAttribute = "key";
|
|
52 static const DataType runtimeTypeAttribute = "runtimeType";
|
|
53 static const DataType idAttribute = "id";
|
|
54 static const DataType keyTypeAttribute = "keyType";
|
|
55 static const DataType valueTypeAttribute = "valueType";
|
|
56 }
|
|
57
|
|
58 private
|
|
59 {
|
|
60 DataType archiveType = "org.dsource.orange.xml";
|
|
61 DataType archiveVersion = "0.1";
|
|
62
|
|
63 Document!(U) doc;
|
|
64 doc.Node lastElement;
|
|
65 DocPrinter!(U) printer;
|
|
66 doc.Node lastElementSaved;
|
|
67
|
|
68 bool hasBegunArchiving;
|
|
69 bool hasBegunUnarchiving;
|
|
70
|
|
71 DataType[void*] archivedReferences;
|
|
72 void*[DataType] unarchivedReferences;
|
|
73
|
|
74 size_t idCounter;
|
|
75 }
|
|
76
|
|
77 this ()
|
|
78 {
|
|
79 doc = new Document!(U);
|
|
80 }
|
|
81
|
|
82 public void beginArchiving ()
|
|
83 {
|
|
84 if (!hasBegunArchiving)
|
|
85 {
|
|
86 doc.header;
|
|
87 lastElement = doc.tree.element(null, Tags.archiveTag)
|
|
88 .attribute(null, Attributes.typeAttribute, archiveType)
|
|
89 .attribute(null, Attributes.versionAttribute, archiveVersion);
|
|
90 lastElement = lastElement.element(null, Tags.dataTag);
|
|
91
|
|
92 hasBegunArchiving = true;
|
|
93 }
|
|
94 }
|
|
95
|
|
96 public void beginUnarchiving (DataType data)
|
|
97 {
|
|
98 if (!hasBegunUnarchiving)
|
|
99 {
|
|
100 doc.parse(data);
|
|
101 hasBegunUnarchiving = true;
|
|
102
|
|
103 auto set = doc.query[Tags.archiveTag][Tags.dataTag];
|
|
104
|
|
105 if (set.nodes.length == 1)
|
|
106 lastElement = set.nodes[0];
|
|
107
|
|
108 else if (set.nodes.length == 0)
|
|
109 throw new ArchiveException(errorMessage!(ArchiveMode.unarchiving) ~ `The "` ~ to!(string)(Tags.dataTag) ~ `" tag could not be found.`, __FILE__, __LINE__);
|
|
110
|
|
111 else
|
|
112 throw new ArchiveException(errorMessage!(ArchiveMode.unarchiving) ~ `There were more than one "` ~ to!(string)(Tags.dataTag) ~ `" tag.`, __FILE__, __LINE__);
|
|
113 }
|
|
114 }
|
|
115
|
|
116 public DataType data ()
|
|
117 {
|
|
118 if (!printer)
|
|
119 printer = new DocPrinter!(U);
|
|
120
|
|
121 return printer.print(doc);
|
|
122 }
|
|
123
|
|
124 public void reset ()
|
|
125 {
|
|
126 hasBegunArchiving = false;
|
|
127 hasBegunUnarchiving = false;
|
|
128 idCounter = 0;
|
|
129 doc.reset;
|
|
130 }
|
|
131
|
|
132 private void begin ()
|
|
133 {
|
|
134 lastElementSaved = lastElement;
|
|
135 }
|
|
136
|
|
137 private void end ()
|
|
138 {
|
|
139 lastElement = lastElementSaved;
|
|
140 }
|
|
141
|
|
142 public void archive (T) (T value, DataType key, void delegate () dg = null)
|
|
143 {
|
|
144 if (!hasBegunArchiving)
|
|
145 beginArchiving();
|
|
146
|
|
147 restore(lastElement) in {
|
|
148 bool callDelegate = true;
|
|
149
|
|
150 static if (isTypeDef!(T))
|
|
151 archiveTypeDef(value, key);
|
|
152
|
|
153 else static if (isObject!(T))
|
|
154 archiveObject(value, key, callDelegate);
|
|
155
|
|
156 else static if (isStruct!(T))
|
|
157 archiveStruct(value, key);
|
|
158
|
|
159 else static if (isString!(T))
|
|
160 archiveString(value, key);
|
|
161
|
|
162 else static if (isArray!(T))
|
|
163 archiveArray(value, key);
|
|
164
|
|
165 else static if (isAssociativeArray!(T))
|
|
166 archiveAssociativeArray(value, key);
|
|
167
|
|
168 else static if (isPrimitive!(T))
|
|
169 archivePrimitive(value, key);
|
|
170
|
|
171 else static if (isPointer!(T))
|
|
172 archivePointer(value, key, callDelegate);
|
|
173
|
|
174 else static if (isEnum!(T))
|
|
175 archiveEnum(value, key);
|
|
176
|
|
177 else
|
|
178 static assert(false, format!(`The type "`, T, `" cannot be archived.`));
|
|
179
|
|
180 if (callDelegate && dg)
|
|
181 dg();
|
|
182 };
|
|
183 }
|
|
184
|
|
185 private void archiveObject (T) (T value, DataType key, ref bool callDelegate)
|
|
186 {
|
|
187 if (!value)
|
|
188 {
|
|
189 lastElement.element(null, Tags.nullTag)
|
|
190 .attribute(null, Attributes.typeAttribute, toDataType(T.stringof))
|
|
191 .attribute(null, Attributes.keyAttribute, key);
|
|
192 callDelegate = false;
|
|
193 }
|
|
194
|
|
195 else if (auto reference = getArchivedReference(value))
|
|
196 {
|
|
197 archiveReference(key, reference);
|
|
198 callDelegate = false;
|
|
199 }
|
|
200
|
|
201 else
|
|
202 {
|
|
203 DataType id = nextId;
|
|
204
|
|
205 lastElement = lastElement.element(null, Tags.objectTag)
|
|
206 .attribute(null, Attributes.runtimeTypeAttribute, toDataType(value.classinfo.name))
|
|
207 .attribute(null, Attributes.typeAttribute, toDataType(T.stringof))
|
|
208 .attribute(null, Attributes.keyAttribute, key)
|
|
209 .attribute(null, Attributes.idAttribute, id);
|
|
210
|
|
211 addArchivedReference(value, id);
|
|
212 }
|
|
213 }
|
|
214
|
|
215 private void archiveStruct (T) (T value, DataType key)
|
|
216 {
|
|
217 lastElement = lastElement.element(null, Tags.structTag)
|
|
218 .attribute(null, Attributes.typeAttribute, toDataType(T.stringof))
|
|
219 .attribute(null, Attributes.keyAttribute, key);
|
|
220 }
|
|
221
|
|
222 private void archiveString (T) (T value, DataType key)
|
|
223 {
|
|
224 lastElement.element(null, Tags.stringTag, toDataType(value))
|
|
225 .attribute(null, Attributes.typeAttribute, toDataType(BaseTypeOfArray!(T).stringof))
|
|
226 .attribute(null, Attributes.keyAttribute, key);
|
|
227 }
|
|
228
|
|
229 private void archiveArray (T) (T value, DataType key)
|
|
230 {
|
|
231 lastElement = lastElement.element(null, Tags.arrayTag)
|
|
232 .attribute(null, Attributes.typeAttribute, toDataType(BaseTypeOfArray!(T).stringof))
|
|
233 .attribute(null, Attributes.lengthAttribute, toDataType(value.length))
|
|
234 .attribute(null, Attributes.keyAttribute, key);
|
|
235 }
|
|
236
|
|
237 private void archiveAssociativeArray (T) (T value, DataType key)
|
|
238 {
|
|
239 lastElement = lastElement.element(null, Tags.associativeArrayTag)
|
|
240 .attribute(null, Attributes.keyTypeAttribute, toDataType(KeyTypeOfAssociativeArray!(T).stringof))
|
|
241 .attribute(null, Attributes.valueTypeAttribute, toDataType(ValueTypeOfAssociativeArray!(T).stringof))
|
|
242 .attribute(null, Attributes.lengthAttribute, toDataType(value.length))
|
|
243 .attribute(null, Attributes.keyAttribute, key);
|
|
244 }
|
|
245
|
|
246 private void archivePointer (T) (T value, DataType key, ref bool callDelegate)
|
|
247 {
|
|
248 if (auto reference = getArchivedReference(value))
|
|
249 {
|
|
250 archiveReference(key, reference);
|
|
251 callDelegate = false;
|
|
252 }
|
|
253
|
|
254 else
|
|
255 {
|
|
256 DataType id = nextId;
|
|
257
|
|
258 lastElement = lastElement.element(null, Tags.pointerTag)
|
|
259 .attribute(null, Attributes.keyAttribute, key)
|
|
260 .attribute(null, Attributes.idAttribute, id);
|
|
261
|
|
262 addArchivedReference(value, id);
|
|
263 }
|
|
264 }
|
|
265
|
|
266 private void archiveEnum (T) (T value, DataType key)
|
|
267 {
|
|
268 lastElement.element(null, Tags.enumTag, toDataType(value))
|
|
269 .attribute(null, Attributes.typeAttribute, toDataType(T.stringof))
|
|
270 .attribute(null, Attributes.keyAttribute, key);
|
|
271 }
|
|
272
|
|
273 private void archivePrimitive (T) (T value, DataType key)
|
|
274 {
|
|
275 lastElement.element(null, toDataType(T.stringof), toDataType(value))
|
|
276 .attribute(null, Attributes.keyAttribute, key);
|
|
277 }
|
|
278
|
|
279 private void archiveTypeDef (T) (T value, DataType key)
|
|
280 {
|
|
281 lastElement = lastElement.element(null, Tags.typedefTag)
|
|
282 .attribute(null, Attributes.typeAttribute, toDataType(BaseTypeOfTypeDef!(T).stringof));
|
|
283 .attribute(null, Attributes.key, key);
|
|
284 }
|
|
285
|
|
286 public T unarchive (T) (DataType key, T delegate (T) dg = null)
|
|
287 {
|
|
288 if (!hasBegunUnarchiving)
|
|
289 beginUnarchiving(data);
|
|
290
|
|
291 return restore!(T)(lastElement) in {
|
|
292 T value;
|
|
293
|
|
294 bool callDelegate = true;
|
|
295
|
|
296 static if (isTypeDef!(T))
|
|
297 value = unarchiveTypeDef!(T)(key);
|
|
298
|
|
299 else static if (isObject!(T))
|
|
300 value = unarchiveObject!(T)(key, callDelegate);
|
|
301
|
|
302 else static if (isStruct!(T))
|
|
303 value = unarchiveStruct!(T)(key);
|
|
304
|
|
305 else static if (isString!(T))
|
|
306 value = unarchiveString!(T)(key);
|
|
307
|
|
308 else static if (isArray!(T))
|
|
309 value = unarchiveArray!(T)(key);
|
|
310
|
|
311 else static if (isAssociativeArray!(T))
|
|
312 value = unarchiveAssociativeArray!(T)(key);
|
|
313
|
|
314 else static if (isPrimitive!(T))
|
|
315 value = unarchivePrimitive!(T)(key);
|
|
316
|
|
317 else static if (isPointer!(T))
|
|
318 value = unarchivePointer!(T)(key, callDelegate);
|
|
319
|
|
320 else static if (isEnum!(T))
|
|
321 value = unarchiveEnum!(T)(key);
|
|
322
|
|
323 else
|
|
324 static assert(false, format!(`The type "`, T, `" cannot be unarchived.`));
|
|
325
|
|
326 if (callDelegate && dg)
|
|
327 return dg(value);
|
|
328
|
|
329 return value;
|
|
330 };
|
|
331 }
|
|
332
|
|
333 private T unarchiveObject (T) (DataType key, ref bool callDelegate)
|
|
334 {
|
|
335 DataType id = unarchiveReference(key);
|
|
336
|
|
337 if (auto reference = getUnarchivedReference!(T)(id))
|
|
338 {
|
|
339 callDelegate = false;
|
|
340 return *reference;
|
|
341 }
|
|
342
|
|
343 auto tmp = getElement(Tags.objectTag, key, Attributes.keyAttribute, false);
|
|
344
|
|
345 if (!tmp)
|
|
346 {
|
|
347 lastElement = getElement(Tags.nullTag, key);
|
|
348 callDelegate = false;
|
|
349 return null;
|
|
350 }
|
|
351
|
|
352 lastElement = tmp;
|
|
353
|
|
354 auto runtimeType = getValueOfAttribute(Attributes.runtimeTypeAttribute);
|
|
355 auto name = fromDataType!(string)(runtimeType);
|
|
356 id = getValueOfAttribute(Attributes.idAttribute);
|
|
357
|
|
358 T result;
|
|
359
|
|
360 static if (is(typeof(T._ctor)))
|
|
361 {
|
|
362 ParameterTupleOf!(typeof(T._ctor)) params;
|
|
363 result = factory!(T, typeof(params))(name, params);
|
|
364 }
|
|
365
|
|
366 else
|
|
367 result = factory!(T)(name);
|
|
368
|
|
369 addUnarchivedReference(result, id);
|
|
370
|
|
371 return result;
|
|
372 }
|
|
373
|
|
374 private T unarchiveStruct (T) (DataType key)
|
|
375 {
|
|
376 lastElement = getElement(Tags.structTag, key);
|
|
377
|
|
378 return T.init;
|
|
379 }
|
|
380
|
|
381 private T unarchiveString (T) (DataType key)
|
|
382 {
|
|
383 return fromDataType!(T)(getElement(Tags.stringTag, key).value);
|
|
384 }
|
|
385
|
|
386 private T unarchiveArray (T) (DataType key)
|
|
387 {
|
|
388 T value;
|
|
389
|
|
390 lastElement = getElement(Tags.arrayTag, key);
|
|
391 auto length = getValueOfAttribute(Attributes.lengthAttribute);
|
|
392 value.length = fromDataType!(size_t)(length);
|
|
393
|
|
394 return value;
|
|
395 }
|
|
396
|
|
397 private T unarchiveAssociativeArray (T) (DataType key)
|
|
398 {
|
|
399 lastElement = getElement(Tags.associativeArrayTag, key);
|
|
400
|
|
401 return T.init;
|
|
402 }
|
|
403
|
|
404 private T unarchivePointer (T) (DataType key, ref bool callDelegate)
|
|
405 {
|
|
406 DataType id = unarchiveReference(key);
|
|
407
|
|
408 if (auto reference = getUnarchivedReference!(T)(id))
|
|
409 {
|
|
410 callDelegate = false;
|
|
411 return *reference;
|
|
412 }
|
|
413
|
|
414 lastElement = getElement(Tags.pointerTag, key);
|
|
415 id = getValueOfAttribute(Attributes.idAttribute);
|
|
416
|
|
417 T result = new BaseTypeOfPointer!(T);
|
|
418
|
|
419 addUnarchivedReference(result, id);
|
|
420
|
|
421 return result;
|
|
422 }
|
|
423
|
|
424 private T unarchiveEnum (T) (DataType key)
|
|
425 {
|
|
426 return fromDataType!(T)(getElement(Tags.enumTag, key).value);
|
|
427 }
|
|
428
|
|
429 private T unarchivePrimitive (T) (DataType key)
|
|
430 {
|
|
431 return fromDataType!(T)(getElement(toDataType(T.stringof), key).value);
|
|
432 }
|
|
433
|
|
434 private T unarchiveTypeDef (T) (DataType key)
|
|
435 {
|
|
436 lastElement = getElement(Tags.typedefTag, key);
|
|
437
|
|
438 return T.init;
|
|
439 }
|
|
440
|
|
441 public AssociativeArrayVisitor!(KeyTypeOfAssociativeArray!(T), ValueTypeOfAssociativeArray!(T)) unarchiveAssociativeArrayVisitor (T) ()
|
|
442 {
|
|
443 return AssociativeArrayVisitor!(KeyTypeOfAssociativeArray!(T), ValueTypeOfAssociativeArray!(T))(this);
|
|
444 }
|
|
445
|
|
446 public void archiveBaseClass (T : Object) (DataType key)
|
|
447 {
|
|
448 lastElement = lastElement.element(null, Tags.baseTag)
|
|
449 .attribute(null, Attributes.typeAttribute, toDataType(T.stringof))
|
|
450 .attribute(null, Attributes.keyAttribute, key);
|
|
451 }
|
|
452
|
|
453 public void unarchiveBaseClass (T : Object) (DataType key)
|
|
454 {
|
|
455 lastElement = getElement(Tags.baseTag, key);
|
|
456 }
|
|
457
|
|
458 template errorMessage (ArchiveMode mode = ArchiveMode.archiving)
|
|
459 {
|
|
460 static if (mode == ArchiveMode.archiving)
|
|
461 const errorMessage = "Could not continue archiving due to unrecognized data format: ";
|
|
462
|
|
463 else static if (mode == ArchiveMode.unarchiving)
|
|
464 const errorMessage = "Could not continue unarchiving due to unrecognized data format: ";
|
|
465 }
|
|
466
|
|
467 private doc.Node getElement (DataType tag, DataType key, DataType attribute = Attributes.keyAttribute, bool throwOnError = true)
|
|
468 {
|
|
469 auto set = lastElement.query[tag].attribute((doc.Node node) {
|
|
470 if (node.name == attribute && node.value == key)
|
|
471 return true;
|
|
472
|
|
473 return false;
|
|
474 });
|
|
475
|
|
476 if (set.nodes.length == 1)
|
|
477 return set.nodes[0].parent;
|
|
478
|
|
479 else
|
|
480 {
|
|
481 if (throwOnError)
|
|
482 {
|
|
483 if (set.nodes.length == 0)
|
|
484 throw new ArchiveException(`Could not find an element "` ~ to!(string)(tag) ~ `" with the attribute "` ~ to!(string)(Attributes.keyAttribute) ~ `" with the value "` ~ to!(string)(key) ~ `".`, __FILE__, __LINE__);
|
|
485
|
|
486 else
|
|
487 throw new ArchiveException(`Could not unarchive the value with the key "` ~ to!(string)(key) ~ `" due to malformed data.`, __FILE__, __LINE__);
|
|
488 }
|
|
489
|
|
490 return null;
|
|
491 }
|
|
492 }
|
|
493
|
|
494 private DataType getValueOfAttribute (DataType attribute)
|
|
495 {
|
|
496 auto set = lastElement.query.attribute(attribute);
|
|
497
|
|
498 if (set.nodes.length == 1)
|
|
499 return set.nodes[0].value;
|
|
500
|
|
501 else if (set.nodes.length == 0)
|
|
502 throw new ArchiveException(`Could not find the attribute "` ~ to!(string)(attribute) ~ `".`, __FILE__, __LINE__);
|
|
503
|
|
504 else
|
|
505 throw new ArchiveException(`Could not unarchive the value of the attribute "` ~ to!(string)(attribute) ~ `" due to malformed data.`, __FILE__, __LINE__);
|
|
506 }
|
|
507
|
|
508 private void addArchivedReference (T) (T value, DataType id)
|
|
509 {
|
|
510 static assert(isReference!(T), format!(`The given type "`, T, `" is not a reference type, i.e. object or pointer.`));
|
|
511
|
|
512 archivedReferences[cast(void*) value] = id;
|
|
513 }
|
|
514
|
|
515 private void addUnarchivedReference (T) (T value, DataType id)
|
|
516 {
|
|
517 static assert(isReference!(T), format!(`The given type "`, T, `" is not a reference type, i.e. object or pointer.`));
|
|
518
|
|
519 unarchivedReferences[id] = cast(void*) value;
|
|
520 }
|
|
521
|
|
522 private DataType getArchivedReference (T) (T value)
|
|
523 {
|
|
524 if (auto tmp = cast(void*) value in archivedReferences)
|
|
525 return *tmp;
|
|
526
|
|
527 return null;
|
|
528 }
|
|
529
|
|
530 private T* getUnarchivedReference (T) (DataType id)
|
|
531 {
|
|
532 if (auto reference = id in unarchivedReferences)
|
|
533 return cast(T*) reference;
|
|
534
|
|
535 return null;
|
|
536 }
|
|
537
|
|
538 private DataType nextId ()
|
|
539 {
|
|
540 return toDataType(idCounter++);
|
|
541 }
|
|
542
|
|
543 private void archiveReference (DataType key, DataType id)
|
|
544 {
|
|
545 lastElement.element(null, Tags.referenceTag, id)
|
|
546 .attribute(null, Attributes.keyAttribute, key);
|
|
547 }
|
|
548
|
|
549 private DataType unarchiveReference (DataType key)
|
|
550 {
|
|
551 auto element = getElement(Tags.referenceTag, key, Attributes.keyAttribute, false);
|
|
552
|
|
553 if (element)
|
|
554 return element.value;
|
|
555
|
|
556 return cast(DataType) null;
|
|
557 }
|
|
558
|
6
|
559 private struct AssociativeArrayVisitor (Key, Value)
|
0
|
560 {
|
|
561 private XMLArchive archive;
|
|
562
|
|
563 static AssociativeArrayVisitor opCall (XMLArchive archive)
|
|
564 {
|
|
565 AssociativeArrayVisitor aai;
|
|
566 aai.archive = archive;
|
|
567
|
|
568 return aai;
|
|
569 }
|
|
570
|
|
571 int opApply(int delegate(ref Key, ref Value) dg)
|
|
572 {
|
|
573 int result;
|
|
574
|
|
575 foreach (node ; archive.lastElement.children)
|
|
576 {
|
|
577 restore(archive.lastElement) in {
|
|
578 archive.lastElement = node;
|
|
579
|
|
580 if (node.attributes.exist)
|
|
581 {
|
|
582 Key key = to!(Key)(archive.getValueOfAttribute(Attributes.keyAttribute));
|
|
583 Value value = to!(Value)(node.value);
|
|
584
|
|
585 result = dg(key, value);
|
|
586 }
|
|
587 };
|
|
588
|
|
589 if (result)
|
|
590 break;
|
|
591 }
|
|
592
|
|
593 return result;
|
|
594 }
|
|
595 }
|
|
596 } |