2075ac961ae06715a4ffe899541667053fa8e563
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 actions
, params
= self
.action(params
, rank
)
371 child
= action
.__class
__(
372 owner
, action
, actions
, params
, rank
, repeat
)
373 owner
.replace(action
, child
)
374 child
.step(owner
, created
)
378 return "%s(%r, %r)" % (type(self
).__name
__, self
.times
, self
.action
)
381 """Conditional actions."""
383 def __init__(self
, cond
, then
, else_
=None):
388 def __getstate__(self
):
390 return [('cond', self
.cond
.expr
),
392 ('else', self
.else_
)]
394 return [('cond', self
.cond
.expr
), ('then', self
.then
)]
396 def __setstate__(self
, state
):
398 state
["else_"] = state
.pop("else", None)
399 state
["cond"] = INumberDef(state
["cond"])
400 self
.__init
__(**state
)
403 def FromXML(cls
, doc
, element
):
404 """Construct using an ElementTree-style element."""
406 for subelem
in element
.getchildren():
407 tag
= realtag(subelem
)
409 cond
= INumberDef(subelem
.text
)
411 then
= ActionDef
.FromXML(doc
, subelem
)
413 else_
= ActionDef
.FromXML(doc
, subelem
)
415 return cls(cond
, then
, else_
)
416 except UnboundLocalError as exc
:
417 raise ParseError(str(exc
))
419 def __call__(self
, owner
, action
, params
, rank
, created
):
420 if self
.cond(params
, rank
):
426 actions
, params
= branch(params
, rank
)
427 child
= action
.__class
__(owner
, action
, actions
, params
, rank
)
428 owner
.replace(action
, child
)
429 child
.step(owner
, created
)
433 """Accelerate over some time."""
438 def __init__(self
, term
, horizontal
=None, vertical
=None):
440 self
.horizontal
= horizontal
441 self
.vertical
= vertical
443 def __getstate__(self
):
444 state
= [('frames', self
.term
.expr
)]
446 state
.append(('horizontal', self
.horizontal
))
448 state
.append(('vertical', self
.vertical
))
451 def __setstate__(self
, state
):
453 self
.__init
__(INumberDef(state
["frames"]), state
.get("horizontal"),
454 state
.get("vertical"))
457 def FromXML(cls
, doc
, element
):
458 """Construct using an ElementTree-style element."""
462 for subelem
in element
.getchildren():
463 tag
= realtag(subelem
)
465 term
= INumberDef(subelem
.text
)
466 elif tag
== "horizontal":
467 horizontal
= Speed
.FromXML(doc
, subelem
)
468 elif tag
== "vertical":
469 vertical
= Speed
.FromXML(doc
, subelem
)
472 return cls(term
, horizontal
, vertical
)
473 except AttributeError:
476 def __call__(self
, owner
, action
, params
, rank
, created
):
477 frames
= self
.term(params
, rank
)
478 horizontal
= self
.horizontal
and self
.horizontal(params
, rank
)
479 vertical
= self
.vertical
and self
.vertical(params
, rank
)
480 action
.accel_frames
= frames
482 mx
, type = horizontal
484 if type == "absolute":
486 elif type == "relative":
488 elif type == "sequence":
490 elif type == "absolute":
491 action
.mx
= (mx
- owner
.mx
) / frames
492 elif type == "relative":
493 action
.mx
= mx
/ frames
497 if type == "absolute":
499 elif type == "relative":
501 elif type == "sequence":
503 elif type == "absolute":
504 action
.my
= (my
- owner
.my
) / frames
505 elif type == "relative":
506 action
.my
= my
/ frames
509 return "%s(%r, horizontal=%r, vertical=%r)" % (
510 type(self
).__name
__, self
.term
, self
.horizontal
, self
.vertical
)
512 class BulletDef(object):
513 """Bullet definition."""
515 def __init__(self
, actions
=(), direction
=None, speed
=None, tags
=(),
517 self
.direction
= direction
519 self
.actions
= list(actions
)
520 self
.tags
= set(tags
)
521 self
.appearance
= appearance
523 def __getstate__(self
):
526 state
.append(("direction", self
.direction
))
528 state
.append(("speed", self
.speed
))
530 state
.append(("actions", self
.actions
))
532 state
.append(("tags", list(self
.tags
)))
534 state
.append(("appearance", self
.appearance
))
537 def __setstate__(self
, state
):
539 self
.__init
__(**state
)
542 def FromXML(cls
, doc
, element
):
543 """Construct using an ElementTree-style element."""
548 for subelem
in element
.getchildren():
549 tag
= realtag(subelem
)
550 if tag
== "direction":
551 direction
= Direction
.FromXML(doc
, subelem
)
553 speed
= Speed
.FromXML(doc
, subelem
)
554 elif tag
== "action":
555 actions
.append(ActionDef
.FromXML(doc
, subelem
))
556 elif tag
== "actionRef":
557 actions
.append(ActionRef
.FromXML(doc
, subelem
))
559 tags
.add(subelem
.text
)
560 dfn
= cls(actions
, direction
, speed
, tags
)
561 doc
._bullets
[element
.get("label")] = dfn
564 def __call__(self
, params
, rank
):
565 actions
= [action(params
, rank
) for action
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
, params
, rank
):
606 return self
.bullet(self
.params(params
, rank
), rank
)
609 return "%s(params=%r, bullet=%r)" % (
610 type(self
).__name
__, self
.params
, self
.bullet
)
612 class ActionDef(object):
613 """Action definition.
615 To support parsing new actions, add tags to
616 ActionDef.CONSTRUCTORS. It maps tag names to classes with a
617 FromXML classmethod, which take the BulletML instance and
618 ElementTree element as arguments.
621 # This is self-referential, so it's filled in later.
622 CONSTRUCTORS
= dict()
624 def __init__(self
, actions
):
625 self
.actions
= list(actions
)
627 def __getstate__(self
):
628 return dict(actions
=self
.actions
)
630 def __setstate__(self
, state
):
632 self
.__init
__(state
["actions"])
635 def FromXML(cls
, doc
, element
):
636 """Construct using an ElementTree-style element."""
638 for subelem
in element
.getchildren():
639 tag
= realtag(subelem
)
641 ctr
= cls
.CONSTRUCTORS
[tag
]
645 actions
.append(ctr
.FromXML(doc
, subelem
))
647 doc
._actions
[element
.get("label")] = dfn
650 def __call__(self
, params
, rank
):
651 return self
.actions
, params
654 return "%s(%r)" % (type(self
).__name
__, self
.actions
)
656 class ActionRef(object):
657 """Run an action by name with parameters."""
659 def __init__(self
, action
, params
=None):
661 self
.params
= params
or ParamList()
663 def __getstate__(self
):
665 if self
.params
.params
:
666 params
= [param
.expr
for param
in self
.params
.params
]
667 state
.append(("params", params
))
668 state
.append(('action', self
.action
))
671 def __setstate__(self
, state
):
673 action
= state
["action"]
674 params
= [NumberDef(param
) for param
in state
.get("params", [])]
675 self
.__init
__(action
, ParamList(params
))
678 def FromXML(cls
, doc
, element
):
679 """Construct using an ElementTree-style element."""
680 action
= cls(element
.get("label"), ParamList
.FromXML(doc
, element
))
681 doc
._action
_refs
.append(action
)
684 def __call__(self
, params
, rank
):
685 return self
.action(self
.params(params
, rank
), rank
)
688 return "%s(params=%r, action=%r)" % (
689 type(self
).__name
__, self
.params
, self
.action
)
691 class Offset(object):
692 """Provide an offset to a bullet's initial position."""
694 VALID_TYPES
= ["relative", "absolute"]
696 def __init__(self
, type, x
, y
):
697 if type not in self
.VALID_TYPES
:
698 raise ValueError("invalid type %r" % type)
699 self
.type = intern(type)
703 def __getstate__(self
):
704 state
= [('type', self
.type)]
706 state
.append(('x', self
.x
.expr
))
708 state
.append(('y', self
.y
.expr
))
711 def __setstate__(self
, state
):
713 x
= NumberDef(state
["x"]) if "x" in state
else None
714 y
= NumberDef(state
["y"]) if "y" in state
else None
715 self
.__init
__(state
["type"], x
, y
)
718 def FromXML(cls
, doc
, element
):
719 """Construct using an ElementTree-style element."""
720 type = element
.get("type", "relative")
723 for subelem
in element
:
724 tag
= realtag(subelem
)
726 x
= NumberDef(subelem
.text
)
728 y
= NumberDef(subelem
.text
)
729 return cls(type, x
, y
)
731 def __call__(self
, params
, rank
):
732 return (self
.x(params
, rank
) if self
.x
else 0,
733 self
.y(params
, rank
) if self
.y
else 0)
735 class FireDef(object):
736 """Fire definition (creates a bullet)."""
738 def __init__(self
, bullet
, direction
=None, speed
=None, offset
=None,
739 tags
=(), appearance
=None):
741 self
.direction
= direction
744 self
.tags
= set(tags
)
745 self
.appearance
= appearance
747 def __getstate__(self
):
750 state
.append(("direction", self
.direction
))
752 state
.append(("speed", self
.speed
))
754 state
.append(("offset", self
.offset
))
756 state
.append(("tags", list(self
.tags
)))
758 state
.append(("appearance", self
.appearance
))
760 params
= self
.bullet
.params
761 except AttributeError:
762 state
.append(('bullet', self
.bullet
))
765 state
.append(('bullet', self
.bullet
))
767 # Strip out empty BulletRefs.
768 state
.append(('bullet', self
.bullet
.bullet
))
771 def __setstate__(self
, state
):
773 self
.__init
__(**state
)
776 def FromXML(cls
, doc
, element
):
777 """Construct using an ElementTree-style element."""
784 for subelem
in element
.getchildren():
785 tag
= realtag(subelem
)
786 if tag
== "direction":
787 direction
= Direction
.FromXML(doc
, subelem
, "aim")
789 speed
= Speed
.FromXML(doc
, subelem
)
790 elif tag
== "bullet":
791 bullet
= BulletDef
.FromXML(doc
, subelem
)
792 elif tag
== "bulletRef":
793 bullet
= BulletRef
.FromXML(doc
, subelem
)
794 elif tag
== "offset":
795 offset
= Offset
.FromXML(doc
, subelem
)
797 tags
.add(subelem
.text
)
798 elif tag
== "appearance":
799 appearance
= subelem
.text
801 fire
= cls(bullet
, direction
, speed
, offset
, tags
, appearance
)
802 except UnboundLocalError as exc
:
803 raise ParseError(str(exc
))
805 doc
._fires
[element
.get("label")] = fire
808 def __call__(self
, owner
, action
, params
, rank
, created
):
809 direction
, speed
, tags
, appearance
, actions
= self
.bullet(params
, rank
)
811 direction
= self
.direction(params
, rank
)
813 speed
= self
.speed(params
, rank
)
814 tags
= tags
.union(self
.tags
)
816 appearance
= self
.appearance
819 direction
, type = direction
820 if type == "aim" or type is None:
821 direction
+= owner
.aim
822 elif type == "sequence":
823 direction
+= action
.previous_fire_direction
824 elif type == "relative":
825 direction
+= owner
.direction
827 direction
= owner
.aim
828 action
.previous_fire_direction
= direction
832 if type == "sequence":
833 speed
+= action
.previous_fire_speed
834 elif type == "relative":
835 # The reference Noiz implementation uses
836 # prvFireSpeed here, but the standard is
837 # pretty clear -- "In case of the type is
838 # "relative", ... the speed is relative to the
839 # speed of this bullet."
843 action
.previous_fire_speed
= speed
845 x
, y
= owner
.x
, owner
.y
847 off_x
, off_y
= self
.offset(params
, rank
)
848 if self
.offset
.type == "relative":
851 x
+= c
* off_x
+ s
* off_y
852 y
+= s
* off_x
- c
* off_y
857 if appearance
is None:
858 appearance
= owner
.appearance
859 bullet
= owner
.__class
__(
860 x
=x
, y
=y
, direction
=direction
, speed
=speed
,
861 target
=owner
.target
, actions
=actions
, rank
=rank
,
862 appearance
=appearance
, tags
=tags
, Action
=action
.__class
__)
863 created
.append(bullet
)
866 return "%s(direction=%r, speed=%r, bullet=%r)" % (
867 type(self
).__name
__, self
.direction
, self
.speed
, self
.bullet
)
869 class FireRef(object):
870 """Fire a bullet by name with parameters."""
872 def __init__(self
, fire
, params
=None):
874 self
.params
= params
or ParamList()
876 def __getstate__(self
):
878 if self
.params
.params
:
879 params
= [param
.expr
for param
in self
.params
.params
]
880 state
.append(("params", params
))
881 state
.append(('fire', self
.fire
))
884 def __setstate__(self
, state
):
887 params
= [NumberDef(param
) for param
in state
.get("params", [])]
888 self
.__init
__(fire
, ParamList(params
))
891 def FromXML(cls
, doc
, element
):
892 """Construct using an ElementTree-style element."""
893 fired
= cls(element
.get("label"), ParamList
.FromXML(doc
, element
))
894 doc
._fire
_refs
.append(fired
)
897 def __call__(self
, owner
, action
, params
, rank
, created
):
898 params
= self
.params(params
, rank
)
899 return self
.fire(owner
, action
, params
, rank
, created
)
902 return "%s(params=%r, fire=%r)" % (
903 type(self
).__name
__, self
.params
, self
.fire
)
905 class BulletML(object):
906 """BulletML document.
908 A BulletML document is a collection of top-level actions and the
911 You can add tags to the BulletML.CONSTRUCTORS dictionary to extend
912 its parsing. It maps tag names to classes with a FromXML
913 classmethod, which take the BulletML instance and ElementTree
914 element as arguments.
924 def __init__(self
, type="none", actions
=None):
925 self
.type = intern(type)
926 self
.actions
= [] if actions
is None else actions
928 def __getstate__(self
):
929 return [('type', self
.type), ('actions', self
.actions
)]
931 def __setstate__(self
, state
):
933 self
.__init
__(state
["type"], actions
=state
.get("actions"))
936 def FromXML(cls
, source
):
937 """Return a BulletML instance based on XML."""
938 if not hasattr(source
, 'read'):
939 source
= StringIO(source
)
942 root
= tree
.parse(source
)
944 doc
= cls(type=root
.get("type", "none"))
949 doc
._bullet
_refs
= []
950 doc
._action
_refs
= []
953 for element
in root
.getchildren():
954 tag
= realtag(element
)
955 if tag
in doc
.CONSTRUCTORS
:
956 doc
.CONSTRUCTORS
[tag
].FromXML(doc
, element
)
959 for ref
in doc
._bullet
_refs
:
960 ref
.bullet
= doc
._bullets
[ref
.bullet
]
961 for ref
in doc
._fire
_refs
:
962 ref
.fire
= doc
._fires
[ref
.fire
]
963 for ref
in doc
._action
_refs
:
964 ref
.action
= doc
._actions
[ref
.action
]
965 except KeyError as exc
:
966 raise ParseError("unknown reference %s" % exc
)
968 doc
.actions
= [act
for name
, act
in doc
._actions
.items()
969 if name
and name
.startswith("top")]
971 del(doc
._bullet
_refs
)
972 del(doc
._action
_refs
)
981 def FromYAML(cls
, source
):
982 """Create a BulletML instance based on YAML."""
984 # Late import to avoid a circular dependency.
986 import bulletml
.bulletyaml
989 raise ParseError("PyYAML is not available")
992 return yaml
.load(source
)
993 except Exception as exc
:
994 raise ParseError(str(exc
))
997 def FromDocument(cls
, source
):
998 """Create a BulletML instance based on a seekable file or string.
1000 This attempts to autodetect if the stream is XML or YAML.
1002 if not hasattr(source
, 'read'):
1003 source
= StringIO(source
)
1004 start
= source
.read(1)
1007 return cls
.FromXML(source
)
1008 elif start
== "!" or start
== "#":
1009 return cls
.FromYAML(source
)
1011 raise ParseError("unknown initial character %r" % start
)
1014 return "%s(type=%r, actions=%r)" % (
1015 type(self
).__name
__, self
.type, self
.actions
)
1017 ActionDef
.CONSTRUCTORS
= dict(
1021 changeSpeed
=ChangeSpeed
,
1022 changeDirection
=ChangeDirection
,
1027 appearance
=Appearance
,
1030 actionRef
=ActionRef
)
1031 ActionDef
.CONSTRUCTORS
["if"] = If