# HG changeset patch # User Laman # Date 2019-10-14 22:36:58 # Node ID e3e6dfbb44f64da25e9df2284338ba957991fa43 # Parent afb861f616bf6dbaf5cbdfa4d37fbca4b7191843 date property, minor tweaks diff --git a/src/diana/sgfParser/node.py b/src/diana/sgfParser/node.py --- a/src/diana/sgfParser/node.py +++ b/src/diana/sgfParser/node.py @@ -98,18 +98,18 @@ class Node: 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 + # there is a beautiful 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)) + output.append(str(node)+"\n") childCount=len(node.children) if childCount==0: # a leaf - output.append(")"*right) + output.append(")"*right+"\n") elif childCount==1: # a line stack.append((node.children[0],0,right)) else: # a branching node diff --git a/src/diana/sgfParser/property.py b/src/diana/sgfParser/property.py --- a/src/diana/sgfParser/property.py +++ b/src/diana/sgfParser/property.py @@ -1,4 +1,5 @@ import re +from datetime import date import logging as log from .propValues import choose, singleton, listOf, compose, number, real, double, color, text, empty, anything, point, move, stone @@ -8,6 +9,10 @@ GAME_INFO=1 UNKNOWN=99 +class DateException(Exception): + pass + + class Property: identRegexp=re.compile(r"[A-Z]+") @@ -32,6 +37,8 @@ class Property: i,x=choose(listOf(anything), singleton(anything))(s,i) res.name="_"+res.name res.value=x + if res.name=="DT": + res=DateProperty(x) i=skipWhitespace(s,i) return (i,res) @@ -46,8 +53,7 @@ class Property: if name in Property.patterns: return Property.patterns[name](s,start) else: - # !! raise or log or ignore - # print('warning, unknown property "{0}" at position {1}'.format(name,start)) + log.info("unknown property %s at position %d",name,start) return choose(listOf(anything), singleton(anything))(s,start) @property @@ -63,8 +69,9 @@ class Property: return res def __str__(self): + name=self.name.lstrip("_") 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) + return "{0}{1}".format(name,val) patterns={ "B":singleton(move), @@ -139,4 +146,42 @@ class Property: } -# !! TODO: date +class DateProperty(Property): + def __init__(self,value): + super().__init__() + self.name="DT" + self.value=[] + self.rawValue=value + self.parse(value) + + def parse(self,s): + regexp=re.compile(r"\d{4}(-\d\d){0,2}(,(\d{4}(-\d\d){0,2}|\d\d(-\d\d)?))*") + match=re.search(regexp,s) + if not match: + raise DateException('Could not parse a DT value: "{0}"'.format(s)) + substr=match.group(0) + dateStrs=substr.split(",") + dates=[] + prevFormat=None + + for s in dateStrs: + try: + (prevFormat,d)=DateProperty.parseSingle(s,prevFormat,dates[-1] if dates else None) + except ValueError: + raise DateException('Could not parse a DT value: "{0}"'.format(s)) + dates.append(d) + self.value=dates + + @staticmethod + def parseSingle(dateStr,prevFormat,prev=None): + tokens=dateStr.split("-") + num_tokens=list(map(int,tokens)) + if len(tokens)==3: + return ("YMD",date(*num_tokens)) + elif len(tokens)==2: + if len(tokens[0])==4: return ("YM",date(*num_tokens,1)) + else: return ("MD",date(prev.year,*num_tokens)) + else: + if len(tokens[0])==4: return ("Y",date(*num_tokens,1,1)) + elif prevFormat in ("YM","M"): return ("M",date(prev.year,*num_tokens,1)) + else: return ("D",date(prev.year,prev.month,*num_tokens)) diff --git a/src/diana/tests/testSgfParser.py b/src/diana/tests/testSgfParser.py --- a/src/diana/tests/testSgfParser.py +++ b/src/diana/tests/testSgfParser.py @@ -1,11 +1,12 @@ from itertools import chain +from datetime import date import unittest from unittest import TestCase import os from sgfParser import strRowCol from sgfParser.collection import Collection -from sgfParser.property import Property +from sgfParser.property import Property,DateProperty,DateException from sgfParser.propValues import text,compose @@ -54,6 +55,29 @@ ghi]""" self.assertEqual(str(parsed[1]), "abc:def") +class TestDateProperty(TestCase): + def testSingle(self): + self.assertEqual(DateProperty.parseSingle("2019","Y")[1], date(2019,1,1)) + self.assertEqual(DateProperty.parseSingle("2019-06","YM")[1], date(2019,6,1)) + self.assertEqual(DateProperty.parseSingle("2019-06-22","YMD")[1], date(2019,6,22)) + d=date(2019,6,21) + self.assertEqual(DateProperty.parseSingle("22","D",d)[1], date(2019,6,22)) + self.assertEqual(DateProperty.parseSingle("07-22","MD",d)[1], date(2019,7,22)) + self.assertEqual(DateProperty.parseSingle("2020-07-22","YMD",d)[1], date(2020,7,22)) + with self.assertRaises(ValueError): + DateProperty.parseSingle("2019-31","YMD") + + def testParse(self): + self.assertEqual(DateProperty("1996-05,06").value, [date(1996,5,1),date(1996,6,1)]) + self.assertEqual(DateProperty("1996-05-06,07,08").value, [date(1996,5,6),date(1996,5,7),date(1996,5,8)]) + self.assertEqual(DateProperty("1996,1997").value, [date(1996,1,1),date(1997,1,1)]) + self.assertEqual(DateProperty("1996-12-27,28,1997-01-03,04").value, [date(1996,12,27),date(1996,12,28),date(1997,1,3),date(1997,1,4)]) + self.assertEqual(DateProperty("1997-05-05,1997-05-06").value, [date(1997,5,5),date(1997,5,6)]) + self.assertEqual(DateProperty("Published on 1997-05-06").value, [date(1997,5,6)]) + with self.assertRaises(DateException): + DateProperty("unknown") + + class TestCollection(TestCase): def testSubtrees(self): c=Collection(""" @@ -64,8 +88,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}\)$") + self.assertRegex(games[0].export(), r"^\(;B\[aa]\n;(PB\[Some Black]|PW\[Some White]|W\[ab]){3}\n;B\[ac]\n\)\n$") + self.assertRegex(games[1].export(), r"^\(;B\[aa]\n;(PB\[Other Black]|PW\[Other White]|W\[bb]){3}\n\)\n$") def testEmptySgf(self): Collection("(;)")