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 tags, the only class you should
7 care about in here is BulletML.
10 from __future__
import division
14 from xml
.etree
.ElementTree
import ElementTree
17 from cStringIO
import StringIO
19 from StringIO
import StringIO
21 from bulletml
.errors
import Error
22 from bulletml
.expr
import NumberDef
, INumberDef
24 class ParseError(Error
):
25 """Raised when an error occurs parsing the XML structure."""
29 """Strip namespace poop off the front of a tag."""
31 return element
.tag
.rsplit('}', 1)[1]
35 class ParamList(object):
36 """List of parameter definitions."""
38 def __init__(self
, params
=()):
39 self
.params
= list(params
)
42 def FromElement(cls
, doc
, element
):
43 """Construct using an ElementTree-style element."""
44 return cls([NumberDef(subelem
.text
) for subelem
in element
45 if realtag(subelem
) == "param"])
47 def __call__(self
, params
, rank
):
48 return [param(params
, rank
) for param
in self
.params
]
51 return "%s(%r)" % (type(self
).__name
__, self
.params
)
53 class Direction(object):
54 """Raw direction value."""
56 VALID_TYPES
= ["relative", "absolute", "aim", "sequence"]
58 def __init__(self
, type, value
):
59 if type not in self
.VALID_TYPES
:
60 raise ValueError("invalid type %r" % type)
65 def FromElement(cls
, doc
, element
, default
="absolute"):
66 """Construct using an ElementTree-style element."""
67 return cls(element
.get("type", default
), NumberDef(element
.text
))
69 def __call__(self
, params
, rank
):
70 return (math
.radians(self
.value(params
, rank
)), self
.type)
73 return "%s(%r, type=%r)" % (
74 type(self
).__name
__, self
.value
, self
.type)
76 class ChangeDirection(object):
77 """Direction change over time."""
79 def __init__(self
, term
, direction
):
81 self
.direction
= direction
84 def FromElement(cls
, doc
, element
):
85 """Construct using an ElementTree-style element."""
86 for subelem
in element
.getchildren():
87 tag
= realtag(subelem
)
88 if tag
== "direction":
89 direction
= Direction
.FromElement(doc
, subelem
)
91 term
= INumberDef(subelem
.text
)
93 return cls(term
, direction
)
94 except UnboundLocalError as exc
:
95 raise ParseError(str(exc
))
97 def __call__(self
, params
, rank
):
98 return self
.term(params
, rank
), self
.direction(params
, rank
)
101 return "%s(term=%r, direction=%r)" % (
102 type(self
).__name
__, self
.term
, self
.direction
)
105 """Raw speed value."""
107 VALID_TYPES
= ["relative", "absolute", "sequence"]
109 def __init__(self
, type, value
):
110 if type not in self
.VALID_TYPES
:
111 raise ValueError("invalid type %r" % type)
116 def FromElement(cls
, doc
, element
):
117 """Construct using an ElementTree-style element."""
118 return cls(element
.get("type", "absolute"), NumberDef(element
.text
))
120 def __call__(self
, params
, rank
):
121 return (self
.value(params
, rank
), self
.type)
124 return "%s(%r, type=%r)" % (type(self
).__name
__, self
.value
, self
.type)
126 class ChangeSpeed(object):
127 """Speed change over time."""
129 def __init__(self
, term
, speed
):
134 def FromElement(cls
, doc
, element
):
135 """Construct using an ElementTree-style element."""
136 for subelem
in element
.getchildren():
137 tag
= realtag(subelem
)
139 speed
= Speed
.FromElement(doc
, subelem
)
141 term
= INumberDef(subelem
.text
)
143 return cls(term
, speed
)
144 except UnboundLocalError as exc
:
145 raise ParseError(str(exc
))
147 def __call__(self
, params
, rank
):
148 return self
.term(params
, rank
), self
.speed(params
, rank
)
151 return "%s(term=%r, speed=%r)" % (
152 type(self
).__name
__, self
.term
, self
.speed
)
155 """Wait for some frames."""
157 def __init__(self
, frames
):
161 def FromElement(cls
, doc
, element
):
162 """Construct using an ElementTree-style element."""
163 return cls(INumberDef(element
.text
))
165 def __call__(self
, params
, rank
):
166 return self
.frames(params
, rank
)
169 return "%s(%r)" % (type(self
).__name
__, self
.frames
)
171 class Vanish(object):
172 """Make the owner disappear."""
178 def FromElement(cls
, doc
, element
):
179 """Construct using an ElementTree-style element."""
183 return "%s()" % (type(self
).__name
__)
185 class Repeat(object):
186 """Repeat an action definition."""
188 def __init__(self
, times
, action
):
193 def FromElement(cls
, doc
, element
):
194 """Construct using an ElementTree-style element."""
195 for subelem
in element
.getchildren():
196 tag
= realtag(subelem
)
198 times
= INumberDef(subelem
.text
)
199 elif tag
== "action":
200 action
= ActionDef
.FromElement(doc
, subelem
)
201 elif tag
== "actionRef":
202 action
= ActionRef
.FromElement(doc
, subelem
)
204 return cls(times
, action
)
205 except UnboundLocalError as exc
:
206 raise ParseError(str(exc
))
208 def __call__(self
, params
, rank
):
209 return self
.times(params
, rank
), self
.action(params
, rank
)
212 return "%s(%r, %r)" % (type(self
).__name
__, self
.times
, self
.action
)
215 """Accelerate over some time."""
220 def __init__(self
, term
, horizontal
=None, vertical
=None):
222 self
.horizontal
= horizontal
223 self
.vertical
= vertical
226 def FromElement(cls
, doc
, element
):
227 """Construct using an ElementTree-style element."""
231 for subelem
in element
.getchildren():
232 tag
= realtag(subelem
)
234 term
= INumberDef(subelem
.text
)
235 elif tag
== "horizontal":
236 horizontal
= Speed
.FromElement(doc
, subelem
)
237 elif tag
== "vertical":
238 vertical
= Speed
.FromElement(doc
, subelem
)
241 return cls(term
, horizontal
, vertical
)
242 except AttributeError:
245 def __call__(self
, params
, rank
):
246 frames
= self
.term(params
, rank
)
247 horizontal
= self
.horizontal
and self
.horizontal(params
, rank
)
248 vertical
= self
.vertical
and self
.vertical(params
, rank
)
249 return frames
, horizontal
, vertical
252 return "%s(%r, horizontal=%r, vertical=%r)" % (
253 type(self
).__name
__, self
.term
, self
.horizontal
, self
.vertical
)
255 class BulletDef(object):
256 """Bullet definition."""
261 def __init__(self
, actions
=[], direction
=None, speed
=None):
262 self
.direction
= direction
264 self
.actions
= list(actions
)
267 def FromElement(cls
, doc
, element
):
268 """Construct using an ElementTree-style element."""
272 for subelem
in element
.getchildren():
273 tag
= realtag(subelem
)
274 if tag
== "direction":
275 direction
= Direction
.FromElement(doc
, subelem
)
277 speed
= Speed
.FromElement(doc
, subelem
)
278 elif tag
== "action":
279 actions
.append(ActionDef
.FromElement(doc
, subelem
))
280 elif tag
== "actionRef":
281 actions
.append(ActionRef
.FromElement(doc
, subelem
))
282 dfn
= cls(actions
, direction
, speed
)
283 doc
.bullets
[element
.get("label")] = dfn
286 def __call__(self
, params
, rank
):
287 actions
= [action(params
, rank
) for action
in self
.actions
]
289 self
.direction
and self
.direction(params
, rank
),
290 self
.speed
and self
.speed(params
, rank
),
294 return "%s(direction=%r, speed=%r, actions=%r)" % (
295 type(self
).__name
__, self
.direction
, self
.speed
, self
.actions
)
297 class BulletRef(object):
298 """Create a bullet by name with parameters."""
300 def __init__(self
, bullet
, params
=None):
302 self
.params
= params
or ParamList()
305 def FromElement(cls
, doc
, element
):
306 """Construct using an ElementTree-style element."""
307 bullet
= cls(element
.get("label"), ParamList
.FromElement(doc
, element
))
308 doc
._bullet
_refs
.append(bullet
)
311 def __call__(self
, params
, rank
):
312 return self
.bullet(self
.params(params
, rank
), rank
)
315 return "%s(params=%r, bullet=%r)" % (
316 type(self
).__name
__, self
.params
, self
.bullet
)
318 class ActionDef(object):
319 """Action definition.
321 To support parsing new actions, add tags to
322 ActionDef.CONSTRUCTORS. It maps tag names to classes with a
323 FromElement classmethod, which take the BulletML instance and
324 ElementTree element as arguments.
327 # This is self-referential, so it's filled in later.
328 CONSTRUCTORS
= dict()
330 def __init__(self
, actions
):
331 self
.actions
= list(actions
)
334 def FromElement(cls
, doc
, element
):
335 """Construct using an ElementTree-style element."""
337 for subelem
in element
.getchildren():
338 tag
= realtag(subelem
)
340 ctr
= cls
.CONSTRUCTORS
[tag
]
344 actions
.append(ctr
.FromElement(doc
, subelem
))
346 doc
.actions
[element
.get("label")] = dfn
349 def __call__(self
, params
, rank
):
350 return self
.actions
, params
353 return "%s(%r)" % (type(self
).__name
__, self
.actions
)
355 class ActionRef(object):
356 """Run an action by name with parameters."""
358 def __init__(self
, action
, params
=None):
360 self
.params
= params
or ParamList()
363 def FromElement(cls
, doc
, element
):
364 """Construct using an ElementTree-style element."""
365 action
= cls(element
.get("label"), ParamList
.FromElement(doc
, element
))
366 doc
._action
_refs
.append(action
)
369 def __call__(self
, params
, rank
):
370 return self
.action(self
.params(params
, rank
), rank
)
373 return "%s(params=%r, action=%r)" % (
374 type(self
).__name
__, self
.params
, self
.action
)
376 class FireDef(object):
377 """Fire definition (creates a bullet)."""
379 def __init__(self
, bullet
, direction
=None, speed
=None):
381 self
.direction
= direction
385 def FromElement(cls
, doc
, element
):
386 """Construct using an ElementTree-style element."""
390 for subelem
in element
.getchildren():
391 tag
= realtag(subelem
)
392 if tag
== "direction":
393 direction
= Direction
.FromElement(doc
, subelem
, "aim")
395 speed
= Speed
.FromElement(doc
, subelem
)
396 elif tag
== "bullet":
397 bullet
= BulletDef
.FromElement(doc
, subelem
)
398 elif tag
== "bulletRef":
399 bullet
= BulletRef
.FromElement(doc
, subelem
)
402 fire
= cls(bullet
, direction
, speed
)
403 except UnboundLocalError as exc
:
404 raise ParseError(str(exc
))
406 doc
.fires
[element
.get("label")] = fire
409 def __call__(self
, params
, rank
):
410 direction
, speed
, actions
= self
.bullet(params
, rank
)
412 direction
= self
.direction(params
, rank
)
414 speed
= self
.speed(params
, rank
)
415 return direction
, speed
, actions
418 return "%s(direction=%r, speed=%r, bullet=%r)" % (
419 type(self
).__name
__, self
.direction
, self
.speed
, self
.bullet
)
421 class FireRef(object):
422 """Fire a bullet by name with parameters."""
424 def __init__(self
, fire
, params
=None):
426 self
.params
= params
or ParamList()
429 def FromElement(cls
, doc
, element
):
430 """Construct using an ElementTree-style element."""
431 fired
= cls(element
.get("label"), ParamList
.FromElement(doc
, element
))
432 doc
._fire
_refs
.append(fired
)
435 def __call__(self
, params
, rank
):
436 """Generate a Bullet from the FireDef and params."""
437 return self
.fire(self
.params(params
, rank
), rank
)
440 return "%s(params=%r, fire=%r)" % (
441 type(self
).__name
__, self
.params
, self
.fire
)
443 class BulletML(object):
444 """BulletML document.
446 A BulletML document is a collection of bullets, actions, and
447 firings, as well as a base game type.
449 You can add tags to the BulletML.CONSTRUCTORS dictionary to extend
450 its parsing. It maps tag names to classes with a FromElement
451 classmethod, which take the BulletML instance and ElementTree
452 element as arguments.
462 def __init__(self
, type="none", bullets
=None, fires
=None, actions
=None):
464 self
.bullets
= {} if bullets
is None else bullets
465 self
.actions
= {} if actions
is None else actions
466 self
.fires
= {} if fires
is None else fires
469 def FromDocument(cls
, source
):
470 """Return a BulletML instance based on a string or file-like."""
471 if isinstance(source
, (str, unicode)):
472 source
= StringIO(source
)
475 root
= tree
.parse(source
)
477 self
= cls(type=root
.get("type", "none"))
479 self
._bullet
_refs
= []
480 self
._action
_refs
= []
483 for element
in root
.getchildren():
484 tag
= realtag(element
)
485 if tag
in self
.CONSTRUCTORS
:
486 self
.CONSTRUCTORS
[tag
].FromElement(self
, element
)
489 for ref
in self
._bullet
_refs
:
490 ref
.bullet
= self
.bullets
[ref
.bullet
]
491 for ref
in self
._fire
_refs
:
492 ref
.fire
= self
.fires
[ref
.fire
]
493 for ref
in self
._action
_refs
:
494 ref
.action
= self
.actions
[ref
.action
]
495 except KeyError as exc
:
496 raise ParseError("unknown reference %s" % exc
)
498 del(self
._bullet
_refs
)
499 del(self
._action
_refs
)
502 self
.bullets
.pop(None, None)
503 self
.actions
.pop(None, None)
504 self
.fires
.pop(None, None)
510 """Get a list of all top-level actions."""
511 return [dfn
for name
, dfn
in self
.actions
.iteritems()
512 if name
and name
.startswith("top")]
515 return "%s(type=%r, bullets=%r, actions=%r, fires=%r)" % (
516 type(self
).__name
__, self
.type, self
.bullets
, self
.actions
,
519 ActionDef
.CONSTRUCTORS
= dict(
523 changeSpeed
=ChangeSpeed
,
524 changeDirection
=ChangeDirection
,