root/04/release-0.4e/sgfparser.py

Revision 21, 11.0 kB (checked in by ug, 5 years ago)

Import release 0.4d

Line 
1 import re
2 import string
3
4 try:
5     import sgfparserC
6     CimportOK = 1
7 except:
8     CimportOK = 0
9    
10 class SGFError(Exception): pass
11
12 reGameStart = re.compile(r'\(\s*;')
13 reRelevant = re.compile(r'[\[\]\(\);]')
14 reStartOfNode = re.compile(r'\s*;\s*')
15
16 def SGFescape(s):
17     t = string.replace(s, '\\', '\\\\')
18     t = string.replace(t, ']', '\\]')
19     return t
20
21 class Cursor:
22
23     """ Initialized with an SGF file. Then game(n); next(n), previous.
24     self.collection is list of (list of lists)
25     self.currentPosition is tuple (current variation,
26                                    no. of node (w.r.t current list))
27     self.parseNode gives dictionary { ID : list of values }, where value can be a string or a dict.
28     self.currentNode is the node at current position
29
30     The sloppy option for __init__ determines if the following things, which are not allowed
31     according to the SGF spec, are accepted nevertheless:
32      - multiple occurrences of a tag in one node
33      - line breaks in AB[[]/AW[]/B[]/W[] tags (i.e. "B[a\nb]")
34     """
35
36     def __init__(self, sgf, sloppy = 1):
37         self.sloppy = sloppy
38        
39         self.collection = self.parse(sgf)
40         self.currentPosition = (self.collection[0], 0) # beginning of first game
41         self.currentNode = self.parseNode(self.currentPosition)
42         self.oldIndices = []
43         self.setFlags()
44        
45
46     def setFlags(self):
47         if not self.noChildren(): self.atEnd = 1
48         else: self.atEnd = 0
49         if self.currentPosition[1] == 0 and not self.oldIndices: self.atStart = 1
50         else: self.atStart = 0
51        
52     def noChildren(self):
53         if not self.collection: return -1
54
55         if len(self.currentPosition[0]) <= self.currentPosition[1]+1: return 0
56         if type(self.currentPosition[0][self.currentPosition[1]+1]) == type([]):
57             return len(self.currentPosition[0][self.currentPosition[1]+1])
58         else: return 1
59
60        
61     def parse(self, sgf):
62
63         if CimportOK:
64             return sgfparserC.parseC(sgf, SGFError)
65
66         current = []
67         p = -1
68         c = []
69         last = ')'
70         inbrackets = 0
71
72         i = 0
73
74         # skip everything before first (; :
75
76         match = reGameStart.search(sgf, i)
77         if not match:
78             self.collection = []
79             return
80         i = match.start()
81        
82         while i < len(sgf):
83
84             match = reRelevant.search(sgf, i)
85             if not match:
86                 break
87             i = match.end() - 1
88            
89             if inbrackets:
90                 if sgf[i]==']':
91                     numberBackslashes = 0
92                     j = i-1
93                     while sgf[j] == '\\':
94                         numberBackslashes += 1
95                         j -= 1
96                     if not (numberBackslashes % 2):
97                         inbrackets = 0
98                 i = i + 1
99                 continue
100
101             if sgf[i] == '[':
102                 inbrackets = 1
103        
104             if sgf[i] == '(':
105                 if last != ')':
106                     if p!= -1: current.append(sgf[p:i])
107                     newlist = []
108                     current.append(newlist)
109                     current = newlist
110                    
111                 newlist = []
112                 current.append(newlist)
113                 c.append(current)
114                 current = newlist
115                
116                 p = -1
117                 last = '('
118        
119             if sgf[i] == ')':
120                 if last != ')' and p != -1: current.append(sgf[p:i])
121                 try:
122                     current = c.pop()
123                 except IndexError:
124                     raise SGFError('Game tree parse error')
125                 last = ')'
126
127             if sgf[i] == ';':
128                 if p != -1: current.append(sgf[p:i])
129                 p = i
130
131             i = i + 1
132
133         if inbrackets or c:
134             raise SGFError('Game tree parse error')
135
136         return current
137
138     def parseNode(self, position):
139         i = 0
140
141         try:
142             s = position[0][position[1]]
143         except:
144             raise SGFError('No node found')
145        
146         match = reStartOfNode.search(s, i)
147         if not match:
148             raise SGFError('No node found')
149         i = match.end()
150
151         node = {}
152         while i < len(s) and not s[i] == ';':
153
154             while i < len(s) and s[i] in string.whitespace: i += 1
155             if i >= len(s): break
156            
157             ID = []
158            
159             while not s[i] == '[':
160                 if s[i] in string.uppercase:
161                     ID.append(s[i])
162                 elif not s[i] in string.lowercase + string.whitespace:
163                     raise SGFError('Invalid Property ID')
164                    
165                 i += 1
166
167                 if i >= len(s):
168                     raise SGFError('Property ID does not have any value')
169
170             i += 1
171
172             key = string.join(ID, '')
173
174             if key == '': raise SGFError('Property does not have a correct ID')
175
176
177             if node.has_key(key):
178                 if not self.sloppy:
179                     raise SGFError('Multiple occurrence of SGF tag')
180             else:
181                 node[key] = []
182                
183             propertyValueList = []
184             while 1:
185                 propValue = []
186                 while s[i] != ']':
187                     if s[i] == '\t':      # convert whitespace to ' '
188                         propValue.append(' ')
189                         i += 1
190                         continue
191                     if s[i] == '\\':
192                         i += 1            # ignore escaped characters, throw away backslash
193                         if s[i:i+2] in ['\n\r', '\r\n']:
194                             i += 2
195                             continue
196                         elif s[i] in ['\n', '\r']:
197                             i += 1
198                             continue
199                     propValue.append(s[i])
200                     i += 1
201                    
202                     if i >= len(s):
203                         raise SGFError('Property value does not end')
204
205                 propertyValueList.append(string.join(propValue, ''))
206
207                 i += 1
208                        
209                 while i < len(s) and s[i] in string.whitespace:
210                     i += 1
211                    
212                 if i >= len(s) or s[i] != '[': break   
213                 else: i += 1
214
215             if key in ['B', 'W', 'AB', 'AW']:
216                 for N in range(len(propertyValueList)):
217                     en = propertyValueList[N]
218                     if self.sloppy:
219                         en = string.replace(en, '\n', '')
220                         en = string.replace(en, '\r', '')
221                     if not (len(en) == 2 or (len(en) == 0 and key in ['B', 'W'])):
222                         raise SGFError
223                     propertyValueList[N] = en
224                                            
225             node[key].extend(propertyValueList)
226            
227         return node
228
229
230     def game(self, n):
231         if n < len(self.collection):
232             self.currentPosition = (self.collection[n], 0)
233             self.currentNode = self.parseNode(self.currentPosition)
234             self.oldIndices = []
235             self.setFlags()
236         else:
237             raise SGFError('Game not found')
238
239     def next(self, n=0):
240         if n >= self.noChildren():
241             raise SGFError('Variation not found')
242
243         if self.currentPosition[1] + 1 == len(self.currentPosition[0]) - 1 \
244            and type(self.currentPosition[0][-1]) == type([]):
245             self.oldIndices.append(self.currentPosition[0]) 
246             self.currentPosition = (self.currentPosition[0][-1][n], 0)
247         else:
248             self.currentPosition = (self.currentPosition[0], self.currentPosition[1]+1)
249
250         try:
251             self.currentNode = self.parseNode(self.currentPosition)
252         except SGFError:
253             if self.currentPosition[1]:
254                 self.currentPosition = (self.currentPosition[0], self.currentPosition[1]-1)
255             else:
256                 c = self.oldIndices.pop()
257                 self.currentPosition = (c, len(c)-2)
258             raise SGFError('SGF Parsing Error')
259        
260         self.setFlags()
261         return self.currentNode
262    
263     def previous(self):
264         if self.currentPosition[1]:
265             self.currentPosition = (self.currentPosition[0], self.currentPosition[1]-1)
266         else:
267             if not self.oldIndices:
268                 raise SGFError('No previous node')
269             c = self.oldIndices.pop()
270             self.currentPosition = (c, len(c)-2)
271
272         self.currentNode = self.parseNode(self.currentPosition)
273         self.setFlags()
274         return self.currentNode
275
276     def getRootNode(self):
277         if not self.collection: return None
278         rootNode = self.parseNode((self.collection[0], 0))
279         return rootNode
280
281
282     def updateCurrentNode(self):
283         """ Put the data in self.currentNode into the corresponding string in self.collection.
284         This will be called from an application which may have modified self.currentNode."""
285
286         self.currentPosition[0][self.currentPosition[1]] = self.nodeToString(self.currentNode)
287
288     def updateRootNode(self, data, n=0):
289         if n >= len(self.collection):
290             raise SGFError('Game not found')
291         self.collection[n][0] = self.rootNodeToString(data)
292
293
294     def rootNodeToString(self, node):
295        
296         result = [';']
297         keylist = ['GM', 'FF', 'SZ', 'PW', 'WR', 'PB', 'BR',
298                    'EV', 'RO', 'DT', 'PC', 'KM', 'RE', 'US', 'GC']
299         for key in keylist:
300             if node.has_key(key):
301                 result.append(key)
302                 result.append('[' + SGFescape(node[key][0]) + ']\n')
303                
304         l = 0
305         for key in node.keys():
306             if not key in keylist:
307                 result.append(key)
308                 l += len(key)
309                 for item in node[key]:
310                     result.append('[' + SGFescape(item) + ']\n')
311                     l += len(item) + 2
312                     if l > 72:
313                         result.append('\n')
314                         l = 0
315                        
316         return string.join(result, '')
317
318     def nodeToString(self, node):
319         l = 0
320         result = [';']
321         for k in node.keys():
322             if l + len(k) > 72:
323                 result.append('\n')
324                 l = 0
325             result.append(k)
326             l += len(k)
327             for item in node[k]:
328                 if l + len(item) > 72:
329                     result.append('\n')
330                     l = 0
331                 l += len(item) + 2
332                 result.append('[' + SGFescape(item) + ']')
333
334         return string.join(result, '')
335
336
337     def variationToString(self, variation, l):
338         result = []
339         # if l + len(variation[0]) > 72:
340
341         for item in variation[:-1]:
342             result.append(item)
343
344         if type(variation[-1]) != type([]):
345             result.append(variation[-1])
346         else:
347             for v in variation[-1]:
348                 result.append('(' + self.variationToString(v, 0) + ')\n')
349
350         return string.join(result, '')
351
352
353     def output(self):
354         result = []
355         for i in self.collection:
356             result.append('(' + self.variationToString(i, 0)+ ')\n')
357
358         return string.join(result, '')
359
360
Note: See TracBrowser for help on using the browser.