root/06/devel-old/sgfparser.py

Revision 175, 15.5 kB (checked in by ug, 2 years ago)

Done some more work on pattern.cc; seems to work now.

Line 
1 # File: sgfparser.py
2
3 ##   Copyright (C) 2001-4 Ulrich Goertz (u@g0ertz.de)
4
5 ##   This is part of Kombilo, a go database program.
6
7 ##   This program is free software; you can redistribute it and/or modify
8 ##   it under the terms of the GNU General Public License as published by
9 ##   the Free Software Foundation; either version 2 of the License, or
10 ##   (at your option) any later version.
11
12 ##   This program is distributed in the hope that it will be useful,
13 ##   but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ##   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 ##   GNU General Public License for more details.
16
17 ##   You should have received a copy of the GNU General Public License
18 ##   along with this program (see doc/license.txt); if not, write to the Free Software
19 ##   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20 ##   The GNU GPL is also currently available at
21 ##   http://www.gnu.org/copyleft/gpl.html
22
23
24 import re
25 import string
26
27 class SGFError(Exception): pass
28
29 reGameStart = re.compile(r'\(\s*;')
30 reRelevant = re.compile(r'[\[\]\(\);]')
31 reStartOfNode = re.compile(r'\s*;\s*')
32
33 def SGFescape(s):
34     t = string.replace(s, '\\', '\\\\')
35     t = string.replace(t, ']', '\\]')
36     return t
37
38 class Node:
39     def __init__(self, previous=None, SGFstring = '', level=0):
40         self.previous = previous
41         self.next = None
42         self.up = None
43         self.down = None
44         self.level = level # self == self.previous.next[self.level]
45
46         self.numChildren = 0
47        
48         self.SGFstring = SGFstring
49         self.parsed = 0
50         if self.SGFstring:
51             self.parseNode()
52         else:
53             self.data = {}
54
55         self.posyD = 0
56
57
58     def getData(self):
59         if not self.parsed: self.parseNode()
60         return self.data
61
62     def pathToNode(self):
63         l = []
64         n = self
65
66         while n.previous:
67             l.append(n.level)
68             n = n.previous
69            
70         l.reverse()
71         return l
72
73
74     def parseNode(self):
75
76         if self.parsed: return
77
78         s = self.SGFstring
79         i = 0
80
81         match = reStartOfNode.search(s, i)
82         if not match:
83             raise SGFError('No node found')
84         i = match.end()
85
86         node = {}
87         while i < len(s):
88
89             while i < len(s) and s[i] in string.whitespace: i += 1
90             if i >= len(s): break
91            
92             ID = []
93            
94             while not s[i] == '[':
95                 if s[i] in string.uppercase:
96                     ID.append(s[i])
97                 elif not s[i] in string.lowercase + string.whitespace:
98                     raise SGFError('Invalid Property ID')
99                    
100                 i += 1
101
102                 if i >= len(s):
103                     raise SGFError('Property ID does not have any value')
104
105             i += 1
106
107             key = string.join(ID, '')
108
109             if key == '': raise SGFError('Property does not have a correct ID')
110
111
112             if node.has_key(key):
113                 if not Node.sloppy:
114                     raise SGFError('Multiple occurrence of SGF tag')
115             else:
116                 node[key] = []
117                
118             propertyValueList = []
119             while 1:
120                 propValue = []
121                 while s[i] != ']':
122                     if s[i] == '\t':      # convert whitespace to ' '
123                         propValue.append(' ')
124                         i += 1
125                         continue
126                     if s[i] == '\\':
127                         i += 1            # ignore escaped characters, throw away backslash
128                         if s[i:i+2] in ['\n\r', '\r\n']:
129                             i += 2
130                             continue
131                         elif s[i] in ['\n', '\r']:
132                             i += 1
133                             continue
134                     propValue.append(s[i])
135                     i += 1
136                    
137                     if i >= len(s):
138                         raise SGFError('Property value does not end')
139
140                 propertyValueList.append(string.join(propValue, ''))
141
142                 i += 1
143                        
144                 while i < len(s) and s[i] in string.whitespace:
145                     i += 1
146                    
147                 if i >= len(s) or s[i] != '[': break   
148                 else: i += 1
149
150             if key in ['B', 'W', 'AB', 'AW']:
151                 for N in range(len(propertyValueList)):
152                     en = propertyValueList[N]
153                     if Node.sloppy:
154                         en = string.replace(en, '\n', '')
155                         en = string.replace(en, '\r', '')
156                     if not (len(en) == 2 or (len(en) == 0 and key in ['B', 'W'])):
157                         raise SGFError('')
158                     propertyValueList[N] = en
159                                            
160             node[key].extend(propertyValueList)
161            
162         self.data = node
163         self.parsed = 1
164
165
166 Node.sloppy = 1
167
168 # ------------------------------------------------------------------------------------
169
170 class Cursor:
171
172     """ Initialized with an SGF file. Then use game(n); next(n), previous to navigate.
173     self.collection is list of Nodes, namely of the root nodes of the game trees.
174     
175     self.currentN is the current Node
176     self.currentNode() returns self.currentN.data
177
178     The sloppy option for __init__ determines if the following things, which are not allowed
179     according to the SGF spec, are accepted nevertheless:
180      - multiple occurrences of a tag in one node
181      - line breaks in AB[]/AW[]/B[]/W[] tags (e.g. "B[a\nb]")
182     """
183
184
185     def __init__(self, sgf, sloppy = 1):
186         Node.sloppy = sloppy
187
188         self.height = 0
189         self.width = 0
190         self.posx = 0
191         self.posy = 0
192
193         self.root = Node(None, '', 0)
194        
195         self.parse(sgf)
196         self.currentN = self.root.next
197
198        
199     def atEnd(self):
200         if self.currentN.next: return 0
201         return 1
202
203     def atStart(self):
204         if self.currentN.previous: return 0
205         else: return 1
206        
207     def noChildren(self):
208         return self.currentN.numChildren
209
210     def currentNode(self):
211         if not self.currentN.parsed:
212             self.currentN.parseNode()
213         return self.currentN.data
214
215     def parse(self, sgf):
216
217         curr = self.root
218        
219         p = -1           # start of the currently parsed node
220         c = []           # list of nodes from which variations started
221         last = ')'       # type of last aprsed bracked ('(' or ')')
222         inbrackets = 0   # are the currently parsed characters in []'s?
223
224         height_previous = 0
225         width_currentVar = 0
226
227         i = 0 # current parser position
228
229         # skip everything before first (; :
230
231         match = reGameStart.search(sgf, i)
232         if not match:
233             raise SGFError('No game found')
234
235         i = match.start()
236        
237         while i < len(sgf):
238
239             match = reRelevant.search(sgf, i)
240             if not match:
241                 break
242             i = match.end() - 1
243            
244             if inbrackets:
245                 if sgf[i]==']':
246                     numberBackslashes = 0
247                     j = i-1
248                     while sgf[j] == '\\':
249                         numberBackslashes += 1
250                         j -= 1
251                     if not (numberBackslashes % 2):
252                         inbrackets = 0
253                 i = i + 1
254                 continue
255
256             if sgf[i] == '[':
257                 inbrackets = 1
258        
259             if sgf[i] == '(':
260                 if last != ')':       # start of first variation of previous node
261                     if p != -1: curr.SGFstring = sgf[p:i]
262
263                 nn = Node()
264                 nn.previous = curr
265
266                 width_currentVar += 1
267                 if width_currentVar > self.width: self.width = width_currentVar
268                
269                 if curr.next:
270                     last = curr.next
271                     while last.down: last = last.down
272                     nn.up = last
273                     last.down = nn
274                     nn.level = last.level + 1
275                     self.height += 1
276                     nn.posyD = self.height - height_previous
277                 else:
278                     curr.next = nn
279                     nn.posyD = 0
280                     height_previous = self.height
281
282                 curr.numChildren += 1
283                
284                 c.append((curr, width_currentVar-1, self.height))
285                
286                 curr = nn
287                
288                 p = -1
289                 last = '('
290        
291             if sgf[i] == ')':
292                 if last != ')' and p != -1:
293                     curr.SGFstring = sgf[p:i]
294                 try:
295                     curr, width_currentVar, height_previous = c.pop()
296                 except IndexError:
297                     raise SGFError('Game tree parse error')
298                 last = ')'
299
300             if sgf[i] == ';':
301                 if p != -1:
302                     curr.SGFstring = sgf[p:i]
303                     nn = Node()
304                     nn.previous = curr
305                     width_currentVar += 1
306                     if width_currentVar > self.width: self.width = width_currentVar
307                     nn.posyD = 0
308                     curr.next = nn
309                     curr.numChildren = 1
310                     curr = nn
311                 p = i
312
313             i = i + 1
314
315         if inbrackets or c:
316             raise SGFError('Game tree parse error')
317
318         n = curr.next
319         n.previous = None
320         n.up = None
321
322         while n.down:
323             n = n.down
324             n.previous = None
325
326
327     def game(self, n):
328         if n < self.root.numChildren:
329             self.posx = 0
330             self.posy = 0
331             self.currentN = self.root.next
332             for i in range(n): self.currentN = self.currentN.down
333         else:
334             raise SGFError('Game not found')
335
336
337     def delVariation(self, c):
338
339         if c.previous:
340             self.delVar(c)
341         else:
342             if c.next:
343                 node = c.next
344                 while node.down:
345                     node = node.down
346                     self.delVar(node.up)
347      
348                 self.delVar(node)
349    
350             c.next = None
351
352
353     def delVar(self, node):
354         if node.up: node.up.down = node.down
355         else: node.previous.next = node.down
356  
357         if node.down:
358             node.down.up = node.up
359             node.down.posyD = node.posyD
360             n = node.down
361             while n:
362                 n.level -= 1
363                 n = n.down
364
365         h = 0
366         n = node
367         while n.next:
368             n = n.next
369             while n.down:
370                 n = n.down
371                 h += n.posyD
372
373         if node.up or node.down: h += 1
374
375         p = node.previous
376         p.numChildren -= 1
377
378         while p:
379             if p.down: p.down.posyD -= h
380             p = p.previous
381  
382
383         self.height -= h
384
385
386     def add(self, st):
387         node = Node(self.currentN,st,0)
388
389         node.down = None
390         node.next = None
391         node.numChildren = 0
392
393         if not self.currentN.next:
394             node.level = 0
395             node.posyD = 0
396             node.up = 0
397
398             self.currentN.next = node
399             self.currentN.numChildren = 1
400         else:
401             n = self.currentN.next
402             while n.down:
403                 n = n.down
404                 self.posy += n.posyD
405                
406    
407             n.down = node
408             node.up = n
409             node.level = n.level + 1
410             node.next = None
411             self.currentN.numChildren += 1
412
413             node.posyD = 1
414             while n.next:
415                 n = n.next
416                 while n.down:
417                     n = n.down
418                     node.posyD += n.posyD
419      
420             self.posy += node.posyD
421
422             self.height += 1
423
424             n = node
425             while n.previous:
426                 n = n.previous
427                 if n.down: n.down.posyD += 1
428
429         self.currentN = node
430
431         self.posx += 1
432         if self.posx > self.width: self.width += 1
433
434
435     def next(self, n=0):
436         if n >= self.noChildren():
437             raise SGFError('Variation not found')
438
439         self.posx += 1
440
441         self.currentN = self.currentN.next
442         for i in range(n):
443             self.currentN = self.currentN.down
444             self.posy += self.currentN.posyD
445         return self.currentNode()
446    
447     def previous(self):
448         if self.currentN.previous:
449             while self.currentN.up:
450                 self.posy -= self.currentN.posyD
451                 self.currentN = self.currentN.up
452             self.currentN = self.currentN.previous
453             self.posx -= 1
454         else:
455             raise SGFError('No previous node')
456         return self.currentNode()
457
458     def getRootNode(self, n):
459         if not self.root: return
460         if n >= self.root.numChildren: raise SGFError('Game not found')
461
462         nn = self.root.next
463         for i in range(n): nn = nn.down
464
465         if not nn.parsed: nn.parseNode()
466
467         return nn.data
468
469
470     def updateCurrentNode(self):
471         """ Put the data in self.currentNode into the corresponding string in self.collection.
472         This will be called from an application which may have modified self.currentNode."""
473
474         self.currentN.SGFstring = self.nodeToString(self.currentN.data)
475        
476
477     def updateRootNode(self, data, n=0):
478         if n >= self.root.numChildren:
479             raise SGFError('Game not found')
480
481         nn = self.root.next
482         for i in range(n): nn = nn.down
483        
484         nn.SGFstring = self.rootNodeToString(data)
485         nn.parsed = 0
486         nn.parseNode()
487
488
489     def rootNodeToString(self, node):
490        
491         result = [';']
492         keylist = ['GM', 'FF', 'SZ', 'PW', 'WR', 'PB', 'BR',
493                    'EV', 'RO', 'DT', 'PC', 'KM', 'RE', 'US', 'GC']
494         for key in keylist:
495             if node.has_key(key):
496                 result.append(key)
497                 result.append('[' + SGFescape(node[key][0]) + ']\n')
498                
499         l = 0
500         for key in node.keys():
501             if not key in keylist:
502                 result.append(key)
503                 l += len(key)
504                 for item in node[key]:
505                     result.append('[' + SGFescape(item) + ']\n')
506                     l += len(item) + 2
507                     if l > 72:
508                         result.append('\n')
509                         l = 0
510                        
511         return string.join(result, '')
512
513     def nodeToString(self, node):
514         l = 0
515         result = [';']
516         for k in node.keys():
517             if l + len(k) > 72:
518                 result.append('\n')
519                 l = 0
520             if not node[k]: continue
521             result.append(k)
522             l += len(k)
523             for item in node[k]:
524                 if l + len(item) > 72:
525                     result.append('\n')
526                     l = 0
527                 l += len(item) + 2
528                 result.append('[' + SGFescape(item) + ']')
529
530         return string.join(result, '')
531
532
533     def outputVar(self, node):
534
535         result = []
536        
537         result.append(node.SGFstring)
538
539         while node.next:
540             node = node.next
541
542             if node.down:
543                 while node.down:
544                     result.append('(' + self.outputVar(node) + ')' )
545                     node = node.down
546
547                 result.append('(' + self.outputVar(node) + ')' )
548                 return string.join(result, '')
549
550             else:
551                 result.append(node.SGFstring)
552
553         return string.join(result, '')
554
555
556
557     def output(self):
558         result = []
559
560         n = self.root.next
561
562         while n:
563             result.append('(' + self.outputVar(n)+ ')\n')
564             n = n.down
565
566         return string.join(result, '')
567
568
Note: See TracBrowser for help on using the browser.