3 This is based on the format described at
4 http://www.asahi-net.or.jp/~cs8k-cyu/bulletml/bulletml_ref_e.html.
6 Unless you are adding support for new actions, the only class you
7 should care about in here is BulletML.
10 from __future__
import division
12 from math
import sin
, cos
, radians
, pi
as PI
14 from xml
.etree
.ElementTree
import ElementTree
16 # Python 3 moved this for no really good reason.
18 from sys
import intern
23 from io
import StringIO
26 from cStringIO
import StringIO
28 from StringIO
import StringIO
30 from bulletml
.errors
import Error
31 from bulletml
.expr
import NumberDef
, INumberDef
34 __all__
= ["ParseError", "BulletML"]
38 class ParseError(Error
):
39 """Raised when an error occurs parsing the XML structure."""
43 """Strip namespace poop off the front of a tag."""
45 return element
.tag
.rsplit('}', 1)[1]
49 class ParamList(object):
50 """List of parameter definitions."""
52 def __init__(self
, params
=()):
53 self
.params
= list(params
)
56 def FromXML(cls
, doc
, element
):
57 """Construct using an ElementTree-style element."""
58 return cls([NumberDef(subelem
.text
) for subelem
in element
59 if realtag(subelem
) == "param"])
61 def __call__(self
, params
, rank
):
62 return [param(params
, rank
) for param
in self
.params
]
65 return "%s(%r)" % (type(self
).__name
__, self
.params
)
67 class Direction(object):
68 """Raw direction value."""
70 VALID_TYPES
= ["relative", "absolute", "aim", "sequence"]
72 def __init__(self
, type, value
):
73 if type not in self
.VALID_TYPES
:
74 raise ValueError("invalid type %r" % type)
75 self
.type = intern(type)
78 def __getstate__(self
):
79 return [('type', self
.type), ('value', self
.value
.expr
)]
81 def __setstate__(self
, state
):
83 self
.__init
__(state
["type"], NumberDef(state
["value"]))
86 def FromXML(cls
, doc
, element
, default
="absolute"):
87 """Construct using an ElementTree-style element."""
88 return cls(element
.get("type", default
), NumberDef(element
.text
))
90 def __call__(self
, params
, rank
):
91 return (radians(self
.value(params
, rank
)), self
.type)
94 return "%s(%r, type=%r)" % (
95 type(self
).__name
__, self
.value
, self
.type)
97 class ChangeDirection(object):
98 """Direction change over time."""
100 def __init__(self
, term
, direction
):
102 self
.direction
= direction
104 def __getstate__(self
):
105 return [('frames', self
.term
.expr
),
106 ('type', self
.direction
.type),
107 ('value', self
.direction
.value
.expr
)]
109 def __setstate__(self
, state
):
111 self
.__init
__(INumberDef(state
["frames"]),
112 Direction(state
["type"], NumberDef(state
["value"])))
115 def FromXML(cls
, doc
, element
):
116 """Construct using an ElementTree-style element."""
117 for subelem
in element
.getchildren():
118 tag
= realtag(subelem
)
119 if tag
== "direction":
120 direction
= Direction
.FromXML(doc
, subelem
)
122 term
= INumberDef(subelem
.text
)
124 return cls(term
, direction
)
125 except UnboundLocalError as exc
:
126 raise ParseError(str(exc
))
128 def __call__(self
, owner
, action
, params
, rank
, created
):
129 frames
= self
.term(params
, rank
)
130 direction
, type = self
.direction(params
, rank
)
131 action
.direction_frames
= frames
132 action
.aiming
= False
133 if type == "sequence":
134 action
.direction
= direction
136 if type == "absolute":
137 direction
-= owner
.direction
138 elif type != "relative": # aim or default
140 direction
+= owner
.aim
- owner
.direction
142 # Normalize to [-pi, pi).
143 direction
= (direction
+ PI
) % PI_2
- PI
145 owner
.direction
+= direction
147 action
.direction
= direction
/ frames
150 return "%s(term=%r, direction=%r)" % (
151 type(self
).__name
__, self
.term
, self
.direction
)
154 """Raw speed value."""
156 VALID_TYPES
= ["relative", "absolute", "sequence"]
158 def __init__(self
, type, value
):
159 if type not in self
.VALID_TYPES
:
160 raise ValueError("invalid type %r" % type)
161 self
.type = intern(type)
164 def __getstate__(self
):
165 return [('type', self
.type), ('value', self
.value
.expr
)]
167 def __setstate__(self
, state
):
169 self
.__init
__(state
["type"], NumberDef(state
["value"]))
172 def FromXML(cls
, doc
, element
):
173 """Construct using an ElementTree-style element."""
174 return cls(element
.get("type", "absolute"), NumberDef(element
.text
))
176 def __call__(self
, params
, rank
):
177 return (self
.value(params
, rank
), self
.type)
180 return "%s(%r, type=%r)" % (type(self
).__name
__, self
.value
, self
.type)
182 class ChangeSpeed(object):
183 """Speed change over time."""
185 def __init__(self
, term
, speed
):
189 def __getstate__(self
):
190 return [('frames', self
.term
.expr
),
191 ('type', self
.speed
.type),
192 ('value', self
.speed
.value
.expr
)]
194 def __setstate__(self
, state
):
196 self
.__init
__(INumberDef(state
["frames"]),
197 Speed(state
["type"], NumberDef(state
["value"])))
200 def FromXML(cls
, doc
, element
):
201 """Construct using an ElementTree-style element."""
202 for subelem
in element
.getchildren():
203 tag
= realtag(subelem
)
205 speed
= Speed
.FromXML(doc
, subelem
)
207 term
= INumberDef(subelem
.text
)
209 return cls(term
, speed
)
210 except UnboundLocalError as exc
:
211 raise ParseError(str(exc
))
213 def __call__(self
, owner
, action
, params
, rank
, created
):
214 frames
= self
.term(params
, rank
)
215 speed
, type = self
.speed(params
, rank
)
216 action
.speed_frames
= frames
218 if type == "absolute":
220 elif type == "relative":
222 elif type == "sequence":
224 elif type == "relative":
225 action
.speed
= speed
/ frames
227 action
.speed
= (speed
- owner
.speed
) / frames
230 return "%s(term=%r, speed=%r)" % (
231 type(self
).__name
__, self
.term
, self
.speed
)
234 """Wait for some frames."""
236 def __init__(self
, frames
):
239 def __getstate__(self
):
240 return dict(frames
=self
.frames
.expr
)
242 def __setstate__(self
, state
):
243 self
.__init
__(INumberDef(state
["frames"]))
246 def FromXML(cls
, doc
, element
):
247 """Construct using an ElementTree-style element."""
248 return cls(INumberDef(element
.text
))
250 def __call__(self
, owner
, action
, params
, rank
, created
):
251 action
.wait_frames
= self
.frames(params
, rank
)
255 return "%s(%r)" % (type(self
).__name
__, self
.frames
)
258 """Set a bullet tag."""
260 def __init__(self
, tag
):
263 def __getstate__(self
):
264 return dict(tag
=self
.tag
)
266 def __setstate__(self
, state
):
267 self
.__init
__(state
["tag"])
270 def FromXML(cls
, doc
, element
):
271 """Construct using an ElementTree-style element."""
272 return cls(element
.text
)
274 def __call__(self
, owner
, action
, params
, rank
, created
):
275 owner
.tags
.add(self
.tag
)
278 """Unset a bullet tag."""
280 def __init__(self
, tag
):
283 def __getstate__(self
):
284 return dict(tag
=self
.tag
)
286 def __setstate__(self
, state
):
287 self
.__init
__(state
["tag"])
290 def FromXML(cls
, doc
, element
):
291 """Construct using an ElementTree-style element."""
292 return cls(element
.text
)
294 def __call__(self
, owner
, action
, params
, rank
, created
):
296 owner
.tags
.remove(self
.tag
)
300 class Appearance(object):
301 """Set a bullet appearance."""
303 def __init__(self
, appearance
):
304 self
.appearance
= appearance
306 def __getstate__(self
):
307 return dict(appearance
=self
.appearance
)
309 def __setstate__(self
, state
):
310 self
.__init
__(state
["appearance"])
313 def FromXML(cls
, doc
, element
):
314 """Construct using an ElementTree-style element."""
315 return cls(element
.text
)
317 def __call__(self
, owner
, action
, params
, rank
, created
):
318 owner
.apearance
= self
.appearance
320 class Vanish(object):
321 """Make the owner disappear."""
327 def FromXML(cls
, doc
, element
):
328 """Construct using an ElementTree-style element."""
332 return "%s()" % (type(self
).__name
__)
334 def __call__(self
, owner
, action
, params
, rank
, created
):
338 class Repeat(object):
339 """Repeat an action definition."""
341 def __init__(self
, times
, action
):
345 def __getstate__(self
):
346 return [('times', self
.times
.expr
), ('action', self
.action
)]
348 def __setstate__(self
, state
):
350 self
.__init
__(INumberDef(state
["times"]), state
["action"])
353 def FromXML(cls
, doc
, element
):
354 """Construct using an ElementTree-style element."""
355 for subelem
in element
.getchildren():
356 tag
= realtag(subelem
)
358 times
= INumberDef(subelem
.text
)
359 elif tag
== "action":
360 action
= ActionDef
.FromXML(doc
, subelem
)
361 elif tag
== "actionRef":
362 action
= ActionRef
.FromXML(doc
, subelem
)
364 return cls(times
, action
)
365 except UnboundLocalError as exc
:
366 raise ParseError(str(exc
))
368 def __call__(self
, owner
, action
, params
, rank
, created
):
369 repeat
= self
.times(params
, rank
)
370 return self
.action(owner
, action
, params
, rank
, created
, repeat
)
373 return "%s(%r, %r)" % (type(self
).__name
__, self
.times
, self
.action
)
376 """Conditional actions."""
378 def __init__(self
, cond
, then
, else_
=None):
383 def __getstate__(self
):
385 return [('cond', self
.cond
.expr
),
387 ('else', self
.else_
)]
389 return [('cond', self
.cond
.expr
), ('then', self
.then
)]
391 def __setstate__(self
, state
):
393 state
["else_"] = state
.pop("else", None)
394 state
["cond"] = INumberDef(state
["cond"])
395 self
.__init
__(**state
)
398 def FromXML(cls
, doc
, element
):
399 """Construct using an ElementTree-style element."""
401 for subelem
in element
.getchildren():
402 tag
= realtag(subelem
)
404 cond
= INumberDef(subelem
.text
)
406 then
= ActionDef
.FromXML(doc
, subelem
)
408 else_
= ActionDef
.FromXML(doc
, subelem
)
410 return cls(cond
, then
, else_
)
411 except UnboundLocalError as exc
:
412 raise ParseError(str(exc
))
414 def __call__(self
, owner
, action
, params
, rank
, created
):
415 if self
.cond(params
, rank
):
421 return branch(owner
, action
, params
, rank
, created
)
425 return "%s(%r, then=%r, else_=%r)" % (
426 type(self
).__name
__, self
.cond
, self
.then
, self
.else_
)
428 return "%s(%r, then=%r)" % (
429 type(self
).__name
__, self
.cond
, self
.then
)
432 """Accelerate over some time."""
437 def __init__(self
, term
, horizontal
=None, vertical
=None):
439 self
.horizontal
= horizontal
440 self
.vertical
= vertical
442 def __getstate__(self
):
443 state
= [('frames', self
.term
.expr
)]
445 state
.append(('horizontal', self
.horizontal
))
447 state
.append(('vertical', self
.vertical
))
450 def __setstate__(self
, state
):
452 self
.__init
__(INumberDef(state
["frames"]), state
.get("horizontal"),
453 state
.get("vertical"))
456 def FromXML(cls
, doc
, element
):
457 """Construct using an ElementTree-style element."""
461 for subelem
in element
.getchildren():
462 tag
= realtag(subelem
)
464 term
= INumberDef(subelem
.text
)
465 elif tag
== "horizontal":
466 horizontal
= Speed
.FromXML(doc
, subelem
)
467 elif tag
== "vertical":
468 vertical
= Speed
.FromXML(doc
, subelem
)
471 return cls(term
, horizontal
, vertical
)
472 except AttributeError:
475 def __call__(self
, owner
, action
, params
, rank
, created
):
476 frames
= self
.term(params
, rank
)
477 horizontal
= self
.horizontal
and self
.horizontal(params
, rank
)
478 vertical
= self
.vertical
and self
.vertical(params
, rank
)
479 action
.accel_frames
= frames
481 mx
, type = horizontal
483 if type == "absolute":
485 elif type == "relative":
487 elif type == "sequence":
489 elif type == "absolute":
490 action
.mx
= (mx
- owner
.mx
) / frames
491 elif type == "relative":
492 action
.mx
= mx
/ frames
496 if type == "absolute":
498 elif type == "relative":
500 elif type == "sequence":
502 elif type == "absolute":
503 action
.my
= (my
- owner
.my
) / frames
504 elif type == "relative":
505 action
.my
= my
/ frames
508 return "%s(%r, horizontal=%r, vertical=%r)" % (
509 type(self
).__name
__, self
.term
, self
.horizontal
, self
.vertical
)
511 class BulletDef(object):
512 """Bullet definition."""
514 def __init__(self
, actions
=(), direction
=None, speed
=None, tags
=(),
516 self
.direction
= direction
518 self
.actions
= list(actions
)
519 self
.tags
= set(tags
)
520 self
.appearance
= appearance
522 def __getstate__(self
):
525 state
.append(("direction", self
.direction
))
527 state
.append(("speed", self
.speed
))
529 state
.append(("actions", self
.actions
))
531 state
.append(("tags", list(self
.tags
)))
533 state
.append(("appearance", self
.appearance
))
536 def __setstate__(self
, state
):
538 self
.__init
__(**state
)
541 def FromXML(cls
, doc
, element
):
542 """Construct using an ElementTree-style element."""
547 for subelem
in element
.getchildren():
548 tag
= realtag(subelem
)
549 if tag
== "direction":
550 direction
= Direction
.FromXML(doc
, subelem
)
552 speed
= Speed
.FromXML(doc
, subelem
)
553 elif tag
== "action":
554 actions
.append(ActionDef
.FromXML(doc
, subelem
))
555 elif tag
== "actionRef":
556 actions
.append(ActionRef
.FromXML(doc
, subelem
))
558 tags
.add(subelem
.text
)
559 dfn
= cls(actions
, direction
, speed
, tags
)
560 doc
._bullets
[element
.get("label")] = dfn
563 def __call__(self
, owner
, action
, params
, rank
, created
):
564 actions
= [a(None, action
, params
, rank
, created
)
565 for a
in self
.actions
]
567 self
.direction
and self
.direction(params
, rank
),
568 self
.speed
and self
.speed(params
, rank
),
574 return "%s(direction=%r, speed=%r, actions=%r)" % (
575 type(self
).__name
__, self
.direction
, self
.speed
, self
.actions
)
577 class BulletRef(object):
578 """Create a bullet by name with parameters."""
580 def __init__(self
, bullet
, params
=None):
582 self
.params
= ParamList() if params
is None else params
584 def __getstate__(self
):
586 if self
.params
.params
:
587 params
= [param
.expr
for param
in self
.params
.params
]
588 state
.append(("params", params
))
589 state
.append(('bullet', self
.bullet
))
592 def __setstate__(self
, state
):
594 bullet
= state
["bullet"]
595 params
= [NumberDef(param
) for param
in state
.get("params", [])]
596 self
.__init
__(bullet
, ParamList(params
))
599 def FromXML(cls
, doc
, element
):
600 """Construct using an ElementTree-style element."""
601 bullet
= cls(element
.get("label"), ParamList
.FromXML(doc
, element
))
602 doc
._bullet
_refs
.append(bullet
)
605 def __call__(self
, owner
, action
, params
, rank
, created
):
606 params
= self
.params(params
, rank
)
607 return self
.bullet(owner
, action
, params
, rank
, created
)
610 return "%s(params=%r, bullet=%r)" % (
611 type(self
).__name
__, self
.params
, self
.bullet
)
613 class ActionDef(object):
614 """Action definition.
616 To support parsing new actions, add tags to
617 ActionDef.CONSTRUCTORS. It maps tag names to classes with a
618 FromXML classmethod, which take the BulletML instance and
619 ElementTree element as arguments.
622 # This is self-referential, so it's filled in later.
623 CONSTRUCTORS
= dict()
625 def __init__(self
, actions
):
626 self
.actions
= list(actions
)
628 def __getstate__(self
):
629 return dict(actions
=self
.actions
)
631 def __setstate__(self
, state
):
633 self
.__init
__(state
["actions"])
636 def FromXML(cls
, doc
, element
):
637 """Construct using an ElementTree-style element."""
639 for subelem
in element
.getchildren():
640 tag
= realtag(subelem
)
642 ctr
= cls
.CONSTRUCTORS
[tag
]
646 actions
.append(ctr
.FromXML(doc
, subelem
))
648 doc
._actions
[element
.get("label")] = dfn
651 def __call__(self
, owner
, action
, params
, rank
, created
=(), repeat
=1):
652 Action
= action
if isinstance(action
, type) else type(action
)
653 parent
= None if owner
is None else action
654 child
= Action(parent
, self
.actions
, params
, rank
, repeat
)
655 if owner
is not None:
656 owner
.replace(parent
, child
)
657 child
.step(owner
, created
)
661 return "%s(%r)" % (type(self
).__name
__, self
.actions
)
663 class ActionRef(object):
664 """Run an action by name with parameters."""
666 def __init__(self
, action
, params
=None):
668 self
.params
= params
or ParamList()
670 def __getstate__(self
):
672 if self
.params
.params
:
673 params
= [param
.expr
for param
in self
.params
.params
]
674 state
.append(("params", params
))
675 state
.append(('action', self
.action
))
678 def __setstate__(self
, state
):
680 action
= state
["action"]
681 params
= [NumberDef(param
) for param
in state
.get("params", [])]
682 self
.__init
__(action
, ParamList(params
))
685 def FromXML(cls
, doc
, element
):
686 """Construct using an ElementTree-style element."""
687 action
= cls(element
.get("label"), ParamList
.FromXML(doc
, element
))
688 doc
._action
_refs
.append(action
)
691 def __call__(self
, owner
, action
, params
, rank
, created
=(), repeat
=1):
692 params
= self
.params(params
, rank
)
693 return self
.action(owner
, action
, params
, rank
, created
, repeat
)
696 return "%s(params=%r, action=%r)" % (
697 type(self
).__name
__, self
.params
, self
.action
)
699 class Offset(object):
700 """Provide an offset to a bullet's initial position."""
702 VALID_TYPES
= ["relative", "absolute"]
704 def __init__(self
, type, x
, y
):
705 if type not in self
.VALID_TYPES
:
706 raise ValueError("invalid type %r" % type)
707 self
.type = intern(type)
711 def __getstate__(self
):
712 state
= [('type', self
.type)]
714 state
.append(('x', self
.x
.expr
))
716 state
.append(('y', self
.y
.expr
))
719 def __setstate__(self
, state
):
721 x
= NumberDef(state
["x"]) if "x" in state
else None
722 y
= NumberDef(state
["y"]) if "y" in state
else None
723 self
.__init
__(state
["type"], x
, y
)
726 def FromXML(cls
, doc
, element
):
727 """Construct using an ElementTree-style element."""
728 type = element
.get("type", "relative")
731 for subelem
in element
:
732 tag
= realtag(subelem
)
734 x
= NumberDef(subelem
.text
)
736 y
= NumberDef(subelem
.text
)
737 return cls(type, x
, y
)
739 def __call__(self
, params
, rank
):
740 return (self
.x(params
, rank
) if self
.x
else 0,
741 self
.y(params
, rank
) if self
.y
else 0)
743 class FireDef(object):
744 """Fire definition (creates a bullet)."""
746 def __init__(self
, bullet
, direction
=None, speed
=None, offset
=None,
747 tags
=(), appearance
=None):
749 self
.direction
= direction
752 self
.tags
= set(tags
)
753 self
.appearance
= appearance
755 def __getstate__(self
):
758 state
.append(("direction", self
.direction
))
760 state
.append(("speed", self
.speed
))
762 state
.append(("offset", self
.offset
))
764 state
.append(("tags", list(self
.tags
)))
766 state
.append(("appearance", self
.appearance
))
768 params
= self
.bullet
.params
769 except AttributeError:
770 state
.append(('bullet', self
.bullet
))
773 state
.append(('bullet', self
.bullet
))
775 # Strip out empty BulletRefs.
776 state
.append(('bullet', self
.bullet
.bullet
))
779 def __setstate__(self
, state
):
781 self
.__init
__(**state
)
784 def FromXML(cls
, doc
, element
):
785 """Construct using an ElementTree-style element."""
792 for subelem
in element
.getchildren():
793 tag
= realtag(subelem
)
794 if tag
== "direction":
795 direction
= Direction
.FromXML(doc
, subelem
, "aim")
797 speed
= Speed
.FromXML(doc
, subelem
)
798 elif tag
== "bullet":
799 bullet
= BulletDef
.FromXML(doc
, subelem
)
800 elif tag
== "bulletRef":
801 bullet
= BulletRef
.FromXML(doc
, subelem
)
802 elif tag
== "offset":
803 offset
= Offset
.FromXML(doc
, subelem
)
805 tags
.add(subelem
.text
)
806 elif tag
== "appearance":
807 appearance
= subelem
.text
809 fire
= cls(bullet
, direction
, speed
, offset
, tags
, appearance
)
810 except UnboundLocalError as exc
:
811 raise ParseError(str(exc
))
813 doc
._fires
[element
.get("label")] = fire
816 def __call__(self
, owner
, action
, params
, rank
, created
):
817 direction
, speed
, tags
, appearance
, actions
= self
.bullet(
818 owner
, action
, params
, rank
, created
)
820 direction
= self
.direction(params
, rank
)
822 speed
= self
.speed(params
, rank
)
823 tags
= tags
.union(self
.tags
)
825 appearance
= self
.appearance
828 direction
, type = direction
829 if type == "aim" or type is None:
830 direction
+= owner
.aim
831 elif type == "sequence":
832 direction
+= action
.previous_fire_direction
833 elif type == "relative":
834 direction
+= owner
.direction
836 direction
= owner
.aim
837 action
.previous_fire_direction
= direction
841 if type == "sequence":
842 speed
+= action
.previous_fire_speed
843 elif type == "relative":
844 # The reference Noiz implementation uses
845 # prvFireSpeed here, but the standard is
846 # pretty clear -- "In case of the type is
847 # "relative", ... the speed is relative to the
848 # speed of this bullet."
852 action
.previous_fire_speed
= speed
854 x
, y
= owner
.x
, owner
.y
856 off_x
, off_y
= self
.offset(params
, rank
)
857 if self
.offset
.type == "relative":
860 x
+= c
* off_x
+ s
* off_y
861 y
+= s
* off_x
- c
* off_y
866 if appearance
is None:
867 appearance
= owner
.appearance
868 bullet
= owner
.__class
__(
869 x
=x
, y
=y
, direction
=direction
, speed
=speed
,
870 target
=owner
.target
, actions
=actions
, rank
=rank
,
871 appearance
=appearance
, tags
=tags
)
872 created
.append(bullet
)
875 return "%s(direction=%r, speed=%r, bullet=%r)" % (
876 type(self
).__name
__, self
.direction
, self
.speed
, self
.bullet
)
878 class FireRef(object):
879 """Fire a bullet by name with parameters."""
881 def __init__(self
, fire
, params
=None):
883 self
.params
= params
or ParamList()
885 def __getstate__(self
):
887 if self
.params
.params
:
888 params
= [param
.expr
for param
in self
.params
.params
]
889 state
.append(("params", params
))
890 state
.append(('fire', self
.fire
))
893 def __setstate__(self
, state
):
896 params
= [NumberDef(param
) for param
in state
.get("params", [])]
897 self
.__init
__(fire
, ParamList(params
))
900 def FromXML(cls
, doc
, element
):
901 """Construct using an ElementTree-style element."""
902 fired
= cls(element
.get("label"), ParamList
.FromXML(doc
, element
))
903 doc
._fire
_refs
.append(fired
)
906 def __call__(self
, owner
, action
, params
, rank
, created
):
907 params
= self
.params(params
, rank
)
908 return self
.fire(owner
, action
, params
, rank
, created
)
911 return "%s(params=%r, fire=%r)" % (
912 type(self
).__name
__, self
.params
, self
.fire
)
914 class BulletML(object):
915 """BulletML document.
917 A BulletML document is a collection of top-level actions and the
920 You can add tags to the BulletML.CONSTRUCTORS dictionary to extend
921 its parsing. It maps tag names to classes with a FromXML
922 classmethod, which take the BulletML instance and ElementTree
923 element as arguments.
933 def __init__(self
, type="none", actions
=None):
934 self
.type = intern(type)
935 self
.actions
= [] if actions
is None else actions
937 def __getstate__(self
):
938 return [('type', self
.type), ('actions', self
.actions
)]
940 def __setstate__(self
, state
):
942 self
.__init
__(state
["type"], actions
=state
.get("actions"))
945 def FromXML(cls
, source
):
946 """Return a BulletML instance based on XML."""
947 if not hasattr(source
, 'read'):
948 source
= StringIO(source
)
951 root
= tree
.parse(source
)
953 doc
= cls(type=root
.get("type", "none"))
958 doc
._bullet
_refs
= []
959 doc
._action
_refs
= []
962 for element
in root
.getchildren():
963 tag
= realtag(element
)
964 if tag
in doc
.CONSTRUCTORS
:
965 doc
.CONSTRUCTORS
[tag
].FromXML(doc
, element
)
968 for ref
in doc
._bullet
_refs
:
969 ref
.bullet
= doc
._bullets
[ref
.bullet
]
970 for ref
in doc
._fire
_refs
:
971 ref
.fire
= doc
._fires
[ref
.fire
]
972 for ref
in doc
._action
_refs
:
973 ref
.action
= doc
._actions
[ref
.action
]
974 except KeyError as exc
:
975 raise ParseError("unknown reference %s" % exc
)
977 doc
.actions
= [act
for name
, act
in doc
._actions
.items()
978 if name
and name
.startswith("top")]
980 del(doc
._bullet
_refs
)
981 del(doc
._action
_refs
)
990 def FromYAML(cls
, source
):
991 """Create a BulletML instance based on YAML."""
993 # Late import to avoid a circular dependency.
995 import bulletml
.bulletyaml
998 raise ParseError("PyYAML is not available")
1001 return yaml
.load(source
)
1002 except Exception as exc
:
1003 raise ParseError(str(exc
))
1006 def FromDocument(cls
, source
):
1007 """Create a BulletML instance based on a seekable file or string.
1009 This attempts to autodetect if the stream is XML or YAML.
1011 if not hasattr(source
, 'read'):
1012 source
= StringIO(source
)
1013 start
= source
.read(1)
1016 return cls
.FromXML(source
)
1017 elif start
== "!" or start
== "#":
1018 return cls
.FromYAML(source
)
1020 raise ParseError("unknown initial character %r" % start
)
1023 return "%s(type=%r, actions=%r)" % (
1024 type(self
).__name
__, self
.type, self
.actions
)
1026 ActionDef
.CONSTRUCTORS
= dict(
1030 changeSpeed
=ChangeSpeed
,
1031 changeDirection
=ChangeDirection
,
1036 appearance
=Appearance
,
1039 actionRef
=ActionRef
)
1040 ActionDef
.CONSTRUCTORS
["if"] = If