File Coverage

blib/lib/Test/AgainstSchema/XML.pm
Criterion Covered Total %
statement 22 24 91.6
branch n/a
condition n/a
subroutine 8 8 100.0
pod n/a
total 30 32 93.7


line stmt bran cond sub pod time code
1             ###############################################################################
2             #
3             # This file copyright (c) 2011 by Randy J. Ray, all rights reserved.
4             #
5             ###############################################################################
6             #
7             # Description: A "specialization" of Test::AgainstSchema that handles XML
8             # data. Can test for XML to be valid against RelaxNG, DTD or
9             # XML Schema. Can also simply test for well-formedness.
10             #
11             # Functions: is_valid_against
12             # is_valid_against_relaxng
13             # is_valid_against_xmlschema
14             # is_valid_against_sgmldtd
15             # is_well_formed_xml
16             #
17             # Libraries: Test::Builder::Module
18             # XML::LibXML
19             # Try::Tiny
20             #
21             # Global Consts: $VERSION
22             #
23             ###############################################################################
24              
25             package Test::AgainstSchema::XML;
26              
27 4     4   119755 use 5.008;
  4         18  
  4         174  
28 4     4   21 use strict;
  4         6  
  4         131  
29 4     4   20 use warnings;
  4         12  
  4         143  
30 4     4   25 use vars qw($VERSION);
  4         6  
  4         438  
31 4         18 use subs qw(is_valid_against is_valid_against_relaxng
32             is_valid_against_xmlschema is_valid_against_sgmldtd
33 4     4   3597 is_well_formed_xml);
  4         72  
34 4     4   275 use base 'Test::Builder::Module';
  4         10  
  4         1284  
35              
36 4     4   4553 use Try::Tiny;
  4         7995  
  4         242  
37 4     4   8045 use XML::LibXML;
  0            
  0            
38              
39             our @EXPORT = qw(is_valid_against_relaxng is_valid_against_rng relaxng_ok
40             is_valid_against_xmlschema is_valid_against_xsd xmlschema_ok
41             is_valid_against_sgmldtd is_valid_against_dtd sgmldtd_ok
42             is_well_formed_xml xml_parses_ok);
43              
44             $VERSION = '0.100';
45             $VERSION = eval $VERSION; ## no critic (ProhibitStringyEval)
46              
47             ###############################################################################
48             #
49             # Sub Name: is_valid_against
50             #
51             # Description: This is the back-end that all of the other test routines
52             # actually use. It assumes that the first argument has
53             # already been converted to a XML::LibXML::{Dtd,Schema,etc.}
54             # object at this point, but the derivation of whether the
55             # target argument is a string, file, etc. is centralized
56             # here. If $schema is null/undef, then we were called from
57             # is_well_formed_xml(), and we are only testing the parsing
58             # of $document, not its validity.
59             #
60             # Arguments: NAME IN/OUT TYPE DESCRIPTION
61             # $schema in ref An object from one of the
62             # ::Dtd, ::Schema or ::RelaxNG
63             # validator classes
64             # $document in varies The XML content to validate--
65             # may be a string, filehandle,
66             # etc.
67             # $name in scalar If passed, this is the "name"
68             # for the test, the text that
69             # is printed in the TAP stream
70             #
71             # Returns: The return value of $TESTBUILDER->ok()
72             #
73             ###############################################################################
74             sub is_valid_against
75             {
76             my ($schema, $document, $name) = @_;
77             my $TESTBUILDER = __PACKAGE__->builder;
78             my $is_valid = 0;
79             my $dom;
80              
81             # Try to get a DOM object out of $document, by hook or by crook:
82             my $parser = XML::LibXML->new();
83             if ($TESTBUILDER->is_fh($document))
84             {
85             # Anything that looks like a file-handle gets treated as such
86             try {
87             $dom = $parser->parse_fh($document);
88             $is_valid = 1;
89             } catch {
90             $dom = $_;
91             };
92             }
93             elsif (ref($document) eq 'XML::LibXML::Document')
94             {
95             # This one is a gimme... they were kind-enough to pre-parse it
96             $dom = $document;
97             $is_valid = 1;
98             }
99             elsif (ref($document) eq 'SCALAR')
100             {
101             # A scalar-ref is presumed to be the XML text passed by reference
102             try {
103             $dom = $parser->parse_string(${$document});
104             $is_valid = 1;
105             } catch {
106             $dom = $_;
107             };
108             }
109             elsif ($document =~ m{
110             # Processing instruction:
111             <[?]xml
112             |
113             # Document type declaration:
114            
115             |
116             # Reasonable approximation of a tag:
117             <[:_\p{IsAlphabetic}][-:.\w]
118             }x)
119             {
120             # If the text looks like XML (has either a declarative PI, a DOCTYPE
121             # declaration or something that looks enough like a tag), assume that
122             # it is directly-passed-in XML content
123             try {
124             $dom = $parser->parse_string($document);
125             $is_valid = 1;
126             } catch {
127             $dom = $_;
128             };
129             }
130             else
131             {
132             # Failing any of the previous tests, assume that it is a filename
133             try {
134             $dom = $parser->parse_file($document);
135             $is_valid = 1;
136             } catch {
137             $dom = $_;
138             };
139             }
140              
141             # Skip the actual testing if whichever parser-call above ended up being
142             # called set an exception in $dom (signified by $is_valid being false).
143             # Also, if $schema is undef that means that we were only testing the XML
144             # itself, so there is no need for this block.
145             if ($is_valid && $schema)
146             {
147             # The XML::LibXML::Schema and XML::LibXML::RelaxNG classes are both
148             # validators, and have the same interface for the part I care about--
149             # a method validate() that takes a DOM object (the result of a parse)
150             # and dies if the document doesn't validate. Alas, the XML::LibXML::Dtd
151             # class *doesn't* follow this convention, so I have to special case
152             # it.
153             if ($schema->isa('XML::LibXML::Dtd'))
154             {
155             # If we have a DTD-derived object, we use the validate() method
156             # on the $dom value itself and pass the compiled DTD as an
157             # argument. The other two do this the other way around...
158             try {
159             $dom->validate($schema);
160             } catch {
161             $dom = $_;
162             $is_valid = 0;
163             };
164             }
165             else
166             {
167             try {
168             $schema->validate($dom);
169             } catch {
170             $dom = $_;
171             $is_valid = 0;
172             };
173             }
174             }
175              
176             # Whatever we ended up with as "$is_valid" is what ok() gets to use
177             return $TESTBUILDER->ok($is_valid, $name) || $TESTBUILDER->diag($dom);
178             }
179              
180             ###############################################################################
181             #
182             # Sub Name: is_valid_against_relaxng
183             #
184             # Description: Test the input against a RelaxNG schema. The first argument
185             # is either a compiled XML::LibXML::RelaxNG object, the text
186             # of a schema or a filename. Convert the argument to a
187             # compiled schema object (if necessary) and filter through
188             # to is_valid_against() with the other arguments. We leave
189             # the evaluation/normalization of the $document argument for
190             # that routine, since that part is common to all of these
191             # type-specific tester-routines.
192             #
193             # Arguments: NAME IN/OUT TYPE DESCRIPTION
194             # $schema in varies The schema (RelaxNG) to test
195             # $document against
196             # $document in varies The document/text to test
197             # $name in scalar If passed, the "name" or label
198             # for the test in the TAP
199             # output stream
200             #
201             # Returns: return value from is_valid_against()
202             #
203             ###############################################################################
204             sub is_valid_against_relaxng
205             {
206             my ($schema, $document, $name) = @_;
207             my $TESTBUILDER = __PACKAGE__->builder;
208             my $dom_schema;
209              
210             if (ref($schema) eq 'XML::LibXML::RelaxNG')
211             {
212             # They passed in an already-compiled object
213             $dom_schema = $schema;
214             }
215             elsif ($TESTBUILDER->is_fh($schema))
216             {
217             # The XML::LibXML::RelaxNG class cannot currently parse directly from a
218             # filehandle, so try calling new(string => ...) on the join'd contents
219             # of the handle
220             my $code = do { local $/ = undef; <$schema> };
221             try {
222             $dom_schema = XML::LibXML::RelaxNG->new(string => $code);
223             } catch {
224             $dom_schema = $_;
225             };
226             }
227             elsif ($schema =~ m{http://relaxng[.]org/ns/structure/1[.]0} or
228             $schema =~ /<(?:[\w.]+:)?grammar/)
229             {
230             # It appears to be a schema contained in the string/scalar... attempt
231             # to parse it
232             try {
233             $dom_schema = XML::LibXML::RelaxNG->new(string => $schema);
234             } catch {
235             $dom_schema = $_;
236             };
237             }
238             elsif (ref($schema) eq 'SCALAR')
239             {
240             # Assume that a scalar reference is the text of a schema passed in by
241             # reference to save stack-space
242             try {
243             $dom_schema = XML::LibXML::RelaxNG->new(string => ${$schema});
244             } catch {
245             $dom_schema = $_;
246             };
247             }
248             elsif (! ref $schema)
249             {
250             # If it isn't a reference but didn't match the pattern above, try using
251             # it as a file-name
252             try {
253             $dom_schema = XML::LibXML::RelaxNG->new(location => $schema);
254             } catch {
255             $dom_schema = $_;
256             };
257             }
258             else
259             {
260             # Can't figure out what it's supposed to be, so just set up to fail the
261             # test with a hopefully-helpful diagnostic.
262             $dom_schema = "Cannot determine how to turn '$schema' into a " .
263             'XML::LibXML::RelaxNG instance';
264             }
265              
266             # If $dom_schema is not a reference, then it is an error message from an
267             # attempt to parse (or from the failure clause, above).
268             if (! ref $dom_schema)
269             {
270             $TESTBUILDER->ok(0, $name);
271             $TESTBUILDER->diag($dom_schema);
272             return 0;
273             }
274              
275             return is_valid_against($dom_schema, $document, $name);
276             }
277              
278             # Semantic-sugar aliases for the above:
279             *relaxng_ok = *is_valid_against_rng = \&is_valid_against_relaxng;
280              
281             ###############################################################################
282             #
283             # Sub Name: is_valid_against_xmlschema
284             #
285             # Description: Test the input against an XML Schema. The first argument
286             # is either a compiled XML::LibXML::Schema object, the text
287             # of a schema or a filename. Convert the argument to a
288             # compiled schema object (if necessary) and filter through
289             # to is_valid_against() with the other arguments. We leave
290             # the evaluation/normalization of the $document argument for
291             # that routine, since that part is common to all of these
292             # type-specific tester-routines.
293             #
294             # Arguments: NAME IN/OUT TYPE DESCRIPTION
295             # $schema in varies The schema (XML Schema) to test
296             # $document against
297             # $document in varies The document/text to test
298             # $name in scalar If passed, the "name" or label
299             # for the test in the TAP
300             # output stream
301             #
302             # Returns: return value from is_valid_against()
303             #
304             ###############################################################################
305             sub is_valid_against_xmlschema
306             {
307             my ($schema, $document, $name) = @_;
308             my $TESTBUILDER = __PACKAGE__->builder;
309             my $dom_schema;
310              
311             if (ref($schema) eq 'XML::LibXML::Schema')
312             {
313             # They passed in an already-compiled object
314             $dom_schema = $schema;
315             }
316             elsif ($TESTBUILDER->is_fh($schema))
317             {
318             # The XML::LibXML::Schema class cannot currently parse directly from a
319             # filehandle, so try calling new(string => ...) on the join'd contents
320             # of the handle
321             my $code = do { local $/ = undef; <$schema> };
322             try {
323             $dom_schema = XML::LibXML::Schema->new(string => $code);
324             } catch {
325             $dom_schema = $_;
326             };
327             }
328             elsif ($schema =~ m{http://www[.]w3[.]org/2001/XMLSchema} or
329             $schema =~ /<(?:[\w.]+:)?schema/)
330             {
331             # It appears to be a schema contained in the string/scalar... attempt
332             # to parse it
333             try {
334             $dom_schema = XML::LibXML::Schema->new(string => $schema);
335             } catch {
336             $dom_schema = $_;
337             };
338             }
339             elsif (ref($schema) eq 'SCALAR')
340             {
341             # Assume that a scalar reference is the text of a schema passed in by
342             # reference to save stack-space
343             try {
344             $dom_schema = XML::LibXML::Schema->new(string => ${$schema});
345             } catch {
346             $dom_schema = $_;
347             };
348             }
349             elsif (! ref $schema)
350             {
351             # If it isn't a reference but didn't match the pattern above, try using
352             # it as a file-name
353             try {
354             $dom_schema = XML::LibXML::Schema->new(location => $schema);
355             } catch {
356             $dom_schema = $_;
357             };
358             }
359             else
360             {
361             # Can't figure out what it's supposed to be, so just set up to fail the
362             # test with a hopefully-helpful diagnostic.
363             $dom_schema = "Cannot determine how to turn '$schema' into a " .
364             'XML::LibXML::Schema instance';
365             }
366              
367             # If $dom_schema is not a reference, then it is an error message from an
368             # attempt to parse (or from the failure clause, above).
369             if (! ref $dom_schema)
370             {
371             $TESTBUILDER->ok(0, $name);
372             $TESTBUILDER->diag($dom_schema);
373             return 0;
374             }
375              
376             return is_valid_against($dom_schema, $document, $name);
377             }
378              
379             # Semantic-sugar aliases for the above:
380             *xmlschema_ok = *is_valid_against_xsd = \&is_valid_against_xmlschema;
381              
382             ###############################################################################
383             #
384             # Sub Name: is_valid_against_sgmldtd
385             #
386             # Description: Test the input against a SGML DTD. The first argument
387             # is either a compiled XML::LibXML::Dtd object, the text
388             # of a DTD or a filename. Convert the argument to a
389             # compiled object (if necessary) and filter through to
390             # is_valid_against() with the other arguments. We leave
391             # the evaluation/normalization of the $document argument for
392             # that routine, since that part is common to all of these
393             # type-specific tester-routines.
394             #
395             # Arguments: NAME IN/OUT TYPE DESCRIPTION
396             # $schema in varies The schema (SGML DTD) to test
397             # $document against
398             # $document in varies The document/text to test
399             # $name in scalar If passed, the "name" or label
400             # for the test in the TAP
401             # output stream
402             #
403             # Returns: return value from is_valid_against()
404             #
405             ###############################################################################
406             sub is_valid_against_sgmldtd
407             {
408             my ($schema, $document, $name) = @_;
409             my $TESTBUILDER = __PACKAGE__->builder;
410             my $dom_schema;
411              
412             if (ref($schema) eq 'XML::LibXML::Dtd')
413             {
414             # They passed in an already-compiled object
415             $dom_schema = $schema;
416             }
417             elsif ($TESTBUILDER->is_fh($schema))
418             {
419             # The XML::LibXML::Dtd class cannot currently parse directly from a
420             # filehandle, so try calling parse_string() on the join'd contents of
421             # the handle
422             my $code = do { local $/ = undef; <$schema> };
423             try {
424             $dom_schema = XML::LibXML::Dtd->parse_string($code);
425             } catch {
426             $dom_schema = $_;
427             };
428             }
429             elsif ($schema =~ /!ENTITY|!ELEMENT|!ATTLIST/)
430             {
431             # It appears to be a DTD contained in the string/scalar... attempt to
432             # parse it
433             try {
434             $dom_schema = XML::LibXML::Dtd->parse_string($schema);
435             } catch {
436             $dom_schema = $_;
437             };
438             }
439             elsif (ref($schema) eq 'SCALAR')
440             {
441             # Assume that a scalar reference is the text of a DTD passed in by
442             # reference to save stack-space
443             try {
444             $dom_schema = XML::LibXML::Dtd->parse_string(${$schema});
445             } catch {
446             $dom_schema = $_;
447             };
448             }
449             elsif (! ref $schema)
450             {
451             # If it isn't a reference but didn't match the pattern above, try using
452             # it as a file-name
453             try {
454             $dom_schema = XML::LibXML::Dtd->new(q{}, $schema);
455             } catch {
456             $dom_schema = $_;
457             };
458             }
459             else
460             {
461             # Can't figure out what it's supposed to be, so just set up to fail the
462             # test with a hopefully-helpful diagnostic.
463             $dom_schema = "Cannot determine how to turn '$schema' into a " .
464             'XML::LibXML::Dtd instance';
465             }
466              
467             # If $dom_schema is not a reference, then it is an error message from an
468             # attempt to parse (or from the failure clause, above).
469             if (! ref $dom_schema)
470             {
471             $TESTBUILDER->ok(0, $name);
472             $TESTBUILDER->diag($dom_schema);
473             return;
474             }
475              
476             return is_valid_against($dom_schema, $document, $name);
477             }
478             # Semantic-sugar aliases for the above:
479             *sgmldtd_ok = *is_valid_against_dtd = \&is_valid_against_sgmldtd;
480              
481             ###############################################################################
482             #
483             # Sub Name: is_well_formed_xml
484             #
485             # Description: Test whether the content passed in parses as XML without
486             # errors. Makes no effort to validate, only parse.
487             #
488             # Arguments: NAME IN/OUT TYPE DESCRIPTION
489             # $document in varies The document/text to test
490             # $name in scalar If passed, the "name" or label
491             # for the test in the TAP
492             # output stream
493             #
494             # Returns: Success: 1
495             # Failure: 0
496             #
497             ###############################################################################
498             sub is_well_formed_xml
499             {
500             my ($document, $name) = @_;
501              
502             return is_valid_against(undef, $document, $name);
503             }
504             # Semantic-sugar alias for the above:
505             *xml_parses_ok = \&is_well_formed_xml;
506              
507             1;
508              
509             __END__