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

Revision 62, 15.8 kB (checked in by ug, 5 years ago)

Create encoding property for class Cursor

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):
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         if self.currentNode().has_key('CA') and self.currentNode()['CA']:
200             self.encoding = self.currentNode()['CA'][0]
201         else:
202             self.encoding = ''
203
204        
205     def setFlags(self):
206         if self.currentN.next: self.atEnd = 0
207         else: self.atEnd = 1
208         if self.currentN.previous: self.atStart = 0
209         else: self.atStart = 1
210        
211     def noChildren(self):
212         return self.currentN.numChildren
213
214     def currentNode(self):
215         if not self.currentN.parsed:
216             self.currentN.parseNode()
217         return self.currentN.data
218
219
220
221     def parse(self, sgf):
222
223         curr = self.root
224        
225         p = -1           # start of the currently parsed node
226         c = []           # list of nodes from which variations started
227         last = ')'       # type of last aprsed bracked ('(' or ')')
228         inbrackets = 0   # are the currently parsed characters in []'s?
229
230         height_previous = 0
231         width_currentVar = 0
232
233         i = 0 # current parser position
234
235         # skip everything before first (; :
236
237         match = reGameStart.search(sgf, i)
238         if not match:
239             raise SGFError('No game found')
240
241         i = match.start()
242        
243         while i < len(sgf):
244
245             match = reRelevant.search(sgf, i)
246             if not match:
247                 break
248             i = match.end() - 1
249            
250             if inbrackets:
251                 if sgf[i]==']':
252                     numberBackslashes = 0
253                     j = i-1
254                     while sgf[j] == '\\':
255                         numberBackslashes += 1
256                         j -= 1
257                     if not (numberBackslashes % 2):
258                         inbrackets = 0
259                 i = i + 1
260                 continue
261
262             if sgf[i] == '[':
263                 inbrackets = 1
264        
265             if sgf[i] == '(':
266                 if last != ')':       # start of first variation of previous node
267                     if p != -1: curr.SGFstring = sgf[p:i]
268
269                 nn = Node()
270                 nn.previous = curr
271
272                 width_currentVar += 1
273                 if width_currentVar > self.width: self.width = width_currentVar
274                
275                 if curr.next:
276                     last = curr.next
277                     while last.down: last = last.down
278                     nn.up = last
279                     last.down = nn
280                     nn.level = last.level + 1
281                     self.height += 1
282                     nn.posyD = self.height - height_previous
283                 else:
284                     curr.next = nn
285                     nn.posyD = 0
286                     height_previous = self.height
287
288                 curr.numChildren += 1
289                
290                 c.append((curr, width_currentVar-1, self.height))
291                
292                 curr = nn
293                
294                 p = -1
295                 last = '('
296        
297             if sgf[i] == ')':
298                 if last != ')' and p != -1:
299                     curr.SGFstring = sgf[p:i]
300                 try:
301                     curr, width_currentVar, height_previous = c.pop()
302                 except IndexError:
303                     raise SGFError('Game tree parse error')
304                 last = ')'
305
306             if sgf[i] == ';':
307                 if p != -1:
308                     curr.SGFstring = sgf[p:i]
309                     nn = Node()
310                     nn.previous = curr
311                     width_currentVar += 1
312                     if width_currentVar > self.width: self.width = width_currentVar
313                     nn.posyD = 0
314                     curr.next = nn
315                     curr.numChildren = 1
316                     curr = nn
317                 p = i
318
319             i = i + 1
320
321         if inbrackets or c:
322             raise SGFError('Game tree parse error')
323
324         n = curr.next
325         n.previous = None
326         n.up = None
327
328         while n.down:
329             n = n.down
330             n.previous = None
331
332
333     def game(self, n):
334         if n < self.root.numChildren:
335             self.posx = 0
336             self.posy = 0
337             self.currentN = self.root.next
338             for i in range(n): self.currentN = self.currentN.down
339             self.setFlags()
340         else:
341             raise SGFError('Game not found')
342
343
344     def delVariation(self, c):
345
346         if c.previous:
347             self.delVar(c)
348         else:
349             if c.next:
350                 node = c.next
351                 while node.down:
352                     node = node.down
353                     self.delVar(node.up)
354      
355                 self.delVar(node)
356    
357             c.next = None
358
359         self.setFlags()
360        
361
362     def delVar(self, node):
363         if node.up: node.up.down = node.down
364         else: node.previous.next = node.down
365  
366         if node.down:
367             node.down.up = node.up
368             node.down.posyD = node.posyD
369             n = node.down
370             while n:
371                 n.level -= 1
372                 n = n.down
373
374         h = 0
375         n = node
376         while n.next:
377             n = n.next
378             while n.down:
379                 n = n.down
380                 h += n.posyD
381
382         if node.up or node.down: h += 1
383
384         p = node.previous
385         p.numChildren -= 1
386
387         while p:
388             if p.down: p.down.posyD -= h
389             p = p.previous
390  
391
392         self.height -= h
393
394
395
396
397     def add(self, st):
398         node = Node(self.currentN,st,0)
399
400         node.down = None
401         node.next = None
402         node.numChildren = 0
403
404         if not self.currentN.next:
405             node.level = 0
406             node.posyD = 0
407             node.up = 0
408
409             self.currentN.next = node
410             self.currentN.numChildren = 1
411         else:
412             n = self.currentN.next
413             while n.down:
414                 n = n.down
415                 self.posy += n.posyD
416                
417    
418             n.down = node
419             node.up = n
420             node.level = n.level + 1
421             node.next = None
422             self.currentN.numChildren += 1
423
424             node.posyD = 1
425             while n.next:
426                 n = n.next
427                 while n.down:
428                     n = n.down
429                     node.posyD += n.posyD
430      
431             self.posy += node.posyD
432
433             self.height += 1
434
435             n = node
436             while n.previous:
437                 n = n.previous
438                 if n.down: n.down.posyD += 1
439
440         self.currentN = node
441
442         self.posx += 1
443         self.setFlags()
444
445         if self.posx > self.width: self.width += 1
446
447
448
449
450
451
452
453     def next(self, n=0):
454         if n >= self.noChildren():
455             raise SGFError('Variation not found')
456
457         self.posx += 1
458
459         self.currentN = self.currentN.next
460         for i in range(n):
461             self.currentN = self.currentN.down
462             self.posy += self.currentN.posyD
463         self.setFlags()
464         return self.currentNode()
465    
466     def previous(self):
467         if self.currentN.previous:
468             while self.currentN.up:
469                 self.posy -= self.currentN.posyD
470                 self.currentN = self.currentN.up
471             self.currentN = self.currentN.previous
472             self.posx -= 1
473         else: raise SGFError('No previous node')
474         self.setFlags()
475         return self.currentNode()
476
477     def getRootNode(self, n):
478         if not self.root: return
479         if n >= self.root.numChildren: raise SGFError('Game not found')
480
481         nn = self.root.next
482         for i in range(n): nn = nn.down
483
484         if not nn.parsed: nn.parseNode()
485
486         return nn.data
487
488
489     def updateCurrentNode(self):
490         """ Put the data in self.currentNode into the corresponding string in self.collection.
491         This will be called from an application which may have modified self.currentNode."""
492
493         self.currentN.SGFstring = self.nodeToString(self.currentN.data)
494        
495
496
497
498     def updateRootNode(self, data, n=0):
499         if n >= self.root.numChildren:
500             raise SGFError('Game not found')
501
502         nn = self.root.next
503         for i in range(n): nn = nn.down
504        
505         nn.SGFstring = self.rootNodeToString(data)
506         nn.parsed = 0
507         nn.parseNode()
508
509
510     def rootNodeToString(self, node):
511        
512         result = [';']
513         keylist = ['GM', 'FF', 'SZ', 'PW', 'WR', 'PB', 'BR',
514                    'EV', 'RO', 'DT', 'PC', 'KM', 'RE', 'US', 'GC']
515         for key in keylist:
516             if node.has_key(key):
517                 result.append(key)
518                 result.append('[' + SGFescape(node[key][0]) + ']\n')
519                
520         l = 0
521         for key in node.keys():
522             if not key in keylist:
523                 result.append(key)
524                 l += len(key)
525                 for item in node[key]:
526                     result.append('[' + SGFescape(item) + ']\n')
527                     l += len(item) + 2
528                     if l > 72:
529                         result.append('\n')
530                         l = 0
531                        
532         return string.join(result, '')
533
534     def nodeToString(self, node):
535         l = 0
536         result = [';']
537         for k in node.keys():
538             if l + len(k) > 72:
539                 result.append('\n')
540                 l = 0
541             if not node[k]: continue
542             result.append(k)
543             l += len(k)
544             for item in node[k]:
545                 if l + len(item) > 72:
546                     result.append('\n')
547                     l = 0
548                 l += len(item) + 2
549                 result.append('[' + SGFescape(item) + ']')
550
551         return string.join(result, '')
552
553
554     def outputVar(self, node):
555
556         result = []
557        
558         result.append(node.SGFstring)
559
560         while node.next:
561             node = node.next
562
563             if node.down:
564                 while node.down:
565                     result.append('(' + self.outputVar(node) + ')' )
566                     node = node.down
567
568                 result.append('(' + self.outputVar(node) + ')' )
569                 return string.join(result, '')
570
571             else:
572                 result.append(node.SGFstring)
573
574         return string.join(result, '')
575
576
577
578     def output(self):
579         result = []
580
581         n = self.root.next
582
583         while n:
584             result.append('(' + self.outputVar(n)+ ')\n')
585             n = n.down
586
587         return string.join(result, '')
588
589
Note: See TracBrowser for help on using the browser.