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   2033 use strict;
  7         8  
  7         210  
7 7     7   24 use warnings FATAL => qw(all);
  7         44  
  7         197  
8 7     7   459 use YATT::Util::Symbol;
  7         9  
  7         423  
9 7     7   26 use YATT::Util;
  7         10  
  7         624  
10 7     7   25 use Carp;
  7         166  
  7         287  
11              
12 7     7   28 use base qw(Exporter);
  7         8  
  7         743  
13             our (@EXPORT_OK, @EXPORT);
14             BEGIN {
15 7     7   23 @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         338 @EXPORT = @EXPORT_OK;
47             }
48              
49 6     6 0 60 sub exports { @EXPORT_OK }
50              
51             sub MY () {__PACKAGE__}
52              
53 7     7   164 our @NODE_MEMBERS; BEGIN {@NODE_MEMBERS = qw(TYPE FLAG NLINES USER_SLOT
54             RAW_NAME BODY)}
55 7     7   1696 use YATT::Util::Enum -prefix => '_', @NODE_MEMBERS;
  7         10  
  7         43  
56              
57             BEGIN {
58 7     7   15 foreach my $name (@NODE_MEMBERS) {
59 42         155 my $offset = MY->can("_$name")->();
60 42         57 my $func = "node_".lc($name);
61 42         70 *{globref(MY, $func)} = sub {
62 1669     1669   5381 shift->[$offset]
63 42         74 };
64 42         61 push @EXPORT_OK, $func;
65 42         1227 push @EXPORT, $func;
66             }
67             }
68              
69             our @NODE_TYPES;
70             our %NODE_TYPES;
71             our @NODE_FORMAT;
72              
73             BEGIN {
74 7     7   73 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         94 $NODE_TYPES{$_->[0]} = keys %NODE_TYPES for @desc;
88 7         12 @NODE_TYPES = map {$_->[0]} @desc;
  77         83  
89 7 100       16 @NODE_FORMAT = map {ref $_->[1] eq 'CODE' ? $_->[1] : [@$_[1..$#$_]]} @desc;
  77         553  
90             }
91              
92             BEGIN {
93 7     7   11 my @type_enum = map {uc($_) . '_TYPE'} @NODE_TYPES;
  77         96  
94 7         30 require YATT::Util::Enum;
95 7         26 import YATT::Util::Enum @type_enum;
96 7         17 push @EXPORT_OK, @type_enum;
97 7         260 push @EXPORT, @type_enum;
98             }
99              
100             # ATTRIBUTE の FLAG の意味は、↓これと "ed_by_element が決める。
101 7     7   170 our @QUOTE_CHAR; BEGIN {@QUOTE_CHAR = ("", '\'', "\"", [qw([ ])])}
102             # XXX: ↓ 役割は減る予定。
103 7     7   3603 our @QUOTE_TYPES; BEGIN {@QUOTE_TYPES = (1, 2, 0)}
104              
105             sub new {
106 8     8 0 1916 my $pack = shift;
107 8         21 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 1197 my $nlines = 0;
115 1491         1819 foreach my $item (@_) {
116 393 100       987 unless (ref $item) {
    50          
117 171         367 $nlines += $item =~ tr,\n,,;
118             } elsif (defined (my $sub = $item->[_NLINES])) {
119 222         438 $nlines += $sub;
120             } else {
121 0         0 $nlines += sum_node_nlines(node_children($item));
122             }
123             }
124 1491         5083 $nlines;
125             }
126              
127             sub create_node {
128 1353     1353 0 2400 my ($pack, $type, $name) = splice @_, 0, 3;
129 1353 100       2378 my ($typename, $flag) = ref $type ? @$type : $type;
130 1353 100       2027 $flag = 0 unless defined $flag;
131 1353         1797 my $typeid = $NODE_TYPES{$typename};
132 1353 50       1823 die "Unknown type: $typename" unless defined $typeid;
133             # DEPEND_ALIGNMENT: SET_NLINES:
134 1353         1789 [$typeid, $flag, sum_node_nlines(@_), undef, $name, @_];
135             }
136              
137             sub create_node_from {
138 138     138 0 277 my ($pack, $orig, $name) = splice @_, 0, 3;
139 138         250 my ($typeid, $flag) = @{$orig}[_TYPE, _FLAG];
  138         254  
140 138 100       711 $name = copy_array($$orig[_RAW_NAME]) unless defined $name;
141             # DEPEND_ALIGNMENT: SET_NLINES:
142 138         399 [$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         15 create_node_from($pack, $orig, $name, @{$orig}[_BODY .. $#$orig]);
  5         18  
148             }
149              
150             sub node_headings {
151 114     114 0 162 my $node = shift;
152 114         491 ([$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 461 my $node = shift;
160 526         2106 @$node - _BODY;
161             }
162              
163             sub node_children {
164 240     240 0 250 my $node = shift;
165 240         354 @{$node}[_BODY .. $#$node];
  240         692  
166             }
167              
168             sub node_type_name {
169 630     630 0 3181 $NODE_TYPES[shift->[_TYPE]];
170             }
171              
172             sub is_attribute {
173 388     388 0 1699 $_[0]->[_TYPE] == ATTRIBUTE_TYPE;
174             }
175              
176             sub is_primary_attribute {
177 186 100 100 186 0 1519 $_[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 74 $_[0]->[_TYPE] == ATTRIBUTE_TYPE
184             && defined $_[0]->[_FLAG]
185             && $_[0]->[_FLAG] == 0;
186             }
187              
188             sub stringify_node {
189 138     138 0 378 my ($node) = shift;
190 138         131 my $type = $node->[_TYPE];
191 138 50 33     423 if (not defined $type or $type eq '') {
192 0         0 die "Invalid node object: ".YATT::Util::terse_dump($node);
193             }
194 138 50       195 if (@NODE_FORMAT <= $type) {
195 0         0 die "Unknown type: $type";
196             }
197 138 100       229 if (ref(my $desc = $NODE_FORMAT[$type]) eq 'CODE') {
198 99         155 $desc->($node, @_);
199             } else {
200 39         58 my ($fmt, $prefix, $suffix) = @$desc;
201 7     7   1108 use YATT::Util::redundant_sprintf;
  7         10  
  7         43  
202 39 100       84 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 1948 my ($node, $first, $sep, $default) = @_;
214 1854         1316 my $raw;
215 1854 100       4170 unless (defined ($raw = $node->[_RAW_NAME])) {
    100          
216 42 100       235 defined $default ? $default : return;
217             } elsif (not ref $raw) {
218             # undef かつ wantarray は只の return に分離した方が良いかも?
219 631         2218 $raw;
220             } else {
221 1181   100     4575 my @names = @$raw[($first || 0) .. $#$raw];
222 149 50       640 wantarray ? @names : join(($sep || ":")
223 1181 100 100     4912 , map {defined $_ ? $_ : ''} @names);
224             }
225             }
226              
227             # node_nsname は namespace 込みのパスを返す。
228              
229             sub node_nsname {
230 99     99 0 152 my ($node, $default, $sep) = @_;
231 99         177 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 702 my ($node, $default, $sep) = @_;
239 561         954 node_path($node, 1, $sep, $default);
240             }
241              
242             sub node_set_nlines {
243 811     811 0 862 my ($node, $nlines) = @_;
244 811         833 $node->[_NLINES] = $nlines;
245 811         17298 $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 24 my ($elem) = @_;
270 21         39 stringify_as_tag($elem, node_nsname($elem), $elem->[_FLAG]);
271             }
272              
273             sub stringify_declarator {
274 6     6 0 9 my ($elem, $strip_ns) = @_;
275             # XXX: 本物にせよ。
276 6         13 my $tag = node_nsname($elem);
277 6         18 my $attlist = stringify_each_by($elem, ' ', ' ', '', _BODY);
278 6         32 ""
279             }
280              
281             sub stringify_root {
282 10     10 0 11 my ($elem) = @_;
283 10         28 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 32 my ($node, $name, $is_ee) = @_;
298 27         50 my $bodystart = node_beginning_of_body($node);
299 27         65 my $tag = do {
300 27 100 66     74 if (defined $name && is_attribute($node)) {
301 6         11 ":$name";
302             } else {
303 21         32 $name;
304             }
305             };
306 27         53 my $attlist = stringify_attlist($node, $bodystart);
307 27 100       43 if ($is_ee) {
308 14 50       59 stringify_each_by($node
309             , $tag ? qq(<$tag$attlist />) : ''
310             , ''
311             , ''
312             , $bodystart);
313             } else {
314 13 50       70 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 32 my ($node) = shift;
324 27   66     58 my $bodystart = shift || node_beginning_of_body($node);
325             # print "[[for @{[$node->get_name]}; <",
326 27 100 100     164 return '' if defined $bodystart and _BODY == $bodystart
      100        
      66        
327             or not defined $bodystart and $#$node < _BODY;
328 13 100       44 stringify_each_by($node, ' ', ' ', '', _BODY
329             , (defined $bodystart ? ($bodystart - 1) : ()))
330             }
331              
332             sub stringify_each_by {
333 151     151 0 207 my ($node, $open, $sep, $close) = splice @_, 0, 4;
334 151   100     249 $open ||= ''; $sep ||= ''; $close ||= '';
  151   100     308  
  151   100     273  
335 151 100       193 my $from = @_ ? shift : _BODY;
336 151 100       174 my $to = @_ ? shift : $#$node;
337 151         117 my $result = $open;
338 151 100 66     401 if (defined $from and defined $to) {
339             $result .= join $sep, map {
340 210 50       304 unless (defined $_) {
  141 100       174  
341 0         0 ''
342             } elsif (ref $_) {
343 94         126 my $s = stringify_node($_);
344 94 50       124 unless (defined $s) {
345 0         0 require YATT::Util;
346 0         0 die "Can't stringify node: ". YATT::Util::terse_dump($_)
347             }
348 94         109 $s;
349             } else {
350 116         189 $_
351             }
352 141         152 } @{$node}[$from .. $to];
353             }
354 151 50       233 $result .= $close if defined $close;
355 151         389 $result;
356             }
357              
358             sub node_beginning_of_body {
359 37     37 0 40 my ($node) = @_;
360             lsearch {
361 50   100 50   127 not ref $_ or not is_primary_attribute($_)
362 37         148 } $node, _BODY;
363             }
364              
365             #----------------------------------------
366              
367             sub create_attlist {
368 447     447 0 547 my ($parser) = shift;
369 447         358 my @result;
370 447         734 while (@_) {
371 514         1462 my ($sp, $name, $eq, @values) = splice @_, 0, 6;
372 514     1433   2123 my $found = lsearch {defined} \@values;
  1433         3294  
373 514         1029 my ($subtype, $attname, @attbody) = do {
374 514 50 100     2878 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         506 (undef, $values[$found]);
380             } else {
381             # parse_entities can return ().
382 239         724 ($QUOTE_TYPES[$found], $name =>
383             $parser->parse_entities($values[$found]));
384             }
385             };
386 514 100       450 my @typed; @typed = split /:/, $attname if defined $attname;
  514         1272  
387             # DEPEND_ALIGNMENT: SET_NLINES:
388 514 100       1914 push @result, [ATTRIBUTE_TYPE, $subtype, 0, undef
389             , @typed > 1 ? \@typed : $attname
390             , @attbody];
391             }
392 447         2453 @result;
393             }
394              
395             sub stringify_attribute {
396 62     62 0 47 my ($node) = @_;
397 62 100 100     176 if (defined $$node[_FLAG] && $$node[_FLAG] >= @QUOTE_CHAR) {
398 6         11 stringify_as_tag($node
399             , node_nsname($node)
400             , $$node[_FLAG] - MY->quoted_by_element(0));
401             } else {
402 56         83 my (@stringify_as) = attribute_stringify_as($node);
403 56 50       78 if (@stringify_as == 1) {
404 0         0 $stringify_as[0]
405             } else {
406 56         92 stringify_each_by($node, @stringify_as, _BODY);
407             }
408             }
409             }
410              
411             sub node_attribute_format {
412 22     22 0 21 my ($node) = @_;
413 22         46 my ($open, $sep, $close) = attribute_stringify_as($node);
414 22         50 ($open, $close);
415             }
416              
417             sub attribute_stringify_as {
418 78     78 0 85 my ($node) = @_;
419 78 100       120 unless (defined $$node[_BODY]) {
420 10         21 (join_or_string($$node[_RAW_NAME]), '', '');
421             } else {
422 68 100       141 my $Q = $$node[_FLAG] ? @QUOTE_CHAR[$$node[_FLAG]] : "";
423 68 100       150 my ($sep, $opn, $clo) = ref $Q ? (' ', @$Q) : ('', $Q, $Q);
424 68         98 my $prefix = join_or_empty(join_or_string($$node[_RAW_NAME]), '=').$opn;
425 68         147 ($prefix, $sep, $clo);
426             }
427             }
428              
429             sub join_or_string {
430 78 100   78 0 183 ref $_[0] ? join(":", @{$_[0]}) : $_[0];
  2         5  
431             }
432              
433             sub join_or_empty {
434 68     68 0 57 my $str = '';
435 68         78 foreach my $item (@_) {
436 131 100       152 return '' unless defined $item;
437 126         139 $str .= $item;
438             }
439 63         102 $str;
440             }
441              
442 248     248 0 989 sub EMPTY_ELEMENT () { 1 + @QUOTE_CHAR }
443              
444             sub quoted_by_element {
445 28     28 0 43 my ($pack, $is_ee) = @_;
446 28 100       55 if ($is_ee) {
447 16         36 EMPTY_ELEMENT;
448             } else {
449 12         37 scalar @QUOTE_CHAR; # 3 for now.
450             }
451             }
452              
453             sub is_quoted_by_element {
454 115     115 0 145 my ($node) = @_;
455 115 100       730 defined $node->[_FLAG] && $node->[_FLAG] >= @QUOTE_CHAR;
456             }
457              
458             sub is_empty_element {
459 124     124 0 170 my ($node) = @_;
460 124 50       526 defined $node->[_FLAG] && $node->[_FLAG] == EMPTY_ELEMENT;
461             }
462              
463             1;