File Coverage

blib/lib/Tk/Tree/XML.pm
Criterion Covered Total %
statement 13 17 76.4
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 22 81.8


line stmt bran cond sub pod time code
1             package Tk::Tree::XML;
2              
3             # Tk::Tree::XML - XML tree widget
4              
5             # Copyright (c) 2008 José Santos. All rights reserved.
6             # This program is free software. It can be redistributed and/or modified under
7             # the same terms as Perl itself.
8              
9 1     1   18484 use strict;
  1         1  
  1         32  
10 1     1   4 use warnings;
  1         1  
  1         22  
11 1     1   4 use Carp;
  1         4  
  1         78  
12              
13             BEGIN {
14 1     1   5 use vars qw($VERSION @ISA);
  1         1  
  1         76  
15 1     1   306 require Tk::Tree;
16 0           require XML::Parser;
17 0           require Tk::Derived;
18 0           $VERSION = '0.01';
19 0           @ISA = qw(Tk::Derived Tk::Tree);
20             }
21              
22             Construct Tk::Widget 'XML';
23              
24             sub Tk::Widget::ScrolledXML { shift->Scrolled('XML' => @_) }
25              
26             # ConfigSpecs default values
27             my $PCDATA_MAX_LENGTH = 80;
28              
29             sub Populate {
30             my ($myself, $args) = @_;
31             $myself->SUPER::Populate($args);
32             $myself->ConfigSpecs(
33             -pcdatamaxlength => ["METHOD", "pcdataMaxLength",
34             "PCDATAMaxLength", $PCDATA_MAX_LENGTH],
35             -pcdatalongsymbol => ["PASSIVE", "pcdataLongSymbol",
36             "PCDATALongSymbol", '...'],
37             -pcdatapreservespace => ["PASSIVE", "pcdataPreserveSpace",
38             "PCDATAPreserveSpace", 0],
39             -itemtype => ["SELF", "itemType", "ItemType", 'text']
40             );
41             }
42              
43             # ConfigSpecs methods
44              
45             # get/set maximum number of characters for visualization of pcdata contents
46             sub pcdatamaxlength {
47             my ($myself, $args) = @_;
48             if (@_ > 1) {
49             $myself->_configure(-pcdatamaxlength => &_pcdata_max_length($args));
50             }
51             return $myself->_cget('-pcdatamaxlength');
52             }
53              
54             # validate given max number of characters for visualization of pcdata contents
55             # return given number if it is valid, $PCDATA_MAX_LENGTH otherwise
56             sub _pcdata_max_length {
57             $_ = shift;
58             /^\+?\d+$/ ? $& : &{ sub {
59             carp "Attempt to assign an invalid value to -pcdatamaxlength: '$_' is" .
60             " not a positive integer. Default value ($PCDATA_MAX_LENGTH)" .
61             " will be used instead.\n";
62             $PCDATA_MAX_LENGTH
63             }};
64             }
65              
66             # application programming interface
67              
68             sub load_xml_file { # load_xml_file($xml_filename)
69             my ($myself, $xmlfile) = @_;
70             my @array = (1, 2, 3);
71             if (!$myself->info('exists', '0')) {
72             $myself->_load_xml('', &_xml_parser->parsefile($xmlfile));
73             $myself->autosetmode;# set up automatic handling of open/close events
74             } else {
75             carp "An XML document has already been loaded into the tree." .
76             " XML file $xmlfile will not be loaded.";
77             }
78             }
79              
80             sub load_xml_string { # load_xml_string($xml_string)
81             my ($myself, $xmlstring) = @_;
82             if (!$myself->info('exists', '0')) {
83             $myself->_load_xml('', &_xml_parser->parse($xmlstring));
84             $myself->autosetmode;# set up automatic handling of open/close events
85             } else {
86             carp "An XML document has already been loaded into the tree." .
87             " XML string will not be loaded.";
88             }
89             }
90              
91             sub get_name { # get_name()
92             my $myself = shift;
93             my $entry_path = $myself->selectionGet();
94             my $is_mixed = ref($myself->entrycget($entry_path, '-data'));
95             $is_mixed ? $myself->entrycget($entry_path, '-text') : undef;
96             }
97              
98             sub get_attrs { # get_attrs()
99             my $myself = shift;
100             my $attrs = $myself->entrycget($myself->selectionGet(), '-data');
101             ref($attrs) ? %{$attrs} : undef;
102             }
103              
104             sub get_text { # get_text()
105             my $myself = shift;
106             my $text = $myself->entrycget($myself->selectionGet(), '-data');
107             ref($text) ? undef : $text;
108             }
109              
110             sub is_mixed { # is_mixed()
111             my $myself = shift;
112             'HASH' eq ref($myself->entrycget($myself->selectionGet(), '-data'));
113             }
114              
115             sub is_pcdata { # is_pcdata()
116             my $myself = shift;
117             !$myself->is_mixed();
118             }
119              
120             # helper methods
121              
122             sub _xml_parser { # _xml_parser(): get an XML::Parser instance.
123             new XML::Parser(Style => 'Tree', ErrorContext => 2)
124             }
125              
126             # _load_xml($parent_path, @children): load XML elems under entry at $parent_path
127             # @children is a list of tag/content pairs where each pair is such as:
128             # - ($element_tag, [%element_attrs, @element_children]) <= element is mixed
129             # - 0, 'pcdata contents' <= element is PCDATA
130             # for each entry, XML -data and -text are set, respectively, to:
131             # attributes and element tag <= element is mixed
132             # pcdata content and formatted pcdata content <= element is PCDATA
133             sub _load_xml {
134             my ($myself, $parent_path, @children) = ($_[0], $_[1], @{$_[2]});
135             my $entry_path;
136             while (@children) {
137             my ($elem_tag, $elem_content) = (shift @children, shift @children);
138             if (!ref $elem_content) { # element is #PCDATA
139             $elem_content =~ s/[\n\t ]*(.*)[\n\t ]*/$1/ # trim spacing
140             unless $myself->cget('-pcdatapreservespace') eq 1;
141             if ('' ne $elem_content) {
142             $entry_path = $myself->addchild(
143             $parent_path, -data => $elem_content,
144             -text => $myself->_format_pcdata($elem_content),
145             );
146             }
147             } else { # element is not pcdata
148             $entry_path = $myself->addchild($parent_path,
149             -data => $elem_content->[0], -text => $elem_tag
150             );
151             shift(@$elem_content); # shift element attributes off
152             $myself->_load_xml($entry_path, $elem_content)
153             unless !scalar @$elem_content; # recursively process children
154             }
155             }
156             }
157              
158             sub _format_pcdata { # _format_pcdata($pcdata): format/return pcdata accordingly
159             my ($myself, $pcdata) = @_;
160             my $pcdata_max_length = $myself->cget('-pcdatamaxlength');
161             length($pcdata) > $pcdata_max_length
162             ? substr($pcdata, 0, $pcdata_max_length) .
163             $myself->cget('-pcdatalongsymbol')
164             : $pcdata;
165             }
166              
167             1;
168              
169             __END__