SgfParser 0.9.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +1 -0
- data/Gemfile.lock +2 -0
- data/README.rdoc +44 -38
- data/SgfParser.gemspec +17 -15
- data/VERSION +1 -1
- data/lib/sgf.rb +8 -0
- data/lib/sgf/{sgf_indent.rb → indenter.rb} +108 -108
- data/lib/sgf/{parser/node.rb → node.rb} +61 -57
- data/lib/sgf/{parser/tree_parse.rb → parser.rb} +120 -111
- data/lib/sgf/{parser/properties.rb → properties.rb} +97 -97
- data/lib/sgf/{parser/tree.rb → tree.rb} +91 -107
- data/sample_usage/parsing_files.rb +18 -18
- data/{sample_sgf → spec/data}/ff4_ex.sgf +0 -0
- data/{sample_sgf → spec/data}/ff4_ex_saved.sgf +0 -0
- data/{sample_sgf → spec/data}/redrose-tartrate.sgf +1068 -1068
- data/{sample_sgf → spec/data}/simple.sgf +11 -11
- data/spec/data/simple_saved.sgf +7 -0
- data/spec/node_spec.rb +63 -33
- data/spec/parser_spec.rb +20 -0
- data/spec/spec_helper.rb +6 -6
- data/spec/tree_spec.rb +29 -41
- metadata +69 -87
- data/lib/sgf/sgfindent.rb +0 -118
- data/lib/sgf_parser.rb +0 -8
- data/sample_sgf/simple_saved.sgf +0 -7
- data/spec/tree_parser_spec.rb +0 -24
@@ -1,111 +1,120 @@
|
|
1
|
-
require 'stringio'
|
2
|
-
|
3
|
-
module
|
4
|
-
class
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
end
|
28
|
-
|
29
|
-
def
|
30
|
-
@
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
@
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
@
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
@
|
44
|
-
end
|
45
|
-
|
46
|
-
def
|
47
|
-
@
|
48
|
-
|
49
|
-
end
|
50
|
-
|
51
|
-
def
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
@
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
end
|
100
|
-
|
101
|
-
def
|
102
|
-
@
|
103
|
-
end
|
104
|
-
|
105
|
-
def
|
106
|
-
@
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
module SGF
|
4
|
+
class Parser
|
5
|
+
|
6
|
+
def initialize sgf
|
7
|
+
@sgf = stringified(sgf)
|
8
|
+
@tree = Tree.new sgf
|
9
|
+
@root = @tree.root
|
10
|
+
end
|
11
|
+
|
12
|
+
def stringified sgf
|
13
|
+
File.exist?(sgf) ? File.read(sgf) : sgf
|
14
|
+
end
|
15
|
+
|
16
|
+
def parse
|
17
|
+
while char = next_character
|
18
|
+
case char
|
19
|
+
when '(' then store_branch
|
20
|
+
when ')' then fetch_branch
|
21
|
+
when ';' then store_node_and_create_new_node
|
22
|
+
when '[' then get_and_store_property
|
23
|
+
else store_character(char)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
@tree
|
27
|
+
end
|
28
|
+
|
29
|
+
def next_character
|
30
|
+
character_available? && @stream.sysread(1)
|
31
|
+
end
|
32
|
+
|
33
|
+
def character_available?
|
34
|
+
@stream ||= StringIO.new clean_string, 'r'
|
35
|
+
!@stream.eof?
|
36
|
+
end
|
37
|
+
|
38
|
+
def clean_string
|
39
|
+
@sgf.gsub! "\\\\n\\\\r", ""
|
40
|
+
@sgf.gsub! "\\\\r\\\\n", ""
|
41
|
+
@sgf.gsub! "\\\\r", ""
|
42
|
+
@sgf.gsub! "\\\\n", ""
|
43
|
+
@sgf
|
44
|
+
end
|
45
|
+
|
46
|
+
def store_branch
|
47
|
+
@branches ||= []
|
48
|
+
@branches.unshift @current_node
|
49
|
+
end
|
50
|
+
|
51
|
+
def current_node
|
52
|
+
@current_node ||= @root
|
53
|
+
end
|
54
|
+
|
55
|
+
def fetch_branch
|
56
|
+
@current_node = @branches.shift
|
57
|
+
clear_temporary_data
|
58
|
+
end
|
59
|
+
|
60
|
+
def store_node_and_create_new_node
|
61
|
+
parent = current_node
|
62
|
+
@current_node = Node.new :parent => parent
|
63
|
+
parent.add_properties content
|
64
|
+
parent.add_children @current_node
|
65
|
+
clear_temporary_data
|
66
|
+
end
|
67
|
+
|
68
|
+
def get_and_store_property
|
69
|
+
@content[@identity] ||= ""
|
70
|
+
@content[@identity] << get_property
|
71
|
+
@identity = ""
|
72
|
+
end
|
73
|
+
|
74
|
+
def get_property
|
75
|
+
buffer = ""
|
76
|
+
while char = next_character
|
77
|
+
case char
|
78
|
+
when "]" then break unless multiple_properties?
|
79
|
+
when "\\" then
|
80
|
+
char << next_character
|
81
|
+
char = "]" if char == "\\]"
|
82
|
+
end
|
83
|
+
|
84
|
+
buffer << char
|
85
|
+
end
|
86
|
+
"[#{buffer}]"
|
87
|
+
end
|
88
|
+
|
89
|
+
def multiple_properties?
|
90
|
+
multiple_properties = false
|
91
|
+
if char = next_character
|
92
|
+
char = next_character if char == "\n"
|
93
|
+
if char == "["
|
94
|
+
multiple_properties = true
|
95
|
+
end
|
96
|
+
@stream.pos -= 1
|
97
|
+
multiple_properties
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def store_character(char)
|
102
|
+
@identity << char unless char == "\n"
|
103
|
+
end
|
104
|
+
|
105
|
+
def clear_temporary_data
|
106
|
+
@content.clear
|
107
|
+
@identity = ""
|
108
|
+
end
|
109
|
+
|
110
|
+
def content
|
111
|
+
@content ||= {}
|
112
|
+
end
|
113
|
+
|
114
|
+
def identity
|
115
|
+
@identity ||= ""
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
@@ -1,97 +1,97 @@
|
|
1
|
-
# A parser for SGF Files. Main usage: SGF::Tree.new :filename => file_name
|
2
|
-
module SgfParser
|
3
|
-
|
4
|
-
# http://www.red-bean.com/sgf/proplist.html
|
5
|
-
|
6
|
-
# Here we define SGF::Properties, so we can figure out what each property
|
7
|
-
# is and does.
|
8
|
-
|
9
|
-
property_string = %Q{AB Add Black setup list of stone
|
10
|
-
AE Add Empty setup list of point
|
11
|
-
AN Annotation game-info simpletext
|
12
|
-
AP Application root composed simpletext ':' simpletext
|
13
|
-
AR Arrow - list of composed point ':' point
|
14
|
-
AS Who adds stones - (LOA) simpletext
|
15
|
-
AW Add White setup list of stone
|
16
|
-
B Black move move
|
17
|
-
BL Black time left move real
|
18
|
-
BM Bad move move double
|
19
|
-
BR Black rank game-info simpletext
|
20
|
-
BT Black team game-info simpletext
|
21
|
-
C Comment - text
|
22
|
-
CA Charset root simpletext
|
23
|
-
CP Copyright game-info simpletext
|
24
|
-
CR Circle - list of point
|
25
|
-
DD Dim points - (inherit) elist of point
|
26
|
-
DM Even position - double
|
27
|
-
DO Doubtful move none
|
28
|
-
DT Date game-info simpletext
|
29
|
-
EV Event game-info simpletext
|
30
|
-
FF Fileformat root number (range: 1-4)
|
31
|
-
FG Figure - none | composed number ":" simpletext
|
32
|
-
GB Good for Black - double
|
33
|
-
GC Game comment game-info text
|
34
|
-
GM Game root number (range: 1-5,7-16)
|
35
|
-
GN Game name game-info simpletext
|
36
|
-
GW Good for White - double
|
37
|
-
HA Handicap game-info (Go) number
|
38
|
-
HO Hotspot - double
|
39
|
-
IP Initial pos. game-info (LOA) simpletext
|
40
|
-
IT Interesting move none
|
41
|
-
IY Invert Y-axis game-info (LOA) simpletext
|
42
|
-
KM Komi game-info (Go) real
|
43
|
-
KO Ko move none
|
44
|
-
LB Label - list of composed point ':' simpletext
|
45
|
-
LN Line - list of composed point ':' point
|
46
|
-
MA Mark - list of point
|
47
|
-
MN set move number move number
|
48
|
-
N Nodename - simpletext
|
49
|
-
OB OtStones Black move number
|
50
|
-
ON Opening game-info simpletext
|
51
|
-
OT Overtime game-info simpletext
|
52
|
-
OW OtStones White move number
|
53
|
-
PB Player Black game-info simpletext
|
54
|
-
PC Place game-info simpletext
|
55
|
-
PL Player to play setup color
|
56
|
-
PM Print move mode - (inherit) number
|
57
|
-
PW Player White game-info simpletext
|
58
|
-
RE Result game-info simpletext
|
59
|
-
RO Round game-info simpletext
|
60
|
-
RU Rules game-info simpletext
|
61
|
-
SE Markup - (LOA) point
|
62
|
-
SL Selected - list of point
|
63
|
-
SO Source game-info simpletext
|
64
|
-
SQ Square - list of point
|
65
|
-
ST Style root number (range: 0-3)
|
66
|
-
SU Setup type game-info (LOA) simpletext
|
67
|
-
SZ Size root (number | composed number ':' number)
|
68
|
-
TB Territory Black - (Go) elist of point
|
69
|
-
TE Tesuji move double
|
70
|
-
TM Timelimit game-info real
|
71
|
-
TR Triangle - list of point
|
72
|
-
TW Territory White - (Go) elist of point
|
73
|
-
UC Unclear pos - double
|
74
|
-
US User game-info simpletext
|
75
|
-
V Value - real
|
76
|
-
VW View - (inherit) elist of point
|
77
|
-
W White move move
|
78
|
-
WL White time left move real
|
79
|
-
WR White rank game-info simpletext
|
80
|
-
WT White team game-info simpletext }
|
81
|
-
|
82
|
-
property_array = property_string.split("\n")
|
83
|
-
hash = {}
|
84
|
-
property_array.each do |set|
|
85
|
-
temp = set.gsub("\t", " ")
|
86
|
-
id = temp[0..3].strip
|
87
|
-
desc = temp[4..19].strip
|
88
|
-
property_type = temp[20..35].strip
|
89
|
-
property_value = temp[37..-1].strip
|
90
|
-
hash[id] = [desc, property_type, property_value]
|
91
|
-
end
|
92
|
-
|
93
|
-
# All this work for this minuscule line!
|
94
|
-
PROPERTIES = hash
|
95
|
-
|
96
|
-
end
|
97
|
-
|
1
|
+
# A parser for SGF Files. Main usage: SGF::Tree.new :filename => file_name
|
2
|
+
module SgfParser
|
3
|
+
|
4
|
+
# http://www.red-bean.com/sgf/proplist.html
|
5
|
+
|
6
|
+
# Here we define SGF::Properties, so we can figure out what each property
|
7
|
+
# is and does.
|
8
|
+
|
9
|
+
property_string = %Q{AB Add Black setup list of stone
|
10
|
+
AE Add Empty setup list of point
|
11
|
+
AN Annotation game-info simpletext
|
12
|
+
AP Application root composed simpletext ':' simpletext
|
13
|
+
AR Arrow - list of composed point ':' point
|
14
|
+
AS Who adds stones - (LOA) simpletext
|
15
|
+
AW Add White setup list of stone
|
16
|
+
B Black move move
|
17
|
+
BL Black time left move real
|
18
|
+
BM Bad move move double
|
19
|
+
BR Black rank game-info simpletext
|
20
|
+
BT Black team game-info simpletext
|
21
|
+
C Comment - text
|
22
|
+
CA Charset root simpletext
|
23
|
+
CP Copyright game-info simpletext
|
24
|
+
CR Circle - list of point
|
25
|
+
DD Dim points - (inherit) elist of point
|
26
|
+
DM Even position - double
|
27
|
+
DO Doubtful move none
|
28
|
+
DT Date game-info simpletext
|
29
|
+
EV Event game-info simpletext
|
30
|
+
FF Fileformat root number (range: 1-4)
|
31
|
+
FG Figure - none | composed number ":" simpletext
|
32
|
+
GB Good for Black - double
|
33
|
+
GC Game comment game-info text
|
34
|
+
GM Game root number (range: 1-5,7-16)
|
35
|
+
GN Game name game-info simpletext
|
36
|
+
GW Good for White - double
|
37
|
+
HA Handicap game-info (Go) number
|
38
|
+
HO Hotspot - double
|
39
|
+
IP Initial pos. game-info (LOA) simpletext
|
40
|
+
IT Interesting move none
|
41
|
+
IY Invert Y-axis game-info (LOA) simpletext
|
42
|
+
KM Komi game-info (Go) real
|
43
|
+
KO Ko move none
|
44
|
+
LB Label - list of composed point ':' simpletext
|
45
|
+
LN Line - list of composed point ':' point
|
46
|
+
MA Mark - list of point
|
47
|
+
MN set move number move number
|
48
|
+
N Nodename - simpletext
|
49
|
+
OB OtStones Black move number
|
50
|
+
ON Opening game-info simpletext
|
51
|
+
OT Overtime game-info simpletext
|
52
|
+
OW OtStones White move number
|
53
|
+
PB Player Black game-info simpletext
|
54
|
+
PC Place game-info simpletext
|
55
|
+
PL Player to play setup color
|
56
|
+
PM Print move mode - (inherit) number
|
57
|
+
PW Player White game-info simpletext
|
58
|
+
RE Result game-info simpletext
|
59
|
+
RO Round game-info simpletext
|
60
|
+
RU Rules game-info simpletext
|
61
|
+
SE Markup - (LOA) point
|
62
|
+
SL Selected - list of point
|
63
|
+
SO Source game-info simpletext
|
64
|
+
SQ Square - list of point
|
65
|
+
ST Style root number (range: 0-3)
|
66
|
+
SU Setup type game-info (LOA) simpletext
|
67
|
+
SZ Size root (number | composed number ':' number)
|
68
|
+
TB Territory Black - (Go) elist of point
|
69
|
+
TE Tesuji move double
|
70
|
+
TM Timelimit game-info real
|
71
|
+
TR Triangle - list of point
|
72
|
+
TW Territory White - (Go) elist of point
|
73
|
+
UC Unclear pos - double
|
74
|
+
US User game-info simpletext
|
75
|
+
V Value - real
|
76
|
+
VW View - (inherit) elist of point
|
77
|
+
W White move move
|
78
|
+
WL White time left move real
|
79
|
+
WR White rank game-info simpletext
|
80
|
+
WT White team game-info simpletext }
|
81
|
+
|
82
|
+
property_array = property_string.split("\n")
|
83
|
+
hash = {}
|
84
|
+
property_array.each do |set|
|
85
|
+
temp = set.gsub("\t", " ")
|
86
|
+
id = temp[0..3].strip
|
87
|
+
desc = temp[4..19].strip
|
88
|
+
property_type = temp[20..35].strip
|
89
|
+
property_value = temp[37..-1].strip
|
90
|
+
hash[id] = [desc, property_type, property_value]
|
91
|
+
end
|
92
|
+
|
93
|
+
# All this work for this minuscule line!
|
94
|
+
PROPERTIES = hash
|
95
|
+
|
96
|
+
end
|
97
|
+
|
@@ -1,107 +1,91 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
class Tree
|
4
|
-
include Enumerable
|
5
|
-
|
6
|
-
attr_accessor :root
|
7
|
-
|
8
|
-
def initialize
|
9
|
-
@root = Node.new
|
10
|
-
@sgf =
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
node.each_child do |child|
|
93
|
-
preorder(child, &block)
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
def method_missing method_name, *args
|
99
|
-
output = @root.children[0].properties[method_name]
|
100
|
-
super(method_name, args) if output.nil?
|
101
|
-
output
|
102
|
-
end
|
103
|
-
|
104
|
-
end
|
105
|
-
|
106
|
-
end
|
107
|
-
|
1
|
+
module SGF
|
2
|
+
|
3
|
+
class Tree
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
attr_accessor :root
|
7
|
+
|
8
|
+
def initialize sgf
|
9
|
+
@root = Node.new
|
10
|
+
@sgf = sgf
|
11
|
+
end
|
12
|
+
|
13
|
+
def each order=:preorder, &block
|
14
|
+
# I know, I know. SGF is only preorder. Well, it's implemented, ain't it?
|
15
|
+
# Stop complaining.
|
16
|
+
case order
|
17
|
+
when :preorder
|
18
|
+
preorder @root, &block
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Compares a tree to another tree, node by node.
|
23
|
+
# Nodes must be the same (same properties, parents and children).
|
24
|
+
def == other_tree
|
25
|
+
one = []
|
26
|
+
two = []
|
27
|
+
each { |node| one << node }
|
28
|
+
other_tree.each { |node| two << node }
|
29
|
+
one == two
|
30
|
+
end
|
31
|
+
|
32
|
+
# Saves the tree as an SGF file. raises an error if a filename is not given.
|
33
|
+
# tree.save :filename => file_name
|
34
|
+
def save args={}
|
35
|
+
raise ArgumentError, "No file name provided" if args[:filename].nil?
|
36
|
+
@savable_sgf = "("
|
37
|
+
@root.children.each { |child| write_node child }
|
38
|
+
@savable_sgf << ")"
|
39
|
+
|
40
|
+
File.open(args[:filename], 'w') { |f| f << @savable_sgf }
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
# Adds a stringified node to the variable @savable_sgf.
|
46
|
+
def write_node node=@root
|
47
|
+
@savable_sgf << ";"
|
48
|
+
unless node.properties.empty?
|
49
|
+
properties = ""
|
50
|
+
node.properties.each do |identity, property|
|
51
|
+
new_property = property[1...-1]
|
52
|
+
new_property.gsub!("]", "\\]") if identity == "C"
|
53
|
+
properties += "#{identity.to_s}[#{new_property}]"
|
54
|
+
end
|
55
|
+
@savable_sgf << properties
|
56
|
+
end
|
57
|
+
|
58
|
+
case node.children.size
|
59
|
+
when 0
|
60
|
+
@savable_sgf << ")"
|
61
|
+
when 1
|
62
|
+
write_node node.children[0]
|
63
|
+
else
|
64
|
+
node.each_child do |child|
|
65
|
+
@savable_sgf << "("
|
66
|
+
write_node child
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Traverse the tree in preorder fashion, starting with the @root node if
|
72
|
+
# no node is given, and activating the passed block on each.
|
73
|
+
def preorder node=@root, &block
|
74
|
+
# stop processing if the block returns false
|
75
|
+
if yield node then
|
76
|
+
node.each_child do |child|
|
77
|
+
preorder(child, &block)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def method_missing method_name, *args
|
83
|
+
output = @root.children[0].properties[method_name]
|
84
|
+
super(method_name, args) if output.nil?
|
85
|
+
output
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|