File Coverage

blib/lib/OTRS/OPM/Parser.pm
Criterion Covered Total %
statement 135 135 100.0
branch 34 34 100.0
condition 10 10 100.0
subroutine 20 20 100.0
pod 4 4 100.0
total 203 203 100.0


line stmt bran cond sub pod time code
1             package OTRS::OPM::Parser;
2              
3             # ABSTRACT: Parser for the .opm file
4              
5             our $VERSION = 1.04;
6              
7 9     9   937065 use Moo;
  9         105415  
  9         45  
8 9     9   17962 use MooX::HandlesVia;
  9         89108  
  9         57  
9 9     9   5252 use OTRS::OPM::Parser::Types qw(:all);
  9         35  
  9         109  
10              
11 9     9   519171 use MIME::Base64 ();
  9         7247  
  9         285  
12 9     9   4244 use Path::Class;
  9         353489  
  9         617  
13 9     9   4822 use Try::Tiny;
  9         12201  
  9         511  
14 9     9   6130 use XML::LibXML;
  9         505557  
  9         84  
15              
16             # declare attributes
17             has product => ( is => 'rw', isa => Str, );
18             has name => ( is => 'rw', isa => Str, );
19             has version => ( is => 'rw', isa => VersionString, );
20             has vendor => ( is => 'rw', isa => Str, );
21             has url => ( is => 'rw', isa => Str, );
22             has license => ( is => 'rw', isa => Str, );
23             has description => ( is => 'rw', isa => Str, );
24             has error_string => ( is => 'rw', isa => Str, );
25             has tree => ( is => 'rw', isa => XMLTree, );
26              
27             has opm_file => (
28             is => 'ro',
29             isa => Str,
30             required => 1,
31             );
32              
33             has files => (
34             is => 'rw',
35             isa => ArrayRef[HashRef],
36             default => sub{ [] },
37             handles_via => 'Array',
38             handles => {
39             add_file => 'push',
40             },
41             );
42              
43             has framework => (
44             handles_via => 'Array',
45             is => 'rw',
46             isa => ArrayRef[FrameworkVersionString],
47             default => sub { [] },
48             handles => {
49             add_framework => 'push',
50             },
51             );
52              
53             has framework_details => (
54             handles_via => 'Array',
55             is => 'rw',
56             isa => ArrayRef[HashRef],
57             default => sub { [] },
58             handles => {
59             add_framework_detail => 'push',
60             },
61             );
62              
63             has dependencies => (
64             handles_via => 'Array',
65             is => 'rw',
66             isa => ArrayRef[HashRef[Str]],
67             default => sub { [] },
68             handles => {
69             add_dependency => 'push',
70             },
71             );
72              
73              
74             sub documentation {
75 9     9 1 11110 my ($self,%params) = @_;
76            
77 9         19 my $doc_file;
78             my $found_file;
79            
80 9   100     42 my $lang = $params{lang} || 'en';
81 9   100     28 my $type = $params{type} || '';
82              
83 9         18 for my $file ( @{ $self->files } ) {
  9         204  
84              
85 35         107 my $filename = $file->{filename};
86 35 100       95 next if $filename !~ m{ \A doc/ }x;
87            
88 19 100       41 if ( !$doc_file ) {
89 8         14 $doc_file = $file;
90 8         12 $found_file = $filename;
91             }
92            
93 19 100       145 next if $filename !~ m{ \A doc/$lang/ }x;
94            
95 9 100       54 if ( $found_file !~ m{ \A doc/$lang/ }x ) {
96 3         6 $doc_file = $file;
97 3         5 $found_file = $filename;
98             }
99            
100 9 100 100     126 next if $type && $filename !~ m{ \A doc/[^/]+/.*\.$type \z }x;
101            
102 7 100 100     56 if ( $type && $found_file !~ m{ \A doc/[^/]+/.*\.$type \z }x ) {
103 1         3 $doc_file = $file;
104 1         2 $found_file = $filename;
105             }
106              
107 7 100       97 last if $found_file =~ m{ \A doc/$lang/.*\.$type \z }x;
108             }
109            
110 9         40 return $doc_file;
111             }
112              
113             sub validate {
114 7     7 1 3872 my ($self) = @_;
115              
116 7         187 $self->error_string( '' );
117            
118 7 100       447 if ( !-e $self->opm_file ) {
119 1         27 $self->error_string( 'File does not exist' );
120 1         29 return;
121             }
122              
123 6         20 my $tree;
124             try {
125 6     6   418 my $parser = XML::LibXML->new;
126 6         125 $tree = $parser->parse_file( $self->opm_file );
127             }
128             catch {
129 1     1   852 $self->error_string( 'Could not parse .opm: ' . $_ );
130 6         65 };
131              
132 6 100       2204 return if $self->error_string;
133              
134             try {
135 5     5   238 my $xsd = $self->_get_xsd;
136 5         37 my $schema = XML::LibXML::Schema->new( string => $xsd );
137              
138 1         169 $schema->validate( $tree );
139             }
140             catch {
141 4     4   7778 $self->error_string( 'Could not validate against XML schema: ' . $_ );
142 5         84 };
143              
144 5 100       2985 return if $self->error_string;
145 1         14 return 1;
146             }
147              
148             sub parse {
149 9     9 1 6006 my ($self) = @_;
150              
151 9         243 $self->error_string( '' );
152            
153 9 100       565 if ( !-e $self->opm_file ) {
154 1         26 $self->error_string( 'File does not exist' );
155 1         29 return;
156             }
157            
158 8         29 my $tree;
159             try {
160 8     8   570 my $parser = XML::LibXML->new;
161 8         222 $tree = $parser->parse_file( $self->opm_file );
162              
163 7         3643 $self->tree( $tree );
164             }
165             catch {
166 1     1   768 $self->error_string( 'Could not parse .opm: ' . $_ );
167 1         130 return;
168 8         100 };
169              
170 8 100       676 return if $self->error_string;
171            
172             # check if the opm file is valid.
173             try {
174 7     7   324 my $xsd = $self->_get_xsd;
175 7         73 XML::LibXML::Schema->new( string => $xsd )
176             }
177             catch {
178 7     7   14940 $self->error_string( 'Could not validate against XML schema: ' . $_ );
179             #return;
180 7         114 };
181            
182 7         5039 my $root = $tree->getDocumentElement;
183            
184             # collect basic data
185 7         67 $self->vendor( $root->findvalue( 'Vendor' ) );
186 7         2144 $self->name( $root->findvalue( 'Name' ) );
187 7         935 $self->license( $root->findvalue( 'License' ) );
188 7         921 $self->version( $root->findvalue( 'Version' ) );
189 7         226 $self->url( $root->findvalue( 'URL' ) );
190              
191 7         943 my $root_name = $root->nodeName;
192 7         49 $root_name =~ s{_package}{};
193              
194 7         150 $self->product( $root_name );
195            
196             # retrieve framework information
197 7         319 my @frameworks = $root->findnodes( 'Framework' );
198            
199             FILE:
200 7         280 for my $framework ( @frameworks ) {
201 30         1293 my $framework_version = $framework->textContent;
202            
203 30         96 my %details = ( Content => $framework_version );
204 30         82 my $maximum = $framework->findvalue( '@Maximum' );
205 30         1614 my $minimum = $framework->findvalue( '@Minimum' );
206              
207 30 100       1365 $details{Maximum} = $maximum if $maximum;
208 30 100       73 $details{Minimum} = $minimum if $minimum;
209            
210             # push framework info to attribute
211 30         647 $self->add_framework( $framework_version );
212 30         1966 $self->add_framework_detail( \%details );
213             }
214              
215             # retrieve file information
216 7         330 my @files = $root->findnodes( 'Filelist/File' );
217            
218             FILE:
219 7         248 for my $file ( @files ) {
220 29         1076 my $name = $file->findvalue( '@Location' );
221            
222             #next FILE if $name !~ m{ \. (?:pl|pm|pod|t) \z }xms;
223 29         1740 my $encode = $file->findvalue( '@Encode' );
224 29 100       1525 next FILE if $encode ne 'Base64';
225            
226 25         238 my $content_base64 = $file->textContent;
227 25         231 my $content = MIME::Base64::decode( $content_base64 );
228            
229             # push file info to attribute
230 25         646 $self->add_file({
231             filename => $name,
232             content => $content,
233             });
234             }
235            
236             # get description - english if available, any other language otherwise
237 7         275 my @descriptions = $root->findnodes( 'Description' );
238 7         209 my $description_string;
239            
240             DESCRIPTION:
241 7         27 for my $description ( @descriptions ) {
242 9         50 $description_string = $description->textContent;
243 9         33 my $language = $description->findvalue( '@Lang' );
244            
245 9 100       516 last DESCRIPTION if $language eq 'en';
246             }
247            
248 7         168 $self->description( $description_string );
249            
250             # get OTRS and CPAN dependencies
251 7         288 my @otrs_deps = $root->findnodes( 'PackageRequired' );
252 7         219 my @cpan_deps = $root->findnodes( 'ModuleRequired' );
253            
254 7         183 my %types = (
255             PackageRequired => 'OTRS',
256             ModuleRequired => 'CPAN',
257             );
258            
259 7         23 for my $dep ( @otrs_deps, @cpan_deps ) {
260 21         806 my $node_type = $dep->nodeName;
261 21         65 my $version = $dep->findvalue( '@Version' );
262 21         1254 my $dep_name = $dep->textContent;
263 21         62 my $dep_type = $types{$node_type};
264            
265 21         473 $self->add_dependency({
266             type => $dep_type,
267             version => $version,
268             name => $dep_name,
269             });
270             }
271            
272 7         343 return 1;
273             }
274              
275             sub as_sopm {
276 2     2 1 2975 my ($self) = @_;
277              
278 2         51 my $tree = $self->tree->cloneNode(1);
279 2         93 my $root = $tree->getDocumentElement;
280            
281 2         9 my @build_host = $root->findnodes( 'BuildHost' );
282 2         115 my @build_date = $root->findnodes( 'BuildDate' );
283            
284 2         76 $root->removeChild( $_ ) for @build_host;
285 2         59 $root->removeChild( $_ ) for @build_date;
286              
287             #$build_host->unbindNode() if $build_host;
288             #$build_date->unbindNode() if $build_date;
289            
290 2         7 my @files = $root->findnodes( 'Filelist/File' );
291 2         66 for my $file ( @files ) {
292 10         30 my ($encode) = $file->findnodes( '@Encode' );
293 10 100       321 $encode->unbindNode() if $encode;
294            
295 10         117 $file->removeChildNodes();
296             }
297            
298 2         12 return $tree->toString;
299             }
300              
301              
302             sub _get_xsd {
303              
304 11     11   36 return q~
305            
306            
307            
308            
309            
310              
311            
312            
313            
314            
315            
316            
317            
318            
319            
320            
321            
322            
323            
324            
325            
326            
327            
328            
329            
330            
331            
332            
333            
334            
335            
336            
337            
338            
339            
340            
341            
342            
343            
344            
345            
346            
347            
348            
349            
350            
351            
352            
353            
354            
355            
356            
357            
358            
359            
360            
361            
362            
363            
364            
365            
366            
367            
368            
369            
370            
371              
372            
373            
374            
375            
376            
377            
378            
379            
380            
381            
382            
383            
384            
385            
386            
387            
388            
389            
390            
391            
392            
393            
394            
395            
396            
397            
398            
399            
400            
401            
402            
403            
404            
405            
406            
407            
408            
409            
410            
411            
412            
413            
414            
415            
416            
417            
418            
419            
420            
421            
422            
423            
424            
425            
426            
427            
428            
429            
430            
431            
432            
433            
434            
435            
436            
437            
438            
439            
440            
441            
442            
443            
444            
445            
446            
447            
448            
449            
450            
451            
452            
453            
454            
455            
456            
457            
458            
459            
460            
461            
462            
463            
464            
465            
466            
467            
468            
469            
470            
471            
472            
473            
474            
475            
476            
477            
478            
479            
480            
481            
482            
483            
484            
485            
486            
487            
488            
489            
490            
491            
492            
493            
494            
495            
496            
497            
498            
499            
500            
501            
502            
503            
504            
505            
506            
507            
508            
509            
510            
511            
512            
513            
514            
515            
516            
517            
518            
519            
520            
521            
522            
523            
524            
525            
526            
527              
528            
529            
530            
531            
532            
533            
534            
535            
536            
537            
538            
539            
540            
541            
542            
543            
544            
545            
546            
547            
548            
549            
550            
551            
552            
553            
554            
555            
556            
557            
558            
559            
560            
561            
562            
563            
564            
565            
566            
567            
568            
569            
570            
571            
572            
573            
574            
575            
576            
577            
578            
579            
580            
581            
582            
583            
584            
585            
586            
587            
588            
589            
590            
591            
592            
593            
594            
595            
596            
597            
598            
599            
600            
601            
602            
603            
604            
605            
606            
607            
608              
609            
610            
611            
612            
613            
614            
615              
616            
617            
618            
619            
620            
621            
622              
623            
624            
625            
626            
627            
628            
629            
630            
631            
632            
633            
634            
635            
636            
637            
638            
639            
640              
641            
642            
643            
644            
645            
646            
647            
648              
649            
650            
651            
652            
653            
654            
655            
656              
657            
658            
659            
660            
661            
662            
663            
664            
665            
666            
667            
668            
669            
670            
671            
672            
673              
674            
675            
676            
677            
678            
679            
680            
681              
682            
683            
684            
685            
686            
687            
688            
689              
690            
691            
692            
693            
694            
695            
696            
697            
698            
699            
700            
701            
702            
703            
704            
705            
706            
707            
708              
709            
710            
711            
712            
713            
714            
715            
716            
717            
718             ~;
719             }
720              
721             1;
722              
723             __END__