Mercurial > projects > orange
comparison orange/serialization/archives/XMLArchive.d @ 0:f7b078e85f7f
First commit
author | Jacob Carlborg <doob@me.com> |
---|---|
date | Wed, 26 May 2010 17:19:13 +0200 |
parents | |
children | ae24aae69a3b |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:f7b078e85f7f |
---|---|
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 | |
559 private struct AssociativeArrayVisitor(Key, Value) | |
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 } |