BulletML.FromDocument: Type detector. Various setstate bug fixes.
[python-bulletml.git] / bulletml / parser.py
index a89d1c5..10b789a 100644 (file)
@@ -24,6 +24,7 @@ except ImportError:
 from bulletml.errors import Error
 from bulletml.expr import NumberDef, INumberDef
 
+
 __all_ = ["ParseError", "BulletML"]
 
 class ParseError(Error):
@@ -49,15 +50,6 @@ class ParamList(object):
         return cls([NumberDef(subelem.text) for subelem in element
                     if realtag(subelem) == "param"])
 
-    def __getstate__(self):
-        if self.params:
-            return dict(params=[param.expr for param in self.params])
-        else:
-            return {}
-
-    def __setstate__(self, state):
-        self.__init__(NumberDef(param) for param in state.get("params", []))
-
     def __call__(self, params, rank):
         return [param(params, rank) for param in self.params]
 
@@ -321,7 +313,7 @@ class Accel(object):
         return state
 
     def __setstate__(self, state):
-        self.__init__(INumberDef(state["term"]), state.get("horizontal"),
+        self.__init__(INumberDef(state["frames"]), state.get("horizontal"),
                       state.get("vertical"))
 
     @classmethod
@@ -419,13 +411,13 @@ class BulletRef(object):
     def __getstate__(self):
         state = dict(bullet=self.bullet)
         if self.params.params:
-            state["params"] = self.params.__getstate__()
+            state["params"] = [param.expr for param in self.params.params]
         return state
 
     def __setstate__(self, state):
         bullet = state["bullet"]
         params = [NumberDef(param) for param in state.get("params", [])]
-        self.__init__(bullet, params)
+        self.__init__(bullet, ParamList(params))
 
     @classmethod
     def FromElement(cls, doc, element):
@@ -460,7 +452,7 @@ class ActionDef(object):
         return dict(actions=self.actions)
 
     def __setstate__(self, state):
-        self.__init__(state)
+        self.__init__(state["actions"])
 
     @classmethod
     def FromElement(cls, doc, element):
@@ -494,13 +486,13 @@ class ActionRef(object):
     def __getstate__(self):
         state = dict(action=self.action)
         if self.params.params:
-            state["params"] = self.params.__getstate__()
+            state["params"] = [param.expr for param in self.params.params]
         return state
 
     def __setstate__(self, state):
         action = state["action"]
         params = [NumberDef(param) for param in state.get("params", [])]
-        self.__init__(action, params)
+        self.__init__(action, ParamList(params))
 
     @classmethod
     def FromElement(cls, doc, element):
@@ -584,6 +576,9 @@ class FireDef(object):
             state["offset"] = self.offset
         return state
 
+    def __setstate__(self, state):
+        self.__init__(**state)
+
     @classmethod
     def FromElement(cls, doc, element):
         """Construct using an ElementTree-style element."""
@@ -633,13 +628,13 @@ class FireRef(object):
     def __getstate__(self):
         state = dict(fire=self.fire)
         if self.params.params:
-            state["params"] = self.params.__getstate__()
+            state["params"] = [param.expr for param in self.params.params]
         return state
 
     def __setstate__(self, state):
         fire = state["fire"]
         params = [NumberDef(param) for param in state.get("params", [])]
-        self.__init__(fire, params)
+        self.__init__(fire, ParamList(params))
 
     @classmethod
     def FromElement(cls, doc, element):
@@ -684,11 +679,11 @@ class BulletML(object):
         return dict(type=self.type, actions=self.actions)
 
     def __setstate__(self, state):
-        self.__init__(state["type"], state.get("actions"))
+        self.__init__(state["type"], actions=state.get("actions"))
 
     @classmethod
-    def FromDocument(cls, source):
-        """Return a BulletML instance based on a string or file-like."""
+    def FromXML(cls, source):
+        """Return a BulletML instance based on XML."""
         if not hasattr(source, 'read'):
             source = StringIO(source)
 
@@ -726,6 +721,39 @@ class BulletML(object):
 
         return self
 
+    @classmethod
+    def FromYAML(cls, source):
+        """Create a BulletML instance based on YAML."""
+
+        # Late import to avoid a circular dependency.
+        try:
+            import bulletml.bulletyaml
+            import yaml
+        except ImportError:
+            raise ParseError("PyYAML is not available")
+        else:
+            try:
+                return yaml.load(source)
+            except Exception, exc:
+                raise ParseError(str(exc))
+
+    @classmethod
+    def FromDocument(cls, source):
+        """Create a BulletML instance based on a seekable file or string.
+
+        This attempts to autodetect if the stream is XML or YAML.
+        """
+        if not hasattr(source, 'read'):
+            source = StringIO(source)
+        start = source.read(1)
+        source.seek(0)
+        if start == "<":
+            return cls.FromXML(source)
+        elif start == "!":
+            return cls.FromYAML(source)
+        else:
+            raise ParseError("unknown initial character %r" % start)
+
     @property
     def top(self):
         """Get a list of all top-level actions."""