X-Git-Url: https://git.yukkurigames.com/?p=python-bulletml.git;a=blobdiff_plain;f=bulletml%2Fparser.py;h=a07cc2dab61f7cb74ba3bbeec0f91d83df08f744;hp=edb9d29e4c544bae3506e1681cbe25141a78c1db;hb=16023be19df43f8b8acc4671e0fabdeffb0e720a;hpb=ba81e7d74da58dc8dfa47949502d2a2759c84309 diff --git a/bulletml/parser.py b/bulletml/parser.py index edb9d29..a07cc2d 100644 --- a/bulletml/parser.py +++ b/bulletml/parser.py @@ -1,6 +1,10 @@ """BulletML parser. -http://www.asahi-net.or.jp/~cs8k-cyu/bulletml/index_e.html +This is based on the format described at +http://www.asahi-net.or.jp/~cs8k-cyu/bulletml/bulletml_ref_e.html. + +Unless you are adding support for new tags, the only class you should +care about in here is BulletML. """ from __future__ import division @@ -17,6 +21,8 @@ except ImportError: from bulletml.errors import Error from bulletml.expr import NumberDef, INumberDef +__all_ = ["ParseError", "BulletML"] + class ParseError(Error): """Raised when an error occurs parsing the XML structure.""" pass @@ -31,7 +37,7 @@ def realtag(element): class ParamList(object): """List of parameter definitions.""" - def __init__(self, params=[]): + def __init__(self, params=()): self.params = list(params) @classmethod @@ -312,7 +318,13 @@ class BulletRef(object): type(self).__name__, self.params, self.bullet) class ActionDef(object): - """Action definition.""" + """Action definition. + + To support parsing new actions, add tags to + ActionDef.CONSTRUCTORS. It maps tag names to classes with a + FromElement classmethod, which take the BulletML instance and + ElementTree element as arguments. + """ # This is self-referential, so it's filled in later. CONSTRUCTORS = dict() @@ -363,19 +375,51 @@ class ActionRef(object): return "%s(params=%r, action=%r)" % ( type(self).__name__, self.params, self.action) +class Offset(object): + """Provide an offset to a bullet's initial position.""" + + VALID_TYPES = ["relative", "absolute"] + + def __init__(self, type, x, y): + if type not in self.VALID_TYPES: + raise ValueError("invalid type %r" % type) + self.type = type + self.x = x + self.y = y + + @classmethod + def FromElement(cls, doc, element): + """Construct using an ElementTree-style element.""" + type = element.get("type", "relative") + x = None + y = None + for subelem in element: + tag = realtag(subelem) + if tag == "x": + x = NumberDef(subelem.text) + elif tag == "y": + y = NumberDef(subelem.text) + return cls(type, x, y) + + def __call__(self, params, rank): + return (self.x(params, rank) if self.x else 0, + self.y(params, rank) if self.y else 0) + class FireDef(object): """Fire definition (creates a bullet).""" - def __init__(self, bullet, direction=None, speed=None): + def __init__(self, bullet, direction=None, speed=None, offset=None): self.bullet = bullet self.direction = direction self.speed = speed + self.offset = offset @classmethod def FromElement(cls, doc, element): """Construct using an ElementTree-style element.""" direction = None speed = None + offset = None for subelem in element.getchildren(): tag = realtag(subelem) @@ -387,9 +431,10 @@ class FireDef(object): bullet = BulletDef.FromElement(doc, subelem) elif tag == "bulletRef": bullet = BulletRef.FromElement(doc, subelem) - + elif tag == "offset": + offset = Offset.FromElement(doc, subelem) try: - fire = cls(bullet, direction, speed) + fire = cls(bullet, direction, speed, offset) except UnboundLocalError as exc: raise ParseError(str(exc)) else: @@ -402,7 +447,7 @@ class FireDef(object): direction = self.direction(params, rank) if self.speed: speed = self.speed(params, rank) - return direction, speed, actions + return direction, speed, actions, self.offset def __repr__(self): return "%s(direction=%r, speed=%r, bullet=%r)" % ( @@ -423,7 +468,6 @@ class FireRef(object): return fired def __call__(self, params, rank): - """Generate a Bullet from the FireDef and params.""" return self.fire(self.params(params, rank), rank) def __repr__(self): @@ -435,6 +479,12 @@ class BulletML(object): A BulletML document is a collection of bullets, actions, and firings, as well as a base game type. + + You can add tags to the BulletML.CONSTRUCTORS dictionary to extend + its parsing. It maps tag names to classes with a FromElement + classmethod, which take the BulletML instance and ElementTree + element as arguments. + """ CONSTRUCTORS = dict( @@ -443,14 +493,15 @@ class BulletML(object): fire=FireDef, ) - def __init__(self, type="none", bullets={}, fires={}, actions={}): + def __init__(self, type="none", bullets=None, fires=None, actions=None): self.type = type - self.bullets = {} - self.actions = {} - self.fires = {} + self.bullets = {} if bullets is None else bullets + self.actions = {} if actions is None else actions + self.fires = {} if fires is None else fires @classmethod def FromDocument(cls, source): + """Return a BulletML instance based on a string or file-like.""" if isinstance(source, (str, unicode)): source = StringIO(source)