File Coverage

blib/lib/XML/DT.pm
Criterion Covered Total %
statement 279 492 56.7
branch 148 300 49.3
condition 52 96 54.1
subroutine 29 50 58.0
pod 20 20 100.0
total 528 958 55.1


line stmt bran cond sub pod time code
1             ## -*- cperl -*-
2              
3             package XML::DT;
4 6     6   556867 use 5.008006;
  6         37  
5              
6 6     6   26 use strict;
  6         10  
  6         137  
7              
8 6     6   2644 use Data::Dumper;
  6         27456  
  6         361  
9              
10 6     6   3632 use HTTP::Tiny;
  6         232047  
  6         300  
11             my $ua = HTTP::Tiny->new();
12              
13 6     6   2793 use XML::DTDParser "ParseDTDFile";
  6         36037  
  6         373  
14              
15 6     6   3401 use XML::LibXML ':libxml';
  6         176048  
  6         36  
16             our $PARSER = 'XML::LibXML';
17              
18 6     6   3064 use parent 'Exporter';
  6         1413  
  6         40  
19              
20 6         32871 use vars qw($c $u %v $q @dtcontext %dtcontextcount @dtatributes
21 6     6   346 @dtattributes );
  6         12  
22              
23             our @EXPORT = qw(&dt &dtstring &dturl &inctxt &ctxt &mkdtskel &inpath
24             &mkdtskel_fromDTD &mkdtdskel &tohtml &toxml &MMAPON $c %v $q $u
25             &xmltree &pathdturl @dtcontext %dtcontextcount
26             @dtatributes @dtattributes &pathdt &pathdtstring
27             &father &gfather &ggfather &root);
28              
29             our $VERSION = '0.69';
30              
31             =encoding utf-8
32              
33             =head1 NAME
34              
35             XML::DT - a package for down translation of XML files
36              
37             =head1 SYNOPSIS
38              
39             use XML::DT;
40              
41             %xml=( 'music' => sub{"Music from: $c\n"},
42             'lyrics' => sub{"Lyrics from: $v{name}\n"},
43             'title' => sub{ uc($c) },
44             '-userdata => { something => 'I like' },
45             '-default' => sub{"$q:$c"} );
46              
47             print dt($filename,%xml);
48              
49             =head1 ABSTRACT
50              
51             This module is a XML down processor. It maps tag (element)
52             names to functions to process that element and respective
53             contents.
54              
55             =head1 DESCRIPTION
56              
57             This module processes XML files with an approach similar to
58             OMNIMARK. As XML parser it uses XML::LibXML module in an independent
59             way.
60              
61             You can parse HTML files as if they were XML files. For this, you must
62             supply an extra option to the hash:
63              
64             %hander = ( -html => 1,
65             ...
66             );
67              
68             You can also ask the parser to recover from XML errors:
69              
70             %hander = ( -recover => 1,
71             ...
72             );
73              
74             =head1 Functions
75              
76             =head2 dt
77              
78             Down translation function C
receives a filename and a set of
79             expressions (functions) defining the processing and associated values
80             for each element.
81              
82             =head2 dtstring
83              
84             C works in a similar way with C
but takes input from a
85             string instead of a file.
86              
87             =head2 dturl
88              
89             C works in a similar way with C
but takes input from an
90             Internet url instead of a file.
91              
92             =head2 pathdt
93              
94             The C function is a C
function which can handle a subset
95             of XPath on handler keys. Example:
96              
97             %handler = (
98             "article/title" => sub{ toxml("h1",{},$c) },
99             "section/title" => sub{ toxml("h2",{},$c) },
100             "title" => sub{ $c },
101             "//image[@type='jpg']" => sub{ "JPEG: " },
102             "//image[@type='bmp']" => sub{ "BMP: sorry, no bitmaps on the web" },
103             );
104              
105             pathdt($filename, %handler);
106              
107             Here are some examples of valid XPath expressions under XML::DT:
108              
109             /aaa
110             /aaa/bbb
111             //ccc - ccc somewhere (same as "ccc")
112             /*/aaa/*
113             //* - same as "-default"
114             /aaa[@id] - aaa with an attribute id
115             /*[@*] - root with an attribute
116             /aaa[not(@name)] - aaa with no attribute "name"
117             //bbb[@name='foo'] - ... attribute "name" = "foo"
118             /ccc[normalize-space(@name)='bbb']
119             //*[name()='bbb'] - complex way of saying "//bbb"
120             //*[starts-with(name(),'aa')] - an element named "aa.*"
121             //*[contains(name(),'c')] - an element ".*c.*"
122             //aaa[string-length(name())=4] "...."
123             //aaa[string-length(name())<4] ".{1,4}"
124             //aaa[string-length(name())>5] ".{5,}"
125              
126             Note that not all XPath is currently handled by XML::DT. A lot of
127             XPath will never be added to XML::DT because is not in accordance with
128             the down translation model. For more documentation about XPath check
129             the specification at http://www.w3c.org or some tutorials under
130             http://www.zvon.org
131              
132             =head2 pathdtstring
133              
134             Like the C function but supporting XPath.
135              
136             =head2 pathdturl
137              
138             Like the C function but supporting XPath.
139              
140              
141             =head2 ctxt
142              
143             Returns the context element of the currently being processed
144             element. So, if you call C you will get your father element,
145             and so on.
146              
147             =head2 inpath
148              
149             C is true if the actual element path matches the
150             provided pattern. This function is meant to be used in the element
151             functions in order to achieve context dependent processing.
152              
153             =head2 inctxt
154              
155             C is true if the actual element father matches the
156             provided pattern.
157              
158             =head2 toxml
159              
160             This is the default "-default" function. It can be used to generate
161             XML based on C<$c> C<$q> and C<%v> variables. Example: add a new
162             attribute to element C without changing it:
163              
164             %handler=( ...
165             ele1 => sub { $v{at1} = "v1"; toxml(); },
166             )
167              
168             C can also be used with 3 arguments: tag, attributes and contents
169              
170             toxml("a",{href=> "http://local/f.html"}, "example")
171              
172             returns:
173              
174             example
175              
176             Empty tags are written as empty tags. If you want an empty tag with opening and
177             closing tags, then use the C.
178              
179             =head2 tohtml
180              
181             See C.
182              
183             =head2 xmltree
184              
185             This simple function just makes a HASH reference:
186              
187             { -c => $c, -q => $q, all_the_other_attributes }
188              
189             The function C understands this structure and makes XML with it.
190              
191             =head2 mkdtskel
192              
193             Used by the mkdtskel script to generate automatically a XML::DT perl
194             script file based on an XML file. Check C manpage for
195             details.
196              
197             =head2 mkdtskel_fromDTD
198              
199             Used by the mkdtskel script to generate automatically a XML::DT perl
200             script file based on an DTD file. Check C manpage for
201             details.
202              
203             =head2 mkdtdskel
204              
205             Used by the mkdtskel script to generate automatically a XML::DT perl
206             script file based on a DTD file. Check C manpage for
207             details.
208              
209             =head1 Accessing parents
210              
211             With XML::DT you can access an element parent (or grand-parent)
212             attributes, till the root of the XML document.
213              
214             If you use c<$dtattributes[1]{foo} = 'bar'> on a processing function,
215             you are defining the attribute C for that element parent.
216              
217             In the same way, you can use C<$dtattributes[2]> to access the
218             grand-parent. C<$dtattributes[-1]> is, as expected, the XML document
219             root element.
220              
221             There are some shortcuts:
222              
223             =over 4
224              
225             =item C
226              
227             =item C
228              
229             =item C
230              
231             You can use these functions to access to your C, grand-father
232             (C) or great-grand-father (C):
233              
234             father("x"); # returns value for attribute "x" on father element
235             father("x", "value"); # sets value for attribute "x" on father
236             # element
237              
238             You can also use it directly as a reference to C<@dtattributes>:
239              
240             father->{"x"}; # gets the attribute
241             father->{"x"} = "value"; # sets the attribute
242             $attributes = father; # gets all attributes reference
243              
244              
245             =item C
246              
247             You can use it as a function to access to your tree root element.
248              
249             root("x"); # gets attribute C on root element
250             root("x", "value"); # sets value for attribute C on root
251              
252             You can also use it directly as a reference to C<$dtattributes[-1]>:
253              
254             root->{"x"}; # gets the attribute x
255             root->{"x"} = "value"; # sets the attribute x
256             $attributes = root; # gets all attributes reference
257              
258             =back
259              
260             =head1 User provided element processing functions
261              
262             The user must provide an HASH with a function for each element, that
263             computes element output. Functions can use the element name C<$q>, the
264             element content C<$c> and the attribute values hash C<%v>.
265              
266             All those global variables are defined in C<$CALLER::>.
267              
268             Each time an element is find the associated function is called.
269              
270             Content is calculated by concatenation of element contents strings and
271             interior elements return values.
272              
273             =head2 C<-default> function
274              
275             When a element has no associated function, the function associated
276             with C<-default> called. If no C<-default> function is defined the
277             default function returns a XML like string for the element.
278              
279             When you use C definitions, you often need do set C<-default>
280             function to return just the contents: C.
281              
282             =head2 C<-outputenc> option
283              
284             C<-outputenc> defines the output encoding (default is Unicode UTF8).
285              
286             =head2 C<-inputenc> option
287              
288             C<-inputenc> forces a input encoding type. Whenever that is possible,
289             define the input encoding in the XML file:
290              
291            
292              
293             =head2 C<-pcdata> function
294              
295             C<-pcdata> function is used to define transformation over the
296             contents. Typically this function should look at context (see
297             C function)
298              
299             The default C<-pcdata> function is the identity
300              
301             =head2 C<-cdata> function
302              
303             You can process C<> in a way different from pcdata. If you
304             define a C<-cdata> method, it will be used. Otherwise, the C<-pcdata>
305             method is called.
306              
307             =head2 C<-begin> function
308              
309             Function to be executed before processing XML file.
310              
311             Example of use: initialization of side-effect variables
312              
313             =head2 C<-end> function
314              
315             Function to be executed after processing XML file. I can use C<$c>
316             content value. The value returned by C<-end> will be the C
return
317             value.
318              
319             Example of use: post-processing of returned contents
320              
321             =head2 C<-recover> option
322              
323             If set, the parser will try to recover in XML errors.
324              
325             =head2 C<-html> option
326              
327             If set, the parser will try to recover in errors. Note that this
328             differs from the previous one in the sense it uses some knowledge of
329             the HTML structure for the recovery.
330              
331             =head2 C<-userdata> option
332              
333             Use this to pass any information you like to your handlers. The data
334             structure you pass in this option will be available as C<< $u >> in
335             your code. -- New in 0.62.
336              
337              
338             =head1 Elements with values other than strings (C<-type>)
339              
340             By default all elements return strings, and contents (C<$c>) is the
341             concatenation of the strings returned by the sub-elements.
342              
343             In some situations the XML text contains values that are better
344             processed as a structured type.
345              
346             The following types (functors) are available:
347              
348             =over 4
349              
350             =item THE_CHILD
351              
352             Return the result of processing the only child of the element.
353              
354             =item LAST_CHILD
355              
356             Returns the result of processing the last child of the element.
357              
358             =item STR
359              
360             concatenates all the sub-elements returned values (DEFAULT) all the
361             sub-element should return strings to be concatenated;
362              
363             =item SEQ
364              
365             makes an ARRAY with all the sub elements contents; attributes are
366             ignored (they should be processed in the sub-element). (returns a ref)
367             If you have different types of sub-elements, you should use SEQH
368              
369             =item SEQH
370              
371             makes an ARRAY of HASH with all the sub elements (returns a ref); for
372             each sub-element:
373              
374             -q => element name
375             -c => contents
376             at1 => at value1 for each attribute
377              
378             =item MAP
379              
380             makes an HASH with the sub elements; keys are the sub-element names,
381             values are their contents. Attributes are ignored. (they should be
382             processed in the sub-element) (returns a ref)
383              
384             =item MULTIMAP
385              
386             makes an HASH of ARRAY; keys are the sub-element names; values are
387             lists of contents; attributes are ignored (they should be processed in
388             the sub-element); (returns a ref)
389              
390             =item MMAPON(element-list)
391              
392             makes an HASH with the sub-elements; keys are the sub-element names,
393             values are their contents; attributes are ignored (they should be
394             processed in the sub-element); for all the elements contained in the
395             element-list, it is created an ARRAY with their contents. (returns a
396             ref)
397              
398             =item XML
399              
400             return a reference to an HASH with:
401              
402             -q => element name
403             -c => contents
404             at1 => at value1 for each attribute
405              
406             =item ZERO
407              
408             don't process the sub-elements; return ""
409              
410             =back
411              
412             When you use C definitions, you often need do set C<-default>
413             function returning just the contents C.
414              
415             =head2 An example:
416              
417             use XML::DT;
418             %handler = ( contacts => sub{ [ split(";",$c)] },
419             -default => sub{$c},
420             -type => { institution => 'MAP',
421             degrees => MMAPON('name')
422             tels => 'SEQ' }
423             );
424             $a = dt ("f.xml", %handler);
425              
426             with the following f.xml
427              
428            
429            
430             U.M.
431             University of Minho
432            
433             1111
434             1112
435             1113
436            
437             Portugal
438             J.Joao; J.Rocha; J.Ramalho
439            
440             Computer science
441             Informatica
442             history
443            
444              
445             would make $a
446              
447             { 'name' => [ 'Computer science',
448             'Informatica ',
449             ' history ' ],
450             'institution' => { 'tels' => [ 1111, 1112, 1113 ],
451             'name' => 'University of Minho',
452             'where' => 'Portugal',
453             'id' => 'U.M.',
454             'contacts' => [ 'J.Joao',
455             ' J.Rocha',
456             ' J.Ramalho' ] } };
457              
458              
459             =head1 DT Skeleton generation
460              
461             It is possible to build an initial processor program based on an example
462              
463             To do this use the function C.
464              
465             Example:
466              
467             perl -MXML::DT -e 'mkdtskel "f.xml"' > f.pl
468              
469             =head1 DTD skeleton generation
470              
471             It makes a naive DTD based on an example(s).
472              
473             To do this use the function C.
474              
475             Example:
476              
477             perl -MXML::DT -e 'mkdtdskel "f.xml"' > f.dtd
478              
479             =head1 SEE ALSO
480              
481             mkdtskel(1) and mkdtdskel(1)
482              
483             =head1 AUTHORS
484              
485             Home for XML::DT;
486              
487             http://natura.di.uminho.pt/~jj/perl/XML/
488              
489             Jose Joao Almeida,
490              
491             Alberto Manuel Simões,
492              
493             =head1 ACKNOWLEDGEMENTS
494              
495             Michel Rodriguez
496              
497             José Carlos Ramalho
498              
499             Mark A. Hillebrand
500              
501             =head1 COPYRIGHT AND LICENSE
502              
503             Copyright 1999-2012 Project Natura.
504              
505             This library is free software; you can redistribute it
506             and/or modify it under the same terms as Perl itself.
507              
508             =cut
509              
510              
511              
512             our %ty = ();
513              
514             sub dt {
515 13     13 1 5975 my ($file, %xml)=@_;
516 13         17 my ($parser, $tree);
517              
518             # Treat -decl option
519 13         21 my $declr = "";
520 13 50       32 if ($xml{-declr}) {
521 0 0       0 if ($xml{-outputenc}) {
522 0         0 $declr = "\n";
523             } else {
524 0         0 $declr = "\n";
525             }
526             }
527              
528 13         22 %ty = ();
529 13 50       27 %ty = (%{$xml{'-type'}}) if defined($xml{'-type'});
  0         0  
530 13         38 $ty{-ROOT} = "NONE";
531              
532 13 50       30 &{$xml{-begin}} if $xml{-begin};
  0         0  
533              
534             # TODO --- how to force encoding with XML::LibXML?
535             # $xml{-inputenc}
536              
537             # create a new LibXML parser
538 13         51 $parser = XML::LibXML->new();
539              
540             #### We don't wan't DT to load everytime the DTD (I Think!)
541 13         205 $parser->validation(0);
542             # $parser->expand_xinclude(0); # testing
543 13         169 $parser->load_ext_dtd(0);
544 13         129 $parser->expand_entities(0);
545 13 50       133 $parser->expand_xincludes(1) if $xml{'-xinclude'};
546              
547             # parse the file
548 13         16 my $doc;
549 13 50       33 if ( $xml{'-recover'}) {
    50          
550 0         0 $parser->recover(1);
551 0         0 eval {
552 0     0   0 local $SIG{__WARN__} = sub{};
553 0         0 $doc = $parser->parse_file($file);
554             };
555 0 0       0 return undef if !$doc;
556             }
557             elsif ( $xml{'-html'}) {
558 0         0 $parser->recover(1);
559 0         0 eval {
560 0     0   0 local $SIG{__WARN__} = sub{};
561 0         0 $doc = $parser->parse_html_file($file);
562             };
563 0 0       0 return undef if !$doc;
564             }
565             else {
566 13         33 $doc = $parser->parse_file($file)
567             }
568              
569             # get the document root element
570 13         3148 $tree = $doc->getDocumentElement();
571              
572 13         25 my $return = "";
573             # execute End action if it exists
574 13 100       30 if($xml{-end}) {
575 1         6 $c = _omni("-ROOT", \%xml, $tree);
576 1         2 $return = &{$xml{-end}}
  1         2  
577             } else {
578 12         29 $return = _omni("-ROOT",\%xml, $tree)
579             }
580              
581 13 50       24 if ($declr) {
582 0         0 return $declr.$return;
583             } else {
584 13         47 return $return;
585             }
586             }
587              
588              
589             sub ctxt {
590 0     0 1 0 my $level = $_[0];
591 0         0 $dtcontext[-$level-1];
592             }
593              
594             sub inpath {
595 0     0 1 0 my $pattern = shift ;
596 0         0 join ("/", @dtcontext) =~ m!\b$pattern\b!;
597             }
598              
599              
600             sub inctxt {
601 38     38 1 39 my $pattern = shift ;
602             # see if is in root context...
603 38 100 100     136 return 1 if (($pattern eq "^" && @dtcontext==1) || $pattern eq ".*");
      100        
604 11         108 join("/", @dtcontext) =~ m!$pattern/[^/]*$! ;
605             }
606              
607             sub father {
608 0     0 1 0 my ($a,$b)=@_;
609 0 0       0 if (defined($b)){$dtattributes[1]{$a} = $b}
  0 0       0  
610 0         0 elsif(defined($a)){$dtattributes[1]{$a} }
611 0         0 else {$dtattributes[1]}
612             }
613              
614             sub gfather {
615 0     0 1 0 my ($a,$b)=@_;
616 0 0       0 if (defined($b)){$dtattributes[2]{$a} = $b}
  0 0       0  
617 0         0 elsif(defined($a)){$dtattributes[2]{$a} }
618 0         0 else {$dtattributes[2]}
619             }
620              
621              
622             sub ggfather {
623 0     0 1 0 my ($a,$b)=@_;
624 0 0       0 if (defined($b)){$dtattributes[3]{$a} = $b}
  0 0       0  
625 0         0 elsif(defined($a)){$dtattributes[3]{$a} }
626 0         0 else {$dtattributes[3]}
627             }
628              
629              
630             sub root { ### the root
631 0     0 1 0 my ($a,$b)=@_;
632 0 0       0 if (defined($b)){$dtattributes[-1]{$a} = $b }
  0 0       0  
633 0         0 elsif(defined($a)){$dtattributes[-1]{$a} }
634 0         0 else {$dtattributes[-1] }
635             }
636              
637             sub pathdtstring{
638 12     12 1 5874 my $string = shift;
639 12         26 my %h = _pathtodt(@_);
640 12         29 return dtstring($string,%h);
641             }
642              
643              
644              
645             sub pathdturl{
646 0     0 1 0 my $url = shift;
647 0         0 my %h = _pathtodt(@_);
648 0         0 return dturl($url,%h);
649             }
650              
651              
652              
653             sub dturl{
654 0     0 1 0 my $url = shift;
655 0         0 my $contents = $ua->get($url);
656 0 0       0 if ($contents->{success}) {
657             # warn("JJ ok\n");
658 0         0 return dtstring($contents->{content}, @_);
659             } else {
660             # warn("JJ not ok\n");
661 0         0 warn("$contents->{status}: $contents->{reason}");
662 0         0 return undef;
663             }
664             }
665              
666              
667              
668             sub dtstring {
669 20     20 1 4233 my ($string, %xml)=@_;
670 20         27 my ($parser, $tree);
671              
672 20         25 my $declr = "";
673 20 100       47 if ($xml{-declr}) {
674 1 50       3 if ($xml{-outputenc}) {
675 0         0 $declr = "\n";
676             } else {
677 1         2 $declr = "\n";
678             }
679             }
680              
681 20 100       46 $xml{'-type'} = {} unless defined $xml{'-type'};
682 20         22 %ty = (%{$xml{'-type'}}, -ROOT => "NONE");
  20         74  
683              
684             # execute Begin action if it exists
685 20 50       41 if ($xml{-begin}) {
686 0         0 &{$xml{-begin}}
  0         0  
687             }
688              
689 20 100       36 if ($xml{-inputenc}) {
690 3         30 $string = XML::LibXML::encodeToUTF8($xml{-inputenc}, $string);
691             }
692              
693             # create a new LibXML parser
694 20         64 $parser = XML::LibXML->new();
695 20         255 $parser->validation(0);
696 20         234 $parser->load_ext_dtd(0);
697 20         186 $parser->expand_entities(0);
698              
699             # parse the string
700 20         187 my $doc;
701 20 50       48 if ( $xml{'-recover'}) {
    50          
702 0         0 $parser->recover(1);
703 0         0 eval {
704 0     0   0 local $SIG{__WARN__} = sub{};
705 0         0 $doc = $parser->parse_string($string);
706             };
707 0 0       0 return undef if !$doc;
708             }
709             elsif ( $xml{'-html'}) {
710 0         0 $parser->recover(1);
711 0         0 eval{
712 0     0   0 local $SIG{__WARN__} = sub{};
713 0         0 $doc = $parser->parse_html_string($string);
714             };
715             # if ($@) { return undef; }
716 0 0       0 return undef unless defined $doc;
717             } else {
718 20         45 $doc = $parser->parse_string($string);
719             }
720              
721             # get the document root element
722 20         2864 $tree = $doc->getDocumentElement();
723              
724 20         27 my $return;
725              
726             # Check if we have an end function
727 20 50       40 if ($xml{-end}) {
728 0         0 $c = _omni("-ROOT", \%xml, $tree);
729 0         0 $return = &{$xml{-end}}
  0         0  
730             } else {
731 20         47 $return = _omni("-ROOT", \%xml, $tree)
732             }
733              
734 20 100       44 if ($declr) {
735 1         6 return $declr.$return;
736             } else {
737 19         67 return $return;
738             }
739             }
740              
741              
742              
743             sub pathdt{
744 0     0 1 0 my $file = shift;
745 0         0 my %h = _pathtodt(@_);
746 0         0 return dt($file,%h);
747             }
748              
749              
750              
751             # Parsing dos predicados do XPath
752             sub _testAttr {
753 9     9   15 my $atr = shift;
754 9         18 for ($atr) {
755 9         13 s/name\(\)/'$q'/g;
756             # s/\@([A-Za-z_]+)/'$v{$1}'/g;
757 9 50       31 s/\@([A-Za-z_]+)/defined $v{$1}?"'$v{$1}'":"''"/ge;
  4         19  
758 9 100       22 s/\@\*/keys %v?"'1'":"''"/ge;
  5         14  
759 9 50       68 if (/^not\((.*)\)$/) {
    100          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
760 0         0 return ! _testAttr($1);
761             } elsif (/^('|")([^\1]*)(\1)\s*=\s*('|")([^\4]*)\4$/) {
762 4         19 return ($2 eq $5);
763             } elsif (/^(.*?)normalize-space\((['"])([^\2)]*)\2\)(.*)$/) {
764 0         0 my ($back,$forward)=($1,$4);
765 0         0 my $x = _normalize_space($3);
766 0         0 return _testAttr("$back'$x'$forward");
767             } elsif (/starts-with\((['"])([^\1))]*)\1,(['"])([^\3))]*)\3\)/) {
768 0         0 my $x = _starts_with($2,$4);
769 0         0 return $x;
770             } elsif (/contains\((['"])([^\1))]*)\1,(['"])([^\3))]*)\3\)/) {
771 0         0 my $x = _contains($2,$4);
772 0         0 return $x;
773             } elsif (/^(.*?)string-length\((['"])([^\2]*)\2\)(.*)$/) {
774 0         0 my ($back,$forward) = ($1,$4);
775 0         0 my $x = length($3);
776 0         0 return _testAttr("$back$x$forward");
777             } elsif (/^(\d+)\s*=(\d+)$/) {
778 0         0 return ($1 == $2);
779             } elsif (/^(\d+)\s*<(\d+)$/) {
780 0         0 return ($1 < $2);
781             } elsif (/^(\d+)\s*>(\d+)$/) {
782 0         0 return ($1 > $2);
783             } elsif (/^(['"])([^\1]*)\1$/) {
784 5         20 return $2;
785             }
786             }
787 0         0 return 0; #$atr;
788             }
789              
790              
791              
792             # Funcao auxiliar de teste de predicados do XPath
793             sub _starts_with {
794 0     0   0 my ($string,$preffix) = @_;
795 0 0 0     0 return 0 unless ($string && $preffix);
796 0 0       0 return 1 if ($string =~ m!^$preffix!);
797 0         0 return 0;
798             }
799              
800              
801             # Funcao auxiliar de teste de predicados do XPath
802             sub _contains {
803 0     0   0 my ($string,$s) = @_;
804 0 0 0     0 return 0 unless ($string && $s);
805 0 0       0 return 1 if ($string =~ m!$s!);
806 0         0 return 0;
807             }
808              
809              
810             # Funcao auxiliar de teste de predicados do XPath
811             sub _normalize_space {
812 4     4   116 my $z = shift;
813 4         22 $z =~ /^\s*(.*?)\s*$/;
814 4         9 $z = $1;
815 4         11 $z =~ s!\s+! !g;
816 4         18 return $z;
817             }
818              
819              
820             sub _pathtodt {
821 12     12   32 my %h = @_;
822 12         19 my %aux=();
823 12         13 my %aux2=();
824 12         14 my %n = ();
825 12         22 my $z;
826 12         35 for $z (keys %h) {
827             # TODO - Make it more generic
828 16 100       94 if ( $z=~m{\w+(\|\w+)+}) {
    100          
829 1         3 my @tags = split /\|/, $z;
830 1         3 for(@tags) {
831 3         6 $aux2{$_}=$h{$z}
832             }
833             }
834             elsif ( $z=~m{(//|/|)(.*)/([^\[]*)(?:\[(.*)\])?} ) {
835 10         35 my ($first,$second,$third,$fourth) = ($1,$2,$3,$4);
836 10 100 100     34 if (($first eq "/") && (!$second)) {
837 7         11 $first = "";
838 7         7 $second = '.*';
839 7         16 $third =~ s!\*!-default!;
840             } else {
841 3         6 $second =~ s!\*!\[^/\]\+!g;
842 3         4 $second =~ s!/$!\(/\.\*\)\?!g;
843 3         4 $second =~ s!//!\(/\.\*\)\?/!g;
844 3         6 $third =~ s!\*!-default!g;
845             }
846 10         12 push( @{$aux{$third}} , [$first,$second,$h{$z},$fourth]);
  10         39  
847             }
848 5         7 else { $aux2{$z}=$h{$z};}
849             }
850 12         23 for $z (keys %aux){
851             my $code = sub {
852 38     38   37 my $l;
853 38         38 for $l (@{$aux{$z}}) {
  38         55  
854 38         42 my $prefix = "";
855 38 100 100     100 $prefix = "^" unless (($l->[0]) or ($l->[1]));
856 38 50 66     61 $prefix = "^" if (($l->[0] eq "/") && ($l->[1]));
857 38 100       50 if ($l->[3]) {
858 9 100 66     28 if(inctxt("$prefix$l->[1]") && _testAttr($l->[3]))
859 4         7 {return &{$l->[2]}; }
  4         9  
860             } else {
861 29 100       54 if(inctxt("$prefix$l->[1]")) {return &{$l->[2]};}
  20         22  
  20         34  
862             }
863             }
864 14 50       26 return &{ $aux2{$z}} if $aux2{$z} ;
  0         0  
865 14 100       22 return &{ $h{-default}} if $h{-default};
  4         7  
866 10         16 &toxml();
867 10         37 };
868 10         20 $n{$z} = $code;
869             }
870 12         19 for $z (keys %aux2){
871 8   33     26 $n{$z} ||= $aux2{$z} ;
872             }
873 12         37 return %n;
874             }
875              
876              
877              
878             sub _omni {
879 242     242   419 my ($par, $xml, @l) = @_;
880             my $defaulttype =
881             (exists($xml->{-type}) && exists($xml->{-type}{-default}))
882             ?
883 242 100 100     625 $xml->{-type}{-default} : "STR";
884 242   66     568 my $type = $ty{$par} || $defaulttype;
885 242         266 my %typeargs = ();
886              
887 242 50       364 if (ref($type) eq "mmapon") {
888 0         0 $typeargs{$_} = 1 for (@$type);
889 0         0 $type = "MMAPON";
890             }
891              
892 242         230 my $r ;
893 242 100 33     642 if( $type eq 'STR') { $r = "" }
  202 50 66     203  
    100 66        
    100 33        
    50 33        
    50          
    50          
    50          
    0          
894 0         0 elsif( $type eq 'THE_CHILD' or $type eq 'LAST_CHILD') { $r = 0 }
895 5         9 elsif( $type eq 'SEQ' or $type eq "ARRAY") { $r = [] }
896 2         2 elsif( $type eq 'SEQH' or $type eq "ARRAYOFHASH") { $r = [] }
897 0         0 elsif( $type eq 'MAP' or $type eq "HASH") { $r = {} }
898 0         0 elsif( $type eq 'MULTIMAP') { $r = {} }
899 0         0 elsif( $type eq 'MMAPON' or $type eq "HASHOFARRAY") { $r = {} }
900 33         42 elsif( $type eq 'NONE') { $r = "" }
901 0         0 elsif( $type eq 'ZERO') { return "" }
902              
903 242         256 my ($name, $val, @val, $atr, $aux);
904              
905 242         262 $u = $xml->{-userdata};
906 242         342 while(@l) {
907 507         562 my $tree = shift @l;
908 507 50       1262 next unless $tree;
909              
910 507 50       2763 $name = ref($tree) eq "XML::LibXML::CDATASection" ? "-pcdata" : $tree->getName();
911              
912 507 50       1157 if (ref($tree) eq "XML::LibXML::CDATASection") {
    50          
    100          
    50          
    0          
913 0         0 $val = $tree->getData();
914              
915 0         0 $name = "-cdata";
916 0 0       0 $aux = (defined($xml->{-outputenc}))?_fromUTF8($val,$xml->{-outputenc}):$val;
917              
918 0 0       0 if (defined($xml->{-cdata})) {
    0          
919 0         0 push(@dtcontext,"-cdata");
920 0         0 $c = $aux;
921 0         0 $aux = &{$xml->{-cdata}};
  0         0  
922 0         0 pop(@dtcontext);
923             } elsif (defined($xml->{-pcdata})) {
924 0         0 push(@dtcontext,"-pcdata");
925 0         0 $c = $aux;
926 0         0 $aux = &{$xml->{-pcdata}};
  0         0  
927 0         0 pop(@dtcontext);
928             }
929              
930             } elsif (ref($tree) eq "XML::LibXML::Comment") {
931             ### At the moment, treat as Text
932             ### We will need to change this, I hope!
933 0         0 $val = "";
934 0         0 $name = "-pcdata";
935 0 0       0 $aux= (defined($xml->{-outputenc}))?_fromUTF8($val, $xml->{-outputenc}):$val;
936 0 0       0 if (defined($xml->{-pcdata})) {
937 0         0 push(@dtcontext,"-pcdata");
938 0         0 $c = $aux;
939 0         0 $aux = &{$xml->{-pcdata}};
  0         0  
940 0         0 pop(@dtcontext);
941             }
942             }
943             elsif (ref($tree) eq "XML::LibXML::Text") {
944 298         641 $val = $tree->getData();
945              
946 298         336 $name = "-pcdata";
947 298 100       436 $aux = (defined($xml->{-outputenc}))?_fromUTF8($val,$xml->{-outputenc}):$val;
948              
949 298 50       451 if (defined($xml->{-pcdata})) {
950 0         0 push(@dtcontext,"-pcdata");
951 0         0 $c = $aux;
952 0         0 $aux = &{$xml->{-pcdata}};
  0         0  
953 0         0 pop(@dtcontext);
954             }
955              
956             } elsif (ref($tree) eq "XML::LibXML::Element") {
957 209         293 my %atr = _nodeAttributes($tree);
958 209         294 $atr = \%atr;
959              
960 209 100       330 if (exists($xml->{-ignorecase})) {
961 40         57 $name = lc($name);
962 40         67 for (keys %$atr) {
963 8         19 my ($k,$v) = (lc($_),$atr->{$_});
964 8         12 delete($atr->{$_});
965 8         16 $atr->{$k} = $v;
966             }
967             }
968              
969 209         298 push(@dtcontext,$name);
970 209         288 $dtcontextcount{$name}++;
971 209         243 unshift(@dtatributes, $atr);
972 209         205 unshift(@dtattributes, $atr);
973 209         710 $aux = _omniele($xml, $name, _omni($name, $xml, ($tree->getChildnodes())), $atr);
974 209         832 shift(@dtatributes);
975 209         1574 shift(@dtattributes);
976 209         212 pop(@dtcontext); $dtcontextcount{$name}--;
  209         314  
977             } elsif (ref($tree) eq "XML::LibXML::Node") {
978 0 0       0 if ($tree->nodeType == XML_ENTITY_REF_NODE) {
979             # if we get here, is because we are not expanding entities (I think)
980 0 0       0 if ($tree->textContent) {
981 0         0 $aux = $tree->textContent;
982             } else {
983 0         0 $aux = '&'.$tree->nodeName.';';
984             }
985             } else {
986 0         0 print STDERR "Not handled, generic node of type: [",$tree->nodeType,"]\n";
987             }
988             } else {
989 0         0 print STDERR "Not handled: [",ref($tree),"]\n";
990             }
991              
992 507 100 33     927 if ($type eq "STR"){ if (defined($aux)) {$r .= $aux} ;}
  461 100 66     605  
  446 50 66     791  
    100 33        
    100          
    50          
    50          
    50          
    50          
993             elsif ($type eq "THE_CHILD" or $type eq "LAST_CHILD"){
994 0 0       0 $r = $aux unless _whitepc($aux, $name); }
995             elsif ($type eq "SEQ" or $type eq "ARRAY"){
996 9 50       15 push(@$r, $aux) unless _whitepc($aux, $name);}
997             elsif ($type eq "SEQH" or $type eq "ARRAYHASH"){
998 4 50       7 push(@$r,{"-c" => $aux,
999             "-q" => $name,
1000             _nodeAttributes($tree)
1001             }) unless _whitepc($aux,$name);
1002             }
1003             elsif($type eq "MMAPON"){
1004 0 0       0 if(not _whitepc($aux,$name)){
1005 0 0       0 if(! $typeargs{$name}) {
1006 0 0       0 warn "duplicated tag '$name'\n" if(defined($r->{$name}));
1007 0         0 $r->{$name} = $aux }
1008 0 0       0 else { push(@{$r->{$name}},$aux) unless _whitepc($aux,$name)}}
  0         0  
1009             }
1010             elsif($type eq "MAP" or $type eq "HASH"){
1011 0 0       0 if(not _whitepc($aux,$name)){
1012 0 0       0 warn "duplicated tag '$name'\n" if(defined($r->{$name}));
1013 0         0 $r->{$name} = $aux }}
1014             elsif($type eq "MULTIMAP"){
1015 0 0       0 push(@{$r->{$name}},$aux) unless _whitepc($aux,$name)}
  0         0  
1016 33         100 elsif($type eq "NONE"){ $r = $aux;}
1017 0         0 else { $r="undefined type !!!"}
1018             }
1019 242         551 $r;
1020             }
1021              
1022              
1023              
1024             sub _omniele {
1025 209     209   236 my $xml = shift;
1026 209         201 my $aux;
1027 209         308 ($q, $c, $aux) = @_;
1028              
1029 209         354 %v = %$aux;
1030              
1031 209 100       319 if (defined($xml->{-outputenc})) {
1032 3         7 for (keys %v){
1033             $v{$_} = _fromUTF8($v{$_}, $xml->{-outputenc})
1034 1         6 }
1035             }
1036              
1037 209 100       390 if (defined $xml->{$q})
    100          
    50          
1038 53         54 { &{$xml->{$q}} }
  53         90  
1039             elsif (defined $xml->{'-default'})
1040 149         144 { &{$xml->{'-default'}} }
  149         227  
1041             elsif (defined $xml->{'-tohtml'})
1042 0         0 { tohtml() }
1043             else
1044 7         15 { toxml() }
1045             }
1046              
1047              
1048              
1049 0     0 1 0 sub xmltree { +{'-c' => $c, '-q' => $q, %v} }
1050              
1051             sub tohtml {
1052 44     44 1 55 my ($q,$v,$c);
1053            
1054 44 50       76 if (not @_) {
    100          
1055 0         0 ($q,$v,$c) = ($XML::DT::q, \%XML::DT::v, $XML::DT::c);
1056             } elsif (ref($_[0])) {
1057 21         25 $c = shift;
1058             } else {
1059 23         36 ($q,$v,$c) = @_;
1060             }
1061            
1062 44 100 66     114 if (not ref($c)) {
    100 33        
    50          
    50          
1063 23 50 100     109 if ($q eq "-pcdata") {
    100 100        
      100        
1064 0         0 return $c
1065             } elsif ($q eq "link" || $q eq "br" || $q eq "hr" || $q eq "img") {
1066 4         7 return _openTag($q,$v)
1067             } else {
1068 19         26 return _openTag($q,$v) . "$c"
1069             }
1070             }
1071             elsif (ref($c) eq "HASH" && $c->{'-q'} && $c->{'-c'}) {
1072 16         38 my %a = %$c;
1073 16         32 my ($q,$c) = delete @a{"-q","-c"};
1074 16 100       57 tohtml($q,\%a,(ref($c)?tohtml($c):$c));
1075             }
1076             elsif (ref($c) eq "HASH") {
1077             _openTag($q,$v).
1078             join("",map {($_ ne "-pcdata")
1079             ? ( (ref($c->{$_}) eq "ARRAY")
1080             ? "<$_>".
1081 0         0 join("\n<$_>", @{$c->{$_}}).
1082             "\n"
1083 0 0       0 : tohtml($_,{},$c->{$_})."\n" )
    0          
1084             : () }
1085 0         0 keys %{$c} ) .
  0         0  
1086             "$c->{-pcdata}" } ######## "NOTYetREady"
1087             elsif (ref($c) eq "ARRAY") {
1088 5 50 33     14 if (defined($q) && exists($ty{$q}) && $ty{$q} eq "SEQH") {
    50 0        
1089 0         0 tohtml($q,$v,join("\n",map {tohtml($_)} @$c))
  0         0  
1090             } elsif (defined $q) {
1091 0         0 tohtml($q,$v,join("",@{$c}))
  0         0  
1092             } else {
1093 5 50       10 join("\n",map {(ref($_)?tohtml($_):$_)} @$c)
  8         14  
1094             }
1095             }
1096             }
1097              
1098             sub toxml {
1099 118     118 1 207 my ($q,$v,$c);
1100              
1101 118 100       196 if (not @_) {
    100          
1102 64         106 ($q, $v, $c) = ($XML::DT::q, \%XML::DT::v, $XML::DT::c);
1103             } elsif (ref($_[0])) {
1104 27         32 $c = shift;
1105             } else {
1106 27         45 ($q, $v, $c) = @_;
1107             }
1108              
1109 118 100 66     309 if (not ref($c)) {
    100 33        
    50          
    50          
1110 91 100       154 if ($q eq "-pcdata") {
    100          
1111 3         31 return $c
1112             } elsif ($c eq "") {
1113 3         19 return _emptyTag($q,$v)
1114             } else {
1115 85         126 return _openTag($q,$v) . "$c"
1116             }
1117             }
1118             elsif (ref($c) eq "HASH" && $c->{'-q'} && $c->{'-c'}) {
1119 20         61 my %a = %$c;
1120 20         45 my ($q,$c) = delete @a{"-q","-c"};
1121             ### _openTag($q,\%a).toxml($c).).
1122             ### toxml($q,\%a,join("\n",map {toxml($_)} @$c))
1123 20 100       123 toxml($q,\%a,(ref($c)?toxml($c):$c));
1124             }
1125             elsif (ref($c) eq "HASH") {
1126             _openTag($q,$v).
1127             join("",map {($_ ne "-pcdata")
1128             ? ( (ref($c->{$_}) eq "ARRAY")
1129             ? "<$_>".
1130 0         0 join("\n<$_>", @{$c->{$_}}).
1131             "\n"
1132 0 0       0 : toxml($_,{},$c->{$_})."\n" )
    0          
1133             : () }
1134 0         0 keys %{$c} ) .
  0         0  
1135             "$c->{-pcdata}" } ######## "NOTYetREady"
1136             elsif (ref($c) eq "ARRAY") {
1137 7 50 33     26 if (defined($q) && exists($ty{$q}) && $ty{$q} eq "SEQH") {
    50 0        
1138 0         0 toxml($q,$v,join("\n",map {toxml($_)} @$c))
  0         0  
1139             } elsif (defined $q) {
1140 0         0 toxml($q,$v,join("",@{$c}))
  0         0  
1141             } else {
1142 7 50       14 join("\n",map {(ref($_)?toxml($_):$_)} @$c)
  12         31  
1143             }
1144             }
1145             }
1146              
1147              
1148             sub _openTag{
1149 108     108   145 "<$_[0]". join("",map {" $_=\"$_[1]{$_}\""} keys %{$_[1]} ).">"
  13         88  
  108         493  
1150             }
1151              
1152             sub _emptyTag{
1153 3     3   6 "<$_[0]". join("",map {" $_=\"$_[1]{$_}\""} keys %{$_[1]} )."/>"
  0         0  
  3         21  
1154             }
1155              
1156              
1157             sub mkdtskel_fromDTD {
1158 1     1 1 832 my $filename = shift;
1159 1         7 my $file = ParseDTDFile($filename);
1160              
1161 1         5573 print <<'PERL';
1162             #!/usr/bin/perl
1163             use warnings;
1164             use strict;
1165             use XML::DT;
1166             my $filename = shift;
1167              
1168             # Variable Reference
1169             #
1170             # $c - contents after child processing
1171             # $q - element name (tag)
1172             # %v - hash of attributes
1173              
1174             my %handler=(
1175             # '-outputenc' => 'ISO-8859-1',
1176             # '-default' => sub{"<$q>$c"},
1177             PERL
1178              
1179              
1180 1         9 for (sort keys %{$file}) {
  1         11  
1181 3         12 print " '$_' => sub { },";
1182             print " # attributes: ",
1183 3 100       13 join(", ", keys %{$file->{$_}{attributes}}) if exists($file->{$_}{attributes});
  2         12  
1184 3         12 print "\n";
1185             }
1186              
1187              
1188 1         20 print <<'PERL';
1189             );
1190              
1191             print dt($filename, %handler);
1192             PERL
1193              
1194             }
1195              
1196             sub mkdtskel{
1197 1     1 1 230 my @files = @_;
1198 1         3 my $name;
1199 1         3 my $HTML = "";
1200 1         2 my %element;
1201             my %att;
1202             my %mkdtskel =
1203             ('-default' => sub{
1204 10     10   16 $element{$q}++;
1205 10         15 for (keys %v) {
1206 2         5 $att{$q}{$_} = 1
1207             };
1208 10         20 ""},
1209              
1210             '-end' => sub{
1211 1     1   12 print <<'END';
1212             #!/usr/bin/perl
1213             use XML::DT;
1214             use warnings;
1215             use strict;
1216             my $filename = shift;
1217              
1218             # Variable Reference
1219             #
1220             # $c - contents after child processing
1221             # $q - element name (tag)
1222             # %v - hash of attributes
1223              
1224             my %handler=(
1225             # '-outputenc' => 'ISO-8859-1',
1226             # '-default' => sub{"<$q>$c"},
1227             END
1228 1         3 print $HTML;
1229 1         8 for $name (sort keys %element) {
1230 3         8 print " '$name' => sub{ }, #";
1231 3         8 print " $element{$name} occurrences;";
1232             print ' attributes: ',
1233 3 100       6 join(', ', keys %{$att{$name}}) if $att{$name};
  2         5  
1234             # print " \"\$q:\$c\"\n";
1235 3         6 print "\n";
1236             }
1237 1         3 print <<'END';
1238             );
1239             print dt($filename, %handler);
1240             END
1241             }
1242 1         11 );
1243              
1244 1         3 my $file = shift(@files);
1245 1         16 while($file =~ /^-/){
1246 0 0       0 if ($file eq "-html") {
    0          
1247 0         0 $HTML = " '-html' => 1,\n";
1248 0         0 $mkdtskel{'-html'} = 1;}
1249 0         0 elsif($file eq "-latin1") { $mkdtskel{'-inputenc'}='ISO-8859-1';}
1250 0         0 else { die("usage mktskel [-html] [-latin1] file \n")}
1251 0         0 $file=shift(@files)}
1252              
1253 1         10 dt($file,%mkdtskel)
1254             }
1255              
1256              
1257              
1258             sub _nodeAttributes {
1259 213     213   227 my $node = shift;
1260 213         238 my %answer = ();
1261 213         416 my @attrs = $node->getAttributes();
1262 213         301 for (@attrs) {
1263 37 50       64 if (ref($_) eq "XML::LibXML::Namespace") {
1264             # TODO: This should not be ignored, I think.
1265             # This sould be converted on a standard attribute with
1266             # key 'namespace' and respective contents
1267             } else {
1268 37         177 $answer{$_->getName()} = $_->getValue();
1269             }
1270             }
1271 213         464 return %answer;
1272             }
1273              
1274              
1275             sub mkdtdskel {
1276 0     0 1 0 my @files = @_;
1277 0         0 my $name;
1278             my %att;
1279 0         0 my %ele;
1280 0         0 my %elel;
1281 0         0 my $root;
1282             my %handler=(
1283             '-outputenc' => 'ISO-8859-1',
1284             '-default' => sub{
1285 0     0   0 $elel{$q}++;
1286 0 0       0 $root = $q unless ctxt(1);
1287 0         0 $ele{ctxt(1)}{$q} ++;
1288 0         0 for(keys(%v)){$att{$q}{$_} ++ } ;
  0         0  
1289             },
1290 0 0   0   0 '-pcdata' => sub{ if ($c =~ /[^ \t\n]/){ $ele{ctxt(1)}{"#PCDATA"}=1 }},
  0         0  
1291 0         0 );
1292              
1293 0         0 while($files[0] =~ /^-/){
1294 0 0       0 if ($files[0] eq "-html") { $handler{'-html'} = 1;}
  0 0       0  
1295 0         0 elsif($files[0] eq "-latin1") { $handler{'-inputenc'}='ISO-8859-1';}
1296 0         0 else { die("usage mkdtdskel [-html] [-latin1] file* \n")}
1297 0         0 shift(@files)}
1298              
1299 0         0 for my $filename (@files){
1300 0         0 dt($filename,%handler);
1301             }
1302              
1303 0         0 print "\n\n";
1304 0         0 delete $elel{$root};
1305              
1306 0         0 for ($root, keys %elel){
1307 0         0 _putele($_, \%ele);
1308 0         0 for $name (keys(%{$att{$_}})) {
  0         0  
1309 0         0 print( "\t\n");
1310 0         0 print( "\t\n");
1311             }
1312             }
1313             }
1314              
1315             sub _putele {
1316 0     0   0 my ($e,$ele) = @_;
1317 0         0 my @f ;
1318 0 0       0 if ($ele->{$e}) {
1319 0         0 @f = keys %{$ele->{$e}};
  0         0  
1320 0 0 0     0 print "
1321             (@f >= 1 && $f[0] eq "#PCDATA" ? "" : "*"),
1322             " >\n";
1323 0         0 print "\n";
  0         0  
1324             }
1325             else {
1326 0         0 print "\n";
1327             }
1328             }
1329              
1330             sub _whitepc {
1331 13 100   13   85 $_[1] eq '-pcdata' and $_[0] =~ /^[ \t\r\n]*$/
1332             }
1333              
1334             sub MMAPON {
1335 0     0 1 0 bless([@_],"mmapon")
1336             }
1337              
1338              
1339             sub _fromUTF8 {
1340 4     4   8 my $string = shift;
1341 4         5 my $encode = shift;
1342 4         5 my $ans = eval { XML::LibXML::decodeFromUTF8($encode, $string) };
  4         24  
1343 4 50       7 if ($@) {
1344 0         0 return $string
1345             } else {
1346 4         9 return $ans
1347             }
1348             }
1349              
1350             1;