8bc23c0be3334f3471b046a5ef13512f6dda380b
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 child
= action
.Child(self
.action
, params
, rank
, repeat
)
371 owner
.replace(action
, child
)
372 child
.step(owner
, created
)
376 return "%s(%r, %r)" % (type(self
).__name
__, self
.times
, self
.action
)
379 """Conditional actions."""
381 def __init__(self
, cond
, then
, else_
=None):
386 def __getstate__(self
):
388 return [('cond', self
.cond
.expr
),
390 ('else', self
.else_
)]
392 return [('cond', self
.cond
.expr
), ('then', self
.then
)]
394 def __setstate__(self
, state
):
396 state
["else_"] = state
.pop("else", None)
397 state
["cond"] = INumberDef(state
["cond"])
398 self
.__init
__(**state
)
401 def FromXML(cls
, doc
, element
):
402 """Construct using an ElementTree-style element."""
404 for subelem
in element
.getchildren():
405 tag
= realtag(subelem
)
407 cond
= INumberDef(subelem
.text
)
409 then
= ActionDef
.FromXML(doc
, subelem
)
411 else_
= ActionDef
.FromXML(doc
, subelem
)
413 return cls(cond
, then
, else_
)
414 except UnboundLocalError as exc
:
415 raise ParseError(str(exc
))
417 def __call__(self
, owner
, action
, params
, rank
, created
):
418 if self
.cond(params
, rank
):
424 child
= action
.Child(branch
, params
, rank
)
425 owner
.replace(action
, child
)
426 child
.step(owner
, created
)
431 return "%s(%r, then=%r, else_=%r)" % (
432 type(self
).__name
__, self
.cond
, self
.then
, self
.else_
)
434 return "%s(%r, then=%r)" % (
435 type(self
).__name
__, self
.cond
, self
.then
)
438 """Accelerate over some time."""
443 def __init__(self
, term
, horizontal
=None, vertical
=None):
445 self
.horizontal
= horizontal
446 self
.vertical
= vertical
448 def __getstate__(self
):
449 state
= [('frames', self
.term
.expr
)]
451 state
.append(('horizontal', self
.horizontal
))
453 state
.append(('vertical', self
.vertical
))
456 def __setstate__(self
, state
):
458 self
.__init
__(INumberDef(state
["frames"]), state
.get("horizontal"),
459 state
.get("vertical"))
462 def FromXML(cls
, doc
, element
):
463 """Construct using an ElementTree-style element."""
467 for subelem
in element
.getchildren():
468 tag
= realtag(subelem
)
470 term
= INumberDef(subelem
.text
)
471 elif tag
== "horizontal":
472 horizontal
= Speed
.FromXML(doc
, subelem
)
473 elif tag
== "vertical":
474 vertical
= Speed
.FromXML(doc
, subelem
)
477 return cls(term
, horizontal
, vertical
)
478 except AttributeError:
481 def __call__(self
, owner
, action
, params
, rank
, created
):
482 frames
= self
.term(params
, rank
)
483 horizontal
= self
.horizontal
and self
.horizontal(params
, rank
)
484 vertical
= self
.vertical
and self
.vertical(params
, rank
)
485 action
.accel_frames
= frames
487 mx
, type = horizontal
489 if type == "absolute":
491 elif type == "relative":
493 elif type == "sequence":
495 elif type == "absolute":
496 action
.mx
= (mx
- owner
.mx
) / frames
497 elif type == "relative":
498 action
.mx
= mx
/ frames
502 if type == "absolute":
504 elif type == "relative":
506 elif type == "sequence":
508 elif type == "absolute":
509 action
.my
= (my
- owner
.my
) / frames
510 elif type == "relative":
511 action
.my
= my
/ frames
514 return "%s(%r, horizontal=%r, vertical=%r)" % (
515 type(self
).__name
__, self
.term
, self
.horizontal
, self
.vertical
)
517 class BulletDef(object):
518 """Bullet definition."""
520 def __init__(self
, actions
=(), direction
=None, speed
=None, tags
=(),
522 self
.direction
= direction
524 self
.actions
= list(actions
)
525 self
.tags
= set(tags
)
526 self
.appearance
= appearance
528 def __getstate__(self
):
531 state
.append(("direction", self
.direction
))
533 state
.append(("speed", self
.speed
))
535 state
.append(("actions", self
.actions
))
537 state
.append(("tags", list(self
.tags
)))
539 state
.append(("appearance", self
.appearance
))
542 def __setstate__(self
, state
):
544 self
.__init
__(**state
)
547 def FromXML(cls
, doc
, element
):
548 """Construct using an ElementTree-style element."""
553 for subelem
in element
.getchildren():
554 tag
= realtag(subelem
)
555 if tag
== "direction":
556 direction
= Direction
.FromXML(doc
, subelem
)
558 speed
= Speed
.FromXML(doc
, subelem
)
559 elif tag
== "action":
560 actions
.append(ActionDef
.FromXML(doc
, subelem
))
561 elif tag
== "actionRef":
562 actions
.append(ActionRef
.FromXML(doc
, subelem
))
564 tags
.add(subelem
.text
)
565 dfn
= cls(actions
, direction
, speed
, tags
)
566 doc
._bullets
[element
.get("label")] = dfn
569 def __call__(self
, params
, rank
):
570 actions
= [action(params
, rank
) for action
in self
.actions
]
572 self
.direction
and self
.direction(params
, rank
),
573 self
.speed
and self
.speed(params
, rank
),
579 return "%s(direction=%r, speed=%r, actions=%r)" % (
580 type(self
).__name
__, self
.direction
, self
.speed
, self
.actions
)
582 class BulletRef(object):
583 """Create a bullet by name with parameters."""
585 def __init__(self
, bullet
, params
=None):
587 self
.params
= ParamList() if params
is None else params
589 def __getstate__(self
):
591 if self
.params
.params
:
592 params
= [param
.expr
for param
in self
.params
.params
]
593 state
.append(("params", params
))
594 state
.append(('bullet', self
.bullet
))
597 def __setstate__(self
, state
):
599 bullet
= state
["bullet"]
600 params
= [NumberDef(param
) for param
in state
.get("params", [])]
601 self
.__init
__(bullet
, ParamList(params
))
604 def FromXML(cls
, doc
, element
):
605 """Construct using an ElementTree-style element."""
606 bullet
= cls(element
.get("label"), ParamList
.FromXML(doc
, element
))
607 doc
._bullet
_refs
.append(bullet
)
610 def __call__(self
, params
, rank
):
611 return self
.bullet(self
.params(params
, rank
), rank
)
614 return "%s(params=%r, bullet=%r)" % (
615 type(self
).__name
__, self
.params
, self
.bullet
)
617 class ActionDef(object):
618 """Action definition.
620 To support parsing new actions, add tags to
621 ActionDef.CONSTRUCTORS. It maps tag names to classes with a
622 FromXML classmethod, which take the BulletML instance and
623 ElementTree element as arguments.
626 # This is self-referential, so it's filled in later.
627 CONSTRUCTORS
= dict()
629 def __init__(self
, actions
):
630 self
.actions
= list(actions
)
632 def __getstate__(self
):
633 return dict(actions
=self
.actions
)
635 def __setstate__(self
, state
):
637 self
.__init
__(state
["actions"])
640 def FromXML(cls
, doc
, element
):
641 """Construct using an ElementTree-style element."""
643 for subelem
in element
.getchildren():
644 tag
= realtag(subelem
)
646 ctr
= cls
.CONSTRUCTORS
[tag
]
650 actions
.append(ctr
.FromXML(doc
, subelem
))
652 doc
._actions
[element
.get("label")] = dfn
655 def __call__(self
, params
, rank
):
656 return self
.actions
, params
659 return "%s(%r)" % (type(self
).__name
__, self
.actions
)
661 class ActionRef(object):
662 """Run an action by name with parameters."""
664 def __init__(self
, action
, params
=None):
666 self
.params
= params
or ParamList()
668 def __getstate__(self
):
670 if self
.params
.params
:
671 params
= [param
.expr
for param
in self
.params
.params
]
672 state
.append(("params", params
))
673 state
.append(('action', self
.action
))
676 def __setstate__(self
, state
):
678 action
= state
["action"]
679 params
= [NumberDef(param
) for param
in state
.get("params", [])]
680 self
.__init
__(action
, ParamList(params
))
683 def FromXML(cls
, doc
, element
):
684 """Construct using an ElementTree-style element."""
685 action
= cls(element
.get("label"), ParamList
.FromXML(doc
, element
))
686 doc
._action
_refs
.append(action
)
689 def __call__(self
, params
, rank
):
690 return self
.action(self
.params(params
, rank
), rank
)
693 return "%s(params=%r, action=%r)" % (
694 type(self
).__name
__, self
.params
, self
.action
)
696 class Offset(object):
697 """Provide an offset to a bullet's initial position."""
699 VALID_TYPES
= ["relative", "absolute"]
701 def __init__(self
, type, x
, y
):
702 if type not in self
.VALID_TYPES
:
703 raise ValueError("invalid type %r" % type)
704 self
.type = intern(type)
708 def __getstate__(self
):
709 state
= [('type', self
.type)]
711 state
.append(('x', self
.x
.expr
))
713 state
.append(('y', self
.y
.expr
))
716 def __setstate__(self
, state
):
718 x
= NumberDef(state
["x"]) if "x" in state
else None
719 y
= NumberDef(state
["y"]) if "y" in state
else None
720 self
.__init
__(state
["type"], x
, y
)
723 def FromXML(cls
, doc
, element
):
724 """Construct using an ElementTree-style element."""
725 type = element
.get("type", "relative")
728 for subelem
in element
:
729 tag
= realtag(subelem
)
731 x
= NumberDef(subelem
.text
)
733 y
= NumberDef(subelem
.text
)
734 return cls(type, x
, y
)
736 def __call__(self
, params
, rank
):
737 return (self
.x(params
, rank
) if self
.x
else 0,
738 self
.y(params
, rank
) if self
.y
else 0)
740 class FireDef(object):
741 """Fire definition (creates a bullet)."""
743 def __init__(self
, bullet
, direction
=None, speed
=None, offset
=None,
744 tags
=(), appearance
=None):
746 self
.direction
= direction
749 self
.tags
= set(tags
)
750 self
.appearance
= appearance
752 def __getstate__(self
):
755 state
.append(("direction", self
.direction
))
757 state
.append(("speed", self
.speed
))
759 state
.append(("offset", self
.offset
))
761 state
.append(("tags", list(self
.tags
)))
763 state
.append(("appearance", self
.appearance
))
765 params
= self
.bullet
.params
766 except AttributeError:
767 state
.append(('bullet', self
.bullet
))
770 state
.append(('bullet', self
.bullet
))
772 # Strip out empty BulletRefs.
773 state
.append(('bullet', self
.bullet
.bullet
))
776 def __setstate__(self
, state
):
778 self
.__init
__(**state
)
781 def FromXML(cls
, doc
, element
):
782 """Construct using an ElementTree-style element."""
789 for subelem
in element
.getchildren():
790 tag
= realtag(subelem
)
791 if tag
== "direction":
792 direction
= Direction
.FromXML(doc
, subelem
, "aim")
794 speed
= Speed
.FromXML(doc
, subelem
)
795 elif tag
== "bullet":
796 bullet
= BulletDef
.FromXML(doc
, subelem
)
797 elif tag
== "bulletRef":
798 bullet
= BulletRef
.FromXML(doc
, subelem
)
799 elif tag
== "offset":
800 offset
= Offset
.FromXML(doc
, subelem
)
802 tags
.add(subelem
.text
)
803 elif tag
== "appearance":
804 appearance
= subelem
.text
806 fire
= cls(bullet
, direction
, speed
, offset
, tags
, appearance
)
807 except UnboundLocalError as exc
:
808 raise ParseError(str(exc
))
810 doc
._fires
[element
.get("label")] = fire
813 def __call__(self
, owner
, action
, params
, rank
, created
):
814 direction
, speed
, tags
, appearance
, actions
= self
.bullet(params
, rank
)
816 direction
= self
.direction(params
, rank
)
818 speed
= self
.speed(params
, rank
)
819 tags
= tags
.union(self
.tags
)
821 appearance
= self
.appearance
824 direction
, type = direction
825 if type == "aim" or type is None:
826 direction
+= owner
.aim
827 elif type == "sequence":
828 direction
+= action
.previous_fire_direction
829 elif type == "relative":
830 direction
+= owner
.direction
832 direction
= owner
.aim
833 action
.previous_fire_direction
= direction
837 if type == "sequence":
838 speed
+= action
.previous_fire_speed
839 elif type == "relative":
840 # The reference Noiz implementation uses
841 # prvFireSpeed here, but the standard is
842 # pretty clear -- "In case of the type is
843 # "relative", ... the speed is relative to the
844 # speed of this bullet."
848 action
.previous_fire_speed
= speed
850 x
, y
= owner
.x
, owner
.y
852 off_x
, off_y
= self
.offset(params
, rank
)
853 if self
.offset
.type == "relative":
856 x
+= c
* off_x
+ s
* off_y
857 y
+= s
* off_x
- c
* off_y
862 if appearance
is None:
863 appearance
= owner
.appearance
864 Action
= action
.__class
__
865 actions
= [Action(None, action
, params
, rank
)
866 for action
, params
in actions
]
867 bullet
= owner
.__class
__(
868 x
=x
, y
=y
, direction
=direction
, speed
=speed
,
869 target
=owner
.target
, actions
=actions
, rank
=rank
,
870 appearance
=appearance
, tags
=tags
)
871 created
.append(bullet
)
874 return "%s(direction=%r, speed=%r, bullet=%r)" % (
875 type(self
).__name
__, self
.direction
, self
.speed
, self
.bullet
)
877 class FireRef(object):
878 """Fire a bullet by name with parameters."""
880 def __init__(self
, fire
, params
=None):
882 self
.params
= params
or ParamList()
884 def __getstate__(self
):
886 if self
.params
.params
:
887 params
= [param
.expr
for param
in self
.params
.params
]
888 state
.append(("params", params
))
889 state
.append(('fire', self
.fire
))
892 def __setstate__(self
, state
):
895 params
= [NumberDef(param
) for param
in state
.get("params", [])]
896 self
.__init
__(fire
, ParamList(params
))
899 def FromXML(cls
, doc
, element
):
900 """Construct using an ElementTree-style element."""
901 fired
= cls(element
.get("label"), ParamList
.FromXML(doc
, element
))
902 doc
._fire
_refs
.append(fired
)
905 def __call__(self
, owner
, action
, params
, rank
, created
):
906 params
= self
.params(params
, rank
)
907 return self
.fire(owner
, action
, params
, rank
, created
)
910 return "%s(params=%r, fire=%r)" % (
911 type(self
).__name
__, self
.params
, self
.fire
)
913 class BulletML(object):
914 """BulletML document.
916 A BulletML document is a collection of top-level actions and the
919 You can add tags to the BulletML.CONSTRUCTORS dictionary to extend
920 its parsing. It maps tag names to classes with a FromXML
921 classmethod, which take the BulletML instance and ElementTree
922 element as arguments.
932 def __init__(self
, type="none", actions
=None):
933 self
.type = intern(type)
934 self
.actions
= [] if actions
is None else actions
936 def __getstate__(self
):
937 return [('type', self
.type), ('actions', self
.actions
)]
939 def __setstate__(self
, state
):
941 self
.__init
__(state
["type"], actions
=state
.get("actions"))
944 def FromXML(cls
, source
):
945 """Return a BulletML instance based on XML."""
946 if not hasattr(source
, 'read'):
947 source
= StringIO(source
)
950 root
= tree
.parse(source
)
952 doc
= cls(type=root
.get("type", "none"))
957 doc
._bullet
_refs
= []
958 doc
._action
_refs
= []
961 for element
in root
.getchildren():
962 tag
= realtag(element
)
963 if tag
in doc
.CONSTRUCTORS
:
964 doc
.CONSTRUCTORS
[tag
].FromXML(doc
, element
)
967 for ref
in doc
._bullet
_refs
:
968 ref
.bullet
= doc
._bullets
[ref
.bullet
]
969 for ref
in doc
._fire
_refs
:
970 ref
.fire
= doc
._fires
[ref
.fire
]
971 for ref
in doc
._action
_refs
:
972 ref
.action
= doc
._actions
[ref
.action
]
973 except KeyError as exc
:
974 raise ParseError("unknown reference %s" % exc
)
976 doc
.actions
= [act
for name
, act
in doc
._actions
.items()
977 if name
and name
.startswith("top")]
979 del(doc
._bullet
_refs
)
980 del(doc
._action
_refs
)
989 def FromYAML(cls
, source
):
990 """Create a BulletML instance based on YAML."""
992 # Late import to avoid a circular dependency.
994 import bulletml
.bulletyaml
997 raise ParseError("PyYAML is not available")
1000 return yaml
.load(source
)
1001 except Exception as exc
:
1002 raise ParseError(str(exc
))
1005 def FromDocument(cls
, source
):
1006 """Create a BulletML instance based on a seekable file or string.
1008 This attempts to autodetect if the stream is XML or YAML.
1010 if not hasattr(source
, 'read'):
1011 source
= StringIO(source
)
1012 start
= source
.read(1)
1015 return cls
.FromXML(source
)
1016 elif start
== "!" or start
== "#":
1017 return cls
.FromYAML(source
)
1019 raise ParseError("unknown initial character %r" % start
)
1022 return "%s(type=%r, actions=%r)" % (
1023 type(self
).__name
__, self
.type, self
.actions
)
1025 ActionDef
.CONSTRUCTORS
= dict(
1029 changeSpeed
=ChangeSpeed
,
1030 changeDirection
=ChangeDirection
,
1035 appearance
=Appearance
,
1038 actionRef
=ActionRef
)
1039 ActionDef
.CONSTRUCTORS
["if"] = If