File Coverage

web/cgi-bin/yatt.lib/YATT/LRXML/NodeCursor.pm
Criterion Covered Total %
statement 234 297 78.7
branch 70 114 61.4
condition 25 45 55.5
subroutine 58 74 78.3
pod 0 51 0.0
total 387 581 66.6


line stmt bran cond sub pod time code
1             # -*- mode: perl; coding: utf-8 -*-
2             package YATT::LRXML::NodeCursor; # Location, Zipper?
3 6     6   9424 use strict;
  6         13  
  6         221  
4 6     6   30 use warnings qw(FATAL all NONFATAL misc);
  6         14  
  6         338  
5              
6 6     6   31 use base qw(YATT::Class::Configurable);
  6         12  
  6         639  
7 6     6   36 use YATT::Fields qw(^tree ^cf_metainfo cf_path);
  6         12  
  6         48  
8             sub Path () {'YATT::LRXML::NodeCursor::Path'}
9              
10 6     6   38 use YATT::Util::Symbol;
  6         12  
  6         668  
11 6         400 use YATT::LRXML::Node qw(stringify_node
12             stringify_attlist
13             create_node
14             create_node_from
15 6     6   30 copy_array);
  6         15  
16              
17 6     6   33 use Carp;
  6         9  
  6         440  
18              
19             # XXX: Configurable に init と clone のプロトコルを…って、
20             # fields の中身に依存するから、やばいか。
21              
22 0         0 BEGIN {
23             package YATT::LRXML::NodeCursor::Path;
24 6     6   30 use base qw(YATT::Class::ArrayScanner);
  6         12  
  6         668  
25 6     6   31 use YATT::Fields qw(cf_path cur_postype prev_postype);
  6         11  
  6         26  
26              
27 6     6   32 use YATT::LRXML::Node qw(node_type ATTRIBUTE_TYPE);
  6         11  
  6         322  
28              
29 6     6   31 use YATT::Util::Enum -prefix => 'POSTYPE_', qw(UNKNOWN ATTLIST BODY);
  6     0   14  
  6         63  
30              
31             sub init {
32 1519     1519   3330 my ($self, $array, $path, $index0) = splice @_, 0, 4;
33 1519   50     8990 $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   22 my MY $orig = shift;
42             ref($orig)->new($orig->{cf_array}, $orig->{cf_path}
43             # XXX: To compensate init()
44 11         59 , $orig->{cf_index} - YATT::LRXML::Node::_BODY);
45             }
46              
47             sub parent {
48 204     204   286 my MY $path = shift; $path->{cf_path}
49 204         616 }
50              
51             sub after_next {
52 3307     3307   4665 (my MY $path) = @_;
53             return $path unless defined $path->{cf_index}
54 3307 100 66     10517 and $path->{cf_index} <= $#{$path->{cf_array}};
  3307         15904  
55 2130         4499 my $val = $path->{cf_array}->[$path->{cf_index}];
56 2130         3684 $path->{prev_postype} = $path->{cur_postype};
57 2130 100 100     8256 if (not defined $path->{cur_postype}
58             or $path->{cur_postype} == POSTYPE_ATTLIST) {
59 1439 100 100     5821 $path->{cur_postype} = ref $val && node_type($val) == ATTRIBUTE_TYPE
60             ? POSTYPE_ATTLIST : POSTYPE_BODY;
61             }
62             $path
63 2130         5617 }
64              
65             sub is_beginning {
66 527     527   720 (my MY $path) = @_;
67 527 100       1851 return 1 unless defined $path->{prev_postype};
68 301 50       754 return unless $path->{cur_postype} == POSTYPE_BODY;
69 301         1660 $path->{prev_postype} == POSTYPE_ATTLIST;
70             }
71             }
72              
73 1877     1877 0 7778 sub initargs {qw(tree)}
74              
75             sub new_opened {
76 414     414 0 916 my ($class, $tree) = splice @_, 0, 2;
77 414         2985 $class->new($tree, path => $class->Path->new($tree), @_);
78             }
79              
80             sub new_path {
81 6     6 0 12 my MY $self = shift;
82 6         61 $self->Path->new($self->{tree}, shift); # XXX: tree でいいの?
83             }
84              
85             sub clone_path {
86 6     6 0 13 my MY $self = shift;
87 6   33     24 my Path $path = shift || $self->{cf_path};
88 6 50       40 $self->Path->new($path->{cf_array}, $path ? $path->{cf_path} : undef);
89             }
90              
91             sub clone {
92 1286     1286 0 2229 (my MY $self, my ($path)) = @_;
93             # XXX: 他のパラメータは? 特に、継承先で足したパラメータ。
94             ref($self)->new($self->{tree}
95             , metainfo => $self->{cf_metainfo}
96             , path => ($path || ($self->{cf_path} ? $self->{cf_path}->clone
97 1286   66     6583 : undef)));
98             }
99              
100             sub variant_builder {
101 252     252 0 445 my MY $self = shift;
102 252         478 my Path $orig = $self->{cf_path};
103 252         354 my $variant = do {
104 252 100       566 if (@_) {
105 119         450 $self->create_node(@_);
106             } else {
107 133         573 $self->create_node_from($orig->{cf_array});
108             }
109             };
110 252         825 $self->adopter_for($variant, $orig->{cf_path});
111             }
112              
113             sub adopter_for {
114 255     255 0 546 (my MY $self, my ($array, $path)) = @_;
115 255   66     1599 $self->clone($self->Path->new($array, $path || $self->{cf_path}))
116             }
117              
118             sub add_node {
119 208     208 0 315 my MY $self = shift;
120 208         326 my Path $path = $self->{cf_path};
121 208         293 push @{$path->{cf_array}}, @_;
  208         577  
122 208         616 $self;
123             }
124              
125             sub create_attribute {
126 4     4 0 13 (my MY $self, my ($name)) = splice @_, 0, 2;
127 4         21 $self->create_node([attribute => 0], $name, @_);
128             }
129              
130             sub add_attribute {
131 6     6 0 18 (my MY $self, my ($name)) = splice @_, 0, 2;
132 6         23 $self->add_node(my $attr = $self->create_node([attribute => 0], $name, @_));
133 6         154 $attr;
134             }
135              
136             sub add_filtered_copy {
137 11     11 0 31 (my MY $self, my ($node, $filter, $primary_only)) = @_;
138 11 50       36 my $boundary = $primary_only ? 'is_primary_attribute' : 'readable';
139 11         40 for (; $node->$boundary(); $node->next) {
140 19         33 my @node = do {
141 19 100       49 if ($node->is_attribute) {
142 18 50       74 my ($sub, @rest) = ref $filter eq 'ARRAY' ? @$filter : $filter;
143 18         47 $sub->(@rest, $node->node_name, $node->current);
144             } else {
145 1         4 copy_array($node->current);
146             }
147             };
148 19 100       99 $self->add_node(@node) if @node;
149             }
150 11         37 $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 24 (my MY $self, my ($type, $name)) = splice @_, 0, 3;
196 10         23 my Path $orig = $self->{cf_path};
197 10   50     69 my $wrap = $self->create_node($type || 'unknown', $name, $orig->{cf_array});
198 10         54 my $path = $self->Path->new($wrap, $orig);
199             ref($self)->new($self->{tree}
200             , metainfo => $self->{cf_metainfo}
201 10         49 , path => $path);
202             }
203              
204             sub filter_or_add_from {
205 119     119 0 443 (my MY $self, my ($node, $except, %opts)) = @_;
206             my $boundary = delete $opts{primary_only}
207 119 100       404 ? 'is_primary_attribute' : 'readable';
208 119 50       324 croak "Invalid option: " . join(",", keys %opts) if %opts;
209              
210 119         179 my ($name, @filtered);
211 119         485 for (; $node->$boundary(); $node->next) {
212 109 100 100     302 if ($node->is_attribute
      66        
213             and defined ($name = $node->node_name)
214             and exists $except->{$name}) {
215             # clone は?
216             # name を書き換えても良いのでは?
217 7         21 my $cur = $node->current;
218 7         14 push @filtered, do {
219 7 50       25 if (defined $except->{$name}) {
220 0         0 $self->copy_renamed($cur, $except->{$name});
221             } else {
222 7         26 $cur
223             }
224             };
225             } else {
226 102         276 $self->add_node($node->current);
227             }
228             }
229              
230 119         458 @filtered;
231             }
232              
233             sub open {
234 597     597 0 1797 my MY $self = shift;
235 597         753 my $obj;
236 597 100 33     4424 unless (defined (my Path $path = $self->{cf_path})) {
    50          
237 6         37 $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         2967 $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 2301 my MY $self = shift;
262 6         12 my Path $path = $self->{cf_path};
263 6         15 my $obj = $path->{cf_array}->[$path->{cf_index}];
264 6 50       45 defined $obj && ref $obj eq 'ARRAY';
265             }
266              
267             sub close {
268 3     3 0 782 my MY $self = shift;
269 3 50       10 if (my Path $parent = $self->{cf_path}->parent) {
270 3         7 $parent->{cf_index}++;
271 3         8 $self->clone($parent);
272             } else {
273             return
274 0         0 }
275             }
276              
277             sub parent {
278 201     201 0 282 my MY $self = shift;
279 201         539 $self->clone($self->{cf_path}->parent);
280             }
281              
282             sub can_close {
283 3     3 0 14 my MY $self = shift;
284 3         11 defined $self->{cf_path};
285             }
286              
287             BEGIN {
288 6     6   22 my @delegate_to_path =
289             qw(read
290             current
291             next
292             prev
293             array
294             );
295 6         18 foreach my $meth (@delegate_to_path) {
296 30         86 *{globref(__PACKAGE__, $meth)} = sub {
297 7394     7394   9496 my MY $self = shift;
298 7394 50       17653 return unless defined $self->{cf_path};
299 7394         23701 $self->{cf_path}->$meth(@_);
300 30         105 };
301             }
302              
303 6         18 my @delegate_and_self = qw(go_next);
304 6         14 foreach my $meth (@delegate_and_self) {
305 6         23 *{globref(__PACKAGE__, $meth)} = sub {
306 89     89   167 my MY $self = shift;
307 89 50       283 return unless defined $self->{cf_path};
308 89         417 $self->{cf_path}->$meth(@_);
309 89         331 $self;
310 6         31 };
311             }
312              
313 6         37 foreach my $meth (grep {/^(node|is)_/} YATT::LRXML::Node->exports) {
  252         583  
314 6     6   41 my $for_text = do {no strict 'refs'; \&{"text_$meth"}};
  6         13  
  6         1600  
  132         166  
  132         145  
  132         711  
315 132         529 my $sub = YATT::LRXML::Node->can($meth);
316 132         377 *{globref(__PACKAGE__, $meth)} = sub {
317 4893     4893   6540 my MY $cursor = shift;
318 4893 100       9717 return unless $cursor->readable;
319 4809 100       11582 if (ref(my $value = $cursor->current)) {
320 4015         11595 $sub->($value, @_);
321             } else {
322 794         1986 $for_text->($value, @_);
323             }
324 132         487 };
325             }
326              
327 6         33 foreach my $meth (my @delegate_to_meta = qw(filename)) {
328 6         22 *{globref(__PACKAGE__, $meth)} = sub {
329 18     18   29 my MY $cursor = shift;
330             defined (my $meta = $cursor->{cf_metainfo})
331 18 50       63 or return;
332 18         83 $meta->$meth(@_);
333 6         24 };
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 9423 my MY $self = shift;
347 7296 50       29480 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             $self->{tree}
355 0         0 } else {
356 0         0 $self->{cf_path}->value;
357             }
358             }
359              
360             sub array_size {
361 156     156 0 261 my MY $self = shift;
362 156         205 YATT::LRXML::Node::node_size(do {
363 156 50       414 unless (defined (my Path $path = $self->{cf_path})) {
364 0         0 $self->{tree};
365             } else {
366 156         526 $path->{cf_array};
367             }
368             });
369             }
370              
371             sub size {
372 8     8 0 7784 my MY $self = shift;
373 8 100       39 unless (defined (my Path $path = $self->{cf_path})) {
    50          
    50          
374 6         29 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         10 YATT::LRXML::Node::node_size($obj);
379             } else {
380 0         0 1;
381             }
382             }
383              
384             sub has_parent {
385 211     211 0 364 my MY $self = shift;
386 211 50       505 defined (my Path $path = $self->{cf_path}) or return 0;
387             $path->{cf_path}
388 211         1163 }
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 298 my MY $self = shift;
401 222         617 $self->metainfo->cget('startline');
402             }
403              
404             sub linenum {
405 222     222 0 394 (my MY $self, my ($offset_atstart)) = @_;
406 222         514 my $linenum = $self->startline;
407 222         463 my Path $path = $self->{cf_path};
408 222         269 my $offset = $offset_atstart;
409 222         543 while ($path) {
410             $linenum += $self->count_lines_of(map {
411 374         1045 $path->{cf_array}[$_]
412 404   100     2115 } YATT::LRXML::Node::_BODY .. $path->{cf_index} - 1 + ($offset || 0));
413 404         715 $path = $path->{cf_path};
414 404         934 undef $offset;
415             }
416 222         706 $linenum;
417             }
418              
419             sub count_lines_of {
420             # XXX: 他でも使うように。
421 484     484 0 790 my ($pack) = shift;
422 484         617 my $sum = 0;
423 484         837 foreach my $item (@_) {
424 466 100       981 next unless defined $item;
425 463         553 $sum += do {
426 463 100       856 if (ref $item) {
427 152         470 YATT::LRXML::Node::node_nlines($item);
428             } else {
429 311         763 $item =~ tr:\n::;
430             }
431             };
432             }
433 484         879 $sum;
434             }
435              
436             sub node_is_beginning {
437 527     527 0 768 my MY $self = shift;
438 527 50       1415 my Path $path = $self->{cf_path} or return;
439 527         1089 $path->is_beginning;
440             }
441              
442             sub node_is_end {
443 294     294 0 430 my MY $self = shift;
444 294 50       743 my Path $path = $self->{cf_path} or return;
445 294 50       650 defined $path->{cf_index} or return;
446 294         411 $path->{cf_index} >= $#{$path->{cf_array}};
  294         1301  
447             }
448              
449             *stringify = *stringify_current; *stringify = *stringify_current;
450              
451             sub stringify_current {
452 28     28 0 55 my MY $self = shift;
453 28         71 my Path $path = $self->{cf_path};
454 28 100       122 unless (defined $path) {
    50          
455 9         41 stringify_node($self->{tree});
456             } elsif (ref (my $value = $path->current)) {
457 19         72 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 7 my MY $self = shift;
475 6         8 my @path;
476 6 50       28 if (my Path $path = $self->{cf_path}) {
477             # XXX: 一、ずれてるじゃん、と。引くの?
478 6         8 do {
479 8         20 unshift @path, $path->{cf_index} - YATT::LRXML::Node::_BODY;
480 8         27 $path = $path->{cf_path};
481             } while $path;
482             }
483 6 50       37 wantarray ? @path : join ", ", @path;
484             }
485              
486             sub parse_typespec {
487 239     239 0 373 my MY $self = shift;
488 239         574 my ($head, @rest) = $self->node_children;
489 239 100       1208 unless (defined $head) {
    50          
490             ()
491 130         353 } elsif ($head =~ s{^(\w+((?:\:\w+)*))?(?:([|/?!])(.*))?}{}s) {
492             # $1 can undef.
493 109 100 66     1307 ($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 10 my MY $self = shift;
503 6 50       26 my Path $path = $self->{cf_path} or return;
504 6         11 my $next = $path->{cf_index} + 1;
505 6 100       9 return if $next >= @{$path->{cf_array}};
  6         40  
506 2         5 my $item = $path->{cf_array}[$next];
507 2 50       8 return unless defined $item;
508 2 100       11 return 1 unless ref $item;
509 1         4 not YATT::LRXML::Node::is_primary_attribute($item);
510             }
511              
512 74     74 0 310 sub text_is_attribute { 0 }
513 0     0 0 0 sub text_is_bare_attribute { 0 }
514 46     46 0 178 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 517 sub text_node_type { YATT::LRXML::Node::TEXT_TYPE }
518 3     3 0 14 sub text_node_body { shift }
519 548     548 0 3714 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;