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("(;)")