root/05/release-0.5g/sgfparser.py

Revision 1, 15.6 kB (checked in by ugz, 5 years ago)

Initial repository layout

Line 
1 # File: sgfparser.py
2
3 ##   Copyright (C) 2001-3 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         self.setFlags()
198        
199     def setFlags(self):
200         if self.currentN.next: self.atEnd = 0
201         else: self.atEnd = 1
202         if self.currentN.previous: self.atStart = 0
203         else: self.atStart = 1
204        
205     def noChildren(self):
206         return self.currentN.numChildren
207
208     def currentNode(self):
209         if not self.currentN.parsed:
210             self.currentN.parseNode()
211         return self.currentN.data
212
213
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             self.setFlags()
334         else:
335             raise SGFError('Game not found')
336
337
338     def delVariation(self, c):
339
340         if c.previous:
341             self.delVar(c)
342         else:
343             if c.next:
344                 node = c.next
345                 while node.down:
346                     node = node.down
347                     self.delVar(node.up)
348      
349                 self.delVar(node)
350    
351             c.next = None
352
353         self.setFlags()
354        
355
356     def delVar(self, node):
357         if node.up: node.up.down = node.down
358         else: node.previous.next = node.down
359  
360         if node.down:
361             node.down.up = node.up
362             node.down.posyD = node.posyD
363             n = node.down
364             while n:
365                 n.level -= 1
366                 n = n.down
367
368         h = 0
369         n = node
370         while n.next:
371             n = n.next
372             while n.down:
373                 n = n.down
374                 h += n.posyD
375
376         if node.up or node.down: h += 1
377
378         p = node.previous
379         p.numChildren -= 1
380
381         while p:
382             if p.down: p.down.posyD -= h
383             p = p.previous
384  
385
386         self.height -= h
387
388
389
390
391     def add(self, st):
392         node = Node(self.currentN,st,0)
393
394         node.down = None
395         node.next = None
396         node.numChildren = 0
397
398         if not self.currentN.next:
399             node.level = 0
400             node.posyD = 0
401             node.up = 0
402
403             self.currentN.next = node
404             self.currentN.numChildren = 1
405         else:
406             n = self.currentN.next
407             while n.down:
408                 n = n.down
409                 self.posy += n.posyD
410                
411    
412             n.down = node
413             node.up = n
414             node.level = n.level + 1
415             node.next = None
416             self.currentN.numChildren += 1
417
418             node.posyD = 1
419             while n.next:
420                 n = n.next
421                 while n.down:
422                     n = n.down
423                     node.posyD += n.posyD
424      
425             self.posy += node.posyD
426
427             self.height += 1
428
429             n = node
430             while n.previous:
431                 n = n.previous
432                 if n.down: n.down.posyD += 1
433
434         self.currentN = node
435
436         self.posx += 1
437         self.setFlags()
438
439         if self.posx > self.width: self.width += 1
440
441
442
443
444
445
446
447     def next(self, n=0):
448         if n >= self.noChildren():
449             raise SGFError('Variation not found')
450
451         self.posx += 1
452
453         self.currentN = self.currentN.next
454         for i in range(n):
455             self.currentN = self.currentN.down
456             self.posy += self.currentN.posyD
457         self.setFlags()
458         return self.currentNode()
459    
460     def previous(self):
461         if self.currentN.previous:
462             while self.currentN.up:
463                 self.posy -= self.currentN.posyD
464                 self.currentN = self.currentN.up
465             self.currentN = self.currentN.previous
466             self.posx -= 1
467         else: raise SGFError('No previous node')
468         self.setFlags()
469         return self.currentNode()
470
471     def getRootNode(self, n):
472         if not self.root: return
473         if n >= self.root.numChildren: raise SGFError('Game not found')
474
475         nn = self.root.next
476         for i in range(n): nn = nn.down
477
478         if not nn.parsed: nn.parseNode()
479
480         return nn.data
481
482
483     def updateCurrentNode(self):
484         """ Put the data in self.currentNode into the corresponding string in self.collection.
485         This will be called from an application which may have modified self.currentNode."""
486
487         self.currentN.SGFstring = self.nodeToString(self.currentN.data)
488        
489
490
491
492     def updateRootNode(self, data, n=0):
493         if n >= self.root.numChildren:
494             raise SGFError('Game not found')
495
496         nn = self.root.next
497         for i in range(n): nn = nn.down
498        
499         nn.SGFstring = self.rootNodeToString(data)
500         nn.parsed = 0
501         nn.parseNode()
502
503
504     def rootNodeToString(self, node):
505        
506         result = [';']
507         keylist = ['GM', 'FF', 'SZ', 'PW', 'WR', 'PB', 'BR',
508                    'EV', 'RO', 'DT', 'PC', 'KM', 'RE', 'US', 'GC']
509         for key in keylist:
510             if node.has_key(key):
511                 result.append(key)
512                 result.append('[' + SGFescape(node[key][0]) + ']\n')
513                
514         l = 0
515         for key in node.keys():
516             if not key in keylist:
517                 result.append(key)
518                 l += len(key)
519                 for item in node[key]:
520                     result.append('[' + SGFescape(item) + ']\n')
521                     l += len(item) + 2
522                     if l > 72:
523                         result.append('\n')
524                         l = 0
525                        
526         return string.join(result, '')
527
528     def nodeToString(self, node):
529         l = 0
530         result = [';']
531         for k in node.keys():
532             if l + len(k) > 72:
533                 result.append('\n')
534                 l = 0
535             if not node[k]: continue
536             result.append(k)
537             l += len(k)
538             for item in node[k]:
539                 if l + len(item) > 72:
540                     result.append('\n')
541                     l = 0
542                 l += len(item) + 2
543                 result.append('[' + SGFescape(item) + ']')
544
545         return string.join(result, '')
546
547
548     def outputVar(self, node):
549
550         result = []
551        
552         result.append(node.SGFstring)
553
554         while node.next:
555             node = node.next
556
557             if node.down:
558                 while node.down:
559                     result.append('(' + self.outputVar(node) + ')' )
560                     node = node.down
561
562                 result.append('(' + self.outputVar(node) + ')' )
563                 return string.join(result, '')
564
565             else:
566                 result.append(node.SGFstring)
567
568         return string.join(result, '')
569
570
571
572     def output(self):
573         result = []
574
575         n = self.root.next
576
577         while n:
578             result.append('(' + self.outputVar(n)+ ')\n')
579             n = n.down
580
581         return string.join(result, '')
582
583
Note: See TracBrowser for help on using the browser.