File Coverage

blib/lib/Shebangml/FromXML.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Shebangml::FromXML;
2             $VERSION = v0.0.1;
3              
4 1     1   1061 use warnings;
  1         1  
  1         34  
5 1     1   4 use strict;
  1         446  
  1         38  
6 1     1   6 use Carp;
  1         1  
  1         69  
7              
8 1     1   427 use XML::Parser::Expat;
  0            
  0            
9              
10             use Class::Accessor::Classy;
11             ro 'parser';
12             lo 'output';
13             no Class::Accessor::Classy;
14              
15             use constant DEBUG => 0;
16              
17             =head1 NAME
18              
19             Shebangml::FromXML - SAX parse XML into HBML
20              
21             =head1 SYNOPSIS
22              
23             my $parser = Shebangml::FromXML->new;
24             $parser->parse($source);
25              
26             The $source can be as-per L.
27              
28             =cut
29              
30             =head2 new
31              
32             my $parser = Shebangml::FromXML->new;
33              
34             =cut
35              
36             sub new {
37             my $package = shift;
38             my $class = ref($package) || $package;
39             my $self = {parser => XML::Parser::Expat->new};
40             bless($self, $class);
41              
42             $self->_setup;
43              
44             return($self);
45             } # end subroutine new definition
46             ########################################################################
47              
48             =head2 _setup
49              
50             $parser->_setup;
51              
52             =cut
53              
54             sub _setup {
55             my $self = shift;
56              
57             my @output;
58             $self->{output} = \@output;
59             my @ctx = ({root => 1, had_content => 1});
60              
61             my %shortcuts = (
62             br => '\n;',
63             );
64             my $start = sub {
65             my ($el) = @_;
66              
67             my $short;
68             $el = $short if($short = $shortcuts{$el});
69             unless($ctx[-1]->{had_content}) {
70             $ctx[-1]->{had_content} = 1;
71             push(@output, '{', $el);
72             return($short);
73             }
74             else {
75             if($short) {
76             push(@output, $short);
77             return($short);
78             }
79             push(@output,
80             (@output && $output[-1] =~ m/[\.\w]$/ ? '\\' : ''),
81             $el);
82             return();
83             }
84             };
85             my $out = sub {
86             push(@output, @_);
87             };
88             my $sh = sub {
89             my ($p, $el, @atts) = @_;
90              
91             DEBUG and warn "start handler $el at ", $p->current_byte;
92              
93             $start->($el) and return;
94              
95             # XXX ugh. The only way I'm going to get the &foo; entities in the
96             # attributes is uh... I have to use not expat?
97             # warn $p->original_string;
98              
99             push(@ctx, my $e = { });
100             if(@atts) {
101             @atts = $self->_reduce_atts(@atts);
102              
103             # TODO sorting, quoting, and such
104             my $att = join(' ', @atts);
105             $out->("[$att]");
106             $e->{had_atts} = 1;
107             }
108             elsif($p->original_string =~ m#/>$#) {
109             # do I even need to tag it as empty?
110             $e->{had_atts} = 1;
111             $out->('[]');
112             }
113              
114             };
115             my $eh = sub {
116             my ($p, $el) = @_;
117             $shortcuts{$el} and return; # never closing shortcuts
118             DEBUG and warn "end handler $el at ", $p->current_byte;
119             my $e = pop(@ctx);
120             if($e->{had_content}) {
121             $out->('}');
122             }
123             elsif(not $e->{had_atts}) {
124             # we still have to make it an entity
125             $out->('{}');
126             }
127             else {
128             # XXX may need to output [] or {} to indicate br-like tag?
129             }
130             };
131             my $ch = sub {
132             my ($p, $s) = @_;
133             my $e = $ctx[-1];
134             $out->('{') unless($e->{had_content});
135             $e->{had_content} = 1;
136              
137             # escaping the string...
138             # TODO smarter [] and {} matched handling
139             $s =~ s/([\{\}\[\]])/\\$1/g;
140             $s =~ s/\\/\\\\/g;
141             # TODO < and such need to transform too
142              
143             $out->($s);
144             };
145             my $dt = sub {
146             DEBUG and warn "doctype @_";
147             };
148             my $parser = $self->parser;
149             $parser->setHandlers(
150             Start => $sh,
151             End => $eh,
152             Char => $ch,
153             Doctype => $dt,
154             CdataStart => sub {DEBUG and warn "CdataStart @_"},
155             Comment => sub {DEBUG and warn "comment @_"},
156             Unparsed => sub {
157             DEBUG and warn "unparsed @_";
158             },
159             #Attlist => sub { warn " atts @_"; },
160             Default => sub {
161             DEBUG and warn 'hit default: ',
162             "@_ (", $output[-1] ? $output[-1] : (), ") ",
163             "line ", $parser->current_line,
164             ", column ", $parser->current_column,
165             ", byte ", $parser->current_byte;
166             },
167             # ExternEnt => sub {warn "an externEnt?!"},
168             # Entity => sub {warn "an Entity?! @_"},
169             );
170             } # end subroutine _setup definition
171             ########################################################################
172              
173             =head2 parse
174              
175             $parser->parse($source);
176              
177             =cut
178              
179             sub parse {
180             my $self = shift;
181             my ($source) = @_;
182             $self->parser->parse($source);
183             } # end subroutine parse definition
184             ########################################################################
185              
186             =head2 _reduce_atts
187              
188             my @atts = $self->_reduce_atts(@atts);
189              
190             =cut
191              
192             {
193             my %shortcut = (name => ':', id => '=', class => '@');
194             sub _reduce_atts {
195             my $self = shift;
196             my (@atts) = @_;
197              
198             # yank the id, name, class to the front -- with shortcuts
199             my %num = map({my $n = $_*2; ($atts[$n] => $n)} 0..($#atts/2));
200             my @cuts;
201             my @out;
202             foreach my $k (sort keys(%shortcut)) {
203             if(exists($num{$k})) {
204             push(@cuts, $num{$k});
205             my $att = $atts[$num{$k}+1];
206             if($att =~ m/^\w+$/) {
207             push(@out, $shortcut{$k} . $att);
208             }
209             else {
210             push(@out, $k . qq(="$att"));
211             }
212             }
213             }
214             foreach my $n (sort({$b <=> $a} @cuts)) {
215             splice(@atts, $n, 2);
216             }
217             return(@out,
218             @atts ?
219             map({my $n = $_*2; $atts[$n].'="'.$atts[$n+1].'"'} 0..int($#atts/2))
220             : ()
221             );
222             }} # end subroutine _reduce_atts definition
223             ########################################################################
224              
225              
226             =head1 AUTHOR
227              
228             Eric Wilhelm @
229              
230             http://scratchcomputing.com/
231              
232             =head1 BUGS
233              
234             If you found this module on CPAN, please report any bugs or feature
235             requests through the web interface at L. I will be
236             notified, and then you'll automatically be notified of progress on your
237             bug as I make changes.
238              
239             If you pulled this development version from my /svn/, please contact me
240             directly.
241              
242             =head1 COPYRIGHT
243              
244             Copyright (C) 2008 Eric L. Wilhelm, All Rights Reserved.
245              
246             =head1 NO WARRANTY
247              
248             Absolutely, positively NO WARRANTY, neither express or implied, is
249             offered with this software. You use this software at your own risk. In
250             case of loss, no person or entity owes you anything whatsoever. You
251             have been warned.
252              
253             =head1 LICENSE
254              
255             This program is free software; you can redistribute it and/or modify it
256             under the same terms as Perl itself.
257              
258             =cut
259              
260             # vi:ts=2:sw=2:et:sta
261             1;