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   9314 use strict;
  6         11  
  6         201  
4 6     6   29 use warnings qw(FATAL all NONFATAL misc);
  6         13  
  6         309  
5              
6 6     6   29 use base qw(YATT::Class::Configurable);
  6         12  
  6         556  
7 6     6   32 use YATT::Fields qw(^tree ^cf_metainfo cf_path);
  6         12  
  6         47  
8             sub Path () {'YATT::LRXML::NodeCursor::Path'}
9              
10 6     6   34 use YATT::Util::Symbol;
  6         13  
  6         660  
11 6         404 use YATT::LRXML::Node qw(stringify_node
12             stringify_attlist
13             create_node
14             create_node_from
15 6     6   34 copy_array);
  6         13  
16              
17 6     6   34 use Carp;
  6         10  
  6         492  
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         11  
  6         637  
25 6     6   30 use YATT::Fields qw(cf_path cur_postype prev_postype);
  6         11  
  6         25  
26              
27 6     6   31 use YATT::LRXML::Node qw(node_type ATTRIBUTE_TYPE);
  6         11  
  6         321  
28              
29 6     6   32 use YATT::Util::Enum -prefix => 'POSTYPE_', qw(UNKNOWN ATTLIST BODY);
  6     0   13  
  6         65  
30              
31             sub init {
32 1519     1519   3285 my ($self, $array, $path, $index0) = splice @_, 0, 4;
33 1519   50     8984 $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   25 my MY $orig = shift;
42             ref($orig)->new($orig->{cf_array}, $orig->{cf_path}
43             # XXX: To compensate init()
44 11         63 , $orig->{cf_index} - YATT::LRXML::Node::_BODY);
45             }
46              
47             sub parent {
48 204     204   343 my MY $path = shift; $path->{cf_path}
49 204         624 }
50              
51             sub after_next {
52 3307     3307   4890 (my MY $path) = @_;
53             return $path unless defined $path->{cf_index}
54 3307 100 66     10609 and $path->{cf_index} <= $#{$path->{cf_array}};
  3307         15371  
55 2130         4703 my $val = $path->{cf_array}->[$path->{cf_index}];
56 2130         4190 $path->{prev_postype} = $path->{cur_postype};
57 2130 100 100     8472 if (not defined $path->{cur_postype}
58             or $path->{cur_postype} == POSTYPE_ATTLIST) {
59 1439 100 100     5917 $path->{cur_postype} = ref $val && node_type($val) == ATTRIBUTE_TYPE
60             ? POSTYPE_ATTLIST : POSTYPE_BODY;
61             }
62             $path
63 2130         5632 }
64              
65             sub is_beginning {
66 527     527   945 (my MY $path) = @_;
67 527 100       1861 return 1 unless defined $path->{prev_postype};
68 301 50       847 return unless $path->{cur_postype} == POSTYPE_BODY;
69 301         1693 $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 957 my ($class, $tree) = splice @_, 0, 2;
77 414         3009 $class->new($tree, path => $class->Path->new($tree), @_);
78             }
79              
80             sub new_path {
81 6     6 0 8 my MY $self = shift;
82 6         47 $self->Path->new($self->{tree}, shift); # XXX: tree でいいの?
83             }
84              
85             sub clone_path {
86 6     6 0 11 my MY $self = shift;
87 6   33     23 my Path $path = shift || $self->{cf_path};
88 6 50       43 $self->Path->new($path->{cf_array}, $path ? $path->{cf_path} : undef);
89             }
90              
91             sub clone {
92 1286     1286 0 2085 (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     7138 : undef)));
98             }
99              
100             sub variant_builder {
101 252     252 0 427 my MY $self = shift;
102 252         411 my Path $orig = $self->{cf_path};
103 252         372 my $variant = do {
104 252 100       594 if (@_) {
105 119         411 $self->create_node(@_);
106             } else {
107 133         611 $self->create_node_from($orig->{cf_array});
108             }
109             };
110 252         844 $self->adopter_for($variant, $orig->{cf_path});
111             }
112              
113             sub adopter_for {
114 255     255 0 527 (my MY $self, my ($array, $path)) = @_;
115 255   66     1847 $self->clone($self->Path->new($array, $path || $self->{cf_path}))
116             }
117              
118             sub add_node {
119 208     208 0 363 my MY $self = shift;
120 208         366 my Path $path = $self->{cf_path};
121 208         314 push @{$path->{cf_array}}, @_;
  208         585  
122 208         702 $self;
123             }
124              
125             sub create_attribute {
126 4     4 0 13 (my MY $self, my ($name)) = splice @_, 0, 2;
127 4         24 $self->create_node([attribute => 0], $name, @_);
128             }
129              
130             sub add_attribute {
131 6     6 0 17 (my MY $self, my ($name)) = splice @_, 0, 2;
132 6         28 $self->add_node(my $attr = $self->create_node([attribute => 0], $name, @_));
133 6         188 $attr;
134             }
135              
136             sub add_filtered_copy {
137 11     11 0 26 (my MY $self, my ($node, $filter, $primary_only)) = @_;
138 11 50       39 my $boundary = $primary_only ? 'is_primary_attribute' : 'readable';
139 11         40 for (; $node->$boundary(); $node->next) {
140 19         28 my @node = do {
141 19 100       47 if ($node->is_attribute) {
142 18 50       78 my ($sub, @rest) = ref $filter eq 'ARRAY' ? @$filter : $filter;
143 18         47 $sub->(@rest, $node->node_name, $node->current);
144             } else {
145 1         6 copy_array($node->current);
146             }
147             };
148 19 100       108 $self->add_node(@node) if @node;
149             }
150 11         35 $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 30 (my MY $self, my ($type, $name)) = splice @_, 0, 3;
196 10         27 my Path $orig = $self->{cf_path};
197 10   50     70 my $wrap = $self->create_node($type || 'unknown', $name, $orig->{cf_array});
198 10         62 my $path = $self->Path->new($wrap, $orig);
199             ref($self)->new($self->{tree}
200             , metainfo => $self->{cf_metainfo}
201 10         51 , path => $path);
202             }
203              
204             sub filter_or_add_from {
205 119     119 0 488 (my MY $self, my ($node, $except, %opts)) = @_;
206             my $boundary = delete $opts{primary_only}
207 119 100       427 ? 'is_primary_attribute' : 'readable';
208 119 50       371 croak "Invalid option: " . join(",", keys %opts) if %opts;
209              
210 119         178 my ($name, @filtered);
211 119         467 for (; $node->$boundary(); $node->next) {
212 109 100 100     332 if ($node->is_attribute
      66        
213             and defined ($name = $node->node_name)
214             and exists $except->{$name}) {
215             # clone は?
216             # name を書き換えても良いのでは?
217 7         35 my $cur = $node->current;
218 7         15 push @filtered, do {
219 7 50       29 if (defined $except->{$name}) {
220 0         0 $self->copy_renamed($cur, $except->{$name});
221             } else {
222 7         23 $cur
223             }
224             };
225             } else {
226 102         261 $self->add_node($node->current);
227             }
228             }
229              
230 119         491 @filtered;
231             }
232              
233             sub open {
234 597     597 0 1149 my MY $self = shift;
235 597         819 my $obj;
236 597 100 33     4148 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         2874 $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 478 my MY $self = shift;
262 6         54 my Path $path = $self->{cf_path};
263 6         12 my $obj = $path->{cf_array}->[$path->{cf_index}];
264 6 50       35 defined $obj && ref $obj eq 'ARRAY';
265             }
266              
267             sub close {
268 3     3 0 18 my MY $self = shift;
269 3 50       8 if (my Path $parent = $self->{cf_path}->parent) {
270 3         5 $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 294 my MY $self = shift;
279 201         575 $self->clone($self->{cf_path}->parent);
280             }
281              
282             sub can_close {
283 3     3 0 10 my MY $self = shift;
284 3         8 defined $self->{cf_path};
285             }
286              
287             BEGIN {
288 6     6   24 my @delegate_to_path =
289             qw(read
290             current
291             next
292             prev
293             array
294             );
295 6         16 foreach my $meth (@delegate_to_path) {
296 30         88 *{globref(__PACKAGE__, $meth)} = sub {
297 7394     7394   9656 my MY $self = shift;
298 7394 50       15387 return unless defined $self->{cf_path};
299 7394         25323 $self->{cf_path}->$meth(@_);
300 30         106 };
301             }
302              
303 6         17 my @delegate_and_self = qw(go_next);
304 6         17 foreach my $meth (@delegate_and_self) {
305 6         22 *{globref(__PACKAGE__, $meth)} = sub {
306 89     89   171 my MY $self = shift;
307 89 50       320 return unless defined $self->{cf_path};
308 89         423 $self->{cf_path}->$meth(@_);
309 89         372 $self;
310 6         31 };
311             }
312              
313 6         41 foreach my $meth (grep {/^(node|is)_/} YATT::LRXML::Node->exports) {
  252         601  
314 6     6   38 my $for_text = do {no strict 'refs'; \&{"text_$meth"}};
  6         13  
  6         1525  
  132         169  
  132         143  
  132         711  
315 132         558 my $sub = YATT::LRXML::Node->can($meth);
316 132         350 *{globref(__PACKAGE__, $meth)} = sub {
317 4893     4893   7262 my MY $cursor = shift;
318 4893 100       9190 return unless $cursor->readable;
319 4809 100       11940 if (ref(my $value = $cursor->current)) {
320 4015         11300 $sub->($value, @_);
321             } else {
322 794         2064 $for_text->($value, @_);
323             }
324 132         508 };
325             }
326              
327 6         36 foreach my $meth (my @delegate_to_meta = qw(filename)) {
328 6         23 *{globref(__PACKAGE__, $meth)} = sub {
329 18     18   28 my MY $cursor = shift;
330             defined (my $meta = $cursor->{cf_metainfo})
331 18 50       70 or return;
332 18         82 $meta->$meth(@_);
333 6         21 };
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 9048 my MY $self = shift;
347 7296 50       30303 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 264 my MY $self = shift;
362 156         248 YATT::LRXML::Node::node_size(do {
363 156 50       456 unless (defined (my Path $path = $self->{cf_path})) {
364 0         0 $self->{tree};
365             } else {
366 156         582 $path->{cf_array};
367             }
368             });
369             }
370              
371             sub size {
372 8     8 0 4683 my MY $self = shift;
373 8 100       34 unless (defined (my Path $path = $self->{cf_path})) {
    50          
    50          
374 6         25 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         8 YATT::LRXML::Node::node_size($obj);
379             } else {
380 0         0 1;
381             }
382             }
383              
384             sub has_parent {
385 211     211 0 372 my MY $self = shift;
386 211 50       538 defined (my Path $path = $self->{cf_path}) or return 0;
387             $path->{cf_path}
388 211         1252 }
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 340 my MY $self = shift;
401 222         675 $self->metainfo->cget('startline');
402             }
403              
404             sub linenum {
405 222     222 0 392 (my MY $self, my ($offset_atstart)) = @_;
406 222         615 my $linenum = $self->startline;
407 222         446 my Path $path = $self->{cf_path};
408 222         308 my $offset = $offset_atstart;
409 222         649 while ($path) {
410             $linenum += $self->count_lines_of(map {
411 374         995 $path->{cf_array}[$_]
412 404   100     2085 } YATT::LRXML::Node::_BODY .. $path->{cf_index} - 1 + ($offset || 0));
413 404         790 $path = $path->{cf_path};
414 404         917 undef $offset;
415             }
416 222         701 $linenum;
417             }
418              
419             sub count_lines_of {
420             # XXX: 他でも使うように。
421 484     484 0 766 my ($pack) = shift;
422 484         614 my $sum = 0;
423 484         905 foreach my $item (@_) {
424 466 100       898 next unless defined $item;
425 463         557 $sum += do {
426 463 100       985 if (ref $item) {
427 152         493 YATT::LRXML::Node::node_nlines($item);
428             } else {
429 311         729 $item =~ tr:\n::;
430             }
431             };
432             }
433 484         944 $sum;
434             }
435              
436             sub node_is_beginning {
437 527     527 0 805 my MY $self = shift;
438 527 50       1468 my Path $path = $self->{cf_path} or return;
439 527         1250 $path->is_beginning;
440             }
441              
442             sub node_is_end {
443 294     294 0 424 my MY $self = shift;
444 294 50       883 my Path $path = $self->{cf_path} or return;
445 294 50       752 defined $path->{cf_index} or return;
446 294         427 $path->{cf_index} >= $#{$path->{cf_array}};
  294         1355  
447             }
448              
449             *stringify = *stringify_current; *stringify = *stringify_current;
450              
451             sub stringify_current {
452 28     28 0 57 my MY $self = shift;
453 28         59 my Path $path = $self->{cf_path};
454 28 100       118 unless (defined $path) {
    50          
455 9         34 stringify_node($self->{tree});
456             } elsif (ref (my $value = $path->current)) {
457 19         79 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 9 my MY $self = shift;
475 6         8 my @path;
476 6 50       24 if (my Path $path = $self->{cf_path}) {
477             # XXX: 一、ずれてるじゃん、と。引くの?
478 6         9 do {
479 8         12 unshift @path, $path->{cf_index} - YATT::LRXML::Node::_BODY;
480 8         25 $path = $path->{cf_path};
481             } while $path;
482             }
483 6 50       28 wantarray ? @path : join ", ", @path;
484             }
485              
486             sub parse_typespec {
487 239     239 0 393 my MY $self = shift;
488 239         645 my ($head, @rest) = $self->node_children;
489 239 100       1192 unless (defined $head) {
    50          
490             ()
491 130         373 } elsif ($head =~ s{^(\w+((?:\:\w+)*))?(?:([|/?!])(.*))?}{}s) {
492             # $1 can undef.
493 109 100 66     1286 ($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 12 my MY $self = shift;
503 6 50       23 my Path $path = $self->{cf_path} or return;
504 6         12 my $next = $path->{cf_index} + 1;
505 6 100       12 return if $next >= @{$path->{cf_array}};
  6         35  
506 2         6 my $item = $path->{cf_array}[$next];
507 2 50       7 return unless defined $item;
508 2 100       14 return 1 unless ref $item;
509 1         5 not YATT::LRXML::Node::is_primary_attribute($item);
510             }
511              
512 74     74 0 319 sub text_is_attribute { 0 }
513 0     0 0 0 sub text_is_bare_attribute { 0 }
514 46     46 0 187 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 448 sub text_node_type { YATT::LRXML::Node::TEXT_TYPE }
518 3     3 0 10 sub text_node_body { shift }
519 548     548 0 3458 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;