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 __all_
= ["ParseError", "BulletML"]
26 class ParseError(Error
):
27 """Raised when an error occurs parsing the XML structure."""
31 """Strip namespace poop off the front of a tag."""
33 return element
.tag
.rsplit('}', 1)[1]
37 class ParamList(object):
38 """List of parameter definitions."""
40 def __init__(self
, params
=()):
41 self
.params
= list(params
)
44 def FromElement(cls
, doc
, element
):
45 """Construct using an ElementTree-style element."""
46 return cls([NumberDef(subelem
.text
) for subelem
in element
47 if realtag(subelem
) == "param"])
49 def __call__(self
, params
, rank
):
50 return [param(params
, rank
) for param
in self
.params
]
53 return "%s(%r)" % (type(self
).__name
__, self
.params
)
55 class Direction(object):
56 """Raw direction value."""
58 VALID_TYPES
= ["relative", "absolute", "aim", "sequence"]
60 def __init__(self
, type, value
):
61 if type not in self
.VALID_TYPES
:
62 raise ValueError("invalid type %r" % type)
67 def FromElement(cls
, doc
, element
, default
="absolute"):
68 """Construct using an ElementTree-style element."""
69 return cls(element
.get("type", default
), NumberDef(element
.text
))
71 def __call__(self
, params
, rank
):
72 return (math
.radians(self
.value(params
, rank
)), self
.type)
75 return "%s(%r, type=%r)" % (
76 type(self
).__name
__, self
.value
, self
.type)
78 class ChangeDirection(object):
79 """Direction change over time."""
81 def __init__(self
, term
, direction
):
83 self
.direction
= direction
86 def FromElement(cls
, doc
, element
):
87 """Construct using an ElementTree-style element."""
88 for subelem
in element
.getchildren():
89 tag
= realtag(subelem
)
90 if tag
== "direction":
91 direction
= Direction
.FromElement(doc
, subelem
)
93 term
= INumberDef(subelem
.text
)
95 return cls(term
, direction
)
96 except UnboundLocalError as exc
:
97 raise ParseError(str(exc
))
99 def __call__(self
, params
, rank
):
100 return self
.term(params
, rank
), self
.direction(params
, rank
)
103 return "%s(term=%r, direction=%r)" % (
104 type(self
).__name
__, self
.term
, self
.direction
)
107 """Raw speed value."""
109 VALID_TYPES
= ["relative", "absolute", "sequence"]
111 def __init__(self
, type, value
):
112 if type not in self
.VALID_TYPES
:
113 raise ValueError("invalid type %r" % type)
118 def FromElement(cls
, doc
, element
):
119 """Construct using an ElementTree-style element."""
120 return cls(element
.get("type", "absolute"), NumberDef(element
.text
))
122 def __call__(self
, params
, rank
):
123 return (self
.value(params
, rank
), self
.type)
126 return "%s(%r, type=%r)" % (type(self
).__name
__, self
.value
, self
.type)
128 class ChangeSpeed(object):
129 """Speed change over time."""
131 def __init__(self
, term
, speed
):
136 def FromElement(cls
, doc
, element
):
137 """Construct using an ElementTree-style element."""
138 for subelem
in element
.getchildren():
139 tag
= realtag(subelem
)
141 speed
= Speed
.FromElement(doc
, subelem
)
143 term
= INumberDef(subelem
.text
)
145 return cls(term
, speed
)
146 except UnboundLocalError as exc
:
147 raise ParseError(str(exc
))
149 def __call__(self
, params
, rank
):
150 return self
.term(params
, rank
), self
.speed(params
, rank
)
153 return "%s(term=%r, speed=%r)" % (
154 type(self
).__name
__, self
.term
, self
.speed
)
157 """Wait for some frames."""
159 def __init__(self
, frames
):
163 def FromElement(cls
, doc
, element
):
164 """Construct using an ElementTree-style element."""
165 return cls(INumberDef(element
.text
))
167 def __call__(self
, params
, rank
):
168 return self
.frames(params
, rank
)
171 return "%s(%r)" % (type(self
).__name
__, self
.frames
)
173 class Vanish(object):
174 """Make the owner disappear."""
180 def FromElement(cls
, doc
, element
):
181 """Construct using an ElementTree-style element."""
185 return "%s()" % (type(self
).__name
__)
187 class Repeat(object):
188 """Repeat an action definition."""
190 def __init__(self
, times
, action
):
195 def FromElement(cls
, doc
, element
):
196 """Construct using an ElementTree-style element."""
197 for subelem
in element
.getchildren():
198 tag
= realtag(subelem
)
200 times
= INumberDef(subelem
.text
)
201 elif tag
== "action":
202 action
= ActionDef
.FromElement(doc
, subelem
)
203 elif tag
== "actionRef":
204 action
= ActionRef
.FromElement(doc
, subelem
)
206 return cls(times
, action
)
207 except UnboundLocalError as exc
:
208 raise ParseError(str(exc
))
210 def __call__(self
, params
, rank
):
211 return self
.times(params
, rank
), self
.action(params
, rank
)
214 return "%s(%r, %r)" % (type(self
).__name
__, self
.times
, self
.action
)
217 """Accelerate over some time."""
222 def __init__(self
, term
, horizontal
=None, vertical
=None):
224 self
.horizontal
= horizontal
225 self
.vertical
= vertical
228 def FromElement(cls
, doc
, element
):
229 """Construct using an ElementTree-style element."""
233 for subelem
in element
.getchildren():
234 tag
= realtag(subelem
)
236 term
= INumberDef(subelem
.text
)
237 elif tag
== "horizontal":
238 horizontal
= Speed
.FromElement(doc
, subelem
)
239 elif tag
== "vertical":
240 vertical
= Speed
.FromElement(doc
, subelem
)
243 return cls(term
, horizontal
, vertical
)
244 except AttributeError:
247 def __call__(self
, params
, rank
):
248 frames
= self
.term(params
, rank
)
249 horizontal
= self
.horizontal
and self
.horizontal(params
, rank
)
250 vertical
= self
.vertical
and self
.vertical(params
, rank
)
251 return frames
, horizontal
, vertical
254 return "%s(%r, horizontal=%r, vertical=%r)" % (
255 type(self
).__name
__, self
.term
, self
.horizontal
, self
.vertical
)
257 class BulletDef(object):
258 """Bullet definition."""
263 def __init__(self
, actions
=[], direction
=None, speed
=None):
264 self
.direction
= direction
266 self
.actions
= list(actions
)
269 def FromElement(cls
, doc
, element
):
270 """Construct using an ElementTree-style element."""
274 for subelem
in element
.getchildren():
275 tag
= realtag(subelem
)
276 if tag
== "direction":
277 direction
= Direction
.FromElement(doc
, subelem
)
279 speed
= Speed
.FromElement(doc
, subelem
)
280 elif tag
== "action":
281 actions
.append(ActionDef
.FromElement(doc
, subelem
))
282 elif tag
== "actionRef":
283 actions
.append(ActionRef
.FromElement(doc
, subelem
))
284 dfn
= cls(actions
, direction
, speed
)
285 doc
.bullets
[element
.get("label")] = dfn
288 def __call__(self
, params
, rank
):
289 actions
= [action(params
, rank
) for action
in self
.actions
]
291 self
.direction
and self
.direction(params
, rank
),
292 self
.speed
and self
.speed(params
, rank
),
296 return "%s(direction=%r, speed=%r, actions=%r)" % (
297 type(self
).__name
__, self
.direction
, self
.speed
, self
.actions
)
299 class BulletRef(object):
300 """Create a bullet by name with parameters."""
302 def __init__(self
, bullet
, params
=None):
304 self
.params
= params
or ParamList()
307 def FromElement(cls
, doc
, element
):
308 """Construct using an ElementTree-style element."""
309 bullet
= cls(element
.get("label"), ParamList
.FromElement(doc
, element
))
310 doc
._bullet
_refs
.append(bullet
)
313 def __call__(self
, params
, rank
):
314 return self
.bullet(self
.params(params
, rank
), rank
)
317 return "%s(params=%r, bullet=%r)" % (
318 type(self
).__name
__, self
.params
, self
.bullet
)
320 class ActionDef(object):
321 """Action definition.
323 To support parsing new actions, add tags to
324 ActionDef.CONSTRUCTORS. It maps tag names to classes with a
325 FromElement classmethod, which take the BulletML instance and
326 ElementTree element as arguments.
329 # This is self-referential, so it's filled in later.
330 CONSTRUCTORS
= dict()
332 def __init__(self
, actions
):
333 self
.actions
= list(actions
)
336 def FromElement(cls
, doc
, element
):
337 """Construct using an ElementTree-style element."""
339 for subelem
in element
.getchildren():
340 tag
= realtag(subelem
)
342 ctr
= cls
.CONSTRUCTORS
[tag
]
346 actions
.append(ctr
.FromElement(doc
, subelem
))
348 doc
.actions
[element
.get("label")] = dfn
351 def __call__(self
, params
, rank
):
352 return self
.actions
, params
355 return "%s(%r)" % (type(self
).__name
__, self
.actions
)
357 class ActionRef(object):
358 """Run an action by name with parameters."""
360 def __init__(self
, action
, params
=None):
362 self
.params
= params
or ParamList()
365 def FromElement(cls
, doc
, element
):
366 """Construct using an ElementTree-style element."""
367 action
= cls(element
.get("label"), ParamList
.FromElement(doc
, element
))
368 doc
._action
_refs
.append(action
)
371 def __call__(self
, params
, rank
):
372 return self
.action(self
.params(params
, rank
), rank
)
375 return "%s(params=%r, action=%r)" % (
376 type(self
).__name
__, self
.params
, self
.action
)
378 class FireDef(object):
379 """Fire definition (creates a bullet)."""
381 def __init__(self
, bullet
, direction
=None, speed
=None):
383 self
.direction
= direction
387 def FromElement(cls
, doc
, element
):
388 """Construct using an ElementTree-style element."""
392 for subelem
in element
.getchildren():
393 tag
= realtag(subelem
)
394 if tag
== "direction":
395 direction
= Direction
.FromElement(doc
, subelem
, "aim")
397 speed
= Speed
.FromElement(doc
, subelem
)
398 elif tag
== "bullet":
399 bullet
= BulletDef
.FromElement(doc
, subelem
)
400 elif tag
== "bulletRef":
401 bullet
= BulletRef
.FromElement(doc
, subelem
)
404 fire
= cls(bullet
, direction
, speed
)
405 except UnboundLocalError as exc
:
406 raise ParseError(str(exc
))
408 doc
.fires
[element
.get("label")] = fire
411 def __call__(self
, params
, rank
):
412 direction
, speed
, actions
= self
.bullet(params
, rank
)
414 direction
= self
.direction(params
, rank
)
416 speed
= self
.speed(params
, rank
)
417 return direction
, speed
, actions
420 return "%s(direction=%r, speed=%r, bullet=%r)" % (
421 type(self
).__name
__, self
.direction
, self
.speed
, self
.bullet
)
423 class FireRef(object):
424 """Fire a bullet by name with parameters."""
426 def __init__(self
, fire
, params
=None):
428 self
.params
= params
or ParamList()
431 def FromElement(cls
, doc
, element
):
432 """Construct using an ElementTree-style element."""
433 fired
= cls(element
.get("label"), ParamList
.FromElement(doc
, element
))
434 doc
._fire
_refs
.append(fired
)
437 def __call__(self
, params
, rank
):
438 """Generate a Bullet from the FireDef and params."""
439 return self
.fire(self
.params(params
, rank
), rank
)
442 return "%s(params=%r, fire=%r)" % (
443 type(self
).__name
__, self
.params
, self
.fire
)
445 class BulletML(object):
446 """BulletML document.
448 A BulletML document is a collection of bullets, actions, and
449 firings, as well as a base game type.
451 You can add tags to the BulletML.CONSTRUCTORS dictionary to extend
452 its parsing. It maps tag names to classes with a FromElement
453 classmethod, which take the BulletML instance and ElementTree
454 element as arguments.
464 def __init__(self
, type="none", bullets
=None, fires
=None, actions
=None):
466 self
.bullets
= {} if bullets
is None else bullets
467 self
.actions
= {} if actions
is None else actions
468 self
.fires
= {} if fires
is None else fires
471 def FromDocument(cls
, source
):
472 """Return a BulletML instance based on a string or file-like."""
473 if isinstance(source
, (str, unicode)):
474 source
= StringIO(source
)
477 root
= tree
.parse(source
)
479 self
= cls(type=root
.get("type", "none"))
481 self
._bullet
_refs
= []
482 self
._action
_refs
= []
485 for element
in root
.getchildren():
486 tag
= realtag(element
)
487 if tag
in self
.CONSTRUCTORS
:
488 self
.CONSTRUCTORS
[tag
].FromElement(self
, element
)
491 for ref
in self
._bullet
_refs
:
492 ref
.bullet
= self
.bullets
[ref
.bullet
]
493 for ref
in self
._fire
_refs
:
494 ref
.fire
= self
.fires
[ref
.fire
]
495 for ref
in self
._action
_refs
:
496 ref
.action
= self
.actions
[ref
.action
]
497 except KeyError as exc
:
498 raise ParseError("unknown reference %s" % exc
)
500 del(self
._bullet
_refs
)
501 del(self
._action
_refs
)
504 self
.bullets
.pop(None, None)
505 self
.actions
.pop(None, None)
506 self
.fires
.pop(None, None)
512 """Get a list of all top-level actions."""
513 return [dfn
for name
, dfn
in self
.actions
.iteritems()
514 if name
and name
.startswith("top")]
517 return "%s(type=%r, bullets=%r, actions=%r, fires=%r)" % (
518 type(self
).__name
__, self
.type, self
.bullets
, self
.actions
,
521 ActionDef
.CONSTRUCTORS
= dict(
525 changeSpeed
=ChangeSpeed
,
526 changeDirection
=ChangeDirection
,