diff --git a/src/__init__.py b/src/__init__.py deleted file mode 100644 diff --git a/src/sgfParser/collection.py b/src/sgfParser/collection.py --- a/src/sgfParser/collection.py +++ b/src/sgfParser/collection.py @@ -41,7 +41,7 @@ class GameTree: x.setParent(y) y=x i=skipWhitespace(s,i) - i,x= Node.create(s, i) + i,x=Node.create(s, i) i=skipWhitespace(s,i) i,x=GameTree.create(s,i) while x is not None: @@ -69,7 +69,7 @@ class GameTree: ## Create and return a new game tree containing the provided Node. # - # Ancestor nodes are copied, descendants are taken directly. + # Ancestor nodes are copied, descendants are shared. def _buildSubtree(self,seedNode): node=seedNode.copy() diff --git a/src/sgfParser/node.py b/src/sgfParser/node.py --- a/src/sgfParser/node.py +++ b/src/sgfParser/node.py @@ -68,3 +68,29 @@ class Node: def getProperty(self,name): if name in self.properties: return self.properties[name] else: return None + + ## Returns textual representation of the Node itself, but disregards its children. + def __str__(self): + return ";" + "".join(str(p) for p in self.properties.values()) + + def export(self): + # there is a beatiful recursive solution, which this stack is too narrow to contain + stack=[(self,1,1)] + output=[] + + while len(stack)>0: + node,left,right=stack.pop() + if left>0: output.append("("*left) + output.append(str(node)) + + childCount=len(node.children) + if childCount==0: # a leaf + output.append(")"*right) + elif childCount==1: # a line + stack.append((node.children[0],0,right)) + else: # a branching node + # first child pops first, last child closes parent's parentheses + children=zip(node.children,[1]*childCount,[1]*(childCount-1)+[1+right]) + stack.extend(reversed(children)) + + return "".join(output) diff --git a/src/sgfParser/property.py b/src/sgfParser/property.py --- a/src/sgfParser/property.py +++ b/src/sgfParser/property.py @@ -5,6 +5,29 @@ GAME_INFO=1 UNKNOWN=99 +class Composed: + def __init__(self,a=None,b=None): + self.a=a + self.b=b + + def __str__(self): + return "{0}:{1}".format(self.a,self.b) + + +class Point: + def __init__(self,c,r): + self.r=r + self.c=c + + def __iter__(self): + yield self.c + yield self.r + + def __str__(self): + a=ord("a") + return chr(a+self.c)+chr(a+self.r) + + def choose(*vTypes): def f(s,start): for vType in vTypes: @@ -45,7 +68,7 @@ def compose(vTypeA,vTypeB): if a==None or s[i]!=":": return (start,None) i,b=vTypeB(s,i+1) if b==None: return start,None - return (i,(a,b)) + return (i,Composed(a,b)) return f @@ -126,9 +149,9 @@ def point(s,start): return (m.end(),tuple()) col=m.group(0)[0] row=m.group(0)[1] - col=ord(col) - (ord("a") if "a"<=col<="z" else ord("A")-26) - row=ord(row) - (ord("a") if "a"<=row<="z" else ord("A")-26) - return (m.end(),(col,row)) + col=ord(col)-(ord("a") if "a"<=col<="z" else ord("A")-26) + row=ord(row)-(ord("a") if "a"<=row<="z" else ord("A")-26) + return (m.end(),Point(col,row)) move=point stone=point @@ -177,10 +200,13 @@ class Property: def copy(self): res=Property() res.name=self.name - # !! wouldn't work for listOf(listOf()) res.value=self.value if not isinstance(self.value,list) else self.value[:] return res + def __str__(self): + val="[{0}]".format(self.value) if not isinstance(self.value,list) else "".join("[{0}]".format(x) for x in self.value) + return "{0}{1}".format(self.name,val) + patterns={ "B":singleton(move), "KO":singleton(empty), diff --git a/src/tests/testSgfParser.py b/src/tests/testSgfParser.py --- a/src/tests/testSgfParser.py +++ b/src/tests/testSgfParser.py @@ -29,6 +29,8 @@ class TestCollection(TestCase): games=list(c.listGames()) self.assertEqual(len(games),2) + self.assertRegex(games[0].export(), r"^\(;B\[aa];(PB\[Some Black]|PW\[Some White]|W\[ab]){3};B\[ac]\)$") + self.assertRegex(games[1].export(), r"^\(;B\[aa];(PB\[Other Black]|PW\[Other White]|W\[bb]){3}\)$") def testEmptySgf(self): Collection("(;)")