File Coverage

web/cgi-bin/yatt.lib/YATT/LRXML/Node.pm
Criterion Covered Total %
statement 194 212 91.5
branch 79 98 80.6
condition 42 52 80.7
subroutine 53 56 94.6
pod 0 38 0.0
total 368 456 80.7


line stmt bran cond sub pod time code
1             # -*- mode: perl; coding: utf-8 -*-
2             package YATT::LRXML::Node;
3             # Media か?
4             # To cooperate with JSON easily, Nodes should not rely on OO style.
5              
6 7     7   3094 use strict;
  7         18  
  7         207  
7 7     7   35 use warnings qw(FATAL all NONFATAL misc);
  7         69  
  7         313  
8 7     7   675 use YATT::Util::Symbol;
  7         10  
  7         640  
9 7     7   36 use YATT::Util;
  7         14  
  7         1015  
10 7     7   36 use Carp;
  7         10  
  7         387  
11              
12 7     7   45 use base qw(Exporter);
  7         12  
  7         942  
13             our (@EXPORT_OK, @EXPORT);
14             BEGIN {
15 7     7   42 @EXPORT_OK = qw(stringify_node
16             stringify_attlist
17              
18             create_node
19             create_node_from
20             copy_node_renamed_as
21              
22             create_attlist
23             node_size
24             node_children
25             node_type_name
26             node_name
27             node_nsname
28             node_path
29             node_headings
30             node_set_nlines
31             node_user_data
32             node_user_data_by
33             node_attribute_format
34             is_attribute
35             is_primary_attribute
36             is_bare_attribute
37             is_quoted_by_element
38             is_empty_element
39              
40             quoted_by_element
41              
42             copy_array
43              
44             EMPTY_ELEMENT
45             );
46 7         546 @EXPORT = @EXPORT_OK;
47             }
48              
49 6     6 0 78 sub exports { @EXPORT_OK }
50              
51             sub MY () {__PACKAGE__}
52              
53 7     7   209 our @NODE_MEMBERS; BEGIN {@NODE_MEMBERS = qw(TYPE FLAG NLINES USER_SLOT
54             RAW_NAME BODY)}
55 7     7   3126 use YATT::Util::Enum -prefix => '_', @NODE_MEMBERS;
  7         16  
  7         71  
56              
57             BEGIN {
58 7     7   22 foreach my $name (@NODE_MEMBERS) {
59 42         244 my $offset = MY->can("_$name")->();
60 42         110 my $func = "node_".lc($name);
61 42         144 *{globref(MY, $func)} = sub {
62 1669     1669   7891 shift->[$offset]
63 42         130 };
64 42         94 push @EXPORT_OK, $func;
65 42         2022 push @EXPORT, $func;
66             }
67             }
68              
69             our @NODE_TYPES;
70             our %NODE_TYPES;
71             our @NODE_FORMAT;
72              
73             BEGIN {
74 7     7   123 my @desc = ([text => '%s'] # May not be used.
75             , [comment => '']
76             , [decl_comment => '--%1$s--']
77             , [pi => '' ]
78             , [entity => '%3$s'.'%2$s'.'%1$s;', ['&', '%']]
79              
80             , [root => \&stringify_root]
81             , [element => \&stringify_element]
82             , [attribute => \&stringify_attribute]
83             , [declarator => \&stringify_declarator]
84             , [html => \&stringify_element]
85             , [unknown => \&stringify_unknown]
86             );
87 7         134 $NODE_TYPES{$_->[0]} = keys %NODE_TYPES for @desc;
88 7         17 @NODE_TYPES = map {$_->[0]} @desc;
  77         144  
89 7 100       19 @NODE_FORMAT = map {ref $_->[1] eq 'CODE' ? $_->[1] : [@$_[1..$#$_]]} @desc;
  77         895  
90             }
91              
92             BEGIN {
93 7     7   20 my @type_enum = map {uc($_) . '_TYPE'} @NODE_TYPES;
  77         338  
94 7         80 require YATT::Util::Enum;
95 7         36 import YATT::Util::Enum @type_enum;
96 7         28 push @EXPORT_OK, @type_enum;
97 7         366 push @EXPORT, @type_enum;
98             }
99              
100             # ATTRIBUTE の FLAG の意味は、↓これと "ed_by_element が決める。
101 7     7   287 our @QUOTE_CHAR; BEGIN {@QUOTE_CHAR = ("", '\'', "\"", [qw([ ])])}
102             # XXX: ↓ 役割は減る予定。
103 7     7   6447 our @QUOTE_TYPES; BEGIN {@QUOTE_TYPES = (1, 2, 0)}
104              
105             sub new {
106 8     8 0 4639 my $pack = shift;
107 8         25 bless $pack->create_node(@_), $pack;
108             }
109              
110             # $pack->create_node($typeName, $nodeName, $nodeBody)
111             # $pack->create_node([$typeName, $flag], [@nodePath], @nodeBody)
112              
113             sub sum_node_nlines {
114 1491     1491 0 1817 my $nlines = 0;
115 1491         2770 foreach my $item (@_) {
116 393 100       1307 unless (ref $item) {
    50          
117 171         464 $nlines += $item =~ tr,\n,,;
118             } elsif (defined (my $sub = $item->[_NLINES])) {
119 222         442 $nlines += $sub;
120             } else {
121 0         0 $nlines += sum_node_nlines(node_children($item));
122             }
123             }
124 1491         7367 $nlines;
125             }
126              
127             sub create_node {
128 1353     1353 0 3716 my ($pack, $type, $name) = splice @_, 0, 3;
129 1353 100       3194 my ($typename, $flag) = ref $type ? @$type : $type;
130 1353 100       2865 $flag = 0 unless defined $flag;
131 1353         2607 my $typeid = $NODE_TYPES{$typename};
132 1353 50       2499 die "Unknown type: $typename" unless defined $typeid;
133             # DEPEND_ALIGNMENT: SET_NLINES:
134 1353         2934 [$typeid, $flag, sum_node_nlines(@_), undef, $name, @_];
135             }
136              
137             sub create_node_from {
138 138     138 0 350 my ($pack, $orig, $name) = splice @_, 0, 3;
139 138         240 my ($typeid, $flag) = @{$orig}[_TYPE, _FLAG];
  138         350  
140 138 100       715 $name = copy_array($$orig[_RAW_NAME]) unless defined $name;
141             # DEPEND_ALIGNMENT: SET_NLINES:
142 138         405 [$typeid, $flag, sum_node_nlines(@_), undef, $name, @_]
143             }
144              
145             sub copy_node_renamed_as {
146 5     5 0 15 my ($pack, $name, $orig) = splice @_, 0, 3;
147 5         18 create_node_from($pack, $orig, $name, @{$orig}[_BODY .. $#$orig]);
  5         15  
148             }
149              
150             sub node_headings {
151 114     114 0 185 my $node = shift;
152 114         579 ([$NODE_TYPES[$$node[_TYPE]], $$node[_FLAG]]
153             , $$node[_RAW_NAME]);
154             }
155              
156             sub node_body_starting () { _BODY }
157              
158             sub node_size {
159 526     526 0 858 my $node = shift;
160 526         2902 @$node - _BODY;
161             }
162              
163             sub node_children {
164 240     240 0 370 my $node = shift;
165 240         443 @{$node}[_BODY .. $#$node];
  240         883  
166             }
167              
168             sub node_type_name {
169 630     630 0 4578 $NODE_TYPES[shift->[_TYPE]];
170             }
171              
172             sub is_attribute {
173 388     388 0 2136 $_[0]->[_TYPE] == ATTRIBUTE_TYPE;
174             }
175              
176             sub is_primary_attribute {
177 186 100 100 186 0 1958 $_[0]->[_TYPE] == ATTRIBUTE_TYPE
178             && (! defined $_[0]->[_FLAG]
179             || $_[0]->[_FLAG] < @QUOTE_CHAR);
180             }
181              
182             sub is_bare_attribute {
183 6 100 66 6 0 89 $_[0]->[_TYPE] == ATTRIBUTE_TYPE
184             && defined $_[0]->[_FLAG]
185             && $_[0]->[_FLAG] == 0;
186             }
187              
188             sub stringify_node {
189 138     138 0 933 my ($node) = shift;
190 138         205 my $type = $node->[_TYPE];
191 138 50 33     696 if (not defined $type or $type eq '') {
192 0         0 die "Invalid node object: ".YATT::Util::terse_dump($node);
193             }
194 138 50       331 if (@NODE_FORMAT <= $type) {
195 0         0 die "Unknown type: $type";
196             }
197 138 100       396 if (ref(my $desc = $NODE_FORMAT[$type]) eq 'CODE') {
198 99         243 $desc->($node, @_);
199             } else {
200 39         78 my ($fmt, $prefix, $suffix) = @$desc;
201 7     7   2325 use YATT::Util::redundant_sprintf;
  7         15  
  7         60  
202 39 100       116 sprintf($fmt
    50          
203             , stringify_each_by($node)
204             , node_nsname($node, '')
205             , defined $prefix ? $prefix->[$node->[_FLAG]] : ''
206             , defined $suffix ? $suffix->[$node->[_FLAG]] : '');
207             }
208             }
209              
210             # node_path は name スロットを返す。wantarray 対応。
211              
212             sub node_path {
213 1854     1854 0 2917 my ($node, $first, $sep, $default) = @_;
214 1854         2102 my $raw;
215 1854 100       6312 unless (defined ($raw = $node->[_RAW_NAME])) {
    100          
216 42 100       351 defined $default ? $default : return;
217             } elsif (not ref $raw) {
218             # undef かつ wantarray は只の return に分離した方が良いかも?
219 631         3094 $raw;
220             } else {
221 1181   100     6321 my @names = @$raw[($first || 0) .. $#$raw];
222             wantarray ? @names : join(($sep || ":")
223 1181 50 100     7757 , map {defined $_ ? $_ : ''} @names);
  149 100       986  
224             }
225             }
226              
227             # node_nsname は namespace 込みのパスを返す。
228              
229             sub node_nsname {
230 99     99 0 177 my ($node, $default, $sep) = @_;
231 99         226 scalar node_path($node, 0, $sep, $default);
232             }
233              
234             # node_name は namespace を除いたパスを返す。
235             # yatt:else なら else が返る。
236              
237             sub node_name {
238 561     561 0 928 my ($node, $default, $sep) = @_;
239 561         1126 node_path($node, 1, $sep, $default);
240             }
241              
242             sub node_set_nlines {
243 811     811 0 1271 my ($node, $nlines) = @_;
244 811         1295 $node->[_NLINES] = $nlines;
245 811         16897 $node;
246             }
247              
248             sub node_user_data {
249 0     0 0 0 my ($node) = shift;
250 0 0       0 if (@_) {
251 0         0 $node->[_USER_SLOT] = shift;
252             } else {
253 0         0 $node->[_USER_SLOT];
254             }
255             }
256              
257             sub node_user_data_by {
258 0     0 0 0 my ($node) = shift;
259 0   0     0 my $slot = $node->[_USER_SLOT] ||= do {
260 0         0 my ($obj, $meth) = splice @_, 0, 2;
261 0         0 $obj->$meth(@_);
262             };
263 0 0       0 wantarray ? @$slot : $slot;
264             }
265              
266             #----------------------------------------
267              
268             sub stringify_element {
269 21     21 0 41 my ($elem) = @_;
270 21         61 stringify_as_tag($elem, node_nsname($elem), $elem->[_FLAG]);
271             }
272              
273             sub stringify_declarator {
274 6     6 0 17 my ($elem, $strip_ns) = @_;
275             # XXX: 本物にせよ。
276 6         17 my $tag = node_nsname($elem);
277 6         24 my $attlist = stringify_each_by($elem, ' ', ' ', '', _BODY);
278 6         105 ""
279             }
280              
281             sub stringify_root {
282 10     10 0 18 my ($elem) = @_;
283 10         41 stringify_each_by($elem
284             , ''
285             , ''
286             , ''
287             , _BODY);
288             }
289              
290             sub stringify_unknown {
291 0     0 0 0 die 'unknown';
292             }
293              
294             #----------------------------------------
295              
296             sub stringify_as_tag {
297 27     27 0 53 my ($node, $name, $is_ee) = @_;
298 27         65 my $bodystart = node_beginning_of_body($node);
299 27         100 my $tag = do {
300 27 100 66     111 if (defined $name && is_attribute($node)) {
301 6         19 ":$name";
302             } else {
303 21         43 $name;
304             }
305             };
306 27         80 my $attlist = stringify_attlist($node, $bodystart);
307 27 100       68 if ($is_ee) {
308 14 50       80 stringify_each_by($node
309             , $tag ? qq(<$tag$attlist />) : ''
310             , ''
311             , ''
312             , $bodystart);
313             } else {
314 13 50       82 stringify_each_by($node
    50          
315             , $tag ? qq(<$tag$attlist>) : ''
316             , ''
317             , $tag ? qq() : ''
318             , $bodystart);
319             }
320             }
321              
322             sub stringify_attlist {
323 27     27 0 47 my ($node) = shift;
324 27   66     87 my $bodystart = shift || node_beginning_of_body($node);
325             # print "[[for @{[$node->get_name]}; <",
326 27 100 100     317 return '' if defined $bodystart and _BODY == $bodystart
      100        
      66        
327             or not defined $bodystart and $#$node < _BODY;
328 13 100       63 stringify_each_by($node, ' ', ' ', '', _BODY
329             , (defined $bodystart ? ($bodystart - 1) : ()))
330             }
331              
332             sub stringify_each_by {
333 151     151 0 454 my ($node, $open, $sep, $close) = splice @_, 0, 4;
334 151   100     451 $open ||= ''; $sep ||= ''; $close ||= '';
  151   100     509  
  151   100     450  
335 151 100       324 my $from = @_ ? shift : _BODY;
336 151 100       374 my $to = @_ ? shift : $#$node;
337 151         230 my $result = $open;
338 151 100 66     634 if (defined $from and defined $to) {
339             $result .= join $sep, map {
340 210 50       610 unless (defined $_) {
    100          
341 0         0 ''
342             } elsif (ref $_) {
343 94         204 my $s = stringify_node($_);
344 94 50       225 unless (defined $s) {
345 0         0 require YATT::Util;
346 0         0 die "Can't stringify node: ". YATT::Util::terse_dump($_)
347             }
348 94         276 $s;
349             } else {
350 116         350 $_
351             }
352 141         235 } @{$node}[$from .. $to];
  141         292  
353             }
354 151 50       475 $result .= $close if defined $close;
355 151         672 $result;
356             }
357              
358             sub node_beginning_of_body {
359 37     37 0 58 my ($node) = @_;
360             lsearch {
361 50   100 50   226 not ref $_ or not is_primary_attribute($_)
362 37         204 } $node, _BODY;
363             }
364              
365             #----------------------------------------
366              
367             sub create_attlist {
368 447     447 0 730 my ($parser) = shift;
369 447         591 my @result;
370 447         1072 while (@_) {
371 514         2106 my ($sp, $name, $eq, @values) = splice @_, 0, 6;
372 514     1433   2886 my $found = lsearch {defined} \@values;
  1433         5746  
373 514         1593 my ($subtype, $attname, @attbody) = do {
374 514 50 100     3962 unless (defined $found) {
    100 100        
375 0         0 (undef, $name);
376             } elsif (not defined $name and $found == 2
377             and $values[$found] =~ /^[\w\:\-\.]+$/) {
378             # has single bareword. use it as name and keep value undef.
379 275         734 (undef, $values[$found]);
380             } else {
381             # parse_entities can return ().
382 239         1034 ($QUOTE_TYPES[$found], $name =>
383             $parser->parse_entities($values[$found]));
384             }
385             };
386 514 100       861 my @typed; @typed = split /:/, $attname if defined $attname;
  514         1717  
387             # DEPEND_ALIGNMENT: SET_NLINES:
388 514 100       3147 push @result, [ATTRIBUTE_TYPE, $subtype, 0, undef
389             , @typed > 1 ? \@typed : $attname
390             , @attbody];
391             }
392 447         3479 @result;
393             }
394              
395             sub stringify_attribute {
396 62     62 0 93 my ($node) = @_;
397 62 100 100     273 if (defined $$node[_FLAG] && $$node[_FLAG] >= @QUOTE_CHAR) {
398 6         22 stringify_as_tag($node
399             , node_nsname($node)
400             , $$node[_FLAG] - MY->quoted_by_element(0));
401             } else {
402 56         131 my (@stringify_as) = attribute_stringify_as($node);
403 56 50       137 if (@stringify_as == 1) {
404 0         0 $stringify_as[0]
405             } else {
406 56         147 stringify_each_by($node, @stringify_as, _BODY);
407             }
408             }
409             }
410              
411             sub node_attribute_format {
412 22     22 0 37 my ($node) = @_;
413 22         58 my ($open, $sep, $close) = attribute_stringify_as($node);
414 22         87 ($open, $close);
415             }
416              
417             sub attribute_stringify_as {
418 78     78 0 124 my ($node) = @_;
419 78 100       195 unless (defined $$node[_BODY]) {
420 10         28 (join_or_string($$node[_RAW_NAME]), '', '');
421             } else {
422 68 100       192 my $Q = $$node[_FLAG] ? @QUOTE_CHAR[$$node[_FLAG]] : "";
423 68 100       195 my ($sep, $opn, $clo) = ref $Q ? (' ', @$Q) : ('', $Q, $Q);
424 68         162 my $prefix = join_or_empty(join_or_string($$node[_RAW_NAME]), '=').$opn;
425 68         260 ($prefix, $sep, $clo);
426             }
427             }
428              
429             sub join_or_string {
430 78 100   78 0 347 ref $_[0] ? join(":", @{$_[0]}) : $_[0];
  2         9  
431             }
432              
433             sub join_or_empty {
434 68     68 0 115 my $str = '';
435 68         137 foreach my $item (@_) {
436 131 100       287 return '' unless defined $item;
437 126         214 $str .= $item;
438             }
439 63         150 $str;
440             }
441              
442 248     248 0 1300 sub EMPTY_ELEMENT () { 1 + @QUOTE_CHAR }
443              
444             sub quoted_by_element {
445 28     28 0 60 my ($pack, $is_ee) = @_;
446 28 100       69 if ($is_ee) {
447 16         39 EMPTY_ELEMENT;
448             } else {
449 12         50 scalar @QUOTE_CHAR; # 3 for now.
450             }
451             }
452              
453             sub is_quoted_by_element {
454 115     115 0 195 my ($node) = @_;
455 115 100       907 defined $node->[_FLAG] && $node->[_FLAG] >= @QUOTE_CHAR;
456             }
457              
458             sub is_empty_element {
459 124     124 0 211 my ($node) = @_;
460 124 50       524 defined $node->[_FLAG] && $node->[_FLAG] == EMPTY_ELEMENT;
461             }
462              
463             1;