File Coverage

blib/lib/XML/BindData.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1 2     2   1010 use strict;
  2         2  
  2         47  
2 2     2   6 use warnings;
  2         2  
  2         79  
3              
4             package XML::BindData;
5             $XML::BindData::VERSION = '0.2';
6 2     2   487 use XML::LibXML;
  0            
  0            
7              
8             sub bind {
9             my ($class, $xml_string, $data) = @_;
10              
11             my $xml = XML::LibXML->load_xml(string => $xml_string);
12             parse_node($xml->documentElement, $data);
13              
14             return $xml->toStringC14N(1);
15             }
16              
17             sub parse_node {
18             my ($node, $context) = @_;
19              
20             if (my $if_key = _strip_attr($node, 'tmpl-if')) {
21             my $unless = $if_key =~ s/^!//;
22             my $val = _get($context, $if_key);
23             if ( (!$unless && ! defined $val)
24             || ( $unless && defined $val)) {
25             $node->unbindNode;
26             }
27             }
28              
29             if (my $each_key = _strip_attr($node, 'tmpl-each')) {
30             my $parent = $node->parentNode;
31              
32             my $to_add = _get($context, $each_key);
33             if (!$to_add || ref $to_add ne 'ARRAY') {
34             $to_add = [];
35             }
36              
37             foreach my $subcontext ( reverse @$to_add ) {
38             my $new = $node->cloneNode(1); # deep clone
39             parse_node($new, $subcontext);
40             $parent->insertAfter( $new, $node );
41             }
42             $node->unbindNode;
43             return;
44             }
45              
46             if (my $binding = _strip_attr($node, 'tmpl-bind')) {
47             my $val = _get($context, $binding);
48              
49             my $default = _strip_attr($node, 'tmpl-default');
50              
51             unless (defined $val) {
52             $val = defined $default ? $default : '';
53             }
54              
55             $node->appendTextNode($val);
56             }
57              
58             if (my $attr_map = _strip_attr($node, 'tmpl-attr-map')) {
59             my @attributes = map { [ split qr/:/ ] } split qr/,/, $attr_map;
60              
61             foreach (@attributes) {
62             $node->setAttribute($_->[0], _get($context, $_->[1]));
63             }
64             }
65              
66             if ( my $attr_defaults = _strip_attr( $node, 'tmpl-attr-defaults' ) ) {
67             my @attributes = map { [ split qr/:/ ] } split qr/,/,
68             $attr_defaults;
69              
70             foreach (@attributes) {
71             $node->setAttribute( $_->[0], $_->[1] )
72             unless defined $node->getAttribute( $_->[0] );
73             }
74             }
75              
76             my @children = grep {
77             $_->nodeType eq XML_ELEMENT_NODE
78             } $node->childNodes;
79             parse_node($_, $context) foreach @children;
80             }
81              
82             sub _get {
83             my ($context, $key) = @_;
84              
85             return '' if !defined $key;
86              
87             return $context if $key eq 'this';
88              
89             my @parts = split qr/\./, $key;
90             foreach (@parts) {
91             $context = $context->{$_};
92             }
93             return $context;
94             }
95              
96             sub _strip_attr {
97             my ($node, $attr_name) = @_;
98              
99             if (my $attributes = $node->attributes) {
100             if (my $attr = $attributes->removeNamedItem($attr_name)) {
101             return $attr->nodeValue;
102             }
103             }
104             }
105              
106             1;
107              
108             __END__