File Coverage

web/cgi-bin/yatt.lib/YATT/LRXML/NodeCursor.pm
Criterion Covered Total %
statement 235 297 79.1
branch 70 114 61.4
condition 26 45 57.7
subroutine 59 74 79.7
pod 0 51 0.0
total 390 581 67.1


line stmt bran cond sub pod time code
1             # -*- mode: perl; coding: utf-8 -*-
2             package YATT::LRXML::NodeCursor; # Location, Zipper?
3 6     6   8626 use strict;
  6         8  
  6         227  
4 6     6   23 use warnings FATAL => qw(all);
  6         10  
  6         232  
5              
6 6     6   20 use base qw(YATT::Class::Configurable);
  6         8  
  6         485  
7 6     6   24 use YATT::Fields qw(^tree ^cf_metainfo cf_path);
  6         7  
  6         37  
8             sub Path () {'YATT::LRXML::NodeCursor::Path'}
9              
10 6     6   26 use YATT::Util::Symbol;
  6         7  
  6         515  
11 6         278 use YATT::LRXML::Node qw(stringify_node
12             stringify_attlist
13             create_node
14             create_node_from
15 6     6   23 copy_array);
  6         8  
16              
17 6     6   22 use Carp;
  6         8  
  6         328  
18              
19             # XXX: Configurable に init と clone のプロトコルを…って、
20             # fields の中身に依存するから、やばいか。
21              
22 6     6   6697 BEGIN {
23             package YATT::LRXML::NodeCursor::Path;
24 6     6   21 use base qw(YATT::Class::ArrayScanner);
  6         7  
  6         554  
25 6     6   20 use YATT::Fields qw(cf_path cur_postype prev_postype);
  6         9  
  6         21  
26              
27 6     6   21 use YATT::LRXML::Node qw(node_type ATTRIBUTE_TYPE);
  6         7  
  6         251  
28              
29 6     6   24 use YATT::Util::Enum -prefix => 'POSTYPE_', qw(UNKNOWN ATTLIST BODY);
  6         7  
  6         45  
30              
31             sub init {
32 1519     1519   2610 my ($self, $array, $path, $index0) = splice @_, 0, 4;
33 1519   50     7430 $self->SUPER::init(array => $array
34             , index => ($index0 || 0)
35             + YATT::LRXML::Node::_BODY
36             , path => $path, @_)
37             ->after_next;
38             }
39              
40             sub clone {
41 11     11   18 my MY $orig = shift;
42 11         72 ref($orig)->new($orig->{cf_array}, $orig->{cf_path}
43             # XXX: To compensate init()
44             , $orig->{cf_index} - YATT::LRXML::Node::_BODY);
45             }
46              
47             sub parent {
48 204     204   190 my MY $path = shift; $path->{cf_path}
  204         424  
49             }
50              
51             sub after_next {
52 3307     3307   2805 (my MY $path) = @_;
53 3307         9863 return $path unless defined $path->{cf_index}
54 3307 100 66     6112 and $path->{cf_index} <= $#{$path->{cf_array}};
55 2130         3110 my $val = $path->{cf_array}->[$path->{cf_index}];
56 2130         2640 $path->{prev_postype} = $path->{cur_postype};
57 2130 100 100     6002 if (not defined $path->{cur_postype}
58             or $path->{cur_postype} == POSTYPE_ATTLIST) {
59 1439 100 100     3841 $path->{cur_postype} = ref $val && node_type($val) == ATTRIBUTE_TYPE
60             ? POSTYPE_ATTLIST : POSTYPE_BODY;
61             }
62             $path
63 2130         3444 }
64              
65             sub is_beginning {
66 527     527   482 (my MY $path) = @_;
67 527 100       1298 return 1 unless defined $path->{prev_postype};
68 301 50       581 return unless $path->{cur_postype} == POSTYPE_BODY;
69 301         1181 $path->{prev_postype} == POSTYPE_ATTLIST;
70             }
71             }
72              
73 1877     1877 0 4696 sub initargs {qw(tree)}
74              
75             sub new_opened {
76 414     414 0 675 my ($class, $tree) = splice @_, 0, 2;
77 414         1981 $class->new($tree, path => $class->Path->new($tree), @_);
78             }
79              
80             sub new_path {
81 6     6 0 7 my MY $self = shift;
82 6         43 $self->Path->new($self->{tree}, shift); # XXX: tree でいいの?
83             }
84              
85             sub clone_path {
86 6     6 0 8 my MY $self = shift;
87 6   33     41 my Path $path = shift || $self->{cf_path};
88 6 50       39 $self->Path->new($path->{cf_array}, $path ? $path->{cf_path} : undef);
89             }
90              
91             sub clone {
92 1286     1286 0 1371 (my MY $self, my ($path)) = @_;
93             # XXX: 他のパラメータは? 特に、継承先で足したパラメータ。
94 1286   66     4877 ref($self)->new($self->{tree}
95             , metainfo => $self->{cf_metainfo}
96             , path => ($path || ($self->{cf_path} ? $self->{cf_path}->clone
97             : undef)));
98             }
99              
100             sub variant_builder {
101 252     252 0 322 my MY $self = shift;
102 252         307 my Path $orig = $self->{cf_path};
103 252         214 my $variant = do {
104 252 100       482 if (@_) {
105 119         316 $self->create_node(@_);
106             } else {
107 133         479 $self->create_node_from($orig->{cf_array});
108             }
109             };
110 252         664 $self->adopter_for($variant, $orig->{cf_path});
111             }
112              
113             sub adopter_for {
114 255     255 0 400 (my MY $self, my ($array, $path)) = @_;
115 255   66     1161 $self->clone($self->Path->new($array, $path || $self->{cf_path}))
116             }
117              
118             sub add_node {
119 208     208 0 238 my MY $self = shift;
120 208         304 my Path $path = $self->{cf_path};
121 208         203 push @{$path->{cf_array}}, @_;
  208         440  
122 208         516 $self;
123             }
124              
125             sub create_attribute {
126 4     4 0 10 (my MY $self, my ($name)) = splice @_, 0, 2;
127 4         23 $self->create_node([attribute => 0], $name, @_);
128             }
129              
130             sub add_attribute {
131 6     6 0 16 (my MY $self, my ($name)) = splice @_, 0, 2;
132 6         24 $self->add_node(my $attr = $self->create_node([attribute => 0], $name, @_));
133 6         146 $attr;
134             }
135              
136             sub add_filtered_copy {
137 11     11 0 33 (my MY $self, my ($node, $filter, $primary_only)) = @_;
138 11 50       47 my $boundary = $primary_only ? 'is_primary_attribute' : 'readable';
139 11         40 for (; $node->$boundary(); $node->next) {
140 19         24 my @node = do {
141 19 100       56 if ($node->is_attribute) {
142 18 50       70 my ($sub, @rest) = ref $filter eq 'ARRAY' ? @$filter : $filter;
143 18         50 $sub->(@rest, $node->node_name, $node->current);
144             } else {
145 1         6 copy_array($node->current);
146             }
147             };
148 19 100       96 $self->add_node(@node) if @node;
149             }
150 11         58 $self;
151             }
152              
153             sub copy_from {
154 0     0 0 0 (my MY $clone, my MY $orig) = @_;
155 0         0 for (my $n = $orig->clone; $n->readable; $n->next) {
156 0         0 $clone->add_node(copy_array($n->current));
157             }
158 0         0 $clone;
159             }
160              
161             sub clone_filtered_by {
162 0     0 0 0 my MY $orig = shift;
163             # XXX: $orig を next してしまって、良いのか? clone した方が良いかも?
164 0         0 my MY $clone = $orig->variant_builder;
165 0         0 my ($hash, $all) = @_;
166 0 0       0 my $boundary = $all ? 'readable' : 'is_primary_attribute';
167 0         0 for (; $orig->$boundary(); $orig->next) {
168 0         0 my @name;
169 0 0 0     0 if ($orig->is_attribute and @name = $orig->node_path
      0        
170             and $hash->{$name[0]}) {
171 0         0 ${$hash->{$name[0]}} = $orig->current;
  0         0  
172 0         0 next;
173             }
174 0         0 $clone->add_node(copy_array($orig->current));
175             }
176 0         0 $clone;
177             }
178              
179             sub copy {
180 0     0 0 0 (my MY $self, my ($node)) = @_;
181 0         0 copy_array($node);
182             }
183              
184             sub copy_renamed {
185 0     0 0 0 (my MY $self, my ($name, $node)) = @_;
186 0 0       0 if (defined $name) {
187 0         0 $self->create_node_from
188             ($node, $name, copy_array(YATT::LRXML::Node::node_children($node)));
189             } else {
190 0         0 copy_array($node);
191             }
192             }
193              
194             sub make_wrapped {
195 10     10 0 22 (my MY $self, my ($type, $name)) = splice @_, 0, 3;
196 10         18 my Path $orig = $self->{cf_path};
197 10   50     56 my $wrap = $self->create_node($type || 'unknown', $name, $orig->{cf_array});
198 10         45 my $path = $self->Path->new($wrap, $orig);
199 10         35 ref($self)->new($self->{tree}
200             , metainfo => $self->{cf_metainfo}
201             , path => $path);
202             }
203              
204             sub filter_or_add_from {
205 119     119 0 375 (my MY $self, my ($node, $except, %opts)) = @_;
206 119 100       495 my $boundary = delete $opts{primary_only}
207             ? 'is_primary_attribute' : 'readable';
208 119 50       294 croak "Invalid option: " . join(",", keys %opts) if %opts;
209              
210 119         124 my ($name, @filtered);
211 119         405 for (; $node->$boundary(); $node->next) {
212 109 100 100     273 if ($node->is_attribute
      100        
213             and defined ($name = $node->node_name)
214             and exists $except->{$name}) {
215             # clone は?
216             # name を書き換えても良いのでは?
217 7         20 my $cur = $node->current;
218 7         12 push @filtered, do {
219 7 50       21 if (defined $except->{$name}) {
220 0         0 $self->copy_renamed($cur, $except->{$name});
221             } else {
222 7         20 $cur
223             }
224             };
225             } else {
226 102         195 $self->add_node($node->current);
227             }
228             }
229              
230 119         367 @filtered;
231             }
232              
233             sub open {
234 597     597 0 622 my MY $self = shift;
235 597         620 my $obj;
236 597 100 33     3239 unless (defined (my Path $path = $self->{cf_path})) {
    50          
237 6         16 $self->clone($self->new_path);
238             } elsif (not defined ($obj = $path->{cf_array}->[$path->{cf_index}])
239             or ref $obj ne 'ARRAY') {
240 0         0 $obj;
241             } else {
242             # 本当に clone が良いのだろうか?
243 591         2152 $self->clone($self->Path->new($obj, $path));
244             }
245             }
246              
247             # cursor 本体ではなく、path だけが欲しいときのために。
248             # ← open をカスタマイズしたい時に用いる。
249             sub open_path {
250 0     0 0 0 my MY $self = shift;
251 0 0       0 unless (defined (my Path $path = $self->{cf_path})) {
252 0         0 $self->new_path;
253             } else {
254 0         0 my $obj = $path->{cf_array}->[$path->{cf_index}];
255 0 0 0     0 die "Not an object!" unless defined $obj && ref $obj eq 'ARRAY';
256 0         0 $self->Path->new($obj, $path);
257             }
258             }
259              
260             sub can_open {
261 6     6 0 534 my MY $self = shift;
262 6         9 my Path $path = $self->{cf_path};
263 6         10 my $obj = $path->{cf_array}->[$path->{cf_index}];
264 6 50       37 defined $obj && ref $obj eq 'ARRAY';
265             }
266              
267             sub close {
268 3     3 0 33 my MY $self = shift;
269 3 50       6 if (my Path $parent = $self->{cf_path}->parent) {
270 3         3 $parent->{cf_index}++;
271 3         4 $self->clone($parent);
272             } else {
273             return
274 0         0 }
275             }
276              
277             sub parent {
278 201     201 0 207 my MY $self = shift;
279 201         524 $self->clone($self->{cf_path}->parent);
280             }
281              
282             sub can_close {
283 3     3 0 9 my MY $self = shift;
284 3         6 defined $self->{cf_path};
285             }
286              
287             BEGIN {
288 6     6   18 my @delegate_to_path =
289             qw(read
290             current
291             next
292             prev
293             array
294             );
295 6         14 foreach my $meth (@delegate_to_path) {
296 30         69 *{globref(__PACKAGE__, $meth)} = sub {
297 7394     7394   5520 my MY $self = shift;
298 7394 50       10311 return unless defined $self->{cf_path};
299 7394         14448 $self->{cf_path}->$meth(@_);
300 30         59 };
301             }
302              
303 6         11 my @delegate_and_self = qw(go_next);
304 6         8 foreach my $meth (@delegate_and_self) {
305 6         14 *{globref(__PACKAGE__, $meth)} = sub {
306 89     89   150 my MY $self = shift;
307 89 50       243 return unless defined $self->{cf_path};
308 89         371 $self->{cf_path}->$meth(@_);
309 89         282 $self;
310 6         26 };
311             }
312              
313 6         31 foreach my $meth (grep {/^(node|is)_/} YATT::LRXML::Node->exports) {
  252         283  
314 6     6   31 my $for_text = do {no strict 'refs'; \&{"text_$meth"}};
  6         18  
  6         979  
  132         90  
  132         82  
  132         395  
315 132         341 my $sub = YATT::LRXML::Node->can($meth);
316 132         188 *{globref(__PACKAGE__, $meth)} = sub {
317 4893     4893   4012 my MY $cursor = shift;
318 4893 100       5613 return unless $cursor->readable;
319 4809 100       6987 if (ref(my $value = $cursor->current)) {
320 4015         7815 $sub->($value, @_);
321             } else {
322 794         1533 $for_text->($value, @_);
323             }
324 132         319 };
325             }
326              
327 6         21 foreach my $meth (my @delegate_to_meta = qw(filename)) {
328 6         14 *{globref(__PACKAGE__, $meth)} = sub {
329 18     18   27 my MY $cursor = shift;
330 18 50       61 defined (my $meta = $cursor->{cf_metainfo})
331             or return;
332 18         72 $meta->$meth(@_);
333 6         14 };
334             }
335             }
336              
337             sub rewind {
338 0     0 0 0 my MY $self = shift;
339 0 0       0 if (my Path $path = $self->{cf_path}) {
340 0         0 $path->{cf_index} = YATT::LRXML::Node::_BODY;
341             }
342             $self
343 0         0 }
344              
345             sub readable {
346 7296     7296 0 5567 my MY $self = shift;
347 7296 50       19078 defined $self->{cf_path} && $self->{cf_path}->readable;
348             }
349              
350             # value, size は全体。
351             sub value {
352 0     0 0 0 my MY $self = shift;
353 0 0       0 unless (defined $self->{cf_path}) {
354 0         0 $self->{tree}
355             } else {
356 0         0 $self->{cf_path}->value;
357             }
358             }
359              
360             sub array_size {
361 156     156 0 191 my MY $self = shift;
362 156         187 YATT::LRXML::Node::node_size(do {
363 156 50       317 unless (defined (my Path $path = $self->{cf_path})) {
364 0         0 $self->{tree};
365             } else {
366 156         455 $path->{cf_array};
367             }
368             });
369             }
370              
371             sub size {
372 8     8 0 2967 my MY $self = shift;
373 8 100       34 unless (defined (my Path $path = $self->{cf_path})) {
    50          
    50          
374 6         22 YATT::LRXML::Node::node_size($self->{tree});
375             } elsif (not defined (my $obj = $path->{cf_array}->[$path->{cf_index}])) {
376 0         0 0
377             } elsif (ref $obj) {
378 2         7 YATT::LRXML::Node::node_size($obj);
379             } else {
380 0         0 1;
381             }
382             }
383              
384             sub has_parent {
385 211     211 0 206 my MY $self = shift;
386 211 50       457 defined (my Path $path = $self->{cf_path}) or return 0;
387 211         1025 $path->{cf_path}
388             }
389              
390             sub depth {
391 0     0 0 0 my MY $self = shift;
392 0         0 my $depth = 0;
393 0         0 while (defined (my Path $path = $self->{cf_path})) {
394 0         0 $depth++;
395             }
396 0         0 $depth;
397             }
398              
399             sub startline {
400 222     222 0 277 my MY $self = shift;
401 222         523 $self->metainfo->cget('startline');
402             }
403              
404             sub linenum {
405 222     222 0 294 (my MY $self, my ($offset_atstart)) = @_;
406 222         458 my $linenum = $self->startline;
407 222         292 my Path $path = $self->{cf_path};
408 222         229 my $offset = $offset_atstart;
409 222         407 while ($path) {
410 374         828 $linenum += $self->count_lines_of(map {
411 404   100     1576 $path->{cf_array}[$_]
412             } YATT::LRXML::Node::_BODY .. $path->{cf_index} - 1 + ($offset || 0));
413 404         497 $path = $path->{cf_path};
414 404         627 undef $offset;
415             }
416 222         529 $linenum;
417             }
418              
419             sub count_lines_of {
420             # XXX: 他でも使うように。
421 484     484 0 478 my ($pack) = shift;
422 484         380 my $sum = 0;
423 484         590 foreach my $item (@_) {
424 466 100       736 next unless defined $item;
425 463         332 $sum += do {
426 463 100       573 if (ref $item) {
427 152         379 YATT::LRXML::Node::node_nlines($item);
428             } else {
429 311         536 $item =~ tr:\n::;
430             }
431             };
432             }
433 484         636 $sum;
434             }
435              
436             sub node_is_beginning {
437 527     527 0 575 my MY $self = shift;
438 527 50       1062 my Path $path = $self->{cf_path} or return;
439 527         934 $path->is_beginning;
440             }
441              
442             sub node_is_end {
443 294     294 0 329 my MY $self = shift;
444 294 50       665 my Path $path = $self->{cf_path} or return;
445 294 50       529 defined $path->{cf_index} or return;
446 294         328 $path->{cf_index} >= $#{$path->{cf_array}};
  294         859  
447             }
448              
449             *stringify = *stringify_current; *stringify = *stringify_current;
450              
451             sub stringify_current {
452 28     28 0 43 my MY $self = shift;
453 28         54 my Path $path = $self->{cf_path};
454 28 100       93 unless (defined $path) {
    50          
455 9         32 stringify_node($self->{tree});
456             } elsif (ref (my $value = $path->current)) {
457 19         70 stringify_node($value);
458             } else {
459 0         0 $value;
460             }
461             }
462              
463             sub stringify_all {
464 0     0 0 0 my MY $self = shift;
465 0         0 my Path $path = $self->{cf_path};
466 0 0       0 unless (defined $path) {
467 0         0 stringify_node($self->{tree});
468             } else {
469 0         0 stringify_node($path->{cf_array});
470             }
471             }
472              
473             sub path_list {
474 6     6 0 5 my MY $self = shift;
475 6         8 my @path;
476 6 50       11 if (my Path $path = $self->{cf_path}) {
477             # XXX: 一、ずれてるじゃん、と。引くの?
478 6         7 do {
479 8         12 unshift @path, $path->{cf_index} - YATT::LRXML::Node::_BODY;
480 8         19 $path = $path->{cf_path};
481             } while $path;
482             }
483 6 50       24 wantarray ? @path : join ", ", @path;
484             }
485              
486             sub parse_typespec {
487 239     239 0 274 my MY $self = shift;
488 239         458 my ($head, @rest) = $self->node_children;
489 239 100       967 unless (defined $head) {
    50          
490             ()
491 130         224 } elsif ($head =~ s{^(\w+((?:\:\w+)*))?(?:([|/?!])(.*))?}{}s) {
492             # $1 can undef.
493 109 100 66     1059 ($1 && $2 ? [split /:/, $1] : $1
    100          
    100          
494             , default => @rest ? [defined $4 ? ($4) : (), @rest] : $4
495             , default_mode => $3)
496             } else {
497 0         0 (undef);
498             }
499             }
500              
501             sub next_is_body {
502 6     6 0 8 my MY $self = shift;
503 6 50       13 my Path $path = $self->{cf_path} or return;
504 6         10 my $next = $path->{cf_index} + 1;
505 6 100       8 return if $next >= @{$path->{cf_array}};
  6         22  
506 2         5 my $item = $path->{cf_array}[$next];
507 2 50       4 return unless defined $item;
508 2 100       8 return 1 unless ref $item;
509 1         4 not YATT::LRXML::Node::is_primary_attribute($item);
510             }
511              
512 74     74 0 230 sub text_is_attribute { 0 }
513 0     0 0 0 sub text_is_bare_attribute { 0 }
514 46     46 0 144 sub text_is_primary_attribute { 0 }
515 0     0 0 0 sub text_is_quoted_by_element { 0 }
516 0     0 0 0 sub text_node_size { 1 }
517 123     123 0 369 sub text_node_type { YATT::LRXML::Node::TEXT_TYPE }
518 3     3 0 9 sub text_node_body { shift }
519 548     548 0 2573 sub text_node_type_name { 'text' }
520 0     0 0   sub text_node_flag { 0 }
521 0     0 0   sub text_node_name { undef }
522             sub text_node_children {
523 0 0   0 0   if (ref $_[0]) {
524 0           YATT::LRXML::Node::node_children($_[0])
525             } else {
526 0           $_[0];
527             }
528             }
529              
530             1;