blib/lib/Test/XML/Easy.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 208 | 221 | 94.1 |
branch | 85 | 94 | 90.4 |
condition | 25 | 28 | 89.2 |
subroutine | 16 | 16 | 100.0 |
pod | 4 | 4 | 100.0 |
total | 338 | 363 | 93.1 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | package Test::XML::Easy; | ||||||
2 | |||||||
3 | 11 | 11 | 699329 | use strict; | |||
11 | 33 | ||||||
11 | 542 | ||||||
4 | 11 | 11 | 69 | use warnings; | |||
11 | 25 | ||||||
11 | 514 | ||||||
5 | |||||||
6 | 11 | 11 | 63 | use vars qw(@EXPORT @ISA); | |||
11 | 25 | ||||||
11 | 933 | ||||||
7 | 11 | 11 | 75 | use Exporter; | |||
11 | 36 | ||||||
11 | 964 | ||||||
8 | @ISA = qw(Exporter); | ||||||
9 | |||||||
10 | our $VERSION = '0.01'; | ||||||
11 | |||||||
12 | 11 | 11 | 68 | use Carp qw(croak); | |||
11 | 21 | ||||||
11 | 875 | ||||||
13 | |||||||
14 | 11 | 11 | 22998 | use XML::Easy::Text qw(xml10_read_document xml10_write_document); | |||
11 | 119297 | ||||||
11 | 1348 | ||||||
15 | 11 | 11 | 20470 | use XML::Easy::Classify qw(is_xml_element); | |||
11 | 409732 | ||||||
11 | 1859 | ||||||
16 | 11 | 11 | 138 | use XML::Easy::Syntax qw($xml10_s_rx); | |||
11 | 25 | ||||||
11 | 1216 | ||||||
17 | |||||||
18 | 11 | 11 | 70 | use Test::Builder; | |||
11 | 29 | ||||||
11 | 35164 | ||||||
19 | my $tester = Test::Builder->new(); | ||||||
20 | |||||||
21 | =head1 NAME | ||||||
22 | |||||||
23 | Test::XML::Easy - test XML with XML::Easy | ||||||
24 | |||||||
25 | =head1 SYNOPSIS | ||||||
26 | |||||||
27 | use Test::More tests => 2; | ||||||
28 | use Test::XML::Easy; | ||||||
29 | |||||||
30 | is_xml $some_xml, <<'ENDOFXML', "a test"; | ||||||
31 | |||||||
32 | |
||||||
33 | |
||||||
34 | |
||||||
35 | |||||||
36 | ENDOFXML | ||||||
37 | |||||||
38 | is_xml $some_xml, <<'ENDOFXML', { ignore_whitespace => 1, description => "my test" }; | ||||||
39 | |
||||||
40 | |
||||||
41 | |
||||||
42 | |||||||
43 | ENDOFXML | ||||||
44 | |||||||
45 | isnt_xml $some_xml, $some_xml_it_must_not_be; | ||||||
46 | |||||||
47 | is_well_formed_xml $some_xml; | ||||||
48 | |||||||
49 | =head1 DESCRIPTION | ||||||
50 | |||||||
51 | A simple testing tool, with only pure Perl dependancies, that checks if | ||||||
52 | two XML documents are "the same". In particular this module will check if | ||||||
53 | the documents schemantically equal as defined by the XML 1.0 specification | ||||||
54 | (i.e. that the two documents would construct the same DOM | ||||||
55 | model when parsed, so things like character sets and if you've used two tags | ||||||
56 | or a self closing tags aren't important.) | ||||||
57 | |||||||
58 | This modules is a strict superset of B |
||||||
59 | were using that module to check if two identical documents were the same then | ||||||
60 | this module should function as a drop in replacement. Be warned, however, | ||||||
61 | that this module by default is a lot stricter about how the XML documents | ||||||
62 | are allowed to differ. | ||||||
63 | |||||||
64 | =head2 Functions | ||||||
65 | |||||||
66 | This module, by default, exports a number of functions into your namespace. | ||||||
67 | |||||||
68 | =over | ||||||
69 | |||||||
70 | =item is_xml($xml_to_test, $expected_xml[, $options_hashref]) | ||||||
71 | |||||||
72 | Tests that the passed XML is "the same" as the expected XML. | ||||||
73 | |||||||
74 | XML can be passed into this function in one of two ways; Either you can | ||||||
75 | provide a string (which the function will parse for you) or you can pass in | ||||||
76 | B |
||||||
77 | |||||||
78 | This funtion takes several options as the third argument. These can be | ||||||
79 | passed in as a hashref: | ||||||
80 | |||||||
81 | =over | ||||||
82 | |||||||
83 | =item description | ||||||
84 | |||||||
85 | The name of the test that will be used in constructing the C |
||||||
86 | test output. | ||||||
87 | |||||||
88 | =item ignore_whitespace | ||||||
89 | |||||||
90 | Ignore many whitespace differences in text nodes. Currently | ||||||
91 | this has the same effect as turning on C |
||||||
92 | and C |
||||||
93 | |||||||
94 | =item ignore_surrounding_whitespace | ||||||
95 | |||||||
96 | Ignore differences in leading and trailing whitespace | ||||||
97 | between elements. This means that | ||||||
98 | |||||||
99 | foo bar baz |
||||||
100 | |||||||
101 | Is considered the same as | ||||||
102 | |||||||
103 |
|
||||||
104 | foo bar baz | ||||||
105 | |||||||
106 | |||||||
107 | And even | ||||||
108 | |||||||
109 |
|
||||||
110 | this is my cat: | ||||||
111 | |||||||
112 | |||||||
113 | Is considered the same as: | ||||||
114 | |||||||
115 |
|
||||||
116 | this is my cat: | ||||||
117 | |||||||
118 | |||||||
119 | Even though, to a web-browser, that extra space is significant whitespace | ||||||
120 | and the two documents would be renderd differently. | ||||||
121 | |||||||
122 | However, as comments are completely ignored (we treat them as if they were | ||||||
123 | never even in the document) the following: | ||||||
124 | |||||||
125 | foobar |
||||||
126 | |||||||
127 | would be considered different to | ||||||
128 | |||||||
129 |
|
||||||
130 | foo | ||||||
131 | |||||||
132 | bar | ||||||
133 | |||||||
134 | |||||||
135 | As it's the same as comparing the string | ||||||
136 | |||||||
137 | "foobar" | ||||||
138 | |||||||
139 | And: | ||||||
140 | |||||||
141 | "foo | ||||||
142 | |||||||
143 | bar" | ||||||
144 | |||||||
145 | The same is true for processing instructions and DTD declarations. | ||||||
146 | |||||||
147 | =item ignore_leading_whitespace | ||||||
148 | |||||||
149 | The same as C |
||||||
150 | the whitespace immediately after an element start or end tag not | ||||||
151 | immedately before. | ||||||
152 | |||||||
153 | =item ignore_trailing_whitespace | ||||||
154 | |||||||
155 | The same as C |
||||||
156 | the whitespace immediately before an element start or end tag not | ||||||
157 | immedately after. | ||||||
158 | |||||||
159 | =item ignore_different_whitespace | ||||||
160 | |||||||
161 | If set to a true value ignores differences in what characters | ||||||
162 | make up whitespace in text nodes. In other words, this option | ||||||
163 | makes the comparison only care that wherever there's whitespace | ||||||
164 | in the expected XML there's any whitespace in the actual XML | ||||||
165 | at all, not what that whitespace is made up of. | ||||||
166 | |||||||
167 | It means the following | ||||||
168 | |||||||
169 |
|
||||||
170 | foo bar baz | ||||||
171 | |||||||
172 | |||||||
173 | Is the same as | ||||||
174 | |||||||
175 |
|
||||||
176 | foo | ||||||
177 | bar | ||||||
178 | baz | ||||||
179 | |||||||
180 | |||||||
181 | But not the same as | ||||||
182 | |||||||
183 |
|
||||||
184 | foobarbaz | ||||||
185 | |||||||
186 | |||||||
187 | This setting has no effect on attribute comparisons. | ||||||
188 | |||||||
189 | =item verbose | ||||||
190 | |||||||
191 | If true, print obsessive amounts of debug info out while | ||||||
192 | checking things | ||||||
193 | |||||||
194 | =item show_xml | ||||||
195 | |||||||
196 | This prints out in the diagnostic messages the expected and | ||||||
197 | actual XML on failure. | ||||||
198 | |||||||
199 | =back | ||||||
200 | |||||||
201 | If a third argument is passed to this function and that argument | ||||||
202 | is not a hashref then it will be assumed that this argument is | ||||||
203 | the the description as passed above. i.e. | ||||||
204 | |||||||
205 | is_xml $xml, $expected, "my test"; | ||||||
206 | |||||||
207 | is the same as | ||||||
208 | |||||||
209 | is_xml $xml, $expected, { description => "my test" }; | ||||||
210 | |||||||
211 | =cut | ||||||
212 | |||||||
213 | sub is_xml($$;$) { | ||||||
214 | 57 | 57 | 1 | 57678 | my $got = shift; | ||
215 | 57 | 102 | my $expected = shift; | ||||
216 | |||||||
217 | 57 | 100 | 180 | unless (defined $expected) { | |||
218 | 2 | 535 | croak("expected argument must be defined"); | ||||
219 | } | ||||||
220 | |||||||
221 | # munge the options | ||||||
222 | |||||||
223 | 55 | 85 | my $got_original = $got; | ||||
224 | 55 | 74 | my $expected_original = $expected; | ||||
225 | |||||||
226 | 55 | 80 | my $options = shift; | ||||
227 | 55 | 100 | 424 | $options = { description => $options } unless ref $options eq "HASH"; | |||
228 | 55 | 100 | 210 | $options = { %{$options}, description => "xml test" } unless defined $options->{description}; | |||
42 | 195 | ||||||
229 | 55 | 100 | 244 | unless (is_xml_element($expected)) { | |||
230 | # throws an exception if there isn't a problem. | ||||||
231 | 54 | 298 | $expected = eval { xml10_read_document($expected) }; | ||||
54 | 844 | ||||||
232 | 54 | 100 | 181 | if ($@) { | |||
233 | 2 | 528 | croak "Couldn't parse expected XML document: $@"; | ||||
234 | } | ||||||
235 | } | ||||||
236 | |||||||
237 | # convert into something useful if needed | ||||||
238 | 53 | 100 | 174 | unless (is_xml_element($got)) { | |||
239 | 51 | 270 | my $parsed = eval { xml10_read_document($got) }; | ||||
51 | 582 | ||||||
240 | 51 | 100 | 126 | if ($@) { | |||
241 | 4 | 15 | $tester->ok(0, $options->{description}); | ||||
242 | 4 | 11 | $tester->diag("Couldn't parse submitted XML document:"); | ||||
243 | 4 | 14 | $tester->diag(" $@"); | ||||
244 | 4 | 28 | return; | ||||
245 | } | ||||||
246 | |||||||
247 | 47 | 96 | $got = $parsed; | ||||
248 | } | ||||||
249 | |||||||
250 | 49 | 100 | 164 | if(_is_xml($got,$expected,$options,"", {})) { | |||
251 | 23 | 109 | $tester->ok(1,$options->{description}); | ||||
252 | 23 | 6219 | return 1; | ||||
253 | } | ||||||
254 | |||||||
255 | 26 | 100 | 272 | if ($options->{show_xml}) { | |||
256 | 3 | 10 | $tester->diag("The XML that we expected was:"); | ||||
257 | 3 | 100 | 173 | if (is_xml_element($expected_original)) | |||
258 | 1 | 14 | { $tester->diag(xml10_write_document($expected_original)) } | ||||
259 | else | ||||||
260 | 2 | 15 | { $tester->diag($expected_original) } | ||||
261 | |||||||
262 | 3 | 216 | $tester->diag("The XML that we received was:"); | ||||
263 | 3 | 100 | 153 | if (is_xml_element($got_original)) | |||
264 | 1 | 12 | { $tester->diag(xml10_write_document($got_original)) } | ||||
265 | else | ||||||
266 | 2 | 12 | { $tester->diag($got_original) } | ||||
267 | } | ||||||
268 | |||||||
269 | 26 | 421 | return; | ||||
270 | } | ||||||
271 | push @EXPORT, "is_xml"; | ||||||
272 | |||||||
273 | sub _is_xml { | ||||||
274 | 75 | 75 | 102 | my $got = shift; | |||
275 | 75 | 99 | my $expected = shift; | ||||
276 | 75 | 87 | my $options = shift; | ||||
277 | |||||||
278 | # this is the path | ||||||
279 | 75 | 295 | my $path = shift; | ||||
280 | |||||||
281 | # the index is used to keep track of how many of a particular | ||||||
282 | # typename of a particular element we've seen as previous siblings | ||||||
283 | # of the node that just got in. It's a hashref with type_name and | ||||||
284 | # the index. | ||||||
285 | 75 | 119 | my $index = shift; | ||||
286 | |||||||
287 | # change where the errors are reported from | ||||||
288 | 75 | 118 | local $Test::Builder::Level = $Test::Builder::Level + 1; | ||||
289 | |||||||
290 | # work out the details of the node we're looking at | ||||||
291 | # nb add one to the index because xpath is weirdly 1-index | ||||||
292 | # not 0-indexed like most other modern languages | ||||||
293 | 75 | 249 | my $got_name = $got->type_name(); | ||||
294 | 75 | 100 | 359 | my $got_index = ($index->{ $got_name } || 0) + 1; | |||
295 | |||||||
296 | ### check if we've got a node to compare to | ||||||
297 | |||||||
298 | 75 | 50 | 199 | unless ($expected) { | |||
299 | 0 | 0 | $tester->ok(0, $options->{description}); | ||||
300 | 0 | 0 | $tester->diag("Element '$path/$got_name\[$got_index]' was not expected"); | ||||
301 | 0 | 0 | return; | ||||
302 | } | ||||||
303 | |||||||
304 | ### check the node name | ||||||
305 | |||||||
306 | # work out the details of the node we're comparing with | ||||||
307 | 75 | 195 | my $expected_name = $expected->type_name(); | ||||
308 | 75 | 100 | 279 | my $expected_index = ($index->{ $expected_name } || 0) + 1; | |||
309 | |||||||
310 | # alter the index hashref to record we've seen another node | ||||||
311 | # of this name | ||||||
312 | 75 | 247 | $index->{$got_name}++; | ||||
313 | |||||||
314 | 75 | 50 | 190 | $tester->diag("comparing '$path/$got_name\[$expected_index]' to '$path/$expected_name\[$expected_index]'...") if $options->{verbose}; | |||
315 | |||||||
316 | 75 | 100 | 184 | if ($got_name ne $expected_name) { | |||
317 | 4 | 20 | $tester->ok(0, $options->{description}); | ||||
318 | 4 | 1180 | $tester->diag("Element '$path/$got_name\[$got_index]' does not match '$path/$expected_name\[$expected_index]'"); | ||||
319 | 4 | 197 | return; | ||||
320 | } | ||||||
321 | 71 | 50 | 174 | $tester->diag("...matched name") if $options->{verbose}; | |||
322 | |||||||
323 | ### check the attributes | ||||||
324 | |||||||
325 | # we're not looking at decendents, so burn the path of | ||||||
326 | # this node into the path we got passed in | ||||||
327 | 71 | 265 | $path .= "/$got_name\[$got_index]"; | ||||
328 | |||||||
329 | # XML::Easy returns read only data structures | ||||||
330 | # we want to modify these to keep track of what | ||||||
331 | # we've processed, so we need to copy them | ||||||
332 | 71 | 130 | my %got_attr = %{ $got->attributes }; | ||||
71 | 542 | ||||||
333 | 71 | 178 | my $expected_attr = $expected->attributes; | ||||
334 | |||||||
335 | 71 | 89 | foreach my $attr (keys %{ $expected_attr }) { | ||||
71 | 244 | ||||||
336 | 6 | 50 | 16 | $tester->diag("checking attribute '$path/\@$attr'...") if $options->{verbose}; | |||
337 | |||||||
338 | 6 | 100 | 19 | if (!exists($got_attr{$attr})) { | |||
339 | 1 | 8 | $tester->ok(0, $options->{description}); | ||||
340 | 1 | 676 | $tester->diag("expected attribute '$path/\@$attr' not found"); | ||||
341 | 1 | 113 | return; | ||||
342 | } | ||||||
343 | 5 | 50 | 13 | $tester->diag("...found attribute") if $options->{verbose}; | |||
344 | |||||||
345 | 5 | 12 | my $expected_string = $expected_attr->{$attr}; | ||||
346 | 5 | 10 | my $got_string = delete $got_attr{$attr}; | ||||
347 | |||||||
348 | 5 | 100 | 208 | if ($expected_string ne $got_string) { | |||
349 | 1 | 12 | $tester->ok(0, $options->{description}); | ||||
350 | 1 | 3189 | $tester->diag("attribute value for '$path/\@$attr' didn't match"); | ||||
351 | 1 | 258 | $tester->diag("found value:\n"); | ||||
352 | 1 | 230 | $tester->diag(" '$got_string'\n"); | ||||
353 | 1 | 67 | $tester->diag("expected value:\n"); | ||||
354 | 1 | 228 | $tester->diag(" '$expected_string'\n"); | ||||
355 | 1 | 241 | return; | ||||
356 | } | ||||||
357 | 4 | 50 | 13 | $tester->diag("...the attribute contents matched") if $options->{verbose}; | |||
358 | } | ||||||
359 | 69 | 100 | 669 | if (keys %got_attr) { | |||
360 | 2 | 9 | $tester->ok(0, $options->{description}); | ||||
361 | 2 | 100 | 1132 | $tester->diag("found extra unexpected attribute".(keys %got_attr>1 ? "s":"").":"); | |||
362 | 2 | 383 | $tester->diag(" '$path/\@$_'") foreach sort keys %got_attr; | ||||
363 | 2 | 264 | return; | ||||
364 | } | ||||||
365 | 67 | 50 | 154 | $tester->diag("the attributes all matched") if $options->{verbose}; | |||
366 | |||||||
367 | ### check the child nodes | ||||||
368 | |||||||
369 | # create a new index to pass to our children distint from | ||||||
370 | # the index that was passed in to us (as that one was created | ||||||
371 | # by our parent for me and my siblings) | ||||||
372 | 67 | 102 | my $child_index = {}; | ||||
373 | |||||||
374 | # grab the child text...element...text...element...text... | ||||||
375 | 67 | 181 | my $got_content = $got->content; | ||||
376 | 67 | 135 | my $expected_content = $expected->content; | ||||
377 | |||||||
378 | # step though the text/elements | ||||||
379 | # nb this loop works in steps of two; The other $i++ | ||||||
380 | # is half way through the loop below | ||||||
381 | 67 | 117 | for (my $i = 0; $i < @{$got_content}; $i++) { | ||||
86 | 209 | ||||||
382 | |||||||
383 | ### check the text node | ||||||
384 | |||||||
385 | # extract the text from the object | ||||||
386 | 86 | 132 | my $got_text = $got_content->[ $i ]; | ||||
387 | 86 | 115 | my $expected_text = $expected_content->[ $i ]; | ||||
388 | 86 | 122 | my $comp_got_text = $got_text; | ||||
389 | 86 | 97 | my $comp_expected_text = $expected_text; | ||||
390 | |||||||
391 | 86 | 100 | 100 | 559 | if ($options->{ignore_whitespace} || $options->{ignore_leading_whitespace} || $options->{ignore_surrounding_whitespace}) { | ||
66 | |||||||
392 | 10 | 133 | $comp_got_text =~ s/ \A (?:$xml10_s_rx)* //x; | ||||
393 | 10 | 89 | $comp_expected_text =~ s/ \A (?:$xml10_s_rx)* //x; | ||||
394 | } | ||||||
395 | |||||||
396 | 86 | 100 | 100 | 628 | if ($options->{ignore_whitespace} || $options->{ignore_trailing_whitespace} || $options->{ignore_surrounding_whitespace}) { | ||
66 | |||||||
397 | 10 | 184 | $comp_got_text =~ s/ (?:$xml10_s_rx)* \z//x; | ||||
398 | 10 | 173 | $comp_expected_text =~ s/ (?:$xml10_s_rx)* \z//x; | ||||
399 | } | ||||||
400 | |||||||
401 | 86 | 100 | 100 | 570 | if ($options->{ignore_whitespace} || $options->{ignore_different_whitespace}) { | ||
402 | 8 | 86 | $comp_got_text =~ s/ (?:$xml10_s_rx)+ / /gx; | ||||
403 | 8 | 66 | $comp_expected_text =~ s/ (?:$xml10_s_rx)+ / /gx; | ||||
404 | } | ||||||
405 | |||||||
406 | 86 | 100 | 277 | if ($comp_got_text ne $comp_expected_text) { | |||
407 | |||||||
408 | 18 | 71 | $tester->ok(0, $options->{description}); | ||||
409 | |||||||
410 | # I don't like these error message not being specific with xpath but as | ||||||
411 | # far as I know there's no easy way to express in xpath the text immediatly following | ||||||
412 | # a particular element. The best I could come up with was this mouthful: | ||||||
413 | # "$path/following-sibling::text()[ previous-sibling::*[1] == $path ]" | ||||||
414 | |||||||
415 | 18 | 100 | 100 | 14597 | if ($i == 0) { | ||
5 | 100 | 18 | |||||
416 | 13 | 100 | 100 | 34 | if (@{ $got_content } == 1 && @{ $expected_content } == 1) { | ||
13 | 48 | ||||||
11 | 43 | ||||||
417 | 10 | 52 | $tester->diag("text inside '$path' didn't match"); | ||||
418 | } else { | ||||||
419 | 3 | 11 | $tester->diag("text immediately inside opening tag of '$path' didn't match"); | ||||
420 | } | ||||||
421 | 2 | 8 | } elsif ($i == @{ $got_content} - 1 && $i == @{ $expected_content } - 1 ) { | ||||
422 | 1 | 6 | $tester->diag("text immediately before closing tag of '$path' didn't match"); | ||||
423 | } else { | ||||||
424 | 4 | 14 | my $name = $got_content->[ $i - 1 ]->type_name; | ||||
425 | 4 | 7 | my $ind = $child_index->{ $name }; | ||||
426 | 4 | 18 | $tester->diag("text immediately after '$path/$name\[$ind]' didn't match"); | ||||
427 | } | ||||||
428 | |||||||
429 | 18 | 1259 | $tester->diag("found:\n"); | ||||
430 | 18 | 1208 | $tester->diag(" '$got_text'\n"); | ||||
431 | 18 | 1255 | $tester->diag("expected:\n"); | ||||
432 | 18 | 1214 | $tester->diag(" '$expected_text'\n"); | ||||
433 | |||||||
434 | 18 | 50 | 1233 | if ($options->{verbose}) { | |||
435 | 0 | 0 | $tester->diag("compared found text:\n"); | ||||
436 | 0 | 0 | $tester->diag(" '$comp_got_text'\n"); | ||||
437 | 0 | 0 | $tester->diag("against text:\n"); | ||||
438 | 0 | 0 | $tester->diag(" '$comp_expected_text'\n"); | ||||
439 | } | ||||||
440 | |||||||
441 | 18 | 115 | return; | ||||
442 | } | ||||||
443 | |||||||
444 | # move onto the next (elemnent) node if we didn't reach the end | ||||||
445 | 68 | 312 | $i++; | ||||
446 | 68 | 100 | 83 | last if $i >= @{$got_content}; | |||
68 | 210 | ||||||
447 | |||||||
448 | ### check the element node | ||||||
449 | |||||||
450 | # simply recurse for that node | ||||||
451 | # (don't bother checking if the expected node is defined or not, the case | ||||||
452 | # where it isn't is handled at the start of _is_xml) | ||||||
453 | 26 | 100 | 157 | return unless _is_xml( | |||
454 | $got_content->[$i], | ||||||
455 | $expected_content->[$i], | ||||||
456 | $options, | ||||||
457 | $path, | ||||||
458 | $child_index | ||||||
459 | ); | ||||||
460 | } | ||||||
461 | |||||||
462 | # check if we expected more nodes | ||||||
463 | 42 | 50 | 58 | if (@{ $expected_content } > @{ $got_content }) { | |||
42 | 71 | ||||||
42 | 113 | ||||||
464 | 0 | 0 | my $expected_nom = $expected_content->[ scalar @{ $got_content } ]->type_name; | ||||
0 | 0 | ||||||
465 | 0 | 0 | my $expected_ind = $child_index->{ $expected_nom } + 1; | ||||
466 | 0 | 0 | $tester->diag("Couldn't find expected node '$path/$expected_nom\[$expected_ind]'"); | ||||
467 | 0 | 0 | $tester->ok(0, $options->{description}); | ||||
468 | 0 | 0 | return; | ||||
469 | } | ||||||
470 | |||||||
471 | 42 | 292 | return 1; | ||||
472 | } | ||||||
473 | |||||||
474 | =item isnt_xml($xml_to_test, $not_expected_xml[, $options_hashref]) | ||||||
475 | |||||||
476 | Exactly the same as C |
||||||
477 | if and only if what is passed is different to the not expected XML. | ||||||
478 | |||||||
479 | By different, of course, we mean schematically different according to the | ||||||
480 | XML 1.0 specification. For example, this will fail: | ||||||
481 | |||||||
482 | isnt_xml " |
||||||
483 | |||||||
484 | as those are schematically the same XML documents. | ||||||
485 | |||||||
486 | However, it's worth noting that the first argument doesn't even have to be | ||||||
487 | valid XML for the test to pass. Both these pass as they're not schemantically | ||||||
488 | identical to the not expected XML: | ||||||
489 | |||||||
490 | isnt_xml undef, $not_expecteded_xml; | ||||||
491 | isnt_xml " |
||||||
492 | |||||||
493 | as invalid XML is not ever schemanitcally identical to a valid XML document. | ||||||
494 | |||||||
495 | If you want to insist what you pass in is valid XML, but just not the | ||||||
496 | same as the other xml document you pass in then you can use two tests: | ||||||
497 | |||||||
498 | is_well_formed_xml $xml; | ||||||
499 | isnt_xml $xml, $not_expected_xml; | ||||||
500 | |||||||
501 | This function accepts the C |
||||||
502 | turning it on doesn't actually output anything extra - there's not useful this | ||||||
503 | function can output that would help you diagnose the failure case. | ||||||
504 | |||||||
505 | =cut | ||||||
506 | |||||||
507 | sub isnt_xml($$;$) { | ||||||
508 | 11 | 11 | 1 | 9510 | my $got = shift; | ||
509 | 11 | 21 | my $expected = shift; | ||||
510 | 11 | 19 | my $options = shift; | ||||
511 | |||||||
512 | 11 | 100 | 53 | $options = { description => $options } unless ref $options eq "HASH"; | |||
513 | 11 | 100 | 35 | $options = { %{$options}, description => "not xml test" } | |||
9 | 47 | ||||||
514 | unless defined $options->{description}; | ||||||
515 | |||||||
516 | # temporarly ignore test output and just get the result of running | ||||||
517 | # the is_xml function as normal | ||||||
518 | 11 | 57 | $tester = bless {}, "Test::XML::Easy::Ignore"; | ||||
519 | 11 | 100 | 18 | my $result = eval { is_xml($got, $expected, $options) ? 0 : 1 }; | |||
11 | 42 | ||||||
520 | 11 | 61 | $tester = Test::Builder->new(); | ||||
521 | |||||||
522 | # did we get an error? Note we don't check $@ directly incase | ||||||
523 | # it's been reset by a weird DESTROY() eval... | ||||||
524 | 11 | 100 | 66 | 145 | unless (defined($result) && length $result) { croak $@; } | ||
2 | 365 | ||||||
525 | |||||||
526 | 9 | 100 | 21 | if ($result) { | |||
527 | 6 | 35 | $tester->ok(1, $options->{description}); | ||||
528 | 6 | 2798 | return 1; | ||||
529 | } | ||||||
530 | |||||||
531 | 3 | 15 | $tester->ok(0, $options->{description}); | ||||
532 | 3 | 1499 | $tester->diag("Unexpectedly matched the XML we didn't expect"); | ||||
533 | 3 | 100 | 211 | if ($options->{show_xml}) { | |||
534 | 2 | 8 | $tester->diag("The XML that we received was:"); | ||||
535 | 2 | 100 | 131 | if (is_xml_element($got)) | |||
536 | 1 | 10 | { $tester->diag(xml10_write_document($got)) } | ||||
537 | else | ||||||
538 | 1 | 9 | { $tester->diag($got) } | ||||
539 | } | ||||||
540 | 3 | 143 | return; | ||||
541 | } | ||||||
542 | push @EXPORT, "isnt_xml"; | ||||||
543 | |||||||
544 | =item is_well_formed_xml($string_containing_xml[, $description]) | ||||||
545 | |||||||
546 | Passes if and only if the string passed contains well formed XML. | ||||||
547 | |||||||
548 | =cut | ||||||
549 | |||||||
550 | sub is_well_formed_xml($;$) { | ||||||
551 | 4 | 4 | 1 | 1989 | my $xml_string = shift; | ||
552 | 4 | 7 | my $options = shift; | ||||
553 | |||||||
554 | 4 | 100 | 19 | $options = { description => $options } unless ref $options eq "HASH"; | |||
555 | 4 | 100 | 13 | $options = { %{$options}, description => "xml well formed test" } | |||
2 | 11 | ||||||
556 | unless defined $options->{description}; | ||||||
557 | |||||||
558 | 4 | 100 | 8 | if(eval { xml10_read_document($xml_string); 1 }) { | |||
4 | 39 | ||||||
3 | 22 | ||||||
559 | 3 | 14 | $tester->ok(1, $options->{description}); | ||||
560 | 3 | 941 | return 1; | ||||
561 | } | ||||||
562 | |||||||
563 | 1 | 6 | $tester->ok(0, $options->{description}); | ||||
564 | 1 | 570 | $tester->diag($@); | ||||
565 | 1 | 69 | return; | ||||
566 | } | ||||||
567 | push @EXPORT, "is_well_formed_xml"; | ||||||
568 | |||||||
569 | =item isnt_well_formed_xml($string_not_containing_xml[, $description]) | ||||||
570 | |||||||
571 | Passes if and only if the string passed does not contain well formed XML. | ||||||
572 | |||||||
573 | =cut | ||||||
574 | |||||||
575 | sub isnt_well_formed_xml($;$) { | ||||||
576 | 4 | 4 | 1 | 2271 | my $xml_string = shift; | ||
577 | 4 | 6 | my $options = shift; | ||||
578 | |||||||
579 | 4 | 100 | 19 | $options = { description => $options } unless ref $options eq "HASH"; | |||
580 | 4 | 100 | 12 | $options = { %{$options}, description => "xml not well formed test" } | |||
2 | 10 | ||||||
581 | unless defined $options->{description}; | ||||||
582 | |||||||
583 | 4 | 100 | 8 | unless (eval { xml10_read_document($xml_string); 1 }) { | |||
4 | 27 | ||||||
1 | 30 | ||||||
584 | 3 | 12 | $tester->ok(1, $options->{description}); | ||||
585 | 3 | 723 | return 1; | ||||
586 | } | ||||||
587 | |||||||
588 | 1 | 5 | $tester->ok(0, $options->{description}); | ||||
589 | 1 | 472 | $tester->diag("Unexpectedly well formed XML"); | ||||
590 | 1 | 242 | return; | ||||
591 | } | ||||||
592 | push @EXPORT, "isnt_well_formed_xml"; | ||||||
593 | |||||||
594 | =back | ||||||
595 | |||||||
596 | =head2 A note on Character Handling | ||||||
597 | |||||||
598 | If you do not pass it an XML::Easy::Element object then these functions will happly parse | ||||||
599 | XML from the characters contained in whatever scalars you passed in. They will not | ||||||
600 | (and cannot) correctly parse data from a scalar that contains binary data (e.g. that | ||||||
601 | you've sucked in from a raw file handle) as they would have no idea what characters | ||||||
602 | those octlets would represent | ||||||
603 | |||||||
604 | As long as your XML document contains legal characters from the ASCII range (i.e. | ||||||
605 | chr(1) to chr(127)) this distintion will not matter to you. | ||||||
606 | |||||||
607 | However, if you use characters above codepoint 127 then you will probably need to | ||||||
608 | convert any bytes you have read in into characters. This is usually done by using | ||||||
609 | C |
||||||
610 | in. | ||||||
611 | |||||||
612 | If you don't know what any of this means I suggest you read the Encode::encode manpage | ||||||
613 | very carefully. Tom Insam's slides at L |
||||||
614 | may or may not help you understand this more (they at the very least contain a | ||||||
615 | cheatsheet for conversion.) | ||||||
616 | |||||||
617 | The author highly recommends those of you using latin-1 characters from a utf-8 source | ||||||
618 | to use B |
||||||
619 | |||||||
620 | =head1 AUTHOR | ||||||
621 | |||||||
622 | Mark Fowler, C<< >> | ||||||
623 | |||||||
624 | Copyright 2009 PhotoBox, All Rights Reserved. | ||||||
625 | |||||||
626 | This program is free software; you can redistribute it and/or modify it | ||||||
627 | under the same terms as Perl itself. | ||||||
628 | |||||||
629 | =head1 BUGS | ||||||
630 | |||||||
631 | There's a few cavets when using this module: | ||||||
632 | |||||||
633 | =over | ||||||
634 | |||||||
635 | =item Not a validating parser | ||||||
636 | |||||||
637 | Infact, we don't process (or compare) DTDs at all. These nodes are completely | ||||||
638 | ignored (it's as if you didn't include them in the string at all.) | ||||||
639 | |||||||
640 | =item Comments and processing instructions are ignored | ||||||
641 | |||||||
642 | We totally ignore comments and processing instructions, and it's as | ||||||
643 | if you didn't include them in the string at all either. | ||||||
644 | |||||||
645 | =item Limited entity handling | ||||||
646 | |||||||
647 | We only support the five "core" named entities (i.e. C<&>, | ||||||
648 | C<<>, C<>>, C<'> and C<">) and numerical character references | ||||||
649 | (in decimal or hex form.) It is not possible to declare further named | ||||||
650 | entities and the precence of undeclared named entities will either cause | ||||||
651 | an exception to be thrown (in the case of the expected string) or the test to | ||||||
652 | fail (in the case of the string you are testing) | ||||||
653 | |||||||
654 | =item No namespace support | ||||||
655 | |||||||
656 | Currently this is only an XML 1.0 parser, and not XML Namespaces aware (further | ||||||
657 | options may be added to later version of this module to enable namespace support) | ||||||
658 | |||||||
659 | This means the following document: | ||||||
660 | |||||||
661 | |
||||||
662 | |||||||
663 | Is considered to be different to | ||||||
664 | |||||||
665 | |
||||||
666 | |||||||
667 | =item XML whitespace handling | ||||||
668 | |||||||
669 | This module considers "whitespace" to be what the XML specification considers | ||||||
670 | to be whitespace. This is subtily different to what Perl considers to be | ||||||
671 | whitespace. | ||||||
672 | |||||||
673 | =item No node reordering support | ||||||
674 | |||||||
675 | Unlike B |
||||||
676 | significant, and you cannot tell it to ignore the differring order of nodes | ||||||
677 | when comparing the expected and actual output. | ||||||
678 | |||||||
679 | =back | ||||||
680 | |||||||
681 | Please see L |
||||||
682 | details of how to submit bugs, access the source control for | ||||||
683 | this project, and contact the author. | ||||||
684 | |||||||
685 | =head1 SEE ALSO | ||||||
686 | |||||||
687 | L |
||||||
688 | on the underlying xml parser) and L |
||||||
689 | tests using XML::SchemanticDiff) | ||||||
690 | |||||||
691 | =cut | ||||||
692 | |||||||
693 | 1; # End of Test::XML::Easy | ||||||
694 | |||||||
695 | package Test::XML::Easy::Ignore; | ||||||
696 | |||||||
697 | # a handy class you can bless your tester into so we ignore all | ||||||
698 | # calls and don't actually produce any test output | ||||||
699 | |||||||
700 | 9 | 9 | 16 | sub ok { return } | |||
701 | 14 | 14 | 20 | sub diag { return } | |||
702 | |||||||
703 | 1; # End of Test::XML::Easy::Ignore; |