File Coverage

blib/lib/XML/Writer/Compiler.pm
Criterion Covered Total %
statement 14 16 87.5
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 20 22 90.9


line stmt bran cond sub pod time code
1             package XML::Writer::Compiler;
2             BEGIN {
3 1     1   1025 $XML::Writer::Compiler::VERSION = '1.112060';
4             }
5              
6             # ABSTRACT: produce aoa from tree
7              
8 1     1   8 use strict;
  1         3  
  1         39  
9 1     1   6 use warnings;
  1         1  
  1         50  
10 1     1   6 use feature "switch";
  1         1  
  1         103  
11              
12 1     1   1097 use autodie;
  1         32805  
  1         6  
13              
14 1     1   9372 use Moose;
  0            
  0            
15              
16             use Carp;
17              
18             sub buildclass {
19             my ( $self, $pkg, $tree, $firstdepth, $prepend_lib, $fileheader ) = @_;
20              
21             my $rootnode = $tree->look_down( '_tag' => qr/./ );
22              
23             my $lol = __PACKAGE__->mklol( $tree, $firstdepth );
24              
25             my $pkgstr = __PACKAGE__->_mkpkg(
26             $pkg => $lol,
27             $prepend_lib, $fileheader, $rootnode->{_tag}
28             );
29              
30             warn "PKG:$pkg";
31              
32             my @part = split '::', $pkg;
33             my $file = $part[$#part];
34             warn "PART:@part";
35              
36             use File::Spec;
37             my $path = File::Spec->catdir( $prepend_lib ? $prepend_lib : (),
38             @part[ 0 .. $#part - 1 ] );
39             warn "PATH:$path";
40             use File::Path;
41             File::Path::make_path( $path, { verbose => 1 } );
42              
43             $file = File::Spec->catfile( $path, "$file.pm" );
44             warn "FILE:$file";
45             open( my $fh, '>', $file );
46              
47             $fh->print($pkgstr);
48             $pkgstr;
49              
50             }
51              
52             sub mklol {
53             my ( $class, $tree, $firstdepth ) = @_;
54             unless ($firstdepth) {
55             Carp::cluck('Assuming firstdepth == 0');
56             $firstdepth = 0;
57             }
58             open( my $fh, '>', \my $string ) or die "Could not open string for writing";
59             $tree->methodsfrom( $fh, 0, '', $firstdepth );
60              
61             use Perl::Tidy;
62              
63             perltidy( source => \$string, destination => \my $dest );
64             $dest;
65              
66             }
67              
68             sub _mkpkg {
69             my ( $self, $pkg, $lol, $prepend_lib, $extends_string, $rootnode ) = @_;
70              
71             open( my $fh, '>', \my $pkgstr ) or die "Could not open pkg for writing";
72              
73             my $extends = $extends_string ? "extends qw($extends_string)" : '';
74             $extends =~ s/^\s+//g;
75             $fh->printf( <<'EOPKG', $pkg, $extends, $lol, $rootnode );
76             package %s;
77             use Moose;
78              
79             with qw(XML::Writer::Compiler::AutoPackage);
80              
81              
82             %s;
83              
84             use Data::Dumper;
85             use HTML::Element::Library;
86              
87              
88              
89              
90             use XML::Element;
91              
92             has 'data' => (
93             is => 'rw',
94             trigger => \&maybe_morph
95             );
96             has 'writer' => (is => 'rw', isa => 'XML::Writer');
97             has 'string' => (is => 'rw', isa => 'XML::Writer::String');
98              
99              
100             %s;
101              
102             sub xml {
103             my($self)=@_;
104             my $method = '_tag_%s';
105             $self->$method;
106             $self->writer->end;
107             $self;
108             }
109              
110              
111             1;
112              
113             EOPKG
114              
115             use Perl::Tidy;
116              
117             perltidy( source => \$pkgstr, destination => \my $dest );
118             $dest;
119              
120             }
121              
122             1;
123              
124             package XML::Element;
125             BEGIN {
126             $XML::Element::VERSION = '1.112060';
127             }
128              
129             sub cleantag {
130             my ( $self, $andattr ) = @_;
131             my $tag = $self->{_tag};
132              
133             return $tag unless $andattr;
134              
135             my %attr = $self->all_external_attr;
136             my $attr;
137             if ( scalar keys %attr ) {
138             use Data::Dumper;
139             my $d = Data::Dumper->new( [ \%attr ] );
140             $d->Purity(1)->Terse(1);
141             $attr = $d->Dump;
142              
143             $tag .= " => $attr";
144              
145             }
146              
147             $tag;
148             }
149              
150             sub childname {
151             my ($child, $M, $D) = @_;
152             if ( scalar(@$D) == 0 ) {
153             sprintf '$self->_tag_%s; %s', $child->{_tag}, "\n";
154             }
155             else {
156             sprintf '$self->%s_%s; %s', $M, $child->{_tag}, "\n";
157             }
158             }
159              
160              
161             sub tagmethod {
162             my ( $self, $tag, $divecall, $children, $derefchain ) = @_;
163              
164             my $childname;
165             my $methodname = do {
166             if ( scalar(@$derefchain) == 0 ) {
167             "_tag_$tag";
168             }
169             else {
170             sprintf '_tag_%s', join '_', @$derefchain;
171             }
172             };
173             warn "METHODNAME: $methodname... derefchain: @$derefchain";
174              
175              
176             my @children = map { ref($_) ? childname($_, $methodname, $derefchain) : () } @$children;
177             my $childstr = @children ? "@children" : '$self->writer->characters($data)';
178              
179             sprintf( <<'EOSTR', $methodname, $divecall, $tag, $childstr );
180             sub %s {
181             my($self)=@_;
182              
183             my $root = $self->data;
184             %s;
185             my ($attr, $data) = $self->EXTRACT($elementdata);
186             $self->writer->startTag(%s => @$attr);
187              
188             %s;
189             $self->writer->endTag;
190             }
191             EOSTR
192             }
193              
194             sub divecall {
195             my ( $self, $derefstring ) = @_;
196             use Data::Dumper;
197             my $str = Dumper($derefstring);
198             sprintf( <<'EOSTR', "@$derefstring" );
199              
200             my $elementdata = $self->DIVE( $root, qw(%s) ) ;
201            
202             EOSTR
203             }
204              
205             sub methodsfrom {
206             my ( $self, $fh, $depth, $derefstring, $firstdepth ) = @_;
207             $fh = *STDOUT{IO} unless defined $fh;
208             $depth = 0 unless defined $depth;
209              
210             my @newderef;
211              
212             if ( $depth < $firstdepth ) {
213             @newderef = ();
214             }
215             else {
216             if ( $depth == $firstdepth ) {
217             @newderef = $self->cleantag;
218             }
219             if ( $depth > $firstdepth ) {
220             @newderef = ( @$derefstring, $self->cleantag );
221             }
222             }
223              
224             #warn "DEREF: @newderef";
225             my @children = @{ $self->{'_content'} };
226             my $divecall = $self->divecall( \@newderef );
227              
228             #warn "DIVECALL: $divecall";
229             my $tagmethod =
230             $self->tagmethod( $self->cleantag, $divecall, \@children, \@newderef );
231              
232             $fh->print($tagmethod);
233             for (@children) {
234              
235             if ( ref $_ ) { # element
236             #use Data::Dumper;
237             #warn Dumper($_);
238             $_->methodsfrom( $fh, $depth + 1, \@newderef, $firstdepth )
239             ; # recurse
240             }
241             else { # text node
242             ;
243             }
244             }
245              
246             }
247              
248             1;
249              
250             =head1 NAME
251              
252             XML::Writer::Compiler - create XML::Writer based classes whose instances can generate, refine and extend sample XML
253              
254             =head1 SYNOPSIS
255              
256             ROOT=CustomerAdd
257             XML_FILE=$ROOT.xml
258             OUTPUT_PKG = XML::Quickbooks::$ROOT
259             HASH_DEPTH=4 #counting from 0, which level of XML file should hash keys start mapping from
260             OUTPATH_PREPEND=lib
261             EXTENDS=XML::Quickbooks # the XML class we generate, XML::Quickbooks::CustomerAdd has the parent XML::Quickbooks
262              
263             # Now run the compiler on the XML file to produce an XML class, which produces XML when a hashref is supplied
264             # Also possible to subclass the generated XML class to customize XML generation when hashrefs are inadequate
265             xwc $XML_FILE $OUTPUT_PKG $HASH_DEPTH $OUTPUT_PREPEND $EXTENDS
266              
267             =head1 DESCRIPTION
268              
269             XML::Writer::Compiler is a module which takes a sample XML document and creates a single class from it.
270             This class contains methods for each tag of the XML. The instance of the class can generate XML with the content supplied via a
271             hashref. Subclassing the generated class allows more precise control over how the object-oriented XML generation occurs.
272              
273             The CPAN module most similar to XML::Writer::Compiler is L<XML::Toolkit>.
274              
275             =head2 Simple Example
276              
277             =head3 XML
278              
279             <note>
280             <to>
281             <person>
282             Bob
283             </person>
284             </to>
285             <from>Jani</from>
286             <heading>Reminder</heading>
287             <body>Don't forget me this weekend!</body>
288             </note>
289              
290             =head3 Compile XML to Perl class
291              
292             This step is normally done via the F<xwc> script, but you can also write Perl code to compile XML:
293              
294             my $compiler = XML::Writer::Compiler->new;
295              
296             my $tree = XML::TreeBuilder->new( { 'NoExpand' => 0, 'ErrorContext' => 0 } );
297             $tree->parse_file('t/sample.xml');
298              
299             my $pkg = XML::Note';
300             my $class = $compiler->buildclass( $pkg, $tree, 0, '' );
301              
302             =head3 Perl
303            
304             my %data = (
305             note => {
306             to => { person => 'Satan' },
307             from => [ [ via => 'postcard', russia => 'with love' ], 'moneypenny' ]
308             }
309             );
310              
311             my $xml = XML::Note->new;
312             $xml->data(\%data);
313             warn $xml->xml->string->value;
314            
315             =head2 Subclassing Example
316              
317             If a simple substitution from a hashref of data will not suffice, then you can take the methods of the generated class and
318             subclass them for things like repeating or conditional content. See the test file F<repeating.t> for an example.
319              
320             =head1 USAGE of xwc
321              
322             The F<xwc> script is the most common way to convert an XML file to an equivalent Perl class. The script takes the
323             following arguments
324              
325             =head2 xml_file (required)
326              
327             the full/relative path to the sample XML file
328              
329             =head2 perl_pkg
330              
331             the name of the Perl package that will represent the C<xml_file>
332              
333             =head2 hash_depth
334              
335             An XML file has nesting levels, or depth. Oftentimes, the outer levels will never be rewritten via the data hashref you supply.
336             As a result, you dont want to have to several levels of your hashref before you actually specify the data you want to bind.
337              
338             Concretely, let's take some XML from the Quickbooks SDK:
339             L<https://member.developer.intuit.com/qbSDK-current/Common/newOSR/index.html>
340              
341             For instance the CustomerAdd xml starts like this:
342              
343             <QBXML>
344             <QBXMLMsgsRq onError="stopOnError">
345             <CustomerAddRq>
346             <CustomerAdd> <!-- required -->
347             <Name >STRTYPE</Name> <!-- required -->
348             <IsActive >BOOLTYPE</IsActive>
349             ....
350             </QBXML>
351              
352             Now, none of the XML from the root to the XPath C<< QBXML/QBXMLMsgsRq/CustomerAddRq/CustomerAdd >> needs any
353             binding from the hashref. If you compiled this XML and had C<< hash_depth>> set to 0, then to set the name your hashref
354             would have to look like:
355              
356             my %data = ( QBXML => { QXBMLMsgsRq => { CustomerAddRq => { CustomerAdd => { Name => 'Bob Jones' }}}}} ;
357              
358             In contrast , if you compiled this XML and had C<< hash_depth>> set to 4, then to set the name your hashref
359             would only have to look like:
360              
361             my %data = ( Name => 'Bob Jones' } ;
362              
363             =head2 prepend_lib
364              
365             The generated class file has a path generated from splitting the class name on double colon. In most cases, you will want to write
366             the class file to a path with 'lib' prepended and so you would set this option to 'lib'
367              
368             =head2 extends
369              
370             The XML class file is a L<Moose> class and can extend any superclass. E.g., in the L<XML::Quickbooks> distribution, each
371             XML class file extends C<< XML::Quickbooks >>.
372              
373             =head1 SEE ALSO
374              
375             =head2 Source code repository
376              
377             L<https://github.com/metaperl/xml-writer-compiler>
378              
379             =head2 Related modules
380              
381             =head3 XML::Toolkit
382              
383             L<XML::Toolkit>
384              
385             =head3 XML::Element::Tolol
386              
387             L<XML::Element::Tolol>
388              
389             =head2 Relevant links
390              
391             =head3 Moose, the tree structure of XML and object-oriented inheritance hiearchies
392              
393             L<http://perlmonks.org/index.pl?node_id=910617>
394              
395             =head3 Control Windows Quickbooks with Win32::OLE
396              
397             L<http://perlmonks.org/index.pl?node_id=909187>
398              
399             =head3 generating XML with data structures and Perl classes - the XML::Element::Tolol approach
400              
401             L<http://perlmonks.org/index.pl?node_id=913713>
402              
403              
404             =head1 COPYRIGHT
405              
406             Copyright C<< RANGE('2011-07-25', NOW()) >> Terrence Brannon.
407              
408             This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
409              
410             This program is distributed in the hope that it will be useful,
411             but without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose.
412              
413             =head1 AUTHOR
414              
415             Terrence Brannon <tbone@cpan.org>
416              
417              
418