File Coverage

blib/lib/Test/Formats/XML.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


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