File Coverage

blib/lib/Games/SGF.pm
Criterion Covered Total %
statement 699 877 79.7
branch 342 474 72.1
condition 39 68 57.3
subroutine 64 69 92.7
pod 51 51 100.0
total 1195 1539 77.6


line stmt bran cond sub pod time code
1             package Games::SGF;
2              
3 9     9   290000 use strict;
  9         24  
  9         491  
4 9     9   52 use warnings;
  9         30  
  9         483  
5 9     9   52 use Carp qw(carp croak confess);
  9         20  
  9         1366  
6 9         85 use enum qw(
7             :C_=1 BLACK WHITE
8             :DBL_=1 NORM EMPH
9             :V_=1 NONE NUMBER REAL DOUBLE COLOR SIMPLE_TEXT TEXT POINT MOVE STONE
10             BITMASK:VF_=0 NONE EMPTY LIST OPT_COMPOSE
11             :T_=1 MOVE SETUP ROOT GAME_INFO NONE
12             :A_=1 NONE INHERIT
13 9     9   9534 );
  9         36052  
14             #use Clone::PP;
15              
16             =head1 NAME
17              
18             Games::SGF - A general SGF parser
19              
20             =head1 VERSION
21              
22             Version 0.993
23              
24             =cut
25              
26              
27             our $VERSION = 0.993;
28             my( %ff4_properties ) = (
29             # general move properties
30             'B' => { 'type' => T_MOVE, 'value' => V_MOVE },
31             'BL' => { 'type' => T_MOVE, 'value' => V_REAL },
32             'BM' => { 'type' => T_MOVE, 'value' => V_DOUBLE },
33             'DO' => { 'type' => T_MOVE, 'value' => V_NONE },
34             'IT' => { 'type' => T_MOVE, 'value' => V_NONE },
35             'KO' => { 'type' => T_MOVE, 'value' => V_NONE },
36             'MN' => { 'type' => T_MOVE, 'value' => V_NUMBER },
37             'OB' => { 'type' => T_MOVE, 'value' => V_NUMBER },
38             'OW' => { 'type' => T_MOVE, 'value' => V_NUMBER },
39             'TE' => { 'type' => T_MOVE, 'value' => V_DOUBLE },
40             'W' => { 'type' => T_MOVE, 'value' => V_MOVE },
41             'WL' => { 'type' => T_MOVE, 'value' => V_REAL },
42              
43             # general setup properties
44             'AB' => { 'type' => T_SETUP, 'value' => V_STONE, 'value_flags' => VF_LIST },
45             'AE' => { 'type' => T_SETUP, 'value' => V_POINT, 'value_flags' => VF_LIST | VF_OPT_COMPOSE },
46             'AW' => { 'type' => T_SETUP, 'value' => V_STONE, 'value_flags' => VF_LIST },
47             'PL' => { 'type' => T_SETUP, 'value' => V_COLOR },
48              
49             # genreal none inherited properties
50             'DD' => { 'type' => T_NONE, 'value' => V_POINT,
51             'value_flags' => VF_EMPTY | VF_LIST | VF_OPT_COMPOSE,
52             'attrib' => A_INHERIT },
53             'PM' => { 'type' => T_NONE, 'value' => V_NUMBER, 'attrib' => A_INHERIT },
54             'VW' => { 'type' => T_NONE, 'value' => V_POINT,
55             'value_flags' => VF_EMPTY | VF_LIST | VF_OPT_COMPOSE,
56             'attrib' => A_INHERIT },
57              
58             # general none properties
59             'AR' => { 'type' => T_NONE, 'value' => [V_POINT,V_POINT],
60             'value_flags' => VF_LIST },
61             'C' => { 'type' => T_NONE, 'value' => V_TEXT },
62             'CR' => { 'type' => T_NONE, 'value' => V_POINT,
63             'value_flags' => VF_LIST | VF_OPT_COMPOSE },
64             'DM' => { 'type' => T_NONE, 'value' => V_DOUBLE },
65             'FG' => { 'type' => T_NONE, 'value' => [V_NUMBER,V_SIMPLE_TEXT],
66             'value_flags' => VF_EMPTY },
67             'GB' => { 'type' => T_NONE, 'value' => V_DOUBLE },
68             'GW' => { 'type' => T_NONE, 'value' => V_DOUBLE },
69             'HO' => { 'type' => T_NONE, 'value' => V_DOUBLE },
70             'LB' => { 'type' => T_NONE, 'value' => [V_POINT,V_SIMPLE_TEXT],
71             'value_flags' => VF_LIST },
72             'LN' => { 'type' => T_NONE, 'value' => [V_POINT,V_POINT],
73             'value_flags' => VF_LIST },
74             'MA' => { 'type' => T_NONE, 'value' => V_POINT,
75             'value_flags' => VF_EMPTY | VF_LIST | VF_OPT_COMPOSE },
76             'N' => { 'type' => T_NONE, 'value' => V_SIMPLE_TEXT },
77             'SL' => { 'type' => T_NONE, 'value' => V_POINT,
78             'value_flags' => VF_LIST | VF_OPT_COMPOSE },
79             'SQ' => { 'type' => T_NONE, 'value' => V_POINT,
80             'value_flags' => VF_LIST | VF_OPT_COMPOSE },
81             'TR' => { 'type' => T_NONE, 'value' => V_POINT,
82             'value_flags' => VF_LIST | VF_OPT_COMPOSE },
83             'UC' => { 'type' => T_NONE, 'value' => V_DOUBLE },
84             'V' => { 'type' => T_NONE, 'value' => V_REAL },
85              
86             # general root properties
87             'AP' => { 'type' => T_ROOT, 'value' => [V_SIMPLE_TEXT, V_SIMPLE_TEXT] },
88             'CA' => { 'type' => T_ROOT, 'value' => V_SIMPLE_TEXT },
89             'FF' => { 'type' => T_ROOT, 'value' => V_NUMBER },
90             'GM' => { 'type' => T_ROOT, 'value' => V_NUMBER },
91             'ST' => { 'type' => T_ROOT, 'value' => V_NUMBER },
92             'SZ' => { 'type' => T_ROOT, 'value' => V_NUMBER,
93             'value_flags' => VF_OPT_COMPOSE},
94              
95             # general game-info properties
96             'AN' => { 'type' => T_GAME_INFO, 'value' => V_SIMPLE_TEXT },
97             'BR' => { 'type' => T_GAME_INFO, 'value' => V_SIMPLE_TEXT },
98             'BT' => { 'type' => T_GAME_INFO, 'value' => V_SIMPLE_TEXT },
99             'CP' => { 'type' => T_GAME_INFO, 'value' => V_SIMPLE_TEXT },
100             'DT' => { 'type' => T_GAME_INFO, 'value' => V_SIMPLE_TEXT },
101             'EV' => { 'type' => T_GAME_INFO, 'value' => V_SIMPLE_TEXT },
102             'GC' => { 'type' => T_GAME_INFO, 'value' => V_TEXT },
103             'GN' => { 'type' => T_GAME_INFO, 'value' => V_SIMPLE_TEXT },
104             'ON' => { 'type' => T_GAME_INFO, 'value' => V_SIMPLE_TEXT },
105             'OT' => { 'type' => T_GAME_INFO, 'value' => V_SIMPLE_TEXT },
106             'PB' => { 'type' => T_GAME_INFO, 'value' => V_SIMPLE_TEXT },
107             'PC' => { 'type' => T_GAME_INFO, 'value' => V_SIMPLE_TEXT },
108             'PW' => { 'type' => T_GAME_INFO, 'value' => V_SIMPLE_TEXT },
109             'RE' => { 'type' => T_GAME_INFO, 'value' => V_SIMPLE_TEXT },
110             'RO' => { 'type' => T_GAME_INFO, 'value' => V_SIMPLE_TEXT },
111             'RU' => { 'type' => T_GAME_INFO, 'value' => V_SIMPLE_TEXT },
112             'SO' => { 'type' => T_GAME_INFO, 'value' => V_SIMPLE_TEXT },
113             'TM' => { 'type' => T_GAME_INFO, 'value' => V_REAL },
114             'US' => { 'type' => T_GAME_INFO, 'value' => V_SIMPLE_TEXT },
115             'WR' => { 'type' => T_GAME_INFO, 'value' => V_SIMPLE_TEXT },
116             'WT' => { 'type' => T_GAME_INFO, 'value' => V_SIMPLE_TEXT },
117             );
118              
119             =head1 SYNOPSIS
120              
121             use Games::SGF;
122              
123             my $sgf = new Games::SGF();
124              
125             $sgf->setStoneRead( sub { "something useful"} );
126             $sgf->setMoveRead( sub { "something useful"} );
127             $sgf->setPointRead( sub { "something useful"} );
128              
129             $sgf->addTag('KM', $sgf->T_GAME_INFO, $sgf->V_REAL );
130             $sgf->readFile("015-01.sgf");
131             $sgf->setProperty( "AP", $sgf->compose("MyApp", "Version 1.0") );
132              
133             =head1 DISCRIPTION
134              
135             Games::SGF is a general Smart Game Format Parser. It parses
136             the file, and checks the properties against the file format 4
137             standard. No game specific features are implemented, but can be
138             added on in inheriting classes.
139              
140             It is designed so that the user can tell the parser how to handle new
141             tags. It also allows the user to set callbacks to parse Stone,
142             Point, and Move types. These are game specific types.
143              
144             =head2 SGF Structure
145              
146             SGF file contains 1 or more game trees. Each game tree consists of a sequence
147             of nodes followed by a sequence of variations. Each variation also consists a
148             sequence of nodes followed by a sequence of variations.
149              
150             Each node contains a set of properties. Each property has a L, L,
151             L, and an L.
152              
153             =head2 Interface
154              
155             The interface is broken into 3 conceptal parts
156              
157             =over
158              
159             =item SGF Format
160              
161             This is the straight SGF Format which is saved and read using L methods.
162              
163             =item User Format
164              
165             This is the format that the Games::SGF user will come in contact with. Various
166             methods will convert the Uwer Format into the Internal Format which Games::SGF
167             actually deals with.
168              
169             These can take the form of Constants:
170              
171             =over
172              
173             =item Double Values: DBL_NORM and DBL_EMPH
174              
175             =item Color Values: C_BLACK and C_WHITE
176              
177             =back
178              
179             Or with converstion methods:
180              
181             =over
182              
183             =item L
184              
185             =item L
186              
187             =item L
188              
189             =item L
190              
191             =back
192              
193             =item Internal Format
194              
195             If this format differs from the others, you don't need to know.
196              
197             =back
198              
199             Also see: L
200              
201             =head1 METHODS
202              
203             =head2 new
204              
205             new Games::SGF(%options);
206              
207             Creates a SGF object.
208              
209             Options that new will look at.
210              
211             =over
212              
213             =item Fatal
214              
215             =item Warn
216              
217             =item Debug
218              
219             These options operate in the same fashion. There are 3 value cases that it
220             will check. If the value is a code reference it will ccall that subroutine
221             when the event occurs with the event strings passed to it. If the value is
222             true then it croak on Fatal, and carp on Warn or Debug. If the value is
223             false it will be silent. You will still be able to get the error strings
224             by calling L, L, or L.
225              
226              
227             =back
228              
229             =cut
230              
231             sub new {
232 11     11 1 19787 my $inv = shift;
233 11   66     90 my $class = ref( $inv) || $inv;
234 11         53 my( %opts ) = @_;
235 11         28 my $self = {};
236             # stores added tags
237 11         36 $self->{'tags'} = {};
238             # stores stone, point, move handling subroutines
239 11         29 $self->{'game'} = undef;
240 11         30 $self->{'collection'} = undef;
241 11         28 $self->{'parents'} = undef;
242 11         30 $self->{'address'} = undef;
243 11         23 $self->{'node'} = undef;
244              
245              
246             # Default Warnings and Debug statments to silence
247              
248 11 100       54 $self->{'Fatal'} = exists $opts{'Fatal'} ? $opts{'Fatal'} : 1;
249 11 50       57 $self->{'Warn'} = exists $opts{'Warn'} ? $opts{'Warn'} : 0;
250 11 50       52 $self->{'Debug'} = exists $opts{'Debug'} ? $opts{'Debug'} : 0;
251 11         36 $self->{'FatalErrors'} = [];
252 11         33 $self->{'WarnErrors'} = [];
253 11         32 $self->{'DebugErrors'} = [];
254 11         62 return bless $self, $class;
255             }
256              
257             =head2 clone
258              
259             $sgf_copy = $sgf->clone;
260              
261             This will create a completely independent copy of the C<$sgf> object.
262              
263             =cut
264              
265             sub clone {
266 0     0 1 0 my $self = shift;
267 0         0 return Clone::PP::clone($self);
268             }
269              
270             =head2 IO
271              
272             =head3 readText
273              
274             $sgf->readText($text);
275              
276             This takes in a SGF formated string and parses it.
277              
278             =cut
279              
280             sub readText {
281 8     8 1 529 my $self = shift;
282 8         21 my $text = shift;
283 8         32 $self->_clear;
284 8         32 $self->Debug("readText( )");
285 8         48 $self->_read($text);
286 8 50       40 if( $self->Fatal ) {
287 0         0 $self->Debug("readText( ) FAILED ");
288 0         0 return 0;
289             } else {
290 8         22 $self->{'game'} = 0; # first branch
291 8         30 $self->gotoRoot;
292             }
293 8         75 return 1;
294             }
295              
296             =head3 readFile
297              
298             $sgf->readFile($file);
299              
300             This will open the passed file, read it in then parse it.
301              
302             =cut
303              
304             sub readFile {
305 0     0 1 0 my $self = shift;
306 0         0 my $filename = shift;
307 0         0 $self->_clear;
308 0         0 $self->Debug("readFile( '$filename' )" );
309 0         0 my $text;
310             my $fh;
311 0 0       0 if( not open $fh, "<", $filename ) {
312 0         0 $self->Fatal( "readFile( $filename ): FAILED on open\t\t$!" );
313 0         0 return 0;
314             }
315 0 0       0 if(read( $fh, $text, -s $filename) == 0 ) {
316 0         0 $self->Fatal( "readFile( $filename ): FAILED on read\t\t$!" );
317 0         0 return 0;
318             }
319 0         0 close $fh;
320 0         0 return $self->readText($text) ;
321             }
322              
323             =head3 writeText
324              
325             $sgf->writeText;
326              
327             Will return the current collection in SGF form;
328              
329             =cut
330              
331             sub writeText {
332 2     2 1 5 my $self = shift;
333 2         9 $self->_clear;
334 2         8 $self->Debug("writeText( )");
335 2         5 my $text = "";
336             # foreach game
337 2         13 foreach my $game ( @{$self->{'collection'}}) {
  2         7  
338             # write branch
339 2         16 $text .= $self->_write($game);
340 2 50       10 if( $self->Fatal) {
341 0         0 return 0;
342             }
343 2         7 $text .= "\n";
344             }
345 2         11 $self->Debug( "write Text:\t\t$text\n");
346 2         13 return $text;
347             }
348              
349             =head3 writeFile
350              
351             $sgf->writeFile($filename);
352              
353             Will write the current game collection to $filename.
354              
355             =cut
356              
357             sub writeFile {
358 0     0 1 0 my $self = shift;
359 0         0 my $filename = shift;
360 0         0 $self->_clear;
361 0         0 $self->Debug("writeFile( '$filename' )" );
362 0         0 my $text;
363             my $fh;
364 0 0       0 if( not open $fh, ">", $filename ) {
365 0         0 $self->Fatal("writeFile( $filename ): FAILED on open\t\t$!");
366 0         0 return 0;
367             }
368 0         0 print $fh $self->writeText;
369 0         0 close $fh;
370 0 0       0 if( $self->Fatal ) {
371 0         0 return 0;
372             }
373 0         0 return 1;
374             }
375              
376             =head2 Property Manipulation
377              
378             =head3 addTag
379              
380             $sgf->addTag($tagname, $type, $value_type, $flags, $attribute);
381              
382             This add a new tag to the parsing engine. This needs to called before the read
383             or write commands are called. This tag will not override the FF[4] standard
384             properties, or already defined properties.
385              
386             The C<$tagname> is the name of the tag which will be read in, thus if you want
387             to be able to read AAA[...] from an SGF file the tagname needs to be "AAA".
388              
389             The C<$type> needs to be choosen from the L list below. Defaults to
390             C.
391              
392             The C<$value_type> needs to be choosen from the L list below.
393             Defaults to C.
394              
395             The C<$flags> are from the L List. Defaults to C.
396              
397             The C<$attribute> is from the L List. Defaults to C.
398              
399             =cut
400              
401             sub addTag {
402 21     21 1 55 my $self = shift;
403 21         37 my $tagname = shift;
404 21 50       115 unless( $tagname =~ /^[a-zA-Z]+$/ ) {
405 0         0 $self->Fatal("addTag( $tagname ): FAILED\t\t$tagname is of invalid format should pass /^[a-zA-Z]+\$/" );
406 0         0 return 0;
407             }
408 21         60 $self->_clear;
409 21         120 $self->Debug("addTag($tagname, " . join( ", ", @_ ) . " )" );
410 21 100 100     134 if( exists $self->{'tags'}->{$tagname} or exists $ff4_properties{$tagname}) {
411 4         18 $self->Fatal("addTag( $tagname ): FAILED\t\t$tagname already exists");
412 4         33 return 0;
413             }
414 17         67 $self->{'tags'}->{$tagname}->{'type'} = shift;
415 17         84 $self->{'tags'}->{$tagname}->{'value'} = shift;
416 17         57 $self->{'tags'}->{$tagname}->{'value_flags'} = shift;
417 17         35 $self->{'tags'}->{$tagname}->{'attrib'} = shift;
418              
419 17         54 return 1;
420             }
421              
422             =head3 redefineTag
423              
424             $sgf->redefineTag($tag, $type, $value, $value_flags, $attribute);
425              
426             This will overwrite the flags set for C<$tagname>. If one of the args is unset,
427             it will be unaltered. For example:
428              
429             $sgf->redefineTag($tag, , , $flags);
430              
431             Will reset C<$tag>'s $flags leaving all other properties untouched.
432              
433             The property fields are the same defined the same as L.
434              
435             =cut
436              
437             sub redefineTag {
438 11     11 1 16 my $self = shift;
439 11         28 $self->_clear;
440             {
441 11         13 my(@args ) = @_;
  11         25  
442 11         21 foreach(@args) {
443 39 50       97 $_ = "undef" unless defined $_;
444             }
445 11         84 $self->Debug("redefineTag(" . join( ", ", @args ) . " )" );
446             }
447 11         18 my $tagname = shift;
448              
449 11 50       84 unless( $tagname =~ /^[a-zA-Z]+$/ ) {
450 0         0 $self->Fatal("redefineTag($tagname, " . join( ", ", @_ ) . " )" .
451             ": FAILED\t\t$tagname is of invalid format should pass /^[a-zA-Z]+\$/" );
452 0         0 return 0;
453             }
454 11         17 my $type = shift;
455 11         328 my $value = shift;
456 11         15 my $value_flags = shift;
457 11         12 my $attrib = shift;
458 11 100       36 if(exists $ff4_properties{$tagname} ) {# ff4_properties
    100          
459 7 50       65 $self->{'tags'}->{$tagname}->{'type'} = defined $type ? $type : $ff4_properties{$tagname}->{'type'};
460 7 100       32 $self->{'tags'}->{$tagname}->{'value'} = defined $value ? $value : $ff4_properties{$tagname}->{'value'};
461 7 100       20 $self->{'tags'}->{$tagname}->{'value_flags'} = defined $value_flags ? $value_flags : $ff4_properties{$tagname}->{'value_flags'};
462 7 50       41 $self->{'tags'}->{$tagname}->{'attrib'} = defined $attrib ? $attrib : $ff4_properties{$tagname}->{'attrib'};
463 7         27 return 1;
464             } elsif( exists $self->{'tags'}->{$tagname} ) {
465              
466 3 100       9 $self->{'tags'}->{$tagname}->{'type'} = $type if defined $type;
467 3 100       8 $self->{'tags'}->{$tagname}->{'value'} = $value if defined $value;
468 3 100       10 $self->{'tags'}->{$tagname}->{'value_flags'} = $value_flags if defined $value_flags;
469 3 100       8 $self->{'tags'}->{$tagname}->{'attrib'} = $attrib if defined $attrib;
470 3         14 return 1;
471             } else {
472 1         14 $self->Fatal("redefineTag($tagname, " . join( ", ", @_ ) . " )" .
473             ": FAILED\t\t$tagname does not exist" );
474 1         8 return 0;
475             }
476              
477 0         0 return 1;
478             }
479              
480              
481             =head3 setPointRead
482              
483             =cut
484              
485              
486             sub setPointRead {
487 8     8 1 15 my $self = shift;
488 8         11 my $coderef = shift;
489 8         16 $self->_clear;
490 8 100       29 if( exists( $self->{'pointRead'} )) {
491 3         9 $self->Fatal("setPointRead( ): FAILED\t\t already exists");
492 3         14 return 0;
493             }
494 5 100       14 if( ref $coderef eq 'CODE' ) {
495 4         10 $self->{'pointRead'} = $coderef;
496             } else {
497 1         5 $self->Fatal("setPointRead( ): FAILED\t\t is not a CODE Reference");
498 1         3 return 0;
499             }
500 4         49 return 1;
501             }
502              
503             =head3 setMoveRead
504              
505             =cut
506              
507             sub setMoveRead {
508 8     8 1 15 my $self = shift;
509 8         47 $self->_clear;
510 8         11 my $coderef = shift;
511 8 100       25 if( exists( $self->{'moveRead'} )) {
512 3         9 $self->Fatal("setMoveRead( ): FAILED\t\t already exists");
513 3         14 return 0;
514             }
515 5 100       15 if( ref $coderef eq 'CODE' ) {
516 4         12 $self->{'moveRead'} = $coderef;
517             } else {
518 1         3 $self->Fatal("setMoveRead( ): FAILED\t\t is not a CODE Reference");
519 1         4 return 0;
520             }
521 4         21 return 1;
522             }
523              
524             =head3 setStoneRead
525              
526             $sgf->setPointRead(\&coderef);
527             $sgf->setMoveRead(\&coderef);
528             $sgf->setStoneRead(\&coderef);
529              
530             These call backs are called when a properties value needs to be parsed.
531             It takes in a string, and returns a structure of some type. Here is a
532             possible example for a Go point callback:
533              
534             sub parsepoint {
535             my $value = shift;
536             my( $x, $y) = split //, $value;
537             return [ ord($x) - ord('a'), ord($y) - ord('a') ];
538             }
539             # then somewhere else
540             $sgf->setPointParse( \&parsepoint );
541              
542             Note: that you should do more then this in practice, but it gets the
543             across.
544              
545             If the value is an empty string and VF_RMPTY is set then the call back will
546             not be called but return an empty string.
547              
548             =cut
549              
550             sub setStoneRead {
551 5     5 1 10 my $self = shift;
552 5         12 $self->_clear;
553 5         7 my $coderef = shift;
554 5 100       19 if( exists( $self->{'stoneRead'} )) {
555 3         8 $self->Fatal("setStoneRead( ): FAILED\t\t already exists");
556 3         13 return 0;
557             }
558 2 100       8 if( ref $coderef eq 'CODE' ) {
559 1         3 $self->{'stoneRead'} = $coderef;
560             } else {
561 1         3 $self->Fatal("setStoneRead( ): FAILED\t\t is not a CODE Reference");
562 1         3 return 0;
563             }
564 1         3 return 1;
565             }
566              
567              
568             =head3 setPointCheck
569              
570             =cut
571              
572              
573             sub setPointCheck {
574 5     5 1 10 my $self = shift;
575 5         10 $self->_clear;
576 5         7 my $coderef = shift;
577 5 50       16 if( exists( $self->{'pointCheck'} )) {
578 0         0 $self->Fatal("setPointCheck( ): FAILED\t\t already exists");
579 0         0 return 0;
580             }
581 5 100       22 if( ref $coderef eq 'CODE' ) {
582 4         12 $self->{'pointCheck'} = $coderef;
583             } else {
584 1         12 $self->Fatal("setPointCheck( ): FAILED\t\t is not a CODE Reference");
585 1         5 return 0;
586             }
587 4         11 return 1;
588             }
589              
590             =head3 setMoveCheck
591              
592             =cut
593              
594             sub setMoveCheck {
595 5     5 1 14 my $self = shift;
596 5         12 $self->_clear;
597 5         6 my $coderef = shift;
598 5 50       18 if( exists( $self->{'moveCheck'} )) {
599 0         0 $self->Fatal("setMoveCheck( ): FAILED\t\t already exists");
600 0         0 return 0;
601             }
602 5 100       15 if( ref $coderef eq 'CODE' ) {
603 4         20 $self->{'moveCheck'} = $coderef;
604             } else {
605 1         3 $self->Fatal("setMoveCheck( ): FAILED\t\t is not a CODE Reference");
606 1         8 return 0;
607             }
608 4         12 return 1;
609             }
610              
611             =head3 setStoneCheck
612              
613             $sgf->setPointCheck(\&coderef);
614             $sgf->setMoveCheck(\&coderef);
615             $sgf->setStoneCheck(\&coderef);
616              
617             This callback is called when a parameter is stored. The callback takes
618             the structure passed to setProperty, or component if composed, and returns
619             true if it is a valid structure.
620              
621             An Example of a stone check for go is as follows:
622              
623             sub stoneCheck {
624             my $stone = shift;
625             if( ref $stone eq 'ARRAY' and @$stone == 2
626             and $stone->[0] > 0 and $stone->[1] > 0 ) {
627             return 1;
628             } else {
629             return 0;
630             }
631             }
632              
633             If the value is an empty string it will be passed to the check callback
634             only if VF_EMPTY is not set.
635              
636             =cut
637              
638             sub setStoneCheck {
639 2     2 1 3 my $self = shift;
640 2         5 $self->_clear;
641 2         2 my $coderef = shift;
642 2 50       8 if( exists( $self->{'stoneCheck'} )) {
643 0         0 $self->Fatal("setStoneCheck( ): FAILED\t\t already exists");
644 0         0 return 0;
645             }
646 2 100       5 if( ref $coderef eq 'CODE' ) {
647 1         5 $self->{'stoneCheck'} = $coderef;
648             } else {
649 1         3 $self->Fatal("setStoneCheck( ): FAILED\t\t is not a CODE Reference");
650 1         4 return 0;
651             }
652 1         4 return 1;
653             }
654              
655             =head3 setPointWrite
656              
657             =cut
658              
659              
660             sub setPointWrite {
661 5     5 1 10 my $self = shift;
662 5         12 $self->_clear;
663 5         7 my $coderef = shift;
664 5 50       17 if( exists( $self->{'pointWrite'} )) {
665 0         0 $self->Fatal("setPointWrite( ): FAILED\t\t already exists");
666 0         0 return 0;
667             }
668 5 100       18 if( ref $coderef eq 'CODE' ) {
669 4         10 $self->{'pointWrite'} = $coderef;
670             } else {
671 1         2 $self->Fatal("setPointWrite( ): FAILED\t\t is not a CODE Reference");
672 1         4 return 0;
673             }
674 4         21 return 1;
675             }
676              
677             =head3 setMoveWrite
678              
679             =cut
680              
681             sub setMoveWrite {
682 5     5 1 10 my $self = shift;
683 5         26 $self->_clear;
684 5         8 my $coderef = shift;
685 5 50       19 if( exists( $self->{'moveWrite'} )) {
686 0         0 $self->Fatal("setMoveWrite( ): FAILED\t\t already exists");
687 0         0 return 0;
688             }
689 5 100       14 if( ref $coderef eq 'CODE' ) {
690 4         12 $self->{'moveWrite'} = $coderef;
691             } else {
692 1         3 $self->Fatal("setMoveWrite( ): FAILED\t\t is not a CODE Reference");
693 1         4 return 0;
694             }
695 4         12 return 1;
696             }
697              
698             =head3 setStoneWrite
699              
700             $sgf->setPointWrite(\&coderef);
701             $sgf->setMoveWrite(\&coderef);
702             $sgf->setStoneWrite(\&coderef);
703              
704             This callback is called when a parameter is written in text format. The callback takes
705             the structure passed to setProperty, or component if composed, and returns
706             the text string which will be stored.
707              
708             An Example of a stone check for go is as follows:
709              
710             sub stoneWrite {
711             my $stone = shift;
712             my @list = ('a'..'Z','A'..'Z');
713             return $list[$stone->[0] - 1] . $list[$stone->[1] - 1];
714             }
715              
716             If the tag value is an empty string it will not be sent to the write callback, but immedeitely be returned as an empty string.
717              
718             =cut
719              
720             sub setStoneWrite {
721 2     2 1 4 my $self = shift;
722 2         5 $self->_clear;
723 2         3 my $coderef = shift;
724 2 50       7 if( exists( $self->{'stoneWrite'} )) {
725 0         0 $self->Fatal("setStoneWrite( ): FAILED\t\t already exists");
726 0         0 return 0;
727             }
728 2 100       7 if( ref $coderef eq 'CODE' ) {
729 1         3 $self->{'stoneWrite'} = $coderef;
730             } else {
731 1         3 $self->Fatal("setStoneWrite( ): FAILED\t\t is not a CODE Reference");
732 1         4 return 0;
733             }
734 1         35 return 1;
735             }
736              
737             =head3 getTagFlags
738              
739             $flags = getTagFlags($tag);
740             if( $flags & VF_LIST ) {
741             # do something about lists
742             }
743              
744             This will return the flags set on this tag.
745              
746             =cut
747              
748             sub getTagFlags {
749 975     975 1 1093 my $self = shift;
750 975         1903 my $tag = shift;
751 975 100       16525 if( exists( $self->{'tags'}->{$tag}) ) {
    50          
752 84 100       196 if( $self->{'tags'}->{$tag}->{'value_flags'} ) {
753 30         128 return $self->{'tags'}->{$tag}->{'value_flags'};
754             } else {
755 54         190 return 0;
756             }
757             } elsif( exists( $ff4_properties{$tag}) ) {
758 891 100       8835 if( $ff4_properties{$tag}->{'value_flags'} ) {
759 103         457 return $ff4_properties{$tag}->{'value_flags'};
760             } else {
761 788         10873 return 0;
762             }
763             }
764             # default flags
765 0         0 return (VF_EMPTY | VF_LIST); # allow to be empty or list
766             }
767              
768             =head3 getTagType
769              
770             $flags = getTagType($tag);
771             if( $flags & T_NONE ) {
772             # do something about T_NONE tags
773             }
774              
775             This will return the flags set on this tag.
776              
777             =cut
778              
779              
780             sub getTagType {
781 407     407 1 3262 my $self = shift;
782 407         503 my $tag = shift;
783 407 100       1934 if( exists( $self->{'tags'}->{$tag}) ) {
    50          
784 41 100       150 if( $self->{'tags'}->{$tag}->{'type'} ) {
785 26         111 return $self->{'tags'}->{$tag}->{'type'};
786             }
787             } elsif( exists( $ff4_properties{$tag}) ) {
788 366 50       859 if( $ff4_properties{$tag}->{'type'} ) {
789 366         823 return $ff4_properties{$tag}->{'type'};
790             }
791             }
792             # default Type
793 15         29 return T_NONE; # allow to be anywhere
794             }
795              
796             =head3 getTagAttribute
797              
798             $flags = getTagAttribute($tag);
799             if( $flags & A_NONE ) {
800             # do something about about no attributes
801             }
802              
803             This will return the flags set on this tag.
804              
805             =cut
806              
807              
808             sub getTagAttribute {
809 376     376 1 984 my $self = shift;
810 376         481 my $tag = shift;
811 376 100       2819 if( exists($self->{'tags'}->{$tag}) ) {
    50          
812 27 100       91 if( $self->{'tags'}->{$tag}->{'attrib'} ) {
813 3         11 return $self->{'tags'}->{$tag}->{'attrib'};
814             }
815             } elsif( exists( $ff4_properties{$tag}) ) {
816 349 100       971 if( $ff4_properties{$tag}->{'attrib'} ) {
817 21         46 return $ff4_properties{$tag}->{'attrib'};
818             }
819             }
820 352         626 return A_NONE; # don't set inherit
821             }
822              
823             =head3 getTagValueType
824              
825             $valuetype = getTagValueType($tag);
826             if( $flags & V_TEXT ) {
827             # do something about text
828             }
829              
830             This will return the flags set on this tag.
831              
832             =cut
833              
834              
835             sub getTagValueType {
836 2155     2155 1 3102 my $self = shift;
837 2155         3072 my $tag = shift;
838 2155 100       15650 if( exists( $self->{'tags'}->{$tag}) ) {
    50          
839 181 50       549 if( $self->{'tags'}->{$tag}->{'value'} ) {
840 181         742 return $self->{'tags'}->{$tag}->{'value'};
841             }
842             } elsif( exists( $ff4_properties{$tag}) ) {
843 1974 50       6520 if( $ff4_properties{$tag}->{'value'} ) {
844 1974         6555 return $ff4_properties{$tag}->{'value'};
845             }
846             }
847 0         0 return V_TEXT; # allows and preserves any string
848             }
849              
850              
851             =head2 Navigation
852              
853             =head3 nextGame
854              
855             $sgf->nextGame;
856              
857             Sets the node pointer to the next game in the Collection. If the current
858             game is the last game then returns 0 otherwise 1.
859              
860             =cut
861              
862             sub nextGame {
863 0     0 1 0 my $self = shift;
864 0         0 $self->_clear;
865 0         0 $self->Debug("nextGame( )");
866 0         0 my $lastGame = @{$self->{'collection'}} - 1;
  0         0  
867 0         0 my $curGame = $self->{'game'}; # first element is the address game num
868 0 0       0 if( $curGame >= $lastGame ) { # on last game
869 0         0 $self->Warn("nextGame( ): FAILED\t\tCurrently last game in collection");
870 0         0 return 0;
871             } else {
872 0         0 $self->{'game'}++;
873 0         0 $self->gotoRoot;
874 0         0 return 1;
875             }
876             }
877              
878             =head3 prevGame;
879              
880             $sgf->prevGame;
881              
882             Sets the node pointer to the prevoius game in the Collection. If the current
883             game is the first game then returns 0 otherwise 1.
884              
885             =cut
886              
887              
888             sub prevGame {
889 0     0 1 0 my $self = shift;
890 0         0 $self->_clear;
891 0         0 $self->Debug("prevGame( )");
892 0         0 my $curGame = $self->{'game'}; # first element is the address game num
893 0 0       0 if( $curGame <= 0 ) { # on first game
894 0         0 $self->Warn("nextGame( ): FAILED\t\tCurrently first game in collection");
895 0         0 return 0;
896             } else {
897 0         0 $self->{'game'}--;
898 0         0 $self->gotoRoot;
899 0         0 return 1;
900             }
901             }
902              
903             =head3 game
904              
905             $sgf->game; # returns the game number
906             $sgf->game($number); # sets the game to $number
907              
908             =cut
909              
910             sub game {
911 6     6 1 7 my $self = shift;
912 6         9 my $game = shift;
913 6         15 $self->_clear;
914 6         20 $self->Debug("game( $game )");
915              
916 6 50       19 if( defined $game ) {
917 6 50 33     19 unless($game >= 0 and $game < @{$self->{'collection'}} ) {
  6         70  
918 0         0 $self->Warn( "game( $game ): FAILED\t\t$game does not exist");
919 0         0 return 0;
920             }
921 6         16 $self->{'game'} = $game;
922 6         21 $self->gotoRoot;
923 6         9 return 1;
924             } else {
925 0         0 return scalar @{$self->{'collection'}};
  0         0  
926             }
927             }
928              
929              
930             =head3 gotoRoot
931              
932             $sgf->gotoRoot;
933              
934             This will move the pointer to the root node of the game tree.
935              
936             =cut
937              
938             sub gotoRoot {
939 33     33 1 70 my $self = shift;
940 33         76 $self->_clear;
941 33         74 $self->Debug("gotoRoot( )");
942             #$self->{'parents'} = [ $self->{'collection'}->[$self->{'game'}] ];
943 33         99 $self->{'address'} = [$self->{'game'}];
944 33         127 $self->{'node'} = $self->{'collection'}->[$self->{'game'}];
945             }
946              
947             =head3 next
948              
949             $sgf->next;
950              
951             Moves the node pointer ahead one node. If there are variations it will move
952             down the main tree path.
953              
954             Returns 0 if it is the last node, otherwise 1
955              
956             =cut
957              
958             sub next {
959 43     43 1 1463 my $self = shift;
960 43         129 $self->_clear;
961 43         147 $self->Debug("next( )");
962 43         117 return $self->gotoBranch(0);
963             }
964              
965             =head3 prev
966              
967             $sgf->prev;
968              
969             Moves the node pointer back one node. Will move back out of
970             variations.
971              
972             Returns 0 if root node of tree
973              
974             =cut
975              
976             sub prev {
977 13     13 1 19 my $self = shift;
978 13         30 $self->_clear;
979 13         46 $self->Debug("prev( )");
980 13 50       34 if( $self->{'node'}->{'parent'} ) { # if parent exist
981 13         21 $self->{'node'} = $self->{'node'}->{'parent'};
982 13         14 pop @{$self->{'address'}};
  13         20  
983 13         32 return 1;
984             } else { # you are at the root
985 0         0 $self->Warn("prev( ):\t\tYou are at the root");
986 0         0 return 0;
987             }
988             }
989              
990             =head3 branches
991              
992             $sgf->branches;
993              
994             Returns the number of variations for the next move. If there is only the
995             main game path then it will return 1, if there are no more moves left in the
996             branch it will return 0.
997              
998             =cut
999              
1000             sub branches {
1001 180     180 1 220 my $self = shift;
1002 180         505 $self->_clear;
1003 180         634 $self->Debug("branches( )");
1004 180         217 return scalar @{$self->{'node'}->{'branches'}};
  180         947  
1005             }
1006              
1007             =head3 gotoBranch
1008              
1009             $sgf->gotoBranch($n);
1010              
1011             Goes to the first node of the specified Variation. If it returns 4
1012             that means that there is variations C<0..3>,
1013              
1014             Returns 1 on success and 0 on Failure.
1015              
1016             =cut
1017              
1018             sub gotoBranch {
1019 119     119 1 176 my $self = shift;
1020 119         137 my $n = shift;
1021 119         260 $self->_clear;
1022 119         398 $self->Debug("gotoBranch( $n )");
1023 119 50       349 if( not defined $n ) { return 0;}
  0         0  
1024 119 100 66     731 if( $n < $self->branches and $n >= 0) {
    50          
1025 112         249 $self->{'node'} = $self->{'node'}->{'branches'}->[$n];
1026 112         138 push @{$self->{'address'}}, $n;
  112         331  
1027 112         471 return 1;
1028             } elsif( $n == 0 ) {
1029 7         23 $self->Warn("gotoBranch( $n ):\t\tNo more moves");
1030 7         35 return 0;
1031             } else {
1032 0         0 $self->Warn("gotoBranch( $n ):\t\tInvalid Branch");
1033 0         0 return 0;
1034             }
1035             }
1036              
1037             =head3 getAddress
1038              
1039             my $address = $sgf->getAddress;
1040              
1041             # some movement
1042            
1043             $sgf->goto($address);
1044              
1045             This function returns an address of your location in the sgf object. It can then be latter
1046             recalled by using the goto method.
1047              
1048             =cut
1049              
1050             sub getAddress {
1051 6     6 1 7 my $self = shift;
1052 6         11 $self->Debug("getAddress( ):\t(". join( ", ", @{$self->{'address'}}) . ")");
  6         34  
1053 6         8 return [@{$self->{'address'}}];
  6         19  
1054             }
1055              
1056             =head3 goto
1057              
1058            
1059             goto will recall a position inside of an sgf object. Use getAddress returns an address.
1060              
1061             =cut
1062              
1063             sub goto {
1064 6     6 1 9 my $self = shift;
1065 6         17 $self->Debug("goto( " . join( ", ", @{$_[0]}) . " )");
  6         25  
1066 6         9 my( @add ) = @{shift @_};
  6         17  
1067 6         19 $self->game(shift @add);
1068              
1069 6         11 for(@add) {
1070 6         13 $self->gotoBranch($_);
1071             }
1072 6         30 return 1;
1073             }
1074              
1075             ###
1076             #
1077             # TODO: add getAddress and goto address
1078             # the address could be a sequence of variation numbers
1079             # followed by a node number.
1080             # [var_num ...] node_num
1081              
1082              
1083             #######
1084             #
1085             # SGF Manipulation needs restructuring so that it is easier to use, and can be
1086             # used in conjunction with navigation functions.
1087             #
1088             #
1089             # The public functions should be as follows:
1090             # addGame
1091             # removeGame
1092             # addNode
1093             # removeNode
1094             # removeBranch
1095             #
1096             # The movement functions should be
1097             # nextGame
1098             # prevGame
1099             # next
1100             # prev
1101             # getVariations
1102             # gotoBranch
1103             #
1104             # movement and manipulation functions should be independant of
1105             # internal storage structure.
1106              
1107             =head2 SGF Manipulation
1108              
1109             =head3 addGame
1110              
1111             $self->addGame;
1112              
1113             This will add a new game to the collection with the root node added. The
1114             current node pointer will be set to the root node of the new game.
1115              
1116             Returns true on success.
1117              
1118             =cut
1119              
1120             sub addGame {
1121 8     8 1 21 my $self = shift;
1122 8         24 $self->_clear;
1123 8         22 $self->Debug("addGame( )");
1124 8         29 my $newGame = _newNode();
1125 8         18 push @{$self->{'collection'}}, $newGame;
  8         25  
1126 8         17 $self->{'game'} = @{$self->{'collection'}} - 1;
  8         25  
1127 8         18 $self->{'node'} = $newGame;
1128 8         239 $self->gotoRoot();
1129 8 50       30 if( $self->Fatal ) {
1130 0         0 return 0;
1131             } else {
1132 8         36 return 1;
1133             }
1134             }
1135              
1136             =head3 addNode
1137              
1138             $sgf->addNode;
1139              
1140             Adds a node into the game tree. if there is already a continuation of
1141             the branch, then it will add a variation at this point. The node pointer
1142             will be set to the new node.
1143              
1144             Returns 1 on success and 0 on Failure.
1145              
1146             =cut
1147              
1148             sub addNode {
1149 39     39 1 61 my $self = shift;
1150 39         103 $self->_clear;
1151 39         81 $self->Debug("addNode( )");
1152 39         107 my $node = _newNode($self->{'node'}); # use current node as parent
1153             # add new node to branches of current
1154 39         122 my $variations = $self->branches;
1155 39         108 $self->{'node'}->{'branches'}->[$variations] = $node;
1156             # move to new position
1157 39         133 return $self->gotoBranch($variations);
1158             }
1159              
1160              
1161             =head3 removeNode
1162              
1163             $sgf->removeNode;
1164              
1165             Removes current node from tree if it has no sub nodes. If removed
1166             calls C<$sgf->prev> node.
1167              
1168             Returns 1 on success and 0 on Failure.
1169              
1170             =cut
1171              
1172             sub removeNode {
1173 1     1 1 3 my $self = shift;
1174 1         5 $self->_clear;
1175 1         4 $self->Debug("removeNode( )");
1176 1 50       2 if( @{$self->{'node'}->{'branches'}} ) { #
  1         5  
1177 0         0 $self->Warn("removeNode( ): FAILED\t\tCan not remove, since moves after which need to be removed.");
1178 0         0 return 0;
1179             } else {
1180             # remove current node and move to parent.\
1181             # save current node
1182             # goto prev
1183             # look at branches for saved node and remove
1184 1         3 my $rem = $self->{'node'};
1185 1         5 $self->prev;
1186 1         1 for( my $i = 0; $i < @{$self->{'node'}->{'branches'}};$i++) {
  2         9  
1187 2 100       21 if( $rem == $self->{'node'}->{'branches'}->[$i] ) {
1188 1         2 splice @{$self->{'node'}->{'branches'}}, $i, 1;
  1         5  
1189 1         9 return 1;
1190             }
1191             }
1192 0         0 $self->Fatal("removeNode( ):\t\tLogical failure. Node to be removed Does Not Exist");
1193 0         0 return 0;
1194             }
1195             }
1196              
1197             =head3 property
1198              
1199             my( @tags ) = $sgf->property;
1200             my $array_ref = $sgf->property( $value );
1201             my $didSave = $sgf->property( $value , @values );
1202              
1203             This is used to read and set properties on the current node. Will prevent T_MOVE
1204             and T_SETUP types from mixing. Will prevent writing T_ROOT tags to any location
1205             other then the root node. Will Lists from being stored in non list tags. Will
1206             prevent invalid structures from being stored.
1207              
1208             If no options are given it will return all the tags set on this node. Inherited
1209             tags will only be returned if they were set on this node.
1210              
1211             =cut
1212              
1213             # returns 0 on error
1214             # returns 1 on successful set
1215             # returns $arrref on successful get
1216             sub property {
1217 139     139 1 32150 my $self = shift;
1218 139         298 $self->_clear;
1219 139         696 $self->Debug("property( " . join( ", ", @_) .")");
1220 139         205 my $tag = shift;
1221 139         208 my( @values ) = @_;
1222 139 100       410 if( not defined $tag ) {
    100          
1223             # return only tags on this node
1224 5         6 return keys %{$self->{'node'}->{'tags'}};
  5         23  
1225             } elsif( @values == 0 ) {
1226             #get
1227 133         324 return $self->getProperty($tag);
1228             } else {
1229             #set
1230 1         5 return $self->setProperty($tag,@values);
1231             }
1232             }
1233              
1234             =head3 getProperty
1235              
1236             my $array_ref = $sgf->getProperty($tag, $isStrict);
1237             if( $array_ref ) {
1238             # sucess
1239             foreach my $value( @$array_ref ) {
1240             # do something
1241             }
1242             } else {
1243             # failure
1244             }
1245              
1246             Will fetch the the $tag value stored in the current node.
1247              
1248             $isStrict is for fetching inherited tags, if set it will only return an
1249             inherited tag if it is actually set on that node.
1250              
1251             =cut
1252              
1253             sub getProperty {
1254 248     248 1 962 my $self = shift;
1255 248         607 $self->_clear;
1256 248         1075 $self->Debug("getProperty( " . join( ", ", @_) .")");
1257 248         359 my $tag = shift;
1258 248         1537 my $isStrict = shift;
1259 248         638 my $attri = $self->getTagAttribute($tag);
1260              
1261 248 100 100     886 if( $attri == A_INHERIT and not $isStrict) {
1262 11         21 my $node = $self->{'node'};
1263             {
1264 11 100       13 if( exists $node->{'tags'}->{$tag} ) {
  19 100       53  
1265 10         37 return $node->{'tags'}->{$tag};
1266             } elsif($node->{'parent'}) {
1267 8         9 $node = $node->{'parent'};
1268 8         9 redo;
1269             }
1270             }
1271 1         6 $self->Warn( "getProperty( $tag ): FAILED\t\tInherited $tag is not set" );
1272 1         5 return 0;
1273             } else {
1274 237 100       673 if( exists $self->{'node'}->{'tags'}->{$tag} ) {
1275 115         444 return $self->{'node'}->{'tags'}->{$tag};
1276             } else {
1277             # non existent $tag
1278 122         3163 $self->Warn( "getProperty( $tag ): FAILED\t\t$tag is not set" );
1279 122         277 return 0;
1280             }
1281             }
1282             }
1283              
1284             =head3 setProperty
1285              
1286             fail() unless $sgf->setProperty($tag,@values);
1287              
1288             Sets the the $tag value of the current node to @values. This method does
1289             a series of sanity checks before attempting to write. It will fail if any
1290             of the following are true:
1291              
1292             =over
1293              
1294             =item @values > 0 and is not a list
1295              
1296             =item $tag is of type T_ROOT but the current node is not the root node
1297              
1298             =item $tag is a T_MOVE or T_SETUP and the other type is already present in the node
1299              
1300             =item @values are invalid type values
1301              
1302             =item unseting a value that is not set.
1303              
1304             =back
1305              
1306             If @values is not passed then it will remove the property from the node.
1307             This is not the same as setting to a empty value.
1308              
1309             $sgf->setProperty($tag); # will unset the $tag
1310             $sgf->setProperty($tag, "" ); # will set to an empty value
1311              
1312             =cut
1313              
1314             sub setProperty {
1315 128     128 1 164 my $self = shift;
1316 128         259 $self->_clear;
1317 128         582 $self->Debug("setProperty( " . join( ", ", @_) .")");
1318 128         589 my $tag = shift;
1319 128         1312 my( @values ) = @_;
1320 128 100       857 my $isUnSet = (scalar @values == 0) ? 1 : 0; # is unset if empty
1321              
1322 128         300 my $ttype = $self->getTagType($tag);
1323 128         589 my $vtype = $self->getTagValueType($tag);
1324 128         3859 my $flags = $self->getTagFlags($tag);
1325 128         248 my $attri = $self->getTagAttribute($tag);
1326 128         1221 my $isComposable = $self->_maybeComposed($tag);
1327              
1328              
1329 128 100       672 if( $isUnSet ) {
1330 5 50       15 if( exists $self->{'node'}->{'tags'}->{$tag} ) {
1331 5         10 delete $self->{'node'}->{'tags'}->{$tag};
1332 5         19 return 1;
1333             } else {
1334 0         0 $self->Warn("setProperty( \"$tag\", \"". join('", "',@values) . "\" ): FAILED\t\tCan't unset inherited $tag when not set at this node\n");
1335 0         0 return 0;
1336             }
1337             }
1338              
1339              
1340             # reasons to not set the property
1341             # set list values only if VF_LIST
1342             # TODO: VF_LIST && VF_EMPTY????
1343 123 50 66     380 if( @values > 1 and not $flags & VF_LIST ) {
1344 0         0 $self->Warn("setProperty( \"$tag\", \"". join('", "',@values) . "\" ): FAILED\t\tCan't set list for non VF_LIST: ($tag, $flags : " .
1345             join( ":", VF_EMPTY, VF_LIST, VF_OPT_COMPOSE) . ")");
1346 0         0 return 0;
1347             }
1348             # can set T_ROOT if you are at root
1349              
1350 123 50 66     410 if( $ttype == T_ROOT and (0 != $self->{'node'}->{'parent'}) ) {
1351 0         0 $self->Warn("setProperty( \"$tag\", \"". join('", "',@values) . "\" ): FAILED\t\tCan't set T_ROOT($tag) when not at root");
1352 0         0 return 0;
1353             }
1354             # don't set T_MOVE or T_SETUP if other is present
1355             # ASSUMPTION: No Inherited property is a T_MOVE or T_SETUP
1356 123         172 my $tnode = undef;
1357 123         150 foreach( keys %{$self->{'node'}->{'tags'}} ) {
  123         595  
1358 272         627 my $tag_type = $self->getTagType($_);
1359 272 100 100     7909 if( $tnode ) {
    100          
1360 29 50 66     284 if( ($tnode == T_SETUP and $tag_type == T_MOVE)
      66        
      33        
1361             or ($tnode == T_MOVE and $tag_type == T_SETUP) ) {
1362 0         0 $self->Warn("setProperty( \"$tag\", \"". join('", "',@values) . "\" ): FAILED\t\tCan't mix T_SETUP and T_MOVES" );
1363 0         0 return 0;
1364             }
1365             } elsif( ($tag_type == T_MOVE or $tag_type == T_SETUP) ) {
1366 48         114 $tnode = $tag_type;
1367             }
1368             }
1369             # don't set invalid structures
1370 123 50       368 if(not $isUnSet ) {
1371 123         195 foreach( @values ) {
1372             # check compose
1373 134 50 66     366 if( $self->isComposed($_) and not $isComposable ) {
1374 0         0 $self->Warn("setProperty( \"$tag\", \"". join('", "',@values) . "\" ): FAILED\t\tFound Composed value when $tag does not allow it");
1375 0         0 return 0;
1376             }
1377 134 50       408 unless($self->_tagCheck($tag,0, $_)){
1378 0         0 $self->Warn("setProperty( \"$tag\", \"". join('", "',@values) . "\" ): FAILED\t\tCheck Failed");
1379 0         0 return 0;
1380             }
1381             }
1382             }
1383             # If I got here then it is safe to do some damage
1384              
1385             # if inherit use other tree
1386              
1387 123         1682 $self->{'node'}->{'tags'}->{$tag} = [@values];
1388 123         447 return 1;
1389             }
1390              
1391             =head2 Value Type Functions
1392              
1393             =head3 compose
1394              
1395             ($pt1, $pt2) = $sgf->compose($compose);
1396             $compose = $sgf->compose($pt1,$pt2);
1397              
1398             Used for creating and breaking apart composed values. If you will be setting
1399             or fetching a composed value you will be needing this function to breack it
1400             apart.
1401              
1402             =cut
1403              
1404             sub compose {
1405 27     27 1 50 my $self = shift;
1406 27         65 $self->_clear;
1407 27         46 my $cop1 = shift;
1408 27 100       77 if( $self->isComposed($cop1) ) {
1409 10         39 return @$cop1;
1410             } else {
1411 17         30 my $cop2 = shift;
1412 17         149 return bless [$cop1,$cop2], 'Games::SGF::compose';
1413             }
1414             }
1415              
1416             =head3 isComposed
1417              
1418             if( $sgf->isComposed($compose) ) {
1419             ($val1, $val2) = $sgf->compose($compose);
1420             }
1421              
1422              
1423             This returns true if the value passed in is a composed value, otherwise
1424             false.
1425              
1426             =cut
1427              
1428             sub isComposed {
1429 363     363 1 474 my $self = shift;
1430 363         758 $self->_clear;
1431 363         714 my $val = shift;
1432 363         1698 return ref $val eq 'Games::SGF::compose';
1433             }
1434              
1435             =head3 isPoint
1436              
1437             =head3 isStone
1438              
1439             =head3 isMove
1440              
1441             =head3 isEmpty
1442              
1443             $self->isPoint($val);
1444              
1445             Returns true if $val is a point, move or stone.
1446              
1447             The determination for this is if it is blessing class matches
1448             C where type is point, stone, or move.
1449             So as long as read,write,check methods work with it there is no
1450             need for these methods to be overwritten.
1451              
1452             isEmpty will detect an empty tag.
1453              
1454             =cut
1455              
1456             sub isPoint {
1457 35     35 1 53 my $self = shift;
1458 35         127 $self->_clear;
1459 35         51 my $val = ref shift;
1460 35         151 return scalar $val =~ m/^Games::SGF::.*point$/;
1461             }
1462             sub isStone {
1463 9     9 1 16 my $self = shift;
1464 9         26 $self->_clear;
1465 9         22 my $val = ref shift;
1466 9         45 return scalar $val =~ m/^Games::SGF::.*stone$/;
1467             }
1468             sub isMove {
1469 98     98 1 123 my $self = shift;
1470 98         497 my $val = ref shift;
1471 98         8592 return scalar $val =~ m/^Games::SGF::.*move$/;
1472             }
1473             sub isEmpty {
1474 192     192 1 318 my $self = shift;
1475 192         369 my $val = ref shift;
1476 192         729 return scalar $val =~ m/^Games::SGF::.*empty$/;
1477             }
1478              
1479             =head3 point
1480              
1481             =head3 stone
1482              
1483             =head3 move
1484              
1485             $struct = $sgf->move(@cord);
1486             @cord = $sgf->move($struct);
1487              
1488             If a point, stone, or move is passed in, it will be broken into it's parts
1489             and returned. If the parts are passed in it will construct the internal
1490             structure which the parser uses.
1491              
1492             Will treat the outside format the same as the SGF value format. Thus will use
1493             the read and write callbacks for point,stone, and move.
1494              
1495             If the SGF representation is not what you desire then override these.
1496              
1497             =head3 empty
1498              
1499             Will return a empty value, which can be tested with isEmpty.
1500              
1501             =cut
1502              
1503             sub point {
1504 8     8 1 20 my $self = shift;
1505 8         20 $self->_clear;
1506 8 100       26 if( $self->isPoint($_[0]) ) {
1507 1         6 return $self->_typeWrite(V_POINT,$_[0]);
1508             } else {
1509 7         24 return $self->_typeRead(V_POINT, $_[0]);
1510             }
1511             }
1512             sub stone {
1513 6     6 1 14 my $self = shift;
1514 6         20 $self->_clear;
1515 6 100       27 if( $self->isStone($_[0]) ) {
1516 1         6 return $self->_typeWrite(V_STONE, $_[0] );
1517             } else {
1518 5         23 return $self->_typeRead(V_STONE, $_[0]);
1519             }
1520             }
1521             sub move {
1522 32     32 1 64 my $self = shift;
1523 32         70 $self->_clear;
1524 32 100       149 if( $self->isMove($_[0]) ) {
1525 1         6 return $self->_typeWrite(V_MOVE,$_[0]);
1526             } else {
1527 31         97 return $self->_typeRead(V_MOVE, $_[0]);
1528             }
1529             }
1530             sub empty {
1531 2     2 1 3 my $self = shift;
1532 2         8 $self->_clear;
1533 2         3 my $a = "1";
1534 2         16 return bless \$a, 'Games::SGF::empty';
1535             }
1536              
1537             =head2 Error and Diagnostic Methods
1538              
1539             =head3 Fatal
1540              
1541             =head3 Warn
1542              
1543             =head3 Debug
1544              
1545             $self->Fatal( 'Failed to Parse Something');
1546             @errors = $self->Fatal;
1547              
1548             $self->Warn( 'Some recoverable Error Occured');
1549             @warnings = $self->Warn;
1550              
1551             $self->Debug('I am doing something here');
1552             @debug = $self->Debug;
1553              
1554             These methods are used for storing human readable error messages, and
1555             testing if an error has occured.
1556              
1557             Fatal messages are set when there is a failure which can not be corrected,
1558             such as trying to move passed the last node in a branch, or parsing a bad
1559             SGF file.
1560              
1561             Warn messages are set when a failure occurs and it can give a good guess
1562             as to how to proceed. For example, a node can not have more then one a
1563             given property set, but if the tag is for a list it will assume that you
1564             ment to add that element onto the end of the list and spit out a warning.
1565              
1566             Debug messages are saved at various points in the program, these are mainly
1567             finding problems in module code (what is helpful for me to fix a bug).
1568              
1569             If called with no arguments it will return a list of all event strings
1570             currently on the stack.
1571              
1572             Otherwise it will push the arguments onto the event stack.
1573              
1574             =head3 Clear
1575              
1576             $self->Clear;
1577              
1578             This will empty all events in the stack. This is only needed by extension modules,
1579             which need to clear the stack.
1580              
1581             Each time the public methods are called (outside of Games::SGF) the
1582             event stacks will be cleared.
1583              
1584             =cut
1585              
1586              
1587             sub Fatal {
1588 77     77 1 1834 my $self = shift;
1589 77 100       247 if( not @_ ) {
1590 54         76 return @{$self->{'FatalErrors'}};
  54         205  
1591             }
1592 23         26 push @{$self->{'FatalErrors'}}, @_; # save messages
  23         67  
1593              
1594 23         57 my $str = "FATAL:\t".join( "\nFATAL:\t\t",@_);
1595 23 50       85 if( ref $self->{'Fatal'} eq 'CODE') {
    50          
1596 0         0 return $self->{'Fatal'}->($str);
1597             } elsif( $self->{'Fatal'} ) {
1598 0         0 croak($str);
1599             }
1600             }
1601              
1602             sub Warn {
1603 130     130 1 206 my $self = shift;
1604 130 50       279 if( not @_ ) {
1605 0         0 return @{$self->{'WarnErrors'}};
  0         0  
1606             }
1607 130         150 push @{$self->{'WarnErrors'}}, @_; # save messages
  130         339  
1608              
1609 130         372 my $str = "WARN:\t" . join( "\nWARN:\t\t",@_);
1610 130 50       913 if( ref $self->{'Warn'} eq 'CODE') {
    50          
1611 0         0 return $self->{'Warn'}->($str);
1612             } elsif( $self->{'Warn'} ) {
1613 0         0 carp($str);
1614             }
1615             }
1616              
1617             sub Debug {
1618 1869     1869 1 2719 my $self = shift;
1619 1869 50       4739 if( not @_ ) {
1620 0         0 return @{$self->{'DebugErrors'}};
  0         0  
1621             }
1622 1869         2034 push @{$self->{'DebugErrors'}}, @_; # save messages
  1869         4783  
1623              
1624 1869         5407 my $str = "Debug:\t " . join( "\nDebug:\t\t",@_);
1625 1869 50       10097 if( ref $self->{'Debug'} eq 'CODE') {
    50          
1626 0         0 return $self->{'Debug'}->($str);
1627             } elsif( $self->{'Debug'} ) {
1628 0         0 carp($str);
1629             }
1630             }
1631              
1632             sub Clear {
1633 299     299 1 448 my $self = shift;
1634 299         623 $self->{'FatalErrors'} = [];
1635 299         564 $self->{'WarnErrors'} = [];
1636 299         721 $self->{'DebugErrors'} = [];
1637             }
1638              
1639             #######################################
1640             #
1641             # INTERNAL METHODS BELOW
1642             #
1643             #######################################
1644              
1645             # removeVariation
1646             #
1647             # $sgf->removeVariation($n);
1648             #
1649             # This will remove the C<$n> variation from the branch. If you have
1650             # variations C<0..4> and ask it to remove variation C<1> then the
1651             # indexs will be C<0..3>.
1652             #
1653             # Returns 1 on sucess 0 on Failure.
1654              
1655             sub _newNode {
1656 47     47   68 my $parent = shift;
1657 47   100     154 $parent ||= 0;
1658             return {
1659 47         260 'parent' => $parent,
1660             'branches' => [],
1661             'tags' => {}
1662             };
1663             }
1664              
1665             # if the parents caller is not from Games::SGF* then call clear
1666             sub _clear {
1667 1526     1526   2693 my $self = shift;
1668 1526         8907 my $package = caller(1);
1669 1526 100       4395 if( $package =~ m/^Games::SGF/ ) {
1670 1227         2893 return 0;
1671             } else {
1672 299         687 $self->Clear;
1673 299         1348 return 1;
1674             }
1675             }
1676              
1677              
1678             sub _tagRead {
1679 139     139   179 my $self = shift;
1680 139         184 my $tag = shift;
1681 139         157 my $isSecond = shift;
1682 139         305 my( @values ) = @_;
1683 139         955 $self->Debug("_tagRead($tag, $isSecond," . join(", ",@values). ")");
1684              
1685             # composed
1686 139 100       347 if( @values > 1 ) {
1687 8         77 $values[0] = $self->_tagRead($tag,0,$values[0]);
1688 8         51 $values[1] = $self->_tagRead($tag,1,$values[1]);
1689 8         72 return $self->compose(@values);
1690             }
1691 131         290 my $type = $self->getTagValueType($tag);
1692 131 100       366 if( ref $type eq 'ARRAY' ) {
1693 6 100       19 $type = $type->[$isSecond ? 1 : 0];
1694             }
1695              
1696             # if empty just return empty
1697 131 100       309 if( $values[0] eq "" ) {
1698 6 100 33     22 if( $type == 1 ) {
    50          
    50          
1699 2         18 return $self->empty();
1700             } elsif( $self->getTagFlags($tag) & VF_EMPTY ) {
1701 0         0 return $self->empty();
1702             } elsif( not($type == V_POINT or $type == V_MOVE or $type == V_STONE ) ) {
1703 0         0 $self->Fatal("_tagRead($tag, $isSecond," . join(", ",@values). "): FAILED\t\tEmpty tag found where one should not be.");
1704 0         0 return 0;
1705             }
1706             }
1707 129         363 return $self->_typeRead($type,$values[0]);
1708              
1709             }
1710              
1711             sub _typeRead {
1712 172     172   263 my $self = shift;
1713 172         213 my $type = shift;
1714 172         238 my $text = shift;
1715              
1716 172         743 $self->Debug( "_typeRead($type,$text)");
1717             #return $text unless $type;
1718 172 50       1269 if($type == V_COLOR) {
    50          
    100          
    100          
    100          
    100          
    50          
    100          
    100          
    50          
1719 0 0       0 if( $text eq "B" ) {
    0          
1720 0         0 return C_BLACK;
1721             } elsif( $text eq "W" ) {
1722 0         0 return C_WHITE;
1723             } else {
1724 0         0 $self->Fatal("_typeRead( $type, '$text' ): FAILED\t\tInvalid COLOR: '$text'");
1725 0         0 return undef;
1726             }
1727             } elsif( $type == V_DOUBLE ) {
1728 0 0       0 if( $text eq "1" ) {
    0          
1729 0         0 return DBL_NORM;
1730             } elsif( $text eq "2" ) {
1731 0         0 return DBL_EMPH;
1732             } else {
1733 0         0 $self->Fatal("_typeRead( $type, '$text' ): FAILED\t\tInvalid Double: '$text'");
1734 0         0 return undef;
1735             }
1736             } elsif( $type == V_NUMBER) {
1737 28 50       138 if( $text =~ m/^[+-]?[0-9]+$/ ) {
1738 28         10346 return $text;
1739             } else {
1740 0         0 $self->Fatal("_typeRead( $type, '$text' ): FAILED\t\tInvalid NUMBER: '$text'");
1741 0         0 return undef;
1742             }
1743             } elsif( $type == V_REAL ) {
1744 18 50       206 if( $text =~ m/^[+-]?[0-9]+(\.[0-9]+)?$/ ) {
1745 18         70 return $text;
1746             } else {
1747 0         0 $self->Fatal("_typeRead( $type, '$text' ): FAILED\t\tInvalid REAL: '$text'");
1748 0         0 return undef;
1749             }
1750             } elsif( $type == V_TEXT ) {
1751 7         84 return $text;
1752             } elsif( $type == V_SIMPLE_TEXT ) {
1753             #TODO do some final processing
1754             # compact all whitespace
1755 17         57 return $text;
1756             } elsif( $type == V_NONE ) {
1757 0 0       0 if( $text ) {
1758 0         0 $self->Fatal("_typeRead( $type, '$text' ): FAILED\t\tInvalid NONE: '$text'");
1759             } else {
1760 0         0 return $self->empty();
1761             }
1762             # game specific
1763             } elsif( $type == V_POINT ) {
1764             #if sub then call it and pass $text in
1765 27 100       118 if($self->{'pointRead'}) {
1766 17         859 return $self->{'pointRead'}->($text);
1767             } else {
1768 10         68 return bless [$text], 'Games::SGF::point';
1769             }
1770             } elsif( $type == V_STONE ) {
1771 9 100       25 if($self->{'stoneRead'}) {
1772 2         7 return $self->{'stoneRead'}->($text);
1773             } else {
1774 7         104 return bless [$text], 'Games::SGF::stone';
1775             }
1776             } elsif( $type == V_MOVE ) {
1777 66 100       193 if($self->{'moveRead'}) {
1778 20         109 return $self->{'moveRead'}->($text);
1779             } else {
1780 46         613 return bless [$text], 'Games::SGF::move';
1781             }
1782             } else {
1783 0         0 $self->Fatal("_typeRead( $type, '$text' ): FAILED\t\tInvalid type: '$type'");
1784 0         0 return undef;
1785             }
1786             }
1787             # on V_TEXT and V_SIMPLE_TEXT auto escapes :, ], and \
1788             # there should be no need to worry abour composed escaping
1789             #
1790             # adjust to check composed values?
1791             sub _tagCheck {
1792 150     150   191 my $self = shift;
1793 150         199 my $tag = shift;
1794 150         192 my $isSecond = shift;
1795 150         180 my $struct = shift;
1796 150         682 $self->Debug("_tagCheck( $tag, $isSecond, $struct )");
1797              
1798             # composed
1799 150 100       348 if( $self->isComposed($struct) ) {
1800 8         25 my( @val ) = $self->compose($struct);
1801 8         55 $val[0] = $self->_tagCheck($tag,0,$val[0]);
1802 8         149 $val[1] = $self->_tagCheck($tag,1,$val[1]);
1803 8   33     134 return $val[0] && $val[1];
1804             }
1805              
1806 142         367 my $type = $self->getTagValueType($tag);
1807 142 100       359 if( ref $type eq 'ARRAY' ) {
1808 6 100       21 $type = $type->[$isSecond ? 1 : 0];
1809             }
1810             # if empty and VF_EMPTY return true unless point, move, or stone
1811 142 100       329 if( $self->isEmpty($struct) ) {
1812 2 50 0     9 if( $type == V_NONE ) {
    0          
    0          
1813 2         11 return 1;
1814             } elsif( $self->getTagFlags($tag) & VF_EMPTY ) {
1815             # return empty if not move stone or point
1816 0         0 return 1;
1817             } elsif(not( $type == V_POINT or $type == V_MOVE or $type == V_STONE ) ) {
1818 0         0 $self->Fatal("_tagCheck( $tag, $isSecond, $struct ): FAILED\t\tCheck failed with invalid string($tag, $struct)");
1819 0         0 return 0;
1820             }
1821             }
1822 140         416 return $self->_typeCheck($type,$struct);
1823             }
1824              
1825             sub _typeCheck {
1826 140     140   828 my $self = shift;
1827 140         161 my $type = shift;
1828 140         173 my $struct = shift;
1829              
1830 140         3861 $self->Debug( "_typeCheck($type,$struct)");
1831              
1832 140 50       988 if($type == V_COLOR) {
    50          
    100          
    100          
    100          
    100          
    50          
    100          
    100          
    50          
1833 0 0 0     0 if( $struct == C_BLACK or $struct == C_WHITE ) {
1834 0         0 return 1;
1835             } else {
1836 0         0 return 0;
1837             }
1838             } elsif( $type == V_DOUBLE ) {
1839 0 0 0     0 if( $struct == DBL_NORM or $struct == DBL_EMPH ) {
1840 0         0 return 1;
1841             } else {
1842 0         0 return 0;
1843             }
1844             } elsif( $type == V_NUMBER) {
1845 29 50       124 if( $struct =~ m/^[+-]?[0-9]+$/ ) {
1846 29         142 return 1;
1847             } else {
1848 0         0 return 0;
1849             }
1850             } elsif( $type == V_REAL ) {
1851 18 50       112 if( $struct =~ m/^[+-]?[0-9]+(\.[0-9]+)?$/ ) {
1852 18         161 return 1;
1853             } else {
1854 0         0 return 0;
1855             }
1856             } elsif( $type == V_TEXT ) {
1857             #TODO update
1858 14         62 return 1;
1859             } elsif( $type == V_SIMPLE_TEXT ) {
1860             #TODO update
1861 17         73 return 1;
1862             } elsif( $type == V_NONE ) {
1863 0 0       0 if( $struct ) {
1864 0         0 return 0;
1865             } else {
1866 0         0 return 1;
1867             }
1868             } elsif( $type == V_POINT ) {
1869 22 100       76 if($self->{'pointCheck'}) {
1870 19         70 return $self->{'pointCheck'}->($struct);
1871             }
1872             } elsif( $type == V_STONE ) {
1873 4 100       16 if($self->{'stoneCheck'}) {
1874 2         10 return $self->{'stoneCheck'}->($struct);
1875             }
1876             } elsif( $type == V_MOVE ) {
1877 36 100       95 if($self->{'moveCheck'}) {
1878 20         80 return $self->{'moveCheck'}->($struct);
1879             }
1880             } else {
1881 0         0 $self->Fatal( "_typeCheck($type,$struct): FAILED\t\tInvalid type: $type");
1882 0         0 return undef;
1883             }
1884             # maybe game specific stuff shouldn't be pass through
1885 21         97 return 1;
1886             }
1887             sub _tagWrite {
1888 52     52   61 my $self = shift;
1889 52         72 my $tag = shift;
1890 52         54 my $isSecond = shift;
1891 52         59 my $struct = shift;
1892              
1893 52         225 $self->Debug("tagWrite($tag, $isSecond, '$struct')");
1894             # composed
1895 52 100       117 if( $self->isComposed($struct) ) {
1896 2         6 my( @val ) = $self->compose($struct);
1897 2         12 $val[0] = $self->_tagWrite($tag,0,$val[0]);
1898 2         8 $val[1] = $self->_tagWrite($tag,1,$val[1]);
1899 2         9 return join ':', @val;
1900             }
1901              
1902 50         123 my $type = $self->getTagValueType($tag);
1903 50 100       121 if( ref $type eq 'ARRAY' ) {
1904 2 100       8 $type = $type->[$isSecond ? 1 : 0];
1905             }
1906             # if empty just return empty
1907 50 50 33     93 if( $self->isEmpty($struct) and ($self->getTagFlags($tag) & VF_EMPTY
      66        
1908             or $type == V_NONE) ) {
1909             # if still empty it is ment to be empty
1910 1         4 return "";
1911             }
1912 49         120 return $self->_typeWrite($type,$struct);
1913             }
1914             sub _typeWrite {
1915 52     52   63 my $self = shift;
1916 52         60 my $type = shift;
1917 52         72 my $struct = shift;
1918 52         54 my $text;
1919 52         192 $self->Debug("typeWrite($type,'$struct')");
1920 52 50       324 if($type == V_COLOR) {
    50          
    100          
    100          
    100          
    100          
    50          
    100          
    100          
    50          
1921 0 0       0 if( $struct == C_BLACK ) {
    0          
1922 0         0 return "B";
1923             } elsif( $struct == C_WHITE ) {
1924 0         0 return "W";
1925             } else {
1926 0         0 $self->Fatal("typeWrite($type,'$struct'): FAILED\t\tInvalid V_COLOR '$struct'");
1927 0         0 return undef;
1928             }
1929             } elsif( $type == V_DOUBLE ) {
1930 0 0       0 if( $struct == DBL_NORM ) {
    0          
1931 0         0 return "1";
1932             } elsif( $struct == DBL_EMPH ) {
1933 0         0 return "2";
1934             } else {
1935 0         0 $self->Fatal("typeWrite($type,'$struct'): FAILED\t\tInvalid V_DOUBLE '$struct'");
1936 0         0 return undef;
1937             }
1938             } elsif( $type == V_NUMBER) {
1939 14         68 return sprintf( "%d", $struct);
1940             } elsif( $type == V_REAL ) {
1941 8         70 return sprintf( "%f", $struct);
1942             } elsif( $type == V_TEXT ) {
1943 1         4 $struct =~ s/([:\]\\])/\\$1/sg;
1944 1         76 return $struct;
1945             } elsif( $type == V_SIMPLE_TEXT ) {
1946 7         11 $struct =~ s/([:\]\\])/\\$1/sg;
1947 7         22 return $struct;
1948             } elsif( $type == V_NONE ) {
1949 0         0 return "";
1950             } elsif( $type == V_POINT ) {
1951 7 100       24 if($self->{'pointWrite'}) {
1952 6         18 return $self->{'pointWrite'}->($struct);
1953             } else {
1954 1         8 return $struct->[0];
1955             }
1956             } elsif( $type == V_STONE ) {
1957 1 50       4 if($self->{'stoneWrite'}) {
1958 0         0 return $self->{'stoneWrite'}->($struct);
1959             } else {
1960 1         7 return $struct->[0];
1961             }
1962             } elsif( $type == V_MOVE ) {
1963 14 100       45 if($self->{'moveWrite'}) {
1964 9         27 return $self->{'moveWrite'}->($struct);
1965             } else {
1966 5         22 return $struct->[0];
1967             }
1968             } else {
1969 0         0 $self->Fatal("typeWrite($type,'$struct'): FAILED\t\tInvalid type '$type'");
1970 0         0 return undef;
1971             }
1972             # return $struct;
1973             }
1974              
1975              
1976             sub _maybeComposed {
1977 920     920   1663 my $self = shift;
1978 920         2502 my $prop = shift;
1979 920 100 100     5902 if( ref $self->getTagValueType($prop) eq 'ARRAY'
1980             or $self->getTagFlags($prop) & VF_OPT_COMPOSE ) {
1981 181         1524 return 1;
1982             } else {
1983 739         2316 return 0;
1984             }
1985             }
1986             sub _isSimpleText {
1987 354     354   449 my $self = shift;
1988 354         512 my $prop = shift;
1989 354         1831 my $part = shift;
1990 354         653 my $type = $self->getTagValueType($prop);
1991 354 100       783 if( $self->_maybeComposed($prop) ) {
    100          
1992 80 100       225 if( ref $type eq 'ARRAY' ) {
    50          
1993 36 50       91 if( $type->[$part] == V_SIMPLE_TEXT ) {
1994             #carp "Return 1?";
1995 36         102 return 1;
1996             }
1997             } elsif( $type == V_SIMPLE_TEXT ) {
1998             #carp "Return 1?";
1999 0         0 return 1;
2000             }
2001             } elsif( $type == V_SIMPLE_TEXT ) {
2002             #carp "Return 1?";
2003 82         200 return 1;
2004             }
2005 236         672 return 0;
2006             }
2007             sub _isText {
2008 430     430   535 my $self = shift;
2009 430         547 my $prop = shift;
2010 430         483 my $part = shift;
2011 430         800 my $type = $self->getTagValueType($prop);
2012 430 100       2525 if( $self->_maybeComposed($prop) ) {
    100          
2013 80 100       223 if( ref $type eq 'ARRAY' ) {
    50          
2014 36 50       95 if( $type->[$part] == V_TEXT ) {
2015             #carp "Return 1?";
2016 0         0 return 1;
2017             }
2018             } elsif( $type == V_TEXT ) {
2019             #carp "Return 1?";
2020 0         0 return 1;
2021             }
2022             } elsif( $type == V_TEXT ) {
2023             #carp "Return 1?";
2024 76         178 return 1;
2025             }
2026 354         2137 return 0;
2027             }
2028              
2029              
2030             # property is added at start of new tag, variation, or end of variation
2031             sub _read {
2032 8     8   16 my $self = shift;
2033 8         17 my $text = shift;
2034             # Parse state
2035 8         62 my $lastChar = '';
2036 8         16 my $propertyName = '';
2037 8         26 my( @propertyValue ); # for current value
2038 8         18 my $propI = 0;
2039 8         92 my $lastName = '';
2040 8         26 my( @values ) = (); # composed entries are array refs
2041 8         21 my( @variations ) = ();
2042             # Parse flags
2043 8         20 my $inValue = 0;
2044 8         17 my $isEscape = 0;
2045 8         15 my $isFinal = 0;
2046 8         15 my $isStart = 0;
2047 8         14 my $isFirst = 0;
2048 8         22 my $inTree = 0;
2049 8         46 $self->Debug( "_read( ): SGF Dump\n\n$text\n\nEnd SGF Dump");
2050             # each gametree is a [\@sequence,\@gametress]
2051 8         55 for( my $i = 0; $i < length $text;$i++) {
2052             # ( start the game tree
2053             # ) end the game tree
2054             # ; start new node
2055             # [ start prop-value
2056             # ] end prop-value
2057             # a-Z not in [] are labels
2058 1028         5308 my $char = substr($text,$i,1);
2059 1028 100       3809 if( $inValue ) {
    100          
    100          
    100          
    100          
    100          
    50          
2060 561 100 66     7860 if( $char eq ']' and not $isEscape) {
    100 66        
    100          
    100          
2061             # error if not invalue
2062 123 50       264 unless( $inValue ) {
2063 0         0 $self->Fatal("_read(): FAILED\t\t Mismatched ']'");
2064             }
2065 123         622 $self->Debug("_read( ):\t\t\tAdding Property: '$propertyName' "
2066             ."=> '$propertyValue[$propI]'");
2067            
2068 123         514 my $val = $self->_tagRead($propertyName, 0, @propertyValue);
2069 123 50       349 if( defined $val ) {
2070 123         211 push @values, $val;
2071             } else {
2072 0         0 return 0;
2073             }
2074 123         163 $lastName = $propertyName;
2075 123         175 $propertyName = '';
2076 123         251 @propertyValue = ("");
2077 123         146 $propI = 0;
2078 123         122 $inValue = 0;
2079 123         473 next;
2080             } elsif( $char eq ':' and $self->_maybeComposed($propertyName)) {
2081 8 50       36 if($propI >= 1 ) {
2082 0         0 $self->Fatal("_read( ): FAILED\t\tToo Many Compose components in value" );
2083 0         0 return undef;
2084             }
2085 8         14 $propI++;
2086 8         17 $propertyValue[$propI] = ""; # should be redundent
2087 8         35 next;
2088             } elsif( $self->_isText($propertyName, $propI) ) {
2089 76 100       509 if( $isEscape ) {
    100          
    100          
    100          
2090 3 100       13 if( $char eq "\n" ) {
    100          
2091 1         3 $char = ""; # no space
2092             } elsif( $char =~ /\s/ ) {
2093 1         3 $char = " "; # single space
2094             }
2095 3         13 $isEscape = 0;
2096             } elsif( $char eq '\\' ) {
2097 3         5 $isEscape = 1;
2098 3         5 $char = "";
2099             } elsif( $char =~ /\n/ ) {
2100             # makes sure newlines are saved when they are supposed to
2101 1         3 $char = "\n";
2102             } elsif( $char =~ /\s/ ) { # all other whitespace to a space
2103 6         12 $char = " ";
2104             }
2105             } elsif( $self->_isSimpleText($propertyName, $propI ) ) {
2106 118 100       618 if( $isEscape ) {
    100          
    100          
    100          
2107 2 100       8 if( $char eq "\n" ) {
    50          
2108 1         3 $char = ""; # no space
2109             } elsif( $char =~ /\s/ ) {
2110 0         0 $char = " "; # single space
2111             }
2112 2         5 $isEscape = 0;
2113             } elsif( $char eq '\\' ) {
2114 2         3 $isEscape = 1;
2115 2         3 $char = "";
2116             } elsif( $char =~ /\n/ ) {
2117 2         3 $char = " "; # remove all unescaped newlines
2118             } elsif( $char =~ /\s/ ) { # all whitespace to a space
2119 8         15 $char = " ";
2120             }
2121             }
2122 430         896 $propertyValue[$propI] .= $char;
2123             # outside of a value
2124             } elsif( $char eq '(' ) {
2125 14 100       55 if( @values ) {
2126             # TODO this should only be done if attribute is LIST
2127             # GETSTRICT
2128 3         9 my $old = $self->getProperty($lastName, 1);
2129 3 50       9 @values = (@$old, @values) if $old;
2130 3 50       15 return undef if not $self->setProperty($lastName, @values);
2131 3         6 @values = ();
2132             }
2133 14 100       55 if($inTree) {
2134 6         12 $self->Debug("_read()\t\t\t#### Starting GameTree ####");
2135 6         15 push @variations, $self->getAddress;
2136 6 50       15 if( not $self->addNode ) {
2137 0         0 return undef;
2138             }
2139             } else {
2140 8         23 $self->Debug("_read()\t\t\t#### Adding game to collection ####");
2141 8         13 $inTree = 1;
2142 8 50       42 if( not $self->addGame ) {
2143 0         0 return undef;
2144             }
2145             }
2146 14         26 $isStart = 1;
2147             } elsif( $char eq ')' ) {
2148 14 100       57 if( @values ) {
2149 11         43 my $old = $self->getProperty($lastName, 1);
2150 11 50       48 @values = (@$old, @values) if $old;
2151 11 50       39 return undef if not $self->setProperty($lastName, @values);
2152 11         28 @values = ();
2153             }
2154 14 100       37 if( not @variations ) {
2155 8         18 $inTree = 0;
2156             } else {
2157 6         15 $self->Debug("_read()\t\t\t#### Ending Game Tree ####");
2158 6         19 $self->goto(pop @variations);
2159             }
2160             } elsif( $char eq ';' ) {
2161             # $self->Message('DEBUG',"Adding Node\n");
2162 46 100       142 if( @values ) {
2163             # GETSTRICT
2164 32         112 my $old = $self->getProperty($lastName,1 );
2165 32 50       88 @values = (@$old, @values) if $old;
2166 32 50       506 return undef if not $self->setProperty($lastName, @values);
2167 32         78 @values = ();
2168             }
2169             # may be able to remove( addnode )
2170 46 50       195 if( not $inTree ) {
2171 0         0 $self->Fatal('Parse',"Attempted to start node outside"
2172             . "of GameTree: Failed");
2173 0         0 return undef;
2174             }
2175 46 100       166 if( $isStart ) {
    50          
2176 14         27 $isStart = 0;
2177             } elsif( not $self->addNode ) {
2178 0         0 return undef;
2179             }
2180             } elsif( $char eq '[' ) {
2181 123         190 $inValue = 1;
2182 123         156 $isFinal = 0;
2183             # handle tag types here
2184             # T_ROOT only when $current = $node
2185 123         129 $isFirst = 1;
2186 123 100       254 unless( $propertyName ) {
2187 9         21 $isFirst = 0;
2188 9         17 $propertyName = $lastName;
2189             }
2190             } elsif( $char =~ /\s/ ) {
2191             # catch all whitespace
2192             # to make sure it doesn't come in the middle of a
2193             # property name
2194 84 50       218 $isFinal = 1 if $propertyName;
2195             } elsif( $char =~ /[a-zA-Z]/ ) {
2196             # error if final
2197 186 100       4753 if( @values ) {
2198             # GETSTRICT
2199 68         202 my $old = $self->getProperty($lastName,1);
2200 68 100       155 @values = (@$old, @values) if $old;
2201 68 50       179 return undef if not $self->setProperty($lastName, @values);
2202 68         138 @values = ();
2203             }
2204 186 50       395 if( $isFinal ) {
2205 0         0 $self->Fatal( "_read( ): FAILED\t\tTag must have no spaces and must have a value" );
2206             }
2207 186         263 $propertyName .= $char;
2208 186         278 $lastName = "";
2209             } else {
2210 0         0 $self->Fatal("_read( ): FAILED\t\tUnknown condition with char '$char': FAILED" );
2211             # error
2212             }
2213 897         2471 $lastChar = $char;
2214             }
2215 8         46 return 1;
2216             }
2217              
2218             sub _write_tags {
2219 15     15   22 my $self = shift;
2220 15         18 my $hash = shift;
2221 15         19 my $text = "";
2222 15         45 foreach my $tag ( keys %$hash ) {
2223 45         53 my( @values ) = @{$hash->{$tag}};
  45         131  
2224 45         66 $text .= $tag;
2225 45 50       83 if( @values == 0 ) {
2226 0         0 $text .= "[]";
2227             } else {
2228 45         68 foreach my $val( @values ) {
2229 48         55 $text .= "[";
2230             # _type* take care of composed values now
2231             # add value
2232 48         124 $val = $self->_tagWrite($tag,0,$val);
2233 48 50       106 return undef if not defined $val;
2234 48         73 $text .= $val;
2235 48         99 $text .= "]"
2236             }
2237 45         105 $text .= "\n"; # add some white space to make it easier to read
2238             }
2239             }
2240 15         46 return $text;
2241             }
2242              
2243              
2244             sub _write {
2245 4     4   9 my $self = shift;
2246 4         7 my $node = shift;
2247 4 50       16 return "" unless $node;
2248 4         7 my $text = "(";
2249            
2250             # write all linear nodes
2251             # drops the leafs you need to handle 0 branchs
2252             #
2253             # if branches = 0 you are at a leave out and done
2254             #
2255             # if branches = 1 out and move to next node
2256             #
2257             # if branches > 1 out and recurse
2258             #
2259             #
2260             {
2261 4 50       6 if( $node ) {
  15         33  
2262 15         19 $text .= ";";
2263 15         46 $text .= $self->_write_tags($node->{'tags'});
2264             } else {
2265 0         0 last;
2266             }
2267 15 50       41 if( not $node->{'branches'} ) {
  15 100       50  
2268 0         0 last;
2269             } elsif( @{$node->{'branches'}} == 1 ) {
2270 11         14 $node = $node->{'branches'}->[0];
2271 11         20 redo;
2272             } else {
2273             # recurse for each branch
2274 4         6 foreach(@{$node->{'branches'}}) {
  4         14  
2275 2         8 $text .= $self->_write($_);
2276             }
2277 4         7 last;
2278             }
2279             }
2280              
2281 4         8 $text .= ")"; # finish branch
2282 4         5 $text .= "\n"; # white space for readablity
2283              
2284 4         15 return $text;
2285             }
2286              
2287             1;
2288              
2289             __END__