SgfParser 0.9.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|