File Coverage

blib/lib/XML/Ximple.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             # Copyright 2001,2002 Reliance Technology Consultants, Inc.
2             # This program is free software; you can redistribute it and/or modify
3             # it under the terms of the GNU General Public License as published by
4             # the Free Software Foundation; either version 2 of the License, or
5             # (at your option) any later version.
6             #
7             # This program is distributed in the hope that it will be useful,
8             # but WITHOUT ANY WARRANTY; without even the implied warranty of
9             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10             # GNU General Public License for more details.
11             #
12             # You should have received a copy of the GNU General Public License
13             # along with this program; if not, write to the Free Software
14             # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15              
16             package XML::Ximple;
17              
18 1     1   2087 use XML::Parser;
  0            
  0            
19             use strict;
20             use warnings;
21              
22             require Exporter;
23              
24             our @ISA = qw(Exporter);
25              
26              
27             our @EXPORT_OK = qw(
28             parse_xml_file
29             parse_xml
30             get_root_tag
31             ximple_to_string
32             );
33             our $VERSION = '1.02';
34              
35             =head1 NAME
36              
37             XML::Ximple - XML in Perl
38              
39             =head1 DESCRIPTION
40              
41             XiMpLe is a simple XML parser created to provide
42             a tree based XML parser with a more desirable
43             data structure than what was currently availible
44             on CPAN.
45              
46             =head1 SYNOPSIS
47              
48             use XML::Ximple (qw( parse_xml_file
49             parse_xml
50             get_root_tag
51             ximple_to_string ));
52             $ximple_tree = parse_xml_file ( $filename );
53             $ximple_tree = parse_xml ( $string );
54             $ximple_tag = get_root_tag ( $ximple_tree );
55             $string = ximple_to_string ( $ximple_tree );
56              
57             =head1 DATA
58              
59             ::= [ ... ]
60            
61             ::= { tag_name => ,
62             attrib =>
63             content =>
64             tag_type => }
65              
66             ::=
67              
68             ::= { => String, ... }
69            
70             ::= PI | XMLDecl | DOCTYPE | Comment | undef
71              
72             =head1 FUNCTIONS
73              
74             =head2 parse_xml_file
75              
76             Given a filename, parse the file as XML and return a Ximple tree.
77              
78             =head2 parse_xml
79              
80             Given a string, parse it as XML and return a Ximple tree.
81              
82             =head2 get_root_tag
83              
84             Given a Ximple tree, return the root element as a Ximple leaf.
85              
86             =head2 ximple_to_string
87              
88             Given a Ximple tree, return XML as a string. This will format the output XML
89             differently than the input.
90              
91             =head1 EXAMPLE
92              
93             use XML::Ximple (qw(parse_xml get_root_tag ximple_to_string));
94             my $xt = parse_xml(<>);
95             print "This looks like a ";
96             print get_root_tag($xt)->{tag_name};
97             print " type of document.\n";
98             print '-'x80;
99             print ximple_to_string($xt);
100              
101             =head1 SEE ALSO
102              
103             =over
104              
105             =item L
106              
107             =item L
108              
109             =item L
110              
111             =item L
112              
113             =back
114              
115             =head1 SUPPORT
116              
117             =over
118              
119             =item Mailing list
120              
121            
122              
123             =item Web site
124              
125            
126              
127             =back
128              
129             =head1 AUTHOR
130              
131             Reliance Technology Consultants, Inc.
132             Mike MacHenry
133             Mike Burns
134              
135             =cut
136              
137             # $ximple_tree = {
138             # tag_name => "unicycle"
139             # attrib => { color => "chrom",
140             # height => 3,
141             # brand => "foo"}
142             # content => ["The content of a ximple tree\n",
143             # "is heterogenious just like xml",
144             # "itself. For example this is how\n",
145             # "i would make the word",
146             # {
147             # tag_name=>"bold",
148             # attrib=>{},
149             # content=>["cheese"]
150             # },
151             # "appear in a bold tag"]
152             # }
153             #
154             # The content of a ximple tree
155             # is heterogenius just like xml itself. For example this is how
156             # i would make the wordcheese appear in a bold tag
157             #
158             #
159             #if you need more explaination then parse an xml file and
160             #use Data::Dumper to view the result.
161             #there is an optional hash key "tag_type" which is nonexistant
162             #for normal tags, "PI" for processing instructions, "XMLDecl",
163             #for XML declaration tags, "DOCTYPE" for doc type tags and "empty"
164             #for empty tags.
165              
166             my @tree;
167             my $p = new XML::Parser (
168             Namespaces=>"1",
169             Handlers=>{
170             Start => \&SetStartElementHandler,
171             End => \&SetEndElementHandler,
172             Char => \&SetCharacterDataHandler,
173             Proc => \&SetProcessingInstructionHandler,
174             Comment => \&SetCommentHandler,
175             CdataStart => \&SetStartCdataHandler,
176             CdataEnd => \&SetEndCdataHandler,
177             Default => \&SetDefaultHandler,
178             Unparsed => \&SetUnparsedEntityDeclHandler,
179             Notation => \&SetNotationDeclHandler,
180             ExternEnt => \&SetExternalEntityRefHandler,
181             ExternEntFin => \&SetExtEntFinishHandler,
182             Entity => \&SetEntityDeclHandler,
183             Element => \&SetElementDeclHandler,
184             Attlist => \&SetAttListDeclHandler,
185             Doctype => \&SetDoctypeHandler,
186             DoctypeFin => \&SetEndDoctypeHandler,
187             XMLDecl => \&SetXMLDeclHandler
188             }
189             );
190              
191             ######################################################################
192             ##parse_xml
193             ######################################################################
194             sub SetStartElementHandler {
195             my ($expat,$element,%attrib) = @_;
196             push @tree , {tag_name=>$element,attrib=>\%attrib,content=>[]};
197             }
198              
199             sub SetEndElementHandler {
200             my ($expat,$element) = @_;
201             my $tag = pop @tree;
202             $tag->{tag_type} = "empty" if (scalar (@{$tag->{content}}) == 0);
203             push @{$tree[-1]->{content}} , $tag;
204             }
205              
206             sub SetCharacterDataHandler {
207             my ($expat,$string) = @_;
208             push @{$tree[-1]->{content}}, $string;
209             }
210              
211             sub SetProcessingInstructionHandler {
212             my ($expat,$target,$data) = @_;
213             push (@{$tree[-1]->{content}},{
214             tag_name=>$target,
215             tag_type=>"PI",
216             data=>$data
217             });
218             }
219              
220             sub SetCommentHandler {
221             my ($expat,$data) = @_;
222             push (@{$tree[-1]->{content}},{
223             tag_type => "Comment",
224             data => $data
225             });
226             }
227              
228             sub SetStartCdataHandler {
229             #intentionally skipped
230             #doing so escapes all meta charecters in the block
231             }
232              
233             sub SetEndCdataHandler {
234             #intentionally skipped
235             }
236              
237             #the following handlers are left unimplimented due to lack of demand.
238             sub SetDefaultHandler {}
239             sub SetUnparsedEntityDeclHandler {}
240             sub SetNotationDeclHandler {}
241             sub SetExternalEntityRefHandler {}
242             sub SetExtEntFinishHandler {}
243             sub SetEntityDeclHandler {}
244             sub SetElementDeclHandler {}
245             sub SetAttListDeclHandler {}
246              
247             sub SetDoctypeHandler {
248             my ($expat,$name,$sysid,$pupid,$internal) = @_;
249             push (@{$tree[-1]->{content}},{
250             tag_name=>"DOCTYPE",
251             tag_type=>"DOCTYPE",
252             attrib=>{
253             name=>$name,
254             sysid=>$sysid,
255             pupid=>$pupid,
256             internal=>$internal
257             }
258             });
259             }
260              
261             sub SetEndDoctypeHandler {
262             #we now have the DTD. we can set ourselves up for
263             #more wellformedness checking here for the rest of the document
264             }
265              
266             sub SetXMLDeclHandler {
267             my ($expat,$version,$encoding,$standalone) = @_;
268             push (@{$tree[-1]->{content}},{
269             tag_name=>"xml",
270             tag_type=>"XMLDecl",
271             attrib=> {
272             version=>$version,
273             encoding=>$encoding||"UTF-8",
274             standalone=>$standalone||"no",
275             }
276             });
277             }
278              
279             sub parse_xml_file {
280             @tree = ({content=>[]});
281             eval { local $SIG{'__DIE__'}; $p->parsefile(shift)};;
282             if ($@) {
283             #warn $@;
284             return;
285             } else {
286             return $tree[0]->{content};
287             }
288             }
289              
290             sub parse_xml {
291             @tree = ({content=>[]});
292             eval { local $SIG{'__DIE__'}; $p->parse(shift)};;
293             if ($@) {
294             #warn $@;
295             return;
296             } else {
297             return $tree[0]->{content};
298             }
299             }
300              
301             ######################################################################
302             ## ximple_to_string
303             ######################################################################
304              
305             ## ximple_to_string: Ximple_tree Number -> String
306             ## Given a Ximple tree, return a string that is the XML equivalent.
307             sub ximple_to_string {
308             my ($tree,$depth) = @_;
309             $depth ||= 0; ## Depth? Hm?
310             my $xml = "";
311             foreach my $next (@$tree) {
312             if (ref ($next) eq 'HASH') {
313             if (not defined $next->{tag_type}) {
314             $xml .= open_tag_to_string($next,$depth);
315             $xml .= ximple_to_string($next->{content},$depth+1);
316             $xml .= close_tag_to_string ($next,$depth);
317             } elsif ($next->{tag_type} eq 'empty') {
318             $xml .= empty_tag_to_string($next,$depth);
319             } elsif ($next->{tag_type} eq 'DOCTYPE') {
320             $xml .= doctype_to_string($next,$depth);
321             } elsif ($next->{tag_type} eq 'XMLDecl') {
322             $xml .= xmldecl_to_string($next,$depth);
323             } elsif ($next->{tag_type} eq 'PI') {
324             $xml .= pi_to_string($next,$depth);
325             } elsif ($next->{tag_type} eq 'Comment') {
326             $xml .= comment_to_string($next,$depth);
327             } else {
328             die "unsupported tag_type: $next->{tag_type}";
329             }
330             } elsif ($next) {
331             $xml .= xmlize($next);
332             }
333             }
334             return $xml;
335             }
336              
337             ## open_tag_to_string: Tag Number -> String
338             ## Given an open tag, return the XML equivalent.
339             sub open_tag_to_string {
340             my ($tag,$depth) = @_;
341             return
342             "<".$tag->{tag_name}.
343             attrib_to_string ($tag->{attrib}).
344             ">";
345             }
346              
347             ## close_tag_to_string: Tag Number -> String
348             ## the close tag for the ximple tree
349             sub close_tag_to_string {
350             my ($tag,$depth) = @_;
351             return "{tag_name}>\n";
352             }
353              
354             ## empty_tag_to_string: Tag Number -> String
355             sub empty_tag_to_string {
356             my ($tag,$depth) = @_;
357             return
358             "<".$tag->{tag_name}.
359             attrib_to_string ($tag->{attrib}).
360             "/>\n";
361             }
362              
363             ## doctype_to_string: Tag Number -> String
364             sub doctype_to_string {
365             my ($tag,$depth) = @_;
366             my $xml = "{tag_name} $tag->{attrib}{name}";
367             if (defined $tag->{attrib}{pubid}) {
368             $xml .= " PUBLIC \"".xmlize($tag->{attrib}{pubid})."\"";
369             } elsif (defined $tag->{attrib}{sysid}) {
370             $xml .= " SYSTEM \"".xmlize($tag->{attrib}{sysid})."\"";
371             } else {
372             die "no identifier in DOCTYPE";
373             }
374             return $xml.">\n";
375             }
376              
377             ## pi_to_string: Tag Number -> String
378             sub pi_to_string {
379             my ($tag,$depth) = @_;
380             my $xml = "{tag_name};
381             if (defined $tag->{data}) {
382             $xml .= xmlize ($tag->{data});
383             }
384             return $xml."?>\n";
385             }
386              
387             ## xmldecl_to_string: Tag Number -> String
388             sub xmldecl_to_string {
389             my ($tag,$depth) = @_;
390             my $xml = "{tag_name}";
391             $xml .= ' version="'.xmlize($tag->{attrib}{version}).'"';
392             if (defined $tag->{attrib}{encoding}) {
393             $xml .= ' encoding="'.xmlize($tag->{attrib}{encoding}).'"';
394             }
395             if (defined $tag->{attrib}{standalone}) {
396             $xml .= ' standalone="'.xmlize($tag->{attrib}{standalone}).'"';
397             }
398             return $xml."?>\n";
399             }
400              
401             ## comment_to_string: Tag Numer -> String
402             sub comment_to_string {
403             my ($tag,$depth) = @_;
404             return "";
405             }
406              
407             ## attrib_to_string: Tag -> String
408             sub attrib_to_string {
409             my $attrib = shift;
410             my $xml = "";
411             foreach (keys %$attrib) {
412             $xml .= " $_=\"".xmlize($attrib->{$_})."\"";
413             }
414             return $xml;
415             }
416              
417             sub xmlize {
418             my $text = shift;
419             return "" unless defined $text; #dskippy 2002/02/28 why are functions passing undef to this?
420             $text =~ s/&/&/g;
421             $text =~ s/
422             $text =~ s/>/>/g;
423             $text =~ s/"/"/g;
424             return $text;
425             }
426              
427             ######################################################################
428             ##ximple_print
429             ######################################################################
430             sub ximple_print {
431             my ($ximple_tree) = @_;
432             print ximple_to_string ($ximple_tree,0);
433             }
434              
435             ######################################################################
436             ##Accessors
437             ######################################################################
438             sub get_root_tag {
439             my ($ximple_tree) = @_;
440             foreach (@$ximple_tree) {
441             next unless ref;
442             return $_ unless ($_->{tag_type});
443             }
444             return;
445             }
446             1;