port CSS fixes from Mojolicious 6.31 and 6.32
[catagits/DOM-Tiny.git] / t / dom.t
CommitLineData
d6512b50 1use strict;
2use warnings;
3use utf8;
4use Test::More;
5use DOM::Tiny;
dbff35d4 6use JSON::PP ();
d6512b50 7
8# Empty
9is(DOM::Tiny->new, '', 'right result');
10is(DOM::Tiny->new(''), '', 'right result');
11is(DOM::Tiny->new->parse(''), '', 'right result');
12is(DOM::Tiny->new->at('p'), undef, 'no result');
13is(DOM::Tiny->new->append_content(''), '', 'right result');
14is(DOM::Tiny->new->all_text, '', 'right result');
15
16# Simple (basics)
17my $dom
18 = DOM::Tiny->new('<div><div FOO="0" id="a">A</div><div id="b">B</div></div>');
19is $dom->at('#b')->text, 'B', 'right text';
20my @div;
21push @div, $dom->find('div[id]')->map('text')->each;
22is_deeply \@div, [qw(A B)], 'found all div elements with id';
23@div = ();
24$dom->find('div[id]')->each(sub { push @div, $_->text });
25is_deeply \@div, [qw(A B)], 'found all div elements with id';
26is $dom->at('#a')->attr('foo'), 0, 'right attribute';
27is $dom->at('#a')->attr->{foo}, 0, 'right attribute';
28is "$dom", '<div><div foo="0" id="a">A</div><div id="b">B</div></div>',
29 'right result';
30
31# Tap into method chain
32$dom = DOM::Tiny->new->parse('<div id="a">A</div><div id="b">B</div>');
33is_deeply [$dom->find('[id]')->map(attr => 'id')->each], [qw(a b)],
34 'right result';
35is $dom->tap(sub { $_->at('#b')->remove }), '<div id="a">A</div>',
36 'right result';
37
38# Build tree from scratch
39is(DOM::Tiny->new->append_content('<p>')->at('p')->append_content('0')->text,
40 '0', 'right text');
41
42# Simple nesting with healing (tree structure)
43$dom = DOM::Tiny->new(<<EOF);
44<foo><bar a="b&lt;c">ju<baz a23>s<bazz />t</bar>works</foo>
45EOF
46is $dom->tree->[0], 'root', 'right type';
47is $dom->tree->[1][0], 'tag', 'right type';
48is $dom->tree->[1][1], 'foo', 'right tag';
49is_deeply $dom->tree->[1][2], {}, 'empty attributes';
50is $dom->tree->[1][3], $dom->tree, 'right parent';
51is $dom->tree->[1][4][0], 'tag', 'right type';
52is $dom->tree->[1][4][1], 'bar', 'right tag';
53is_deeply $dom->tree->[1][4][2], {a => 'b<c'}, 'right attributes';
54is $dom->tree->[1][4][3], $dom->tree->[1], 'right parent';
55is $dom->tree->[1][4][4][0], 'text', 'right type';
56is $dom->tree->[1][4][4][1], 'ju', 'right text';
57is $dom->tree->[1][4][4][2], $dom->tree->[1][4], 'right parent';
58is $dom->tree->[1][4][5][0], 'tag', 'right type';
59is $dom->tree->[1][4][5][1], 'baz', 'right tag';
60is_deeply $dom->tree->[1][4][5][2], {a23 => undef}, 'right attributes';
61is $dom->tree->[1][4][5][3], $dom->tree->[1][4], 'right parent';
62is $dom->tree->[1][4][5][4][0], 'text', 'right type';
63is $dom->tree->[1][4][5][4][1], 's', 'right text';
64is $dom->tree->[1][4][5][4][2], $dom->tree->[1][4][5], 'right parent';
65is $dom->tree->[1][4][5][5][0], 'tag', 'right type';
66is $dom->tree->[1][4][5][5][1], 'bazz', 'right tag';
67is_deeply $dom->tree->[1][4][5][5][2], {}, 'empty attributes';
68is $dom->tree->[1][4][5][5][3], $dom->tree->[1][4][5], 'right parent';
69is $dom->tree->[1][4][5][6][0], 'text', 'right type';
70is $dom->tree->[1][4][5][6][1], 't', 'right text';
71is $dom->tree->[1][4][5][6][2], $dom->tree->[1][4][5], 'right parent';
72is $dom->tree->[1][5][0], 'text', 'right type';
73is $dom->tree->[1][5][1], 'works', 'right text';
74is $dom->tree->[1][5][2], $dom->tree->[1], 'right parent';
75is "$dom", <<EOF, 'right result';
76<foo><bar a="b&lt;c">ju<baz a23>s<bazz></bazz>t</baz></bar>works</foo>
77EOF
78
79# Select based on parent
80$dom = DOM::Tiny->new(<<EOF);
81<body>
82 <div>test1</div>
83 <div><div>test2</div></div>
84<body>
85EOF
86is $dom->find('body > div')->[0]->text, 'test1', 'right text';
87is $dom->find('body > div')->[1]->text, '', 'no content';
88is $dom->find('body > div')->[2], undef, 'no result';
89is $dom->find('body > div')->size, 2, 'right number of elements';
90is $dom->find('body > div > div')->[0]->text, 'test2', 'right text';
91is $dom->find('body > div > div')->[1], undef, 'no result';
92is $dom->find('body > div > div')->size, 1, 'right number of elements';
93
94# A bit of everything (basic navigation)
95$dom = DOM::Tiny->new->parse(<<EOF);
96<!doctype foo>
97<foo bar="ba&lt;z">
98 test
99 <simple class="working">easy</simple>
100 <test foo="bar" id="test" />
101 <!-- lala -->
102 works well
103 <![CDATA[ yada yada]]>
104 <?boom lalalala ?>
105 <a little bit broken>
106 < very broken
107 <br />
108 more text
109</foo>
110EOF
111ok !$dom->xml, 'XML mode not detected';
112is $dom->tag, undef, 'no tag';
113is $dom->attr('foo'), undef, 'no attribute';
114is $dom->attr(foo => 'bar')->attr('foo'), undef, 'no attribute';
115is $dom->tree->[1][0], 'doctype', 'right type';
116is $dom->tree->[1][1], ' foo', 'right doctype';
117is "$dom", <<EOF, 'right result';
118<!DOCTYPE foo>
119<foo bar="ba&lt;z">
120 test
121 <simple class="working">easy</simple>
122 <test foo="bar" id="test"></test>
123 <!-- lala -->
124 works well
125 <![CDATA[ yada yada]]>
126 <?boom lalalala ?>
127 <a bit broken little>
128 &lt; very broken
129 <br>
130 more text
131</a></foo>
132EOF
133my $simple = $dom->at('foo simple.working[class^="wor"]');
134is $simple->parent->all_text,
135 'test easy works well yada yada < very broken more text', 'right text';
136is $simple->tag, 'simple', 'right tag';
137is $simple->attr('class'), 'working', 'right class attribute';
138is $simple->text, 'easy', 'right text';
139is $simple->parent->tag, 'foo', 'right parent tag';
140is $simple->parent->attr->{bar}, 'ba<z', 'right parent attribute';
141is $simple->parent->children->[1]->tag, 'test', 'right sibling';
142is $simple->to_string, '<simple class="working">easy</simple>',
143 'stringified right';
144$simple->parent->attr(bar => 'baz')->attr({this => 'works', too => 'yea'});
145is $simple->parent->attr('bar'), 'baz', 'right parent attribute';
146is $simple->parent->attr('this'), 'works', 'right parent attribute';
147is $simple->parent->attr('too'), 'yea', 'right parent attribute';
148is $dom->at('test#test')->tag, 'test', 'right tag';
149is $dom->at('[class$="ing"]')->tag, 'simple', 'right tag';
150is $dom->at('[class="working"]')->tag, 'simple', 'right tag';
151is $dom->at('[class$=ing]')->tag, 'simple', 'right tag';
152is $dom->at('[class=working][class]')->tag, 'simple', 'right tag';
153is $dom->at('foo > simple')->next->tag, 'test', 'right tag';
154is $dom->at('foo > simple')->next->next->tag, 'a', 'right tag';
155is $dom->at('foo > test')->previous->tag, 'simple', 'right tag';
156is $dom->next, undef, 'no siblings';
157is $dom->previous, undef, 'no siblings';
158is $dom->at('foo > a')->next, undef, 'no next sibling';
159is $dom->at('foo > simple')->previous, undef, 'no previous sibling';
160is_deeply [$dom->at('simple')->ancestors->map('tag')->each], ['foo'],
161 'right results';
162ok !$dom->at('simple')->ancestors->first->xml, 'XML mode not active';
163
164# Nodes
165$dom = DOM::Tiny->new(
166 '<!DOCTYPE before><p>test<![CDATA[123]]><!-- 456 --></p><?after?>');
167is $dom->at('p')->preceding_nodes->first->content, ' before', 'right content';
168is $dom->at('p')->preceding_nodes->size, 1, 'right number of nodes';
169is $dom->at('p')->child_nodes->last->preceding_nodes->first->content, 'test',
170 'right content';
171is $dom->at('p')->child_nodes->last->preceding_nodes->last->content, '123',
172 'right content';
173is $dom->at('p')->child_nodes->last->preceding_nodes->size, 2,
174 'right number of nodes';
175is $dom->preceding_nodes->size, 0, 'no preceding nodes';
176is $dom->at('p')->following_nodes->first->content, 'after', 'right content';
177is $dom->at('p')->following_nodes->size, 1, 'right number of nodes';
178is $dom->child_nodes->first->following_nodes->first->tag, 'p', 'right tag';
179is $dom->child_nodes->first->following_nodes->last->content, 'after',
180 'right content';
181is $dom->child_nodes->first->following_nodes->size, 2, 'right number of nodes';
182is $dom->following_nodes->size, 0, 'no following nodes';
183is $dom->at('p')->previous_node->content, ' before', 'right content';
184is $dom->at('p')->previous_node->previous_node, undef, 'no more siblings';
185is $dom->at('p')->next_node->content, 'after', 'right content';
186is $dom->at('p')->next_node->next_node, undef, 'no more siblings';
187is $dom->at('p')->child_nodes->last->previous_node->previous_node->content,
188 'test', 'right content';
189is $dom->at('p')->child_nodes->first->next_node->next_node->content, ' 456 ',
190 'right content';
191is $dom->descendant_nodes->[0]->type, 'doctype', 'right type';
192is $dom->descendant_nodes->[0]->content, ' before', 'right content';
193is $dom->descendant_nodes->[0], '<!DOCTYPE before>', 'right content';
194is $dom->descendant_nodes->[1]->tag, 'p', 'right tag';
195is $dom->descendant_nodes->[2]->type, 'text', 'right type';
196is $dom->descendant_nodes->[2]->content, 'test', 'right content';
197is $dom->descendant_nodes->[5]->type, 'pi', 'right type';
198is $dom->descendant_nodes->[5]->content, 'after', 'right content';
199is $dom->at('p')->descendant_nodes->[0]->type, 'text', 'right type';
200is $dom->at('p')->descendant_nodes->[0]->content, 'test', 'right type';
201is $dom->at('p')->descendant_nodes->last->type, 'comment', 'right type';
202is $dom->at('p')->descendant_nodes->last->content, ' 456 ', 'right type';
203is $dom->child_nodes->[1]->child_nodes->first->parent->tag, 'p', 'right tag';
204is $dom->child_nodes->[1]->child_nodes->first->content, 'test', 'right content';
205is $dom->child_nodes->[1]->child_nodes->first, 'test', 'right content';
206is $dom->at('p')->child_nodes->first->type, 'text', 'right type';
207is $dom->at('p')->child_nodes->first->remove->tag, 'p', 'right tag';
208is $dom->at('p')->child_nodes->first->type, 'cdata', 'right type';
209is $dom->at('p')->child_nodes->first->content, '123', 'right content';
210is $dom->at('p')->child_nodes->[1]->type, 'comment', 'right type';
211is $dom->at('p')->child_nodes->[1]->content, ' 456 ', 'right content';
212is $dom->[0]->type, 'doctype', 'right type';
213is $dom->[0]->content, ' before', 'right content';
214is $dom->child_nodes->[2]->type, 'pi', 'right type';
215is $dom->child_nodes->[2]->content, 'after', 'right content';
216is $dom->child_nodes->first->content(' again')->content, ' again',
217 'right content';
218is $dom->child_nodes->grep(sub { $_->type eq 'pi' })->map('remove')
219 ->first->type, 'root', 'right type';
220is "$dom", '<!DOCTYPE again><p><![CDATA[123]]><!-- 456 --></p>', 'right result';
221
222# Modify nodes
223$dom = DOM::Tiny->new('<script>la<la>la</script>');
224is $dom->at('script')->type, 'tag', 'right type';
225is $dom->at('script')->[0]->type, 'raw', 'right type';
226is $dom->at('script')->[0]->content, 'la<la>la', 'right content';
227is "$dom", '<script>la<la>la</script>', 'right result';
228is $dom->at('script')->child_nodes->first->replace('a<b>c</b>1<b>d</b>')->tag,
229 'script', 'right tag';
230is "$dom", '<script>a<b>c</b>1<b>d</b></script>', 'right result';
231is $dom->at('b')->child_nodes->first->append('e')->content, 'c',
232 'right content';
233is $dom->at('b')->child_nodes->first->prepend('f')->type, 'text', 'right type';
234is "$dom", '<script>a<b>fce</b>1<b>d</b></script>', 'right result';
235is $dom->at('script')->child_nodes->first->following->first->tag, 'b',
236 'right tag';
237is $dom->at('script')->child_nodes->first->next->content, 'fce',
238 'right content';
239is $dom->at('script')->child_nodes->first->previous, undef, 'no siblings';
240is $dom->at('script')->child_nodes->[2]->previous->content, 'fce',
241 'right content';
242is $dom->at('b')->child_nodes->[1]->next, undef, 'no siblings';
243is $dom->at('script')->child_nodes->first->wrap('<i>:)</i>')->root,
244 '<script><i>:)a</i><b>fce</b>1<b>d</b></script>', 'right result';
245is $dom->at('i')->child_nodes->first->wrap_content('<b></b>')->root,
246 '<script><i><b>:)</b>a</i><b>fce</b>1<b>d</b></script>', 'right result';
247is $dom->at('b')->child_nodes->first->ancestors->map('tag')->join(','),
248 'b,i,script', 'right result';
249is $dom->at('b')->child_nodes->first->append_content('g')->content, ':)g',
250 'right content';
251is $dom->at('b')->child_nodes->first->prepend_content('h')->content, 'h:)g',
252 'right content';
253is "$dom", '<script><i><b>h:)g</b>a</i><b>fce</b>1<b>d</b></script>',
254 'right result';
255is $dom->at('script > b:last-of-type')->append('<!--y-->')
256 ->following_nodes->first->content, 'y', 'right content';
257is $dom->at('i')->prepend('z')->preceding_nodes->first->content, 'z',
258 'right content';
259is $dom->at('i')->following->last->text, 'd', 'right text';
260is $dom->at('i')->following->size, 2, 'right number of following elements';
261is $dom->at('i')->following('b:last-of-type')->first->text, 'd', 'right text';
262is $dom->at('i')->following('b:last-of-type')->size, 1,
263 'right number of following elements';
264is $dom->following->size, 0, 'no following elements';
265is $dom->at('script > b:last-of-type')->preceding->first->tag, 'i', 'right tag';
266is $dom->at('script > b:last-of-type')->preceding->size, 2,
267 'right number of preceding elements';
268is $dom->at('script > b:last-of-type')->preceding('b')->first->tag, 'b',
269 'right tag';
270is $dom->at('script > b:last-of-type')->preceding('b')->size, 1,
271 'right number of preceding elements';
272is $dom->preceding->size, 0, 'no preceding elements';
273is "$dom", '<script>z<i><b>h:)g</b>a</i><b>fce</b>1<b>d</b><!--y--></script>',
274 'right result';
275
276# XML nodes
277$dom = DOM::Tiny->new->xml(1)->parse('<b>test<image /></b>');
278ok $dom->at('b')->child_nodes->first->xml, 'XML mode active';
279ok $dom->at('b')->child_nodes->first->replace('<br>')->child_nodes->first->xml,
280 'XML mode active';
281is "$dom", '<b><br /><image /></b>', 'right result';
282
283# Treating nodes as elements
284$dom = DOM::Tiny->new('foo<b>bar</b>baz');
285is $dom->child_nodes->first->child_nodes->size, 0, 'no nodes';
286is $dom->child_nodes->first->descendant_nodes->size, 0, 'no nodes';
287is $dom->child_nodes->first->children->size, 0, 'no children';
288is $dom->child_nodes->first->strip->parent, 'foo<b>bar</b>baz', 'no changes';
289is $dom->child_nodes->first->at('b'), undef, 'no result';
290is $dom->child_nodes->first->find('*')->size, 0, 'no results';
291ok !$dom->child_nodes->first->matches('*'), 'no match';
292is_deeply $dom->child_nodes->first->attr, {}, 'no attributes';
293is $dom->child_nodes->first->namespace, undef, 'no namespace';
294is $dom->child_nodes->first->tag, undef, 'no tag';
295is $dom->child_nodes->first->text, '', 'no text';
296is $dom->child_nodes->first->all_text, '', 'no text';
297
298# Class and ID
299$dom = DOM::Tiny->new('<div id="id" class="class">a</div>');
300is $dom->at('div#id.class')->text, 'a', 'right text';
301
302# Deep nesting (parent combinator)
303$dom = DOM::Tiny->new(<<EOF);
304<html>
305 <head>
306 <title>Foo</title>
307 </head>
308 <body>
309 <div id="container">
310 <div id="header">
311 <div id="logo">Hello World</div>
312 <div id="buttons">
313 <p id="foo">Foo</p>
314 </div>
315 </div>
316 <form>
317 <div id="buttons">
318 <p id="bar">Bar</p>
319 </div>
320 </form>
321 <div id="content">More stuff</div>
322 </div>
323 </body>
324</html>
325EOF
326my $p = $dom->find('body > #container > div p[id]');
327is $p->[0]->attr('id'), 'foo', 'right id attribute';
328is $p->[1], undef, 'no second result';
329is $p->size, 1, 'right number of elements';
330my @p;
331@div = ();
332$dom->find('div')->each(sub { push @div, $_->attr('id') });
333$dom->find('p')->each(sub { push @p, $_->attr('id') });
334is_deeply \@p, [qw(foo bar)], 'found all p elements';
335my $ids = [qw(container header logo buttons buttons content)];
336is_deeply \@div, $ids, 'found all div elements';
337is_deeply [$dom->at('p')->ancestors->map('tag')->each],
338 [qw(div div div body html)], 'right results';
339is_deeply [$dom->at('html')->ancestors->each], [], 'no results';
340is_deeply [$dom->ancestors->each], [], 'no results';
341
342# Script tag
343$dom = DOM::Tiny->new(<<EOF);
344<script charset="utf-8">alert('lalala');</script>
345EOF
346is $dom->at('script')->text, "alert('lalala');", 'right script content';
347
348# HTML5 (unquoted values)
349$dom = DOM::Tiny->new(
350 '<div id = test foo ="bar" class=tset bar=/baz/ baz=//>works</div>');
351is $dom->at('#test')->text, 'works', 'right text';
352is $dom->at('div')->text, 'works', 'right text';
353is $dom->at('[foo=bar][foo="bar"]')->text, 'works', 'right text';
354is $dom->at('[foo="ba"]'), undef, 'no result';
355is $dom->at('[foo=bar]')->text, 'works', 'right text';
356is $dom->at('[foo=ba]'), undef, 'no result';
357is $dom->at('.tset')->text, 'works', 'right text';
358is $dom->at('[bar=/baz/]')->text, 'works', 'right text';
359is $dom->at('[baz=//]')->text, 'works', 'right text';
360
361# HTML1 (single quotes, uppercase tags and whitespace in attributes)
362$dom = DOM::Tiny->new(q{<DIV id = 'test' foo ='bar' class= "tset">works</DIV>});
363is $dom->at('#test')->text, 'works', 'right text';
364is $dom->at('div')->text, 'works', 'right text';
365is $dom->at('[foo="bar"]')->text, 'works', 'right text';
366is $dom->at('[foo="ba"]'), undef, 'no result';
367is $dom->at('[foo=bar]')->text, 'works', 'right text';
368is $dom->at('[foo=ba]'), undef, 'no result';
369is $dom->at('.tset')->text, 'works', 'right text';
370
371# Already decoded Unicode snowman and quotes in selector
372$dom = DOM::Tiny->new('<div id="snow&apos;m&quot;an">☃</div>');
373is $dom->at('[id="snow\'m\"an"]')->text, '☃', 'right text';
374is $dom->at('[id="snow\'m\22 an"]')->text, '☃', 'right text';
375is $dom->at('[id="snow\'m\000022an"]')->text, '☃', 'right text';
376is $dom->at('[id="snow\'m\22an"]'), undef, 'no result';
377is $dom->at('[id="snow\'m\21 an"]'), undef, 'no result';
378is $dom->at('[id="snow\'m\000021an"]'), undef, 'no result';
379is $dom->at('[id="snow\'m\000021 an"]'), undef, 'no result';
380is $dom->at("[id='snow\\'m\"an']")->text, '☃', 'right text';
381is $dom->at("[id='snow\\27m\"an']")->text, '☃', 'right text';
382
383# Unicode and escaped selectors
384my $html
385 = '<html><div id="☃x">Snowman</div><div class="x ♥">Heart</div></html>';
386$dom = DOM::Tiny->new($html);
387is $dom->at("#\\\n\\002603x")->text, 'Snowman', 'right text';
388is $dom->at('#\\2603 x')->text, 'Snowman', 'right text';
389is $dom->at("#\\\n\\2603 x")->text, 'Snowman', 'right text';
390is $dom->at(qq{[id="\\\n\\2603 x"]})->text, 'Snowman', 'right text';
391is $dom->at(qq{[id="\\\n\\002603x"]})->text, 'Snowman', 'right text';
392is $dom->at(qq{[id="\\\\2603 x"]})->text, 'Snowman', 'right text';
393is $dom->at("html #\\\n\\002603x")->text, 'Snowman', 'right text';
394is $dom->at('html #\\2603 x')->text, 'Snowman', 'right text';
395is $dom->at("html #\\\n\\2603 x")->text, 'Snowman', 'right text';
396is $dom->at(qq{html [id="\\\n\\2603 x"]})->text, 'Snowman', 'right text';
397is $dom->at(qq{html [id="\\\n\\002603x"]})->text, 'Snowman', 'right text';
398is $dom->at(qq{html [id="\\\\2603 x"]})->text, 'Snowman', 'right text';
399is $dom->at('#☃x')->text, 'Snowman', 'right text';
400is $dom->at('div#☃x')->text, 'Snowman', 'right text';
401is $dom->at('html div#☃x')->text, 'Snowman', 'right text';
402is $dom->at('[id^="☃"]')->text, 'Snowman', 'right text';
403is $dom->at('div[id^="☃"]')->text, 'Snowman', 'right text';
404is $dom->at('html div[id^="☃"]')->text, 'Snowman', 'right text';
405is $dom->at('html > div[id^="☃"]')->text, 'Snowman', 'right text';
406is $dom->at('[id^=☃]')->text, 'Snowman', 'right text';
407is $dom->at('div[id^=☃]')->text, 'Snowman', 'right text';
408is $dom->at('html div[id^=☃]')->text, 'Snowman', 'right text';
409is $dom->at('html > div[id^=☃]')->text, 'Snowman', 'right text';
410is $dom->at(".\\\n\\002665")->text, 'Heart', 'right text';
411is $dom->at('.\\2665')->text, 'Heart', 'right text';
412is $dom->at("html .\\\n\\002665")->text, 'Heart', 'right text';
413is $dom->at('html .\\2665')->text, 'Heart', 'right text';
414is $dom->at(qq{html [class\$="\\\n\\002665"]})->text, 'Heart', 'right text';
415is $dom->at(qq{html [class\$="\\2665"]})->text, 'Heart', 'right text';
416is $dom->at(qq{[class\$="\\\n\\002665"]})->text, 'Heart', 'right text';
417is $dom->at(qq{[class\$="\\2665"]})->text, 'Heart', 'right text';
418is $dom->at('.x')->text, 'Heart', 'right text';
419is $dom->at('html .x')->text, 'Heart', 'right text';
420is $dom->at('.♥')->text, 'Heart', 'right text';
421is $dom->at('html .♥')->text, 'Heart', 'right text';
422is $dom->at('div.♥')->text, 'Heart', 'right text';
423is $dom->at('html div.♥')->text, 'Heart', 'right text';
424is $dom->at('[class$="♥"]')->text, 'Heart', 'right text';
425is $dom->at('div[class$="♥"]')->text, 'Heart', 'right text';
426is $dom->at('html div[class$="♥"]')->text, 'Heart', 'right text';
427is $dom->at('html > div[class$="♥"]')->text, 'Heart', 'right text';
428is $dom->at('[class$=♥]')->text, 'Heart', 'right text';
429is $dom->at('div[class$=♥]')->text, 'Heart', 'right text';
430is $dom->at('html div[class$=♥]')->text, 'Heart', 'right text';
431is $dom->at('html > div[class$=♥]')->text, 'Heart', 'right text';
432is $dom->at('[class~="♥"]')->text, 'Heart', 'right text';
433is $dom->at('div[class~="♥"]')->text, 'Heart', 'right text';
434is $dom->at('html div[class~="♥"]')->text, 'Heart', 'right text';
435is $dom->at('html > div[class~="♥"]')->text, 'Heart', 'right text';
436is $dom->at('[class~=♥]')->text, 'Heart', 'right text';
437is $dom->at('div[class~=♥]')->text, 'Heart', 'right text';
438is $dom->at('html div[class~=♥]')->text, 'Heart', 'right text';
439is $dom->at('html > div[class~=♥]')->text, 'Heart', 'right text';
440is $dom->at('[class~="x"]')->text, 'Heart', 'right text';
441is $dom->at('div[class~="x"]')->text, 'Heart', 'right text';
442is $dom->at('html div[class~="x"]')->text, 'Heart', 'right text';
443is $dom->at('html > div[class~="x"]')->text, 'Heart', 'right text';
444is $dom->at('[class~=x]')->text, 'Heart', 'right text';
445is $dom->at('div[class~=x]')->text, 'Heart', 'right text';
446is $dom->at('html div[class~=x]')->text, 'Heart', 'right text';
447is $dom->at('html > div[class~=x]')->text, 'Heart', 'right text';
448is $dom->at('html'), $html, 'right result';
449is $dom->at('#☃x')->parent, $html, 'right result';
450is $dom->at('#☃x')->root, $html, 'right result';
451is $dom->children('html')->first, $html, 'right result';
452is $dom->to_string, $html, 'right result';
453is $dom->content, $html, 'right result';
454
455# Looks remotely like HTML
456$dom = DOM::Tiny->new(
457 '<!DOCTYPE H "-/W/D HT 4/E">☃<title class=test>♥</title>☃');
458is $dom->at('title')->text, '♥', 'right text';
459is $dom->at('*')->text, '♥', 'right text';
460is $dom->at('.test')->text, '♥', 'right text';
461
462# Replace elements
463$dom = DOM::Tiny->new('<div>foo<p>lalala</p>bar</div>');
464is $dom->at('p')->replace('<foo>bar</foo>'), '<div>foo<foo>bar</foo>bar</div>',
465 'right result';
466is "$dom", '<div>foo<foo>bar</foo>bar</div>', 'right result';
467$dom->at('foo')->replace(DOM::Tiny->new('text'));
468is "$dom", '<div>footextbar</div>', 'right result';
469$dom = DOM::Tiny->new('<div>foo</div><div>bar</div>');
470$dom->find('div')->each(sub { shift->replace('<p>test</p>') });
471is "$dom", '<p>test</p><p>test</p>', 'right result';
472$dom = DOM::Tiny->new('<div>foo<p>lalala</p>bar</div>');
473is $dom->replace('♥'), '♥', 'right result';
474is "$dom", '♥', 'right result';
475$dom->replace('<div>foo<p>lalala</p>bar</div>');
476is "$dom", '<div>foo<p>lalala</p>bar</div>', 'right result';
477is $dom->at('p')->replace(''), '<div>foobar</div>', 'right result';
478is "$dom", '<div>foobar</div>', 'right result';
479is $dom->replace(''), '', 'no result';
480is "$dom", '', 'no result';
481$dom->replace('<div>foo<p>lalala</p>bar</div>');
482is "$dom", '<div>foo<p>lalala</p>bar</div>', 'right result';
483$dom->find('p')->map(replace => '');
484is "$dom", '<div>foobar</div>', 'right result';
485$dom = DOM::Tiny->new('<div>♥</div>');
486$dom->at('div')->content('☃');
487is "$dom", '<div>☃</div>', 'right result';
488$dom = DOM::Tiny->new('<div>♥</div>');
489$dom->at('div')->content("\x{2603}");
490is $dom->to_string, '<div>☃</div>', 'right result';
491is $dom->at('div')->replace('<p>♥</p>')->root, '<p>♥</p>', 'right result';
492is $dom->to_string, '<p>♥</p>', 'right result';
493is $dom->replace('<b>whatever</b>')->root, '<b>whatever</b>', 'right result';
494is $dom->to_string, '<b>whatever</b>', 'right result';
495$dom->at('b')->prepend('<p>foo</p>')->append('<p>bar</p>');
496is "$dom", '<p>foo</p><b>whatever</b><p>bar</p>', 'right result';
497is $dom->find('p')->map('remove')->first->root->at('b')->text, 'whatever',
498 'right result';
499is "$dom", '<b>whatever</b>', 'right result';
500is $dom->at('b')->strip, 'whatever', 'right result';
501is $dom->strip, 'whatever', 'right result';
502is $dom->remove, '', 'right result';
503$dom->replace('A<div>B<p>C<b>D<i><u>E</u></i>F</b>G</p><div>H</div></div>I');
504is $dom->find(':not(div):not(i):not(u)')->map('strip')->first->root,
505 'A<div>BCD<i><u>E</u></i>FG<div>H</div></div>I', 'right result';
506is $dom->at('i')->to_string, '<i><u>E</u></i>', 'right result';
507$dom = DOM::Tiny->new('<div><div>A</div><div>B</div>C</div>');
508is $dom->at('div')->at('div')->text, 'A', 'right text';
509$dom->at('div')->find('div')->map('strip');
510is "$dom", '<div>ABC</div>', 'right result';
511
512# Replace element content
513$dom = DOM::Tiny->new('<div>foo<p>lalala</p>bar</div>');
514is $dom->at('p')->content('bar'), '<p>bar</p>', 'right result';
515is "$dom", '<div>foo<p>bar</p>bar</div>', 'right result';
516$dom->at('p')->content(DOM::Tiny->new('text'));
517is "$dom", '<div>foo<p>text</p>bar</div>', 'right result';
518$dom = DOM::Tiny->new('<div>foo</div><div>bar</div>');
519$dom->find('div')->each(sub { shift->content('<p>test</p>') });
520is "$dom", '<div><p>test</p></div><div><p>test</p></div>', 'right result';
521$dom->find('p')->each(sub { shift->content('') });
522is "$dom", '<div><p></p></div><div><p></p></div>', 'right result';
523$dom = DOM::Tiny->new('<div><p id="☃" /></div>');
524$dom->at('#☃')->content('♥');
525is "$dom", '<div><p id="☃">♥</p></div>', 'right result';
526$dom = DOM::Tiny->new('<div>foo<p>lalala</p>bar</div>');
527$dom->content('♥');
528is "$dom", '♥', 'right result';
529is $dom->content('<div>foo<p>lalala</p>bar</div>'),
530 '<div>foo<p>lalala</p>bar</div>', 'right result';
531is "$dom", '<div>foo<p>lalala</p>bar</div>', 'right result';
532is $dom->content(''), '', 'no result';
533is "$dom", '', 'no result';
534$dom->content('<div>foo<p>lalala</p>bar</div>');
535is "$dom", '<div>foo<p>lalala</p>bar</div>', 'right result';
536is $dom->at('p')->content(''), '<p></p>', 'right result';
537
538# Mixed search and tree walk
539$dom = DOM::Tiny->new(<<EOF);
540<table>
541 <tr>
542 <td>text1</td>
543 <td>text2</td>
544 </tr>
545</table>
546EOF
547my @data;
548for my $tr ($dom->find('table tr')->each) {
549 for my $td (@{$tr->children}) {
550 push @data, $td->tag, $td->all_text;
551 }
552}
553is $data[0], 'td', 'right tag';
554is $data[1], 'text1', 'right text';
555is $data[2], 'td', 'right tag';
556is $data[3], 'text2', 'right text';
557is $data[4], undef, 'no tag';
558
559# RSS
560$dom = DOM::Tiny->new(<<EOF);
561<?xml version="1.0" encoding="UTF-8"?>
562<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
563 <channel>
564 <title>Test Blog</title>
565 <link>http://blog.example.com</link>
566 <description>lalala</description>
567 <generator>DOM::Tiny</generator>
568 <item>
569 <pubDate>Mon, 12 Jul 2010 20:42:00</pubDate>
570 <title>Works!</title>
571 <link>http://blog.example.com/test</link>
572 <guid>http://blog.example.com/test</guid>
573 <description>
574 <![CDATA[<p>trololololo>]]>
575 </description>
576 <my:extension foo:id="works">
577 <![CDATA[
578 [awesome]]
579 ]]>
580 </my:extension>
581 </item>
582 </channel>
583</rss>
584EOF
585ok $dom->xml, 'XML mode detected';
586is $dom->find('rss')->[0]->attr('version'), '2.0', 'right version';
587is_deeply [$dom->at('title')->ancestors->map('tag')->each], [qw(channel rss)],
588 'right results';
589is $dom->at('extension')->attr('foo:id'), 'works', 'right id';
590like $dom->at('#works')->text, qr/\[awesome\]\]/, 'right text';
591like $dom->at('[id="works"]')->text, qr/\[awesome\]\]/, 'right text';
592is $dom->find('description')->[1]->text, '<p>trololololo>', 'right text';
593is $dom->at('pubDate')->text, 'Mon, 12 Jul 2010 20:42:00', 'right text';
594like $dom->at('[id*="ork"]')->text, qr/\[awesome\]\]/, 'right text';
595like $dom->at('[id*="orks"]')->text, qr/\[awesome\]\]/, 'right text';
596like $dom->at('[id*="work"]')->text, qr/\[awesome\]\]/, 'right text';
597like $dom->at('[id*="or"]')->text, qr/\[awesome\]\]/, 'right text';
598ok $dom->at('rss')->xml, 'XML mode active';
599ok $dom->at('extension')->parent->xml, 'XML mode active';
600ok $dom->at('extension')->root->xml, 'XML mode active';
601ok $dom->children('rss')->first->xml, 'XML mode active';
602ok $dom->at('title')->ancestors->first->xml, 'XML mode active';
603
604# Namespace
605$dom = DOM::Tiny->new(<<EOF);
606<?xml version="1.0"?>
607<bk:book xmlns='uri:default-ns'
608 xmlns:bk='uri:book-ns'
609 xmlns:isbn='uri:isbn-ns'>
610 <bk:title>Programming Perl</bk:title>
611 <comment>rocks!</comment>
612 <nons xmlns=''>
613 <section>Nothing</section>
614 </nons>
615 <meta xmlns='uri:meta-ns'>
616 <isbn:number>978-0596000271</isbn:number>
617 </meta>
618</bk:book>
619EOF
620ok $dom->xml, 'XML mode detected';
621is $dom->namespace, undef, 'no namespace';
622is $dom->at('book comment')->namespace, 'uri:default-ns', 'right namespace';
623is $dom->at('book comment')->text, 'rocks!', 'right text';
624is $dom->at('book nons section')->namespace, '', 'no namespace';
625is $dom->at('book nons section')->text, 'Nothing', 'right text';
626is $dom->at('book meta number')->namespace, 'uri:isbn-ns', 'right namespace';
627is $dom->at('book meta number')->text, '978-0596000271', 'right text';
628is $dom->children('bk\:book')->first->{xmlns}, 'uri:default-ns',
629 'right attribute';
630is $dom->children('book')->first->{xmlns}, 'uri:default-ns', 'right attribute';
631is $dom->children('k\:book')->first, undef, 'no result';
632is $dom->children('ook')->first, undef, 'no result';
633is $dom->at('k\:book'), undef, 'no result';
634is $dom->at('ook'), undef, 'no result';
635is $dom->at('[xmlns\:bk]')->{'xmlns:bk'}, 'uri:book-ns', 'right attribute';
636is $dom->at('[bk]')->{'xmlns:bk'}, 'uri:book-ns', 'right attribute';
637is $dom->at('[bk]')->attr('xmlns:bk'), 'uri:book-ns', 'right attribute';
638is $dom->at('[bk]')->attr('s:bk'), undef, 'no attribute';
639is $dom->at('[bk]')->attr('bk'), undef, 'no attribute';
640is $dom->at('[bk]')->attr('k'), undef, 'no attribute';
641is $dom->at('[s\:bk]'), undef, 'no result';
642is $dom->at('[k]'), undef, 'no result';
643is $dom->at('number')->ancestors('meta')->first->{xmlns}, 'uri:meta-ns',
644 'right attribute';
645ok $dom->at('nons')->matches('book > nons'), 'element did match';
646ok !$dom->at('title')->matches('book > nons > section'),
647 'element did not match';
648
649# Dots
650$dom = DOM::Tiny->new(<<EOF);
651<?xml version="1.0"?>
652<foo xmlns:foo.bar="uri:first">
653 <bar xmlns:fooxbar="uri:second">
654 <foo.bar:baz>First</fooxbar:baz>
655 <fooxbar:ya.da>Second</foo.bar:ya.da>
656 </bar>
657</foo>
658EOF
659is $dom->at('foo bar baz')->text, 'First', 'right text';
660is $dom->at('baz')->namespace, 'uri:first', 'right namespace';
661is $dom->at('foo bar ya\.da')->text, 'Second', 'right text';
662is $dom->at('ya\.da')->namespace, 'uri:second', 'right namespace';
663is $dom->at('foo')->namespace, undef, 'no namespace';
664is $dom->at('[xml\.s]'), undef, 'no result';
665is $dom->at('b\.z'), undef, 'no result';
666
667# Yadis
668$dom = DOM::Tiny->new(<<'EOF');
669<?xml version="1.0" encoding="UTF-8"?>
670<XRDS xmlns="xri://$xrds">
671 <XRD xmlns="xri://$xrd*($v*2.0)">
672 <Service>
673 <Type>http://o.r.g/sso/2.0</Type>
674 </Service>
675 <Service>
676 <Type>http://o.r.g/sso/1.0</Type>
677 </Service>
678 </XRD>
679</XRDS>
680EOF
681ok $dom->xml, 'XML mode detected';
682is $dom->at('XRDS')->namespace, 'xri://$xrds', 'right namespace';
683is $dom->at('XRD')->namespace, 'xri://$xrd*($v*2.0)', 'right namespace';
684my $s = $dom->find('XRDS XRD Service');
685is $s->[0]->at('Type')->text, 'http://o.r.g/sso/2.0', 'right text';
686is $s->[0]->namespace, 'xri://$xrd*($v*2.0)', 'right namespace';
687is $s->[1]->at('Type')->text, 'http://o.r.g/sso/1.0', 'right text';
688is $s->[1]->namespace, 'xri://$xrd*($v*2.0)', 'right namespace';
689is $s->[2], undef, 'no result';
690is $s->size, 2, 'right number of elements';
691
692# Yadis (roundtrip with namespace)
693my $yadis = <<'EOF';
694<?xml version="1.0" encoding="UTF-8"?>
695<xrds:XRDS xmlns="xri://$xrd*($v*2.0)" xmlns:xrds="xri://$xrds">
696 <XRD>
697 <Service>
698 <Type>http://o.r.g/sso/3.0</Type>
699 </Service>
700 <xrds:Service>
701 <Type>http://o.r.g/sso/4.0</Type>
702 </xrds:Service>
703 </XRD>
704 <XRD>
705 <Service>
706 <Type test="23">http://o.r.g/sso/2.0</Type>
707 </Service>
708 <Service>
709 <Type Test="23" test="24">http://o.r.g/sso/1.0</Type>
710 </Service>
711 </XRD>
712</xrds:XRDS>
713EOF
714$dom = DOM::Tiny->new($yadis);
715ok $dom->xml, 'XML mode detected';
716is $dom->at('XRDS')->namespace, 'xri://$xrds', 'right namespace';
717is $dom->at('XRD')->namespace, 'xri://$xrd*($v*2.0)', 'right namespace';
718$s = $dom->find('XRDS XRD Service');
719is $s->[0]->at('Type')->text, 'http://o.r.g/sso/3.0', 'right text';
720is $s->[0]->namespace, 'xri://$xrd*($v*2.0)', 'right namespace';
721is $s->[1]->at('Type')->text, 'http://o.r.g/sso/4.0', 'right text';
722is $s->[1]->namespace, 'xri://$xrds', 'right namespace';
723is $s->[2]->at('Type')->text, 'http://o.r.g/sso/2.0', 'right text';
724is $s->[2]->namespace, 'xri://$xrd*($v*2.0)', 'right namespace';
725is $s->[3]->at('Type')->text, 'http://o.r.g/sso/1.0', 'right text';
726is $s->[3]->namespace, 'xri://$xrd*($v*2.0)', 'right namespace';
727is $s->[4], undef, 'no result';
728is $s->size, 4, 'right number of elements';
729is $dom->at('[Test="23"]')->text, 'http://o.r.g/sso/1.0', 'right text';
730is $dom->at('[test="23"]')->text, 'http://o.r.g/sso/2.0', 'right text';
731is $dom->find('xrds\:Service > Type')->[0]->text, 'http://o.r.g/sso/4.0',
732 'right text';
733is $dom->find('xrds\:Service > Type')->[1], undef, 'no result';
734is $dom->find('xrds\3AService > Type')->[0]->text, 'http://o.r.g/sso/4.0',
735 'right text';
736is $dom->find('xrds\3AService > Type')->[1], undef, 'no result';
737is $dom->find('xrds\3A Service > Type')->[0]->text, 'http://o.r.g/sso/4.0',
738 'right text';
739is $dom->find('xrds\3A Service > Type')->[1], undef, 'no result';
740is $dom->find('xrds\00003AService > Type')->[0]->text, 'http://o.r.g/sso/4.0',
741 'right text';
742is $dom->find('xrds\00003AService > Type')->[1], undef, 'no result';
743is $dom->find('xrds\00003A Service > Type')->[0]->text, 'http://o.r.g/sso/4.0',
744 'right text';
745is $dom->find('xrds\00003A Service > Type')->[1], undef, 'no result';
746is "$dom", $yadis, 'successful roundtrip';
747
748# Result and iterator order
749$dom = DOM::Tiny->new('<a><b>1</b></a><b>2</b><b>3</b>');
750my @numbers;
751$dom->find('b')->each(sub { push @numbers, pop, shift->text });
752is_deeply \@numbers, [1, 1, 2, 2, 3, 3], 'right order';
753
754# Attributes on multiple lines
755$dom = DOM::Tiny->new("<div test=23 id='a' \n class='x' foo=bar />");
756is $dom->at('div.x')->attr('test'), 23, 'right attribute';
757is $dom->at('[foo="bar"]')->attr('class'), 'x', 'right attribute';
758is $dom->at('div')->attr(baz => undef)->root->to_string,
759 '<div baz class="x" foo="bar" id="a" test="23"></div>', 'right result';
760
761# Markup characters in attribute values
762$dom = DOM::Tiny->new(qq{<div id="<a>" \n test='='>Test<div id='><' /></div>});
763is $dom->at('div[id="<a>"]')->attr->{test}, '=', 'right attribute';
764is $dom->at('[id="<a>"]')->text, 'Test', 'right text';
765is $dom->at('[id="><"]')->attr->{id}, '><', 'right attribute';
766
767# Empty attributes
768$dom = DOM::Tiny->new(qq{<div test="" test2='' />});
769is $dom->at('div')->attr->{test}, '', 'empty attribute value';
770is $dom->at('div')->attr->{test2}, '', 'empty attribute value';
771is $dom->at('[test]')->tag, 'div', 'right tag';
772is $dom->at('[test2]')->tag, 'div', 'right tag';
773is $dom->at('[test3]'), undef, 'no result';
774is $dom->at('[test=""]')->tag, 'div', 'right tag';
775is $dom->at('[test2=""]')->tag, 'div', 'right tag';
776is $dom->at('[test3=""]'), undef, 'no result';
777
927f1351 778# Multi-line attribute
779$dom = DOM::Tiny->new(qq{<div class="line1\nline2" />});
780is $dom->at('div')->attr->{class}, "line1\nline2", 'multi-line attribute value';
781is $dom->at('.line1')->tag, 'div', 'right tag';
782is $dom->at('.line2')->tag, 'div', 'right tag';
783is $dom->at('.line3'), undef, 'no result';
d6512b50 784
785# Whitespaces before closing bracket
786$dom = DOM::Tiny->new('<div >content</div>');
787ok $dom->at('div'), 'tag found';
788is $dom->at('div')->text, 'content', 'right text';
789is $dom->at('div')->content, 'content', 'right text';
790
791# Class with hyphen
792$dom = DOM::Tiny->new('<div class="a">A</div><div class="a-1">A1</div>');
793@div = ();
794$dom->find('.a')->each(sub { push @div, shift->text });
795is_deeply \@div, ['A'], 'found first element only';
796@div = ();
797$dom->find('.a-1')->each(sub { push @div, shift->text });
798is_deeply \@div, ['A1'], 'found last element only';
799
800# Defined but false text
801$dom = DOM::Tiny->new(
802 '<div><div id="a">A</div><div id="b">B</div></div><div id="0">0</div>');
803@div = ();
804$dom->find('div[id]')->each(sub { push @div, shift->text });
805is_deeply \@div, [qw(A B 0)], 'found all div elements with id';
806
807# Empty tags
808$dom = DOM::Tiny->new('<hr /><br/><br id="br"/><br />');
809is "$dom", '<hr><br><br id="br"><br>', 'right result';
810is $dom->at('br')->content, '', 'empty result';
811
812# Inner XML
813$dom = DOM::Tiny->new('<a>xxx<x>x</x>xxx</a>');
814is $dom->at('a')->content, 'xxx<x>x</x>xxx', 'right result';
815is $dom->content, '<a>xxx<x>x</x>xxx</a>', 'right result';
816
817# Multiple selectors
818$dom = DOM::Tiny->new(
819 '<div id="a">A</div><div id="b">B</div><div id="c">C</div><p>D</p>');
820@div = ();
821$dom->find('p, div')->each(sub { push @div, shift->text });
822is_deeply \@div, [qw(A B C D)], 'found all elements';
823@div = ();
824$dom->find('#a, #c')->each(sub { push @div, shift->text });
825is_deeply \@div, [qw(A C)], 'found all div elements with the right ids';
826@div = ();
827$dom->find('div#a, div#b')->each(sub { push @div, shift->text });
828is_deeply \@div, [qw(A B)], 'found all div elements with the right ids';
829@div = ();
830$dom->find('div[id="a"], div[id="c"]')->each(sub { push @div, shift->text });
831is_deeply \@div, [qw(A C)], 'found all div elements with the right ids';
832$dom = DOM::Tiny->new(
833 '<div id="☃">A</div><div id="b">B</div><div id="♥x">C</div>');
834@div = ();
835$dom->find('#☃, #♥x')->each(sub { push @div, shift->text });
836is_deeply \@div, [qw(A C)], 'found all div elements with the right ids';
837@div = ();
838$dom->find('div#☃, div#b')->each(sub { push @div, shift->text });
839is_deeply \@div, [qw(A B)], 'found all div elements with the right ids';
840@div = ();
841$dom->find('div[id="☃"], div[id="♥x"]')
842 ->each(sub { push @div, shift->text });
843is_deeply \@div, [qw(A C)], 'found all div elements with the right ids';
844
845# Multiple attributes
846$dom = DOM::Tiny->new(<<EOF);
847<div foo="bar" bar="baz">A</div>
848<div foo="bar">B</div>
849<div foo="bar" bar="baz">C</div>
850<div foo="baz" bar="baz">D</div>
851EOF
852@div = ();
853$dom->find('div[foo="bar"][bar="baz"]')->each(sub { push @div, shift->text });
854is_deeply \@div, [qw(A C)], 'found all div elements with the right atributes';
855@div = ();
856$dom->find('div[foo^="b"][foo$="r"]')->each(sub { push @div, shift->text });
857is_deeply \@div, [qw(A B C)], 'found all div elements with the right atributes';
858is $dom->at('[foo="bar"]')->previous, undef, 'no previous sibling';
859is $dom->at('[foo="bar"]')->next->text, 'B', 'right text';
860is $dom->at('[foo="bar"]')->next->previous->text, 'A', 'right text';
861is $dom->at('[foo="bar"]')->next->next->next->next, undef, 'no next sibling';
862
863# Pseudo-classes
864$dom = DOM::Tiny->new(<<EOF);
865<form action="/foo">
866 <input type="text" name="user" value="test" />
867 <input type="checkbox" checked="checked" name="groovy">
868 <select name="a">
869 <option value="b">b</option>
870 <optgroup label="c">
871 <option value="d">d</option>
872 <option selected="selected" value="e">E</option>
873 <option value="f">f</option>
874 </optgroup>
875 <option value="g">g</option>
876 <option selected value="h">H</option>
877 </select>
878 <input type="submit" value="Ok!" />
879 <input type="checkbox" checked name="I">
880 <p id="content">test 123</p>
881 <p id="no_content"><? test ?><!-- 123 --></p>
882</form>
883EOF
884is $dom->find(':root')->[0]->tag, 'form', 'right tag';
885is $dom->find('*:root')->[0]->tag, 'form', 'right tag';
886is $dom->find('form:root')->[0]->tag, 'form', 'right tag';
887is $dom->find(':root')->[1], undef, 'no result';
888is $dom->find(':checked')->[0]->attr->{name}, 'groovy', 'right name';
889is $dom->find('option:checked')->[0]->attr->{value}, 'e', 'right value';
890is $dom->find(':checked')->[1]->text, 'E', 'right text';
891is $dom->find('*:checked')->[1]->text, 'E', 'right text';
892is $dom->find(':checked')->[2]->text, 'H', 'right name';
893is $dom->find(':checked')->[3]->attr->{name}, 'I', 'right name';
894is $dom->find(':checked')->[4], undef, 'no result';
895is $dom->find('option[selected]')->[0]->attr->{value}, 'e', 'right value';
896is $dom->find('option[selected]')->[1]->text, 'H', 'right text';
897is $dom->find('option[selected]')->[2], undef, 'no result';
898is $dom->find(':checked[value="e"]')->[0]->text, 'E', 'right text';
899is $dom->find('*:checked[value="e"]')->[0]->text, 'E', 'right text';
900is $dom->find('option:checked[value="e"]')->[0]->text, 'E', 'right text';
901is $dom->at('optgroup option:checked[value="e"]')->text, 'E', 'right text';
902is $dom->at('select option:checked[value="e"]')->text, 'E', 'right text';
903is $dom->at('select :checked[value="e"]')->text, 'E', 'right text';
904is $dom->at('optgroup > :checked[value="e"]')->text, 'E', 'right text';
905is $dom->at('select *:checked[value="e"]')->text, 'E', 'right text';
906is $dom->at('optgroup > *:checked[value="e"]')->text, 'E', 'right text';
907is $dom->find(':checked[value="e"]')->[1], undef, 'no result';
908is $dom->find(':empty')->[0]->attr->{name}, 'user', 'right name';
909is $dom->find('input:empty')->[0]->attr->{name}, 'user', 'right name';
910is $dom->at(':empty[type^="ch"]')->attr->{name}, 'groovy', 'right name';
911is $dom->at('p')->attr->{id}, 'content', 'right attribute';
912is $dom->at('p:empty')->attr->{id}, 'no_content', 'right attribute';
913
914# More pseudo-classes
915$dom = DOM::Tiny->new(<<EOF);
916<ul>
917 <li>A</li>
918 <li>B</li>
919 <li>C</li>
920 <li>D</li>
921 <li>E</li>
922 <li>F</li>
923 <li>G</li>
924 <li>H</li>
925</ul>
926EOF
927my @li;
928$dom->find('li:nth-child(odd)')->each(sub { push @li, shift->text });
929is_deeply \@li, [qw(A C E G)], 'found all odd li elements';
930@li = ();
931$dom->find('li:NTH-CHILD(ODD)')->each(sub { push @li, shift->text });
932is_deeply \@li, [qw(A C E G)], 'found all odd li elements';
933@li = ();
934$dom->find('li:nth-last-child(odd)')->each(sub { push @li, shift->text });
935is_deeply \@li, [qw(B D F H)], 'found all odd li elements';
936is $dom->find(':nth-child(odd)')->[0]->tag, 'ul', 'right tag';
937is $dom->find(':nth-child(odd)')->[1]->text, 'A', 'right text';
938is $dom->find(':nth-child(1)')->[0]->tag, 'ul', 'right tag';
939is $dom->find(':nth-child(1)')->[1]->text, 'A', 'right text';
940is $dom->find(':nth-last-child(odd)')->[0]->tag, 'ul', 'right tag';
941is $dom->find(':nth-last-child(odd)')->last->text, 'H', 'right text';
942is $dom->find(':nth-last-child(1)')->[0]->tag, 'ul', 'right tag';
943is $dom->find(':nth-last-child(1)')->[1]->text, 'H', 'right text';
944@li = ();
945$dom->find('li:nth-child(2n+1)')->each(sub { push @li, shift->text });
946is_deeply \@li, [qw(A C E G)], 'found all odd li elements';
947@li = ();
948$dom->find('li:nth-child(2n + 1)')->each(sub { push @li, shift->text });
949is_deeply \@li, [qw(A C E G)], 'found all odd li elements';
950@li = ();
951$dom->find('li:nth-last-child(2n+1)')->each(sub { push @li, shift->text });
952is_deeply \@li, [qw(B D F H)], 'found all odd li elements';
953@li = ();
954$dom->find('li:nth-child(even)')->each(sub { push @li, shift->text });
955is_deeply \@li, [qw(B D F H)], 'found all even li elements';
956@li = ();
957$dom->find('li:NTH-CHILD(EVEN)')->each(sub { push @li, shift->text });
958is_deeply \@li, [qw(B D F H)], 'found all even li elements';
959@li = ();
960$dom->find('li:nth-last-child( even )')->each(sub { push @li, shift->text });
961is_deeply \@li, [qw(A C E G)], 'found all even li elements';
962@li = ();
963$dom->find('li:nth-child(2n+2)')->each(sub { push @li, shift->text });
964is_deeply \@li, [qw(B D F H)], 'found all even li elements';
965@li = ();
966$dom->find('li:nTh-chILd(2N+2)')->each(sub { push @li, shift->text });
967is_deeply \@li, [qw(B D F H)], 'found all even li elements';
968@li = ();
969$dom->find('li:nth-child( 2n + 2 )')->each(sub { push @li, shift->text });
970is_deeply \@li, [qw(B D F H)], 'found all even li elements';
971@li = ();
972$dom->find('li:nth-last-child(2n+2)')->each(sub { push @li, shift->text });
973is_deeply \@li, [qw(A C E G)], 'found all even li elements';
974@li = ();
975$dom->find('li:nth-child(4n+1)')->each(sub { push @li, shift->text });
976is_deeply \@li, [qw(A E)], 'found the right li elements';
977@li = ();
978$dom->find('li:nth-last-child(4n+1)')->each(sub { push @li, shift->text });
979is_deeply \@li, [qw(D H)], 'found the right li elements';
980@li = ();
981$dom->find('li:nth-child(4n+4)')->each(sub { push @li, shift->text });
eb9737f2 982is_deeply \@li, [qw(D H)], 'found the right li elements';
d6512b50 983@li = ();
984$dom->find('li:nth-last-child(4n+4)')->each(sub { push @li, shift->text });
eb9737f2 985is_deeply \@li, [qw(A E)], 'found the right li elements';
d6512b50 986@li = ();
987$dom->find('li:nth-child(4n)')->each(sub { push @li, shift->text });
eb9737f2 988is_deeply \@li, [qw(D H)], 'found the right li elements';
d6512b50 989@li = ();
990$dom->find('li:nth-child( 4n )')->each(sub { push @li, shift->text });
eb9737f2 991is_deeply \@li, [qw(D H)], 'found the right li elements';
d6512b50 992@li = ();
993$dom->find('li:nth-last-child(4n)')->each(sub { push @li, shift->text });
eb9737f2 994is_deeply \@li, [qw(A E)], 'found the right li elements';
d6512b50 995@li = ();
996$dom->find('li:nth-child(5n-2)')->each(sub { push @li, shift->text });
eb9737f2 997is_deeply \@li, [qw(C H)], 'found the right li elements';
d6512b50 998@li = ();
999$dom->find('li:nth-child( 5n - 2 )')->each(sub { push @li, shift->text });
eb9737f2 1000is_deeply \@li, [qw(C H)], 'found the right li elements';
d6512b50 1001@li = ();
1002$dom->find('li:nth-last-child(5n-2)')->each(sub { push @li, shift->text });
eb9737f2 1003is_deeply \@li, [qw(A F)], 'found the right li elements';
d6512b50 1004@li = ();
1005$dom->find('li:nth-child(-n+3)')->each(sub { push @li, shift->text });
1006is_deeply \@li, [qw(A B C)], 'found first three li elements';
1007@li = ();
1008$dom->find('li:nth-child( -n + 3 )')->each(sub { push @li, shift->text });
1009is_deeply \@li, [qw(A B C)], 'found first three li elements';
1010@li = ();
1011$dom->find('li:nth-last-child(-n+3)')->each(sub { push @li, shift->text });
1012is_deeply \@li, [qw(F G H)], 'found last three li elements';
1013@li = ();
1014$dom->find('li:nth-child(-1n+3)')->each(sub { push @li, shift->text });
1015is_deeply \@li, [qw(A B C)], 'found first three li elements';
1016@li = ();
1017$dom->find('li:nth-last-child(-1n+3)')->each(sub { push @li, shift->text });
1018is_deeply \@li, [qw(F G H)], 'found first three li elements';
1019@li = ();
1020$dom->find('li:nth-child(3n)')->each(sub { push @li, shift->text });
1021is_deeply \@li, [qw(C F)], 'found every third li elements';
1022@li = ();
1023$dom->find('li:nth-last-child(3n)')->each(sub { push @li, shift->text });
1024is_deeply \@li, [qw(C F)], 'found every third li elements';
1025@li = ();
1026$dom->find('li:NTH-LAST-CHILD(3N)')->each(sub { push @li, shift->text });
1027is_deeply \@li, [qw(C F)], 'found every third li elements';
1028@li = ();
1029$dom->find('li:Nth-Last-Child(3N)')->each(sub { push @li, shift->text });
1030is_deeply \@li, [qw(C F)], 'found every third li elements';
1031@li = ();
eb9737f2 1032$dom->find('li:nth-child( 3 )')->each(sub { push @li, shift->text });
d6512b50 1033is_deeply \@li, ['C'], 'found third li element';
1034@li = ();
eb9737f2 1035$dom->find('li:nth-last-child( +3 )')->each(sub { push @li, shift->text });
d6512b50 1036is_deeply \@li, ['F'], 'found third last li element';
1037@li = ();
1038$dom->find('li:nth-child(1n+0)')->each(sub { push @li, shift->text });
eb9737f2 1039is_deeply \@li, [qw(A B C D E F G)], 'found all li elements';
1040@li = ();
1041$dom->find('li:nth-child(1n-0)')->each(sub { push @li, shift->text });
1042is_deeply \@li, [qw(A B C D E F G)], 'found all li elements';
d6512b50 1043@li = ();
1044$dom->find('li:nth-child(n+0)')->each(sub { push @li, shift->text });
eb9737f2 1045is_deeply \@li, [qw(A B C D E F G)], 'found all li elements';
1046@li = ();
1047$dom->find('li:nth-child(n)')->each(sub { push @li, shift->text });
1048is_deeply \@li, [qw(A B C D E F G)], 'found all li elements';
1049@li = ();
1050$dom->find('li:nth-child(n+0)')->each(sub { push @li, shift->text });
1051is_deeply \@li, [qw(A B C D E F G)], 'found all li elements';
d6512b50 1052@li = ();
1053$dom->find('li:NTH-CHILD(N+0)')->each(sub { push @li, shift->text });
eb9737f2 1054is_deeply \@li, [qw(A B C D E F G)], 'found all li elements';
d6512b50 1055@li = ();
1056$dom->find('li:Nth-Child(N+0)')->each(sub { push @li, shift->text });
eb9737f2 1057is_deeply \@li, [qw(A B C D E F G)], 'found all li elements';
d6512b50 1058@li = ();
1059$dom->find('li:nth-child(n)')->each(sub { push @li, shift->text });
eb9737f2 1060is_deeply \@li, [qw(A B C D E F G)], 'found all li elements';
1061@li = ();
1062$dom->find('li:nth-child(0n+1)')->each(sub { push @li, shift->text });
1063is_deeply \@li, [qw(A)], 'found first li element';
1064is $dom->find('li:nth-child(0n+0)')->size, 0, 'no results';
1065is $dom->find('li:nth-child(0)')->size, 0, 'no results';
1066is $dom->find('li:nth-child()')->size, 0, 'no results';
1067is $dom->find('li:nth-child(whatever)')->size, 0, 'no results';
1068is $dom->find('li:whatever(whatever)')->size, 0, 'no results';
d6512b50 1069
1070# Even more pseudo-classes
1071$dom = DOM::Tiny->new(<<EOF);
1072<ul>
1073 <li>A</li>
1074 <p>B</p>
1075 <li class="test ♥">C</li>
1076 <p>D</p>
1077 <li>E</li>
1078 <li>F</li>
1079 <p>G</p>
1080 <li>H</li>
1081 <li>I</li>
1082</ul>
1083<div>
1084 <div class="☃">J</div>
1085</div>
1086<div>
1087 <a href="http://www.w3.org/DOM/">DOM</a>
1088 <div class="☃">K</div>
1089 <a href="http://www.w3.org/DOM/">DOM</a>
1090</div>
1091EOF
1092my @e;
1093$dom->find('ul :nth-child(odd)')->each(sub { push @e, shift->text });
1094is_deeply \@e, [qw(A C E G I)], 'found all odd elements';
1095@e = ();
1096$dom->find('li:nth-of-type(odd)')->each(sub { push @e, shift->text });
1097is_deeply \@e, [qw(A E H)], 'found all odd li elements';
1098@e = ();
1099$dom->find('li:nth-last-of-type( odd )')->each(sub { push @e, shift->text });
1100is_deeply \@e, [qw(C F I)], 'found all odd li elements';
1101@e = ();
1102$dom->find('p:nth-of-type(odd)')->each(sub { push @e, shift->text });
1103is_deeply \@e, [qw(B G)], 'found all odd p elements';
1104@e = ();
1105$dom->find('p:nth-last-of-type(odd)')->each(sub { push @e, shift->text });
1106is_deeply \@e, [qw(B G)], 'found all odd li elements';
1107@e = ();
1108$dom->find('ul :nth-child(1)')->each(sub { push @e, shift->text });
1109is_deeply \@e, ['A'], 'found first child';
1110@e = ();
1111$dom->find('ul :first-child')->each(sub { push @e, shift->text });
1112is_deeply \@e, ['A'], 'found first child';
1113@e = ();
1114$dom->find('p:nth-of-type(1)')->each(sub { push @e, shift->text });
1115is_deeply \@e, ['B'], 'found first child';
1116@e = ();
1117$dom->find('p:first-of-type')->each(sub { push @e, shift->text });
1118is_deeply \@e, ['B'], 'found first child';
1119@e = ();
1120$dom->find('li:nth-of-type(1)')->each(sub { push @e, shift->text });
1121is_deeply \@e, ['A'], 'found first child';
1122@e = ();
1123$dom->find('li:first-of-type')->each(sub { push @e, shift->text });
1124is_deeply \@e, ['A'], 'found first child';
1125@e = ();
1126$dom->find('ul :nth-last-child(-n+1)')->each(sub { push @e, shift->text });
1127is_deeply \@e, ['I'], 'found last child';
1128@e = ();
1129$dom->find('ul :last-child')->each(sub { push @e, shift->text });
1130is_deeply \@e, ['I'], 'found last child';
1131@e = ();
1132$dom->find('p:nth-last-of-type(-n+1)')->each(sub { push @e, shift->text });
1133is_deeply \@e, ['G'], 'found last child';
1134@e = ();
1135$dom->find('p:last-of-type')->each(sub { push @e, shift->text });
1136is_deeply \@e, ['G'], 'found last child';
1137@e = ();
1138$dom->find('li:nth-last-of-type(-n+1)')->each(sub { push @e, shift->text });
1139is_deeply \@e, ['I'], 'found last child';
1140@e = ();
1141$dom->find('li:last-of-type')->each(sub { push @e, shift->text });
1142is_deeply \@e, ['I'], 'found last child';
1143@e = ();
1144$dom->find('ul :nth-child(-n+3):not(li)')->each(sub { push @e, shift->text });
1145is_deeply \@e, ['B'], 'found first p element';
1146@e = ();
eb9737f2 1147$dom->find('ul :nth-child(-n+3):NOT(li)')->each(sub { push @e, shift->text });
1148is_deeply \@e, ['B'], 'found first p element';
1149@e = ();
d6512b50 1150$dom->find('ul :nth-child(-n+3):not(:first-child)')
1151 ->each(sub { push @e, shift->text });
1152is_deeply \@e, [qw(B C)], 'found second and third element';
1153@e = ();
1154$dom->find('ul :nth-child(-n+3):not(.♥)')->each(sub { push @e, shift->text });
1155is_deeply \@e, [qw(A B)], 'found first and second element';
1156@e = ();
1157$dom->find('ul :nth-child(-n+3):not([class$="♥"])')
1158 ->each(sub { push @e, shift->text });
1159is_deeply \@e, [qw(A B)], 'found first and second element';
1160@e = ();
1161$dom->find('ul :nth-child(-n+3):not(li[class$="♥"])')
1162 ->each(sub { push @e, shift->text });
1163is_deeply \@e, [qw(A B)], 'found first and second element';
1164@e = ();
1165$dom->find('ul :nth-child(-n+3):not([class$="♥"][class^="test"])')
1166 ->each(sub { push @e, shift->text });
1167is_deeply \@e, [qw(A B)], 'found first and second element';
1168@e = ();
1169$dom->find('ul :nth-child(-n+3):not(*[class$="♥"])')
1170 ->each(sub { push @e, shift->text });
1171is_deeply \@e, [qw(A B)], 'found first and second element';
1172@e = ();
1173$dom->find('ul :nth-child(-n+3):not(:nth-child(-n+2))')
1174 ->each(sub { push @e, shift->text });
1175is_deeply \@e, ['C'], 'found third element';
1176@e = ();
1177$dom->find('ul :nth-child(-n+3):not(:nth-child(1)):not(:nth-child(2))')
1178 ->each(sub { push @e, shift->text });
1179is_deeply \@e, ['C'], 'found third element';
1180@e = ();
1181$dom->find(':only-child')->each(sub { push @e, shift->text });
1182is_deeply \@e, ['J'], 'found only child';
1183@e = ();
1184$dom->find('div :only-of-type')->each(sub { push @e, shift->text });
1185is_deeply \@e, [qw(J K)], 'found only child';
1186@e = ();
1187$dom->find('div:only-child')->each(sub { push @e, shift->text });
1188is_deeply \@e, ['J'], 'found only child';
1189@e = ();
1190$dom->find('div div:only-of-type')->each(sub { push @e, shift->text });
1191is_deeply \@e, [qw(J K)], 'found only child';
1192
1193# Sibling combinator
1194$dom = DOM::Tiny->new(<<EOF);
1195<ul>
1196 <li>A</li>
1197 <p>B</p>
1198 <li>C</li>
1199</ul>
1200<h1>D</h1>
1201<p id="♥">E</p>
1202<p id="☃">F<b>H</b></p>
1203<div>G</div>
1204EOF
1205is $dom->at('li ~ p')->text, 'B', 'right text';
1206is $dom->at('li + p')->text, 'B', 'right text';
1207is $dom->at('h1 ~ p ~ p')->text, 'F', 'right text';
1208is $dom->at('h1 + p ~ p')->text, 'F', 'right text';
1209is $dom->at('h1 ~ p + p')->text, 'F', 'right text';
1210is $dom->at('h1 + p + p')->text, 'F', 'right text';
1211is $dom->at('h1 + p+p')->text, 'F', 'right text';
1212is $dom->at('ul > li ~ li')->text, 'C', 'right text';
1213is $dom->at('ul li ~ li')->text, 'C', 'right text';
1214is $dom->at('ul>li~li')->text, 'C', 'right text';
1215is $dom->at('ul li li'), undef, 'no result';
1216is $dom->at('ul ~ li ~ li'), undef, 'no result';
1217is $dom->at('ul + li ~ li'), undef, 'no result';
1218is $dom->at('ul > li + li'), undef, 'no result';
1219is $dom->at('h1 ~ div')->text, 'G', 'right text';
1220is $dom->at('h1 + div'), undef, 'no result';
1221is $dom->at('p + div')->text, 'G', 'right text';
1222is $dom->at('ul + h1 + p + p + div')->text, 'G', 'right text';
1223is $dom->at('ul + h1 ~ p + div')->text, 'G', 'right text';
1224is $dom->at('h1 ~ #♥')->text, 'E', 'right text';
1225is $dom->at('h1 + #♥')->text, 'E', 'right text';
1226is $dom->at('#♥~#☃')->text, 'F', 'right text';
1227is $dom->at('#♥+#☃')->text, 'F', 'right text';
1228is $dom->at('#♥+#☃>b')->text, 'H', 'right text';
1229is $dom->at('#♥ > #☃'), undef, 'no result';
1230is $dom->at('#♥ #☃'), undef, 'no result';
1231is $dom->at('#♥ + #☃ + :nth-last-child(1)')->text, 'G', 'right text';
1232is $dom->at('#♥ ~ #☃ + :nth-last-child(1)')->text, 'G', 'right text';
1233is $dom->at('#♥ + #☃ ~ :nth-last-child(1)')->text, 'G', 'right text';
1234is $dom->at('#♥ ~ #☃ ~ :nth-last-child(1)')->text, 'G', 'right text';
1235is $dom->at('#♥ + :nth-last-child(2)')->text, 'F', 'right text';
1236is $dom->at('#♥ ~ :nth-last-child(2)')->text, 'F', 'right text';
1237is $dom->at('#♥ + #☃ + *:nth-last-child(1)')->text, 'G', 'right text';
1238is $dom->at('#♥ ~ #☃ + *:nth-last-child(1)')->text, 'G', 'right text';
1239is $dom->at('#♥ + #☃ ~ *:nth-last-child(1)')->text, 'G', 'right text';
1240is $dom->at('#♥ ~ #☃ ~ *:nth-last-child(1)')->text, 'G', 'right text';
1241is $dom->at('#♥ + *:nth-last-child(2)')->text, 'F', 'right text';
1242is $dom->at('#♥ ~ *:nth-last-child(2)')->text, 'F', 'right text';
1243
1244# Adding nodes
1245$dom = DOM::Tiny->new(<<EOF);
1246<ul>
1247 <li>A</li>
1248 <p>B</p>
1249 <li>C</li>
1250</ul>
1251<div>D</div>
1252EOF
1253$dom->at('li')->append('<p>A1</p>23');
1254is "$dom", <<EOF, 'right result';
1255<ul>
1256 <li>A</li><p>A1</p>23
1257 <p>B</p>
1258 <li>C</li>
1259</ul>
1260<div>D</div>
1261EOF
1262$dom->at('li')->prepend('24')->prepend('<div>A-1</div>25');
1263is "$dom", <<EOF, 'right result';
1264<ul>
1265 24<div>A-1</div>25<li>A</li><p>A1</p>23
1266 <p>B</p>
1267 <li>C</li>
1268</ul>
1269<div>D</div>
1270EOF
1271is $dom->at('div')->text, 'A-1', 'right text';
1272is $dom->at('iv'), undef, 'no result';
1273$dom->prepend('l')->prepend('alal')->prepend('a');
1274is "$dom", <<EOF, 'no change';
1275<ul>
1276 24<div>A-1</div>25<li>A</li><p>A1</p>23
1277 <p>B</p>
1278 <li>C</li>
1279</ul>
1280<div>D</div>
1281EOF
1282$dom->append('lalala');
1283is "$dom", <<EOF, 'no change';
1284<ul>
1285 24<div>A-1</div>25<li>A</li><p>A1</p>23
1286 <p>B</p>
1287 <li>C</li>
1288</ul>
1289<div>D</div>
1290EOF
1291$dom->find('div')->each(sub { shift->append('works') });
1292is "$dom", <<EOF, 'right result';
1293<ul>
1294 24<div>A-1</div>works25<li>A</li><p>A1</p>23
1295 <p>B</p>
1296 <li>C</li>
1297</ul>
1298<div>D</div>works
1299EOF
1300$dom->at('li')->prepend_content('A3<p>A2</p>')->prepend_content('A4');
1301is $dom->at('li')->text, 'A4A3 A', 'right text';
1302is "$dom", <<EOF, 'right result';
1303<ul>
1304 24<div>A-1</div>works25<li>A4A3<p>A2</p>A</li><p>A1</p>23
1305 <p>B</p>
1306 <li>C</li>
1307</ul>
1308<div>D</div>works
1309EOF
1310$dom->find('li')->[1]->append_content('<p>C2</p>C3')->append_content(' C4')
1311 ->append_content('C5');
1312is $dom->find('li')->[1]->text, 'C C3 C4C5', 'right text';
1313is "$dom", <<EOF, 'right result';
1314<ul>
1315 24<div>A-1</div>works25<li>A4A3<p>A2</p>A</li><p>A1</p>23
1316 <p>B</p>
1317 <li>C<p>C2</p>C3 C4C5</li>
1318</ul>
1319<div>D</div>works
1320EOF
1321
1322# Optional "head" and "body" tags
1323$dom = DOM::Tiny->new(<<EOF);
1324<html>
1325 <head>
1326 <title>foo</title>
1327 <body>bar
1328EOF
1329is $dom->at('html > head > title')->text, 'foo', 'right text';
1330is $dom->at('html > body')->text, 'bar', 'right text';
1331
1332# Optional "li" tag
1333$dom = DOM::Tiny->new(<<EOF);
1334<ul>
1335 <li>
1336 <ol>
1337 <li>F
1338 <li>G
1339 </ol>
1340 <li>A</li>
1341 <LI>B
1342 <li>C</li>
1343 <li>D
1344 <li>E
1345</ul>
1346EOF
1347is $dom->find('ul > li > ol > li')->[0]->text, 'F', 'right text';
1348is $dom->find('ul > li > ol > li')->[1]->text, 'G', 'right text';
1349is $dom->find('ul > li')->[1]->text, 'A', 'right text';
1350is $dom->find('ul > li')->[2]->text, 'B', 'right text';
1351is $dom->find('ul > li')->[3]->text, 'C', 'right text';
1352is $dom->find('ul > li')->[4]->text, 'D', 'right text';
1353is $dom->find('ul > li')->[5]->text, 'E', 'right text';
1354
1355# Optional "p" tag
1356$dom = DOM::Tiny->new(<<EOF);
1357<div>
1358 <p>A</p>
1359 <P>B
1360 <p>C</p>
1361 <p>D<div>X</div>
1362 <p>E<img src="foo.png">
1363 <p>F<br>G
1364 <p>H
1365</div>
1366EOF
1367is $dom->find('div > p')->[0]->text, 'A', 'right text';
1368is $dom->find('div > p')->[1]->text, 'B', 'right text';
1369is $dom->find('div > p')->[2]->text, 'C', 'right text';
1370is $dom->find('div > p')->[3]->text, 'D', 'right text';
1371is $dom->find('div > p')->[4]->text, 'E', 'right text';
1372is $dom->find('div > p')->[5]->text, 'F G', 'right text';
1373is $dom->find('div > p')->[6]->text, 'H', 'right text';
1374is $dom->find('div > p > p')->[0], undef, 'no results';
1375is $dom->at('div > p > img')->attr->{src}, 'foo.png', 'right attribute';
1376is $dom->at('div > div')->text, 'X', 'right text';
1377
1378# Optional "dt" and "dd" tags
1379$dom = DOM::Tiny->new(<<EOF);
1380<dl>
1381 <dt>A</dt>
1382 <DD>B
1383 <dt>C</dt>
1384 <dd>D
1385 <dt>E
1386 <dd>F
1387</dl>
1388EOF
1389is $dom->find('dl > dt')->[0]->text, 'A', 'right text';
1390is $dom->find('dl > dd')->[0]->text, 'B', 'right text';
1391is $dom->find('dl > dt')->[1]->text, 'C', 'right text';
1392is $dom->find('dl > dd')->[1]->text, 'D', 'right text';
1393is $dom->find('dl > dt')->[2]->text, 'E', 'right text';
1394is $dom->find('dl > dd')->[2]->text, 'F', 'right text';
1395
1396# Optional "rp" and "rt" tags
1397$dom = DOM::Tiny->new(<<EOF);
1398<ruby>
1399 <rp>A</rp>
1400 <RT>B
1401 <rp>C</rp>
1402 <rt>D
1403 <rp>E
1404 <rt>F
1405</ruby>
1406EOF
1407is $dom->find('ruby > rp')->[0]->text, 'A', 'right text';
1408is $dom->find('ruby > rt')->[0]->text, 'B', 'right text';
1409is $dom->find('ruby > rp')->[1]->text, 'C', 'right text';
1410is $dom->find('ruby > rt')->[1]->text, 'D', 'right text';
1411is $dom->find('ruby > rp')->[2]->text, 'E', 'right text';
1412is $dom->find('ruby > rt')->[2]->text, 'F', 'right text';
1413
1414# Optional "optgroup" and "option" tags
1415$dom = DOM::Tiny->new(<<EOF);
1416<div>
1417 <optgroup>A
1418 <option id="foo">B
1419 <option>C</option>
1420 <option>D
1421 <OPTGROUP>E
1422 <option>F
1423 <optgroup>G
1424 <option>H
1425</div>
1426EOF
1427is $dom->find('div > optgroup')->[0]->text, 'A', 'right text';
1428is $dom->find('div > optgroup > #foo')->[0]->text, 'B', 'right text';
1429is $dom->find('div > optgroup > option')->[1]->text, 'C', 'right text';
1430is $dom->find('div > optgroup > option')->[2]->text, 'D', 'right text';
1431is $dom->find('div > optgroup')->[1]->text, 'E', 'right text';
1432is $dom->find('div > optgroup > option')->[3]->text, 'F', 'right text';
1433is $dom->find('div > optgroup')->[2]->text, 'G', 'right text';
1434is $dom->find('div > optgroup > option')->[4]->text, 'H', 'right text';
1435
1436# Optional "colgroup" tag
1437$dom = DOM::Tiny->new(<<EOF);
1438<table>
1439 <col id=morefail>
1440 <col id=fail>
1441 <colgroup>
1442 <col id=foo>
1443 <col class=foo>
1444 <colgroup>
1445 <col id=bar>
1446</table>
1447EOF
1448is $dom->find('table > col')->[0]->attr->{id}, 'morefail', 'right attribute';
1449is $dom->find('table > col')->[1]->attr->{id}, 'fail', 'right attribute';
1450is $dom->find('table > colgroup > col')->[0]->attr->{id}, 'foo',
1451 'right attribute';
1452is $dom->find('table > colgroup > col')->[1]->attr->{class}, 'foo',
1453 'right attribute';
1454is $dom->find('table > colgroup > col')->[2]->attr->{id}, 'bar',
1455 'right attribute';
1456
1457# Optional "thead", "tbody", "tfoot", "tr", "th" and "td" tags
1458$dom = DOM::Tiny->new(<<EOF);
1459<table>
1460 <thead>
1461 <tr>
1462 <th>A</th>
1463 <th>D
1464 <tfoot>
1465 <tr>
1466 <td>C
1467 <tbody>
1468 <tr>
1469 <td>B
1470</table>
1471EOF
1472is $dom->at('table > thead > tr > th')->text, 'A', 'right text';
1473is $dom->find('table > thead > tr > th')->[1]->text, 'D', 'right text';
1474is $dom->at('table > tbody > tr > td')->text, 'B', 'right text';
1475is $dom->at('table > tfoot > tr > td')->text, 'C', 'right text';
1476
1477# Optional "colgroup", "thead", "tbody", "tr", "th" and "td" tags
1478$dom = DOM::Tiny->new(<<EOF);
1479<table>
1480 <col id=morefail>
1481 <col id=fail>
1482 <colgroup>
1483 <col id=foo />
1484 <col class=foo>
1485 <colgroup>
1486 <col id=bar>
1487 </colgroup>
1488 <thead>
1489 <tr>
1490 <th>A</th>
1491 <th>D
1492 <tbody>
1493 <tr>
1494 <td>B
1495 <tbody>
1496 <tr>
1497 <td>E
1498</table>
1499EOF
1500is $dom->find('table > col')->[0]->attr->{id}, 'morefail', 'right attribute';
1501is $dom->find('table > col')->[1]->attr->{id}, 'fail', 'right attribute';
1502is $dom->find('table > colgroup > col')->[0]->attr->{id}, 'foo',
1503 'right attribute';
1504is $dom->find('table > colgroup > col')->[1]->attr->{class}, 'foo',
1505 'right attribute';
1506is $dom->find('table > colgroup > col')->[2]->attr->{id}, 'bar',
1507 'right attribute';
1508is $dom->at('table > thead > tr > th')->text, 'A', 'right text';
1509is $dom->find('table > thead > tr > th')->[1]->text, 'D', 'right text';
1510is $dom->at('table > tbody > tr > td')->text, 'B', 'right text';
1511is $dom->find('table > tbody > tr > td')->map('text')->join("\n"), "B\nE",
1512 'right text';
1513
1514# Optional "colgroup", "tbody", "tr", "th" and "td" tags
1515$dom = DOM::Tiny->new(<<EOF);
1516<table>
1517 <colgroup>
1518 <col id=foo />
1519 <col class=foo>
1520 <colgroup>
1521 <col id=bar>
1522 </colgroup>
1523 <tbody>
1524 <tr>
1525 <td>B
1526</table>
1527EOF
1528is $dom->find('table > colgroup > col')->[0]->attr->{id}, 'foo',
1529 'right attribute';
1530is $dom->find('table > colgroup > col')->[1]->attr->{class}, 'foo',
1531 'right attribute';
1532is $dom->find('table > colgroup > col')->[2]->attr->{id}, 'bar',
1533 'right attribute';
1534is $dom->at('table > tbody > tr > td')->text, 'B', 'right text';
1535
1536# Optional "tr" and "td" tags
1537$dom = DOM::Tiny->new(<<EOF);
1538<table>
1539 <tr>
1540 <td>A
1541 <td>B</td>
1542 <tr>
1543 <td>C
1544 </tr>
1545 <tr>
1546 <td>D
1547</table>
1548EOF
1549is $dom->find('table > tr > td')->[0]->text, 'A', 'right text';
1550is $dom->find('table > tr > td')->[1]->text, 'B', 'right text';
1551is $dom->find('table > tr > td')->[2]->text, 'C', 'right text';
1552is $dom->find('table > tr > td')->[3]->text, 'D', 'right text';
1553
1554# Real world table
1555$dom = DOM::Tiny->new(<<EOF);
1556<html>
1557 <head>
1558 <title>Real World!</title>
1559 <body>
1560 <p>Just a test
1561 <table class=RealWorld>
1562 <thead>
1563 <tr>
1564 <th class=one>One
1565 <th class=two>Two
1566 <th class=three>Three
1567 <th class=four>Four
1568 <tbody>
1569 <tr>
1570 <td class=alpha>Alpha
1571 <td class=beta>Beta
1572 <td class=gamma><a href="#gamma">Gamma</a>
1573 <td class=delta>Delta
1574 <tr>
1575 <td class=alpha>Alpha Two
1576 <td class=beta>Beta Two
1577 <td class=gamma><a href="#gamma-two">Gamma Two</a>
1578 <td class=delta>Delta Two
1579 </table>
1580EOF
1581is $dom->find('html > head > title')->[0]->text, 'Real World!', 'right text';
1582is $dom->find('html > body > p')->[0]->text, 'Just a test', 'right text';
1583is $dom->find('p')->[0]->text, 'Just a test', 'right text';
1584is $dom->find('thead > tr > .three')->[0]->text, 'Three', 'right text';
1585is $dom->find('thead > tr > .four')->[0]->text, 'Four', 'right text';
1586is $dom->find('tbody > tr > .beta')->[0]->text, 'Beta', 'right text';
1587is $dom->find('tbody > tr > .gamma')->[0]->text, '', 'no text';
1588is $dom->find('tbody > tr > .gamma > a')->[0]->text, 'Gamma', 'right text';
1589is $dom->find('tbody > tr > .alpha')->[1]->text, 'Alpha Two', 'right text';
1590is $dom->find('tbody > tr > .gamma > a')->[1]->text, 'Gamma Two', 'right text';
1591my @following
1592 = $dom->find('tr > td:nth-child(1)')->map(following => ':nth-child(even)')
1593 ->flatten->map('all_text')->each;
1594is_deeply \@following, ['Beta', 'Delta', 'Beta Two', 'Delta Two'],
1595 'right results';
1596
1597# Real world list
1598$dom = DOM::Tiny->new(<<EOF);
1599<html>
1600 <head>
1601 <title>Real World!</title>
1602 <body>
1603 <ul>
1604 <li>
1605 Test
1606 <br>
1607 123
1608 <p>
1609
1610 <li>
1611 Test
1612 <br>
1613 321
1614 <p>
1615 <li>
1616 Test
1617 3
1618 2
1619 1
1620 <p>
1621 </ul>
1622EOF
1623is $dom->find('html > head > title')->[0]->text, 'Real World!', 'right text';
1624is $dom->find('body > ul > li')->[0]->text, 'Test 123', 'right text';
1625is $dom->find('body > ul > li > p')->[0]->text, '', 'no text';
1626is $dom->find('body > ul > li')->[1]->text, 'Test 321', 'right text';
1627is $dom->find('body > ul > li > p')->[1]->text, '', 'no text';
1628is $dom->find('body > ul > li')->[1]->all_text, 'Test 321', 'right text';
1629is $dom->find('body > ul > li > p')->[1]->all_text, '', 'no text';
1630is $dom->find('body > ul > li')->[2]->text, 'Test 3 2 1', 'right text';
1631is $dom->find('body > ul > li > p')->[2]->text, '', 'no text';
1632is $dom->find('body > ul > li')->[2]->all_text, 'Test 3 2 1', 'right text';
1633is $dom->find('body > ul > li > p')->[2]->all_text, '', 'no text';
1634
1635# Advanced whitespace trimming (punctuation)
1636$dom = DOM::Tiny->new(<<EOF);
1637<html>
1638 <head>
1639 <title>Real World!</title>
1640 <body>
1641 <div>foo <strong>bar</strong>.</div>
1642 <div>foo<strong>, bar</strong>baz<strong>; yada</strong>.</div>
1643 <div>foo<strong>: bar</strong>baz<strong>? yada</strong>!</div>
1644EOF
1645is $dom->find('html > head > title')->[0]->text, 'Real World!', 'right text';
1646is $dom->find('body > div')->[0]->all_text, 'foo bar.', 'right text';
1647is $dom->find('body > div')->[1]->all_text, 'foo, bar baz; yada.', 'right text';
1648is $dom->find('body > div')->[1]->text, 'foo baz.', 'right text';
1649is $dom->find('body > div')->[2]->all_text, 'foo: bar baz? yada!', 'right text';
1650is $dom->find('body > div')->[2]->text, 'foo baz!', 'right text';
1651
1652# Real world JavaScript and CSS
1653$dom = DOM::Tiny->new(<<EOF);
1654<html>
1655 <head>
1656 <style test=works>#style { foo: style('<test>'); }</style>
1657 <script>
1658 if (a < b) {
1659 alert('<123>');
1660 }
1661 </script>
1662 < sCriPt two="23" >if (b > c) { alert('&<ohoh>') }< / scRiPt >
1663 <body>Foo!</body>
1664EOF
1665is $dom->find('html > body')->[0]->text, 'Foo!', 'right text';
1666is $dom->find('html > head > style')->[0]->text,
1667 "#style { foo: style('<test>'); }", 'right text';
1668is $dom->find('html > head > script')->[0]->text,
1669 "\n if (a < b) {\n alert('<123>');\n }\n ", 'right text';
1670is $dom->find('html > head > script')->[1]->text,
1671 "if (b > c) { alert('&<ohoh>') }", 'right text';
1672
1673# More real world JavaScript
1674$dom = DOM::Tiny->new(<<EOF);
1675<!DOCTYPE html>
1676<html>
1677 <head>
1678 <title>Foo</title>
1679 <script src="/js/one.js"></script>
1680 <script src="/js/two.js"></script>
1681 <script src="/js/three.js"></script>
1682 </head>
1683 <body>Bar</body>
1684</html>
1685EOF
1686is $dom->at('title')->text, 'Foo', 'right text';
1687is $dom->find('html > head > script')->[0]->attr('src'), '/js/one.js',
1688 'right attribute';
1689is $dom->find('html > head > script')->[1]->attr('src'), '/js/two.js',
1690 'right attribute';
1691is $dom->find('html > head > script')->[2]->attr('src'), '/js/three.js',
1692 'right attribute';
1693is $dom->find('html > head > script')->[2]->text, '', 'no text';
1694is $dom->at('html > body')->text, 'Bar', 'right text';
1695
1696# Even more real world JavaScript
1697$dom = DOM::Tiny->new(<<EOF);
1698<!DOCTYPE html>
1699<html>
1700 <head>
1701 <title>Foo</title>
1702 <script src="/js/one.js"></script>
1703 <script src="/js/two.js"></script>
1704 <script src="/js/three.js">
1705 </head>
1706 <body>Bar</body>
1707</html>
1708EOF
1709is $dom->at('title')->text, 'Foo', 'right text';
1710is $dom->find('html > head > script')->[0]->attr('src'), '/js/one.js',
1711 'right attribute';
1712is $dom->find('html > head > script')->[1]->attr('src'), '/js/two.js',
1713 'right attribute';
1714is $dom->find('html > head > script')->[2]->attr('src'), '/js/three.js',
1715 'right attribute';
1716is $dom->find('html > head > script')->[2]->text, '', 'no text';
1717is $dom->at('html > body')->text, 'Bar', 'right text';
1718
1719# Inline DTD
1720$dom = DOM::Tiny->new(<<EOF);
1721<?xml version="1.0"?>
1722<!-- This is a Test! -->
1723<!DOCTYPE root [
1724 <!ELEMENT root (#PCDATA)>
1725 <!ATTLIST root att CDATA #REQUIRED>
1726]>
1727<root att="test">
1728 <![CDATA[<hello>world</hello>]]>
1729</root>
1730EOF
1731ok $dom->xml, 'XML mode detected';
1732is $dom->at('root')->attr('att'), 'test', 'right attribute';
1733is $dom->tree->[5][1], ' root [
1734 <!ELEMENT root (#PCDATA)>
1735 <!ATTLIST root att CDATA #REQUIRED>
1736]', 'right doctype';
1737is $dom->at('root')->text, '<hello>world</hello>', 'right text';
1738$dom = DOM::Tiny->new(<<EOF);
1739<!doctype book
1740SYSTEM "usr.dtd"
1741[
1742 <!ENTITY test "yeah">
1743]>
1744<foo />
1745EOF
1746is $dom->tree->[1][1], ' book
1747SYSTEM "usr.dtd"
1748[
1749 <!ENTITY test "yeah">
1750]', 'right doctype';
1751ok !$dom->xml, 'XML mode not detected';
1752is $dom->at('foo'), '<foo></foo>', 'right element';
1753$dom = DOM::Tiny->new(<<EOF);
1754<?xml version="1.0" encoding = 'utf-8'?>
1755<!DOCTYPE foo [
1756 <!ELEMENT foo ANY>
1757 <!ATTLIST foo xml:lang CDATA #IMPLIED>
1758 <!ENTITY % e SYSTEM "myentities.ent">
1759 %myentities;
1760] >
1761<foo xml:lang="de">Check!</fOo>
1762EOF
1763ok $dom->xml, 'XML mode detected';
1764is $dom->tree->[3][1], ' foo [
1765 <!ELEMENT foo ANY>
1766 <!ATTLIST foo xml:lang CDATA #IMPLIED>
1767 <!ENTITY % e SYSTEM "myentities.ent">
1768 %myentities;
1769] ', 'right doctype';
1770is $dom->at('foo')->attr->{'xml:lang'}, 'de', 'right attribute';
1771is $dom->at('foo')->text, 'Check!', 'right text';
1772$dom = DOM::Tiny->new(<<EOF);
1773<!DOCTYPE TESTSUITE PUBLIC "my.dtd" 'mhhh' [
1774 <!ELEMENT foo ANY>
1775 <!ATTLIST foo bar ENTITY 'true'>
1776 <!ENTITY system_entities SYSTEM 'systems.xml'>
1777 <!ENTITY leertaste '&#32;'>
1778 <!-- This is a comment -->
1779 <!NOTATION hmmm SYSTEM "hmmm">
1780] >
1781<?check for-nothing?>
1782<foo bar='false'>&leertaste;!!!</foo>
1783EOF
1784is $dom->tree->[1][1], ' TESTSUITE PUBLIC "my.dtd" \'mhhh\' [
1785 <!ELEMENT foo ANY>
1786 <!ATTLIST foo bar ENTITY \'true\'>
1787 <!ENTITY system_entities SYSTEM \'systems.xml\'>
1788 <!ENTITY leertaste \'&#32;\'>
1789 <!-- This is a comment -->
1790 <!NOTATION hmmm SYSTEM "hmmm">
1791] ', 'right doctype';
1792is $dom->at('foo')->attr('bar'), 'false', 'right attribute';
1793
1794# Broken "font" block and useless end tags
1795$dom = DOM::Tiny->new(<<EOF);
1796<html>
1797 <head><title>Test</title></head>
1798 <body>
1799 <table>
1800 <tr><td><font>test</td></font></tr>
1801 </tr>
1802 </table>
1803 </body>
1804</html>
1805EOF
1806is $dom->at('html > head > title')->text, 'Test', 'right text';
1807is $dom->at('html body table tr td > font')->text, 'test', 'right text';
1808
1809# Different broken "font" block
1810$dom = DOM::Tiny->new(<<EOF);
1811<html>
1812 <head><title>Test</title></head>
1813 <body>
1814 <font>
1815 <table>
1816 <tr>
1817 <td>test1<br></td></font>
1818 <td>test2<br>
1819 </table>
1820 </body>
1821</html>
1822EOF
1823is $dom->at('html > head > title')->text, 'Test', 'right text';
1824is $dom->find('html > body > font > table > tr > td')->[0]->text, 'test1',
1825 'right text';
1826is $dom->find('html > body > font > table > tr > td')->[1]->text, 'test2',
1827 'right text';
1828
1829# Broken "font" and "div" blocks
1830$dom = DOM::Tiny->new(<<EOF);
1831<html>
1832 <head><title>Test</title></head>
1833 <body>
1834 <font>
1835 <div>test1<br>
1836 <div>test2<br></font>
1837 </div>
1838 </body>
1839</html>
1840EOF
1841is $dom->at('html head title')->text, 'Test', 'right text';
1842is $dom->at('html body font > div')->text, 'test1', 'right text';
1843is $dom->at('html body font > div > div')->text, 'test2', 'right text';
1844
1845# Broken "div" blocks
1846$dom = DOM::Tiny->new(<<EOF);
1847<html>
1848 <head><title>Test</title></head>
1849 <body>
1850 <div>
1851 <table>
1852 <tr><td><div>test</td></div></tr>
1853 </div>
1854 </table>
1855 </body>
1856</html>
1857EOF
1858is $dom->at('html head title')->text, 'Test', 'right text';
1859is $dom->at('html body div table tr td > div')->text, 'test', 'right text';
1860
1861# And another broken "font" block
1862$dom = DOM::Tiny->new(<<EOF);
1863<html>
1864 <head><title>Test</title></head>
1865 <body>
1866 <table>
1867 <tr>
1868 <td><font><br>te<br>st<br>1</td></font>
1869 <td>x1<td><img>tes<br>t2</td>
1870 <td>x2<td><font>t<br>est3</font></td>
1871 </tr>
1872 </table>
1873 </body>
1874</html>
1875EOF
1876is $dom->at('html > head > title')->text, 'Test', 'right text';
1877is $dom->find('html body table tr > td > font')->[0]->text, 'te st 1',
1878 'right text';
1879is $dom->find('html body table tr > td')->[1]->text, 'x1', 'right text';
1880is $dom->find('html body table tr > td')->[2]->text, 'tes t2', 'right text';
1881is $dom->find('html body table tr > td')->[3]->text, 'x2', 'right text';
1882is $dom->find('html body table tr > td')->[5], undef, 'no result';
1883is $dom->find('html body table tr > td')->size, 5, 'right number of elements';
1884is $dom->find('html body table tr > td > font')->[1]->text, 't est3',
1885 'right text';
1886is $dom->find('html body table tr > td > font')->[2], undef, 'no result';
1887is $dom->find('html body table tr > td > font')->size, 2,
1888 'right number of elements';
1889is $dom, <<EOF, 'right result';
1890<html>
1891 <head><title>Test</title></head>
1892 <body>
1893 <table>
1894 <tr>
1895 <td><font><br>te<br>st<br>1</font></td>
1896 <td>x1</td><td><img>tes<br>t2</td>
1897 <td>x2</td><td><font>t<br>est3</font></td>
1898 </tr>
1899 </table>
1900 </body>
1901</html>
1902EOF
1903
1904# A collection of wonderful screwups
1905$dom = DOM::Tiny->new(<<'EOF');
1906<!DOCTYPE html>
1907<html lang="en">
1908 <head><title>Wonderful Screwups</title></head>
1909 <body id="screw-up">
1910 <div>
1911 <div class="ewww">
1912 <a href="/test" target='_blank'><img src="/test.png"></a>
1913 <a href='/real bad' screwup: http://localhost/bad' target='_blank'>
1914 <img src="/test2.png">
1915 </div>
1916 </mt:If>
1917 </div>
1918 <b>>la<>la<<>>la<</b>
1919 </body>
1920</html>
1921EOF
1922is $dom->at('#screw-up > b')->text, '>la<>la<<>>la<', 'right text';
1923is $dom->at('#screw-up .ewww > a > img')->attr('src'), '/test.png',
1924 'right attribute';
1925is $dom->find('#screw-up .ewww > a > img')->[1]->attr('src'), '/test2.png',
1926 'right attribute';
1927is $dom->find('#screw-up .ewww > a > img')->[2], undef, 'no result';
1928is $dom->find('#screw-up .ewww > a > img')->size, 2, 'right number of elements';
1929
1930# Broken "br" tag
1931$dom = DOM::Tiny->new('<br< abc abc abc abc abc abc abc abc<p>Test</p>');
1932is $dom->at('p')->text, 'Test', 'right text';
1933
1934# Modifying an XML document
1935$dom = DOM::Tiny->new(<<'EOF');
1936<?xml version='1.0' encoding='UTF-8'?>
1937<XMLTest />
1938EOF
1939ok $dom->xml, 'XML mode detected';
1940$dom->at('XMLTest')->content('<Element />');
1941my $element = $dom->at('Element');
1942is $element->tag, 'Element', 'right tag';
1943ok $element->xml, 'XML mode active';
1944$element = $dom->at('XMLTest')->children->[0];
1945is $element->tag, 'Element', 'right child';
1946is $element->parent->tag, 'XMLTest', 'right parent';
1947ok $element->root->xml, 'XML mode active';
1948$dom->replace('<XMLTest2 /><XMLTest3 just="works" />');
1949ok $dom->xml, 'XML mode active';
1950$dom->at('XMLTest2')->{foo} = undef;
1951is $dom, '<XMLTest2 foo="foo" /><XMLTest3 just="works" />', 'right result';
1952
1953# Ensure HTML semantics
1954ok !DOM::Tiny->new->xml(undef)->parse('<?xml version="1.0"?>')->xml,
1955 'XML mode not detected';
1956$dom
1957 = DOM::Tiny->new->xml(0)->parse('<?xml version="1.0"?><br><div>Test</div>');
1958is $dom->at('div:root')->text, 'Test', 'right text';
1959
1960# Ensure XML semantics
1961ok !!DOM::Tiny->new->xml(1)->parse('<foo />')->xml, 'XML mode active';
1962$dom = DOM::Tiny->new(<<'EOF');
1963<?xml version='1.0' encoding='UTF-8'?>
1964<script>
1965 <table>
1966 <td>
1967 <tr><thead>foo<thead></tr>
1968 </td>
1969 <td>
1970 <tr><thead>bar<thead></tr>
1971 </td>
1972 </table>
1973</script>
1974EOF
1975is $dom->find('table > td > tr > thead')->[0]->text, 'foo', 'right text';
1976is $dom->find('script > table > td > tr > thead')->[1]->text, 'bar',
1977 'right text';
1978is $dom->find('table > td > tr > thead')->[2], undef, 'no result';
1979is $dom->find('table > td > tr > thead')->size, 2, 'right number of elements';
1980
1981# Ensure XML semantics again
1982$dom = DOM::Tiny->new->xml(1)->parse(<<'EOF');
1983<table>
1984 <td>
1985 <tr><thead>foo<thead></tr>
1986 </td>
1987 <td>
1988 <tr><thead>bar<thead></tr>
1989 </td>
1990</table>
1991EOF
1992is $dom->find('table > td > tr > thead')->[0]->text, 'foo', 'right text';
1993is $dom->find('table > td > tr > thead')->[1]->text, 'bar', 'right text';
1994is $dom->find('table > td > tr > thead')->[2], undef, 'no result';
1995is $dom->find('table > td > tr > thead')->size, 2, 'right number of elements';
1996
1997# Nested tables
1998$dom = DOM::Tiny->new(<<'EOF');
1999<table id="foo">
2000 <tr>
2001 <td>
2002 <table id="bar">
2003 <tr>
2004 <td>baz</td>
2005 </tr>
2006 </table>
2007 </td>
2008 </tr>
2009</table>
2010EOF
2011is $dom->find('#foo > tr > td > #bar > tr >td')->[0]->text, 'baz', 'right text';
2012is $dom->find('table > tr > td > table > tr >td')->[0]->text, 'baz',
2013 'right text';
2014
2015# Nested find
2016$dom->parse(<<EOF);
2017<c>
2018 <a>foo</a>
2019 <b>
2020 <a>bar</a>
2021 <c>
2022 <a>baz</a>
2023 <d>
2024 <a>yada</a>
2025 </d>
2026 </c>
2027 </b>
2028</c>
2029EOF
2030my @results;
2031$dom->find('b')->each(
2032 sub {
2033 $_->find('a')->each(sub { push @results, $_->text });
2034 }
2035);
2036is_deeply \@results, [qw(bar baz yada)], 'right results';
2037@results = ();
2038$dom->find('a')->each(sub { push @results, $_->text });
2039is_deeply \@results, [qw(foo bar baz yada)], 'right results';
2040@results = ();
2041$dom->find('b')->each(
2042 sub {
2043 $_->find('c a')->each(sub { push @results, $_->text });
2044 }
2045);
2046is_deeply \@results, [qw(baz yada)], 'right results';
2047is $dom->at('b')->at('a')->text, 'bar', 'right text';
2048is $dom->at('c > b > a')->text, 'bar', 'right text';
2049is $dom->at('b')->at('c > b > a'), undef, 'no result';
2050
2051# Direct hash access to attributes in XML mode
2052$dom = DOM::Tiny->new->xml(1)->parse(<<EOF);
2053<a id="one">
2054 <B class="two" test>
2055 foo
2056 <c id="three">bar</c>
2057 <c ID="four">baz</c>
2058 </B>
2059</a>
2060EOF
2061ok $dom->xml, 'XML mode active';
2062is $dom->at('a')->{id}, 'one', 'right attribute';
2063is_deeply [sort keys %{$dom->at('a')}], ['id'], 'right attributes';
2064is $dom->at('a')->at('B')->text, 'foo', 'right text';
2065is $dom->at('B')->{class}, 'two', 'right attribute';
2066is_deeply [sort keys %{$dom->at('a B')}], [qw(class test)], 'right attributes';
2067is $dom->find('a B c')->[0]->text, 'bar', 'right text';
2068is $dom->find('a B c')->[0]{id}, 'three', 'right attribute';
2069is_deeply [sort keys %{$dom->find('a B c')->[0]}], ['id'], 'right attributes';
2070is $dom->find('a B c')->[1]->text, 'baz', 'right text';
2071is $dom->find('a B c')->[1]{ID}, 'four', 'right attribute';
2072is_deeply [sort keys %{$dom->find('a B c')->[1]}], ['ID'], 'right attributes';
2073is $dom->find('a B c')->[2], undef, 'no result';
2074is $dom->find('a B c')->size, 2, 'right number of elements';
2075@results = ();
2076$dom->find('a B c')->each(sub { push @results, $_->text });
2077is_deeply \@results, [qw(bar baz)], 'right results';
2078is $dom->find('a B c')->join("\n"),
2079 qq{<c id="three">bar</c>\n<c ID="four">baz</c>}, 'right result';
2080is_deeply [keys %$dom], [], 'root has no attributes';
2081is $dom->find('#nothing')->join, '', 'no result';
2082
2083# Direct hash access to attributes in HTML mode
2084$dom = DOM::Tiny->new(<<EOF);
2085<a id="one">
2086 <B class="two" test>
2087 foo
2088 <c id="three">bar</c>
2089 <c ID="four">baz</c>
2090 </B>
2091</a>
2092EOF
2093ok !$dom->xml, 'XML mode not active';
2094is $dom->at('a')->{id}, 'one', 'right attribute';
2095is_deeply [sort keys %{$dom->at('a')}], ['id'], 'right attributes';
2096is $dom->at('a')->at('b')->text, 'foo', 'right text';
2097is $dom->at('b')->{class}, 'two', 'right attribute';
2098is_deeply [sort keys %{$dom->at('a b')}], [qw(class test)], 'right attributes';
2099is $dom->find('a b c')->[0]->text, 'bar', 'right text';
2100is $dom->find('a b c')->[0]{id}, 'three', 'right attribute';
2101is_deeply [sort keys %{$dom->find('a b c')->[0]}], ['id'], 'right attributes';
2102is $dom->find('a b c')->[1]->text, 'baz', 'right text';
2103is $dom->find('a b c')->[1]{id}, 'four', 'right attribute';
2104is_deeply [sort keys %{$dom->find('a b c')->[1]}], ['id'], 'right attributes';
2105is $dom->find('a b c')->[2], undef, 'no result';
2106is $dom->find('a b c')->size, 2, 'right number of elements';
2107@results = ();
2108$dom->find('a b c')->each(sub { push @results, $_->text });
2109is_deeply \@results, [qw(bar baz)], 'right results';
2110is $dom->find('a b c')->join("\n"),
2111 qq{<c id="three">bar</c>\n<c id="four">baz</c>}, 'right result';
2112is_deeply [keys %$dom], [], 'root has no attributes';
2113is $dom->find('#nothing')->join, '', 'no result';
2114
2115# Append and prepend content
2116$dom = DOM::Tiny->new('<a><b>Test<c /></b></a>');
2117$dom->at('b')->append_content('<d />');
2118is $dom->children->[0]->tag, 'a', 'right tag';
2119is $dom->all_text, 'Test', 'right text';
2120is $dom->at('c')->parent->tag, 'b', 'right tag';
2121is $dom->at('d')->parent->tag, 'b', 'right tag';
2122$dom->at('b')->prepend_content('<e>DOM</e>');
2123is $dom->at('e')->parent->tag, 'b', 'right tag';
2124is $dom->all_text, 'DOM Test', 'right text';
2125
2126# Wrap elements
2127$dom = DOM::Tiny->new('<a>Test</a>');
2128is $dom->wrap('<b></b>')->type, 'root', 'right type';
2129is "$dom", '<b><a>Test</a></b>', 'right result';
2130is $dom->at('b')->strip->at('a')->wrap('A')->tag, 'a', 'right tag';
2131is "$dom", '<a>Test</a>', 'right result';
2132is $dom->at('a')->wrap('<b></b>')->tag, 'a', 'right tag';
2133is "$dom", '<b><a>Test</a></b>', 'right result';
2134is $dom->at('a')->wrap('C<c><d>D</d><e>E</e></c>F')->parent->tag, 'd',
2135 'right tag';
2136is "$dom", '<b>C<c><d>D<a>Test</a></d><e>E</e></c>F</b>', 'right result';
2137
2138# Wrap content
2139$dom = DOM::Tiny->new('<a>Test</a>');
2140is $dom->at('a')->wrap_content('A')->tag, 'a', 'right tag';
2141is "$dom", '<a>Test</a>', 'right result';
2142is $dom->wrap_content('<b></b>')->type, 'root', 'right type';
2143is "$dom", '<b><a>Test</a></b>', 'right result';
2144is $dom->at('b')->strip->at('a')->tag('e:a')->wrap_content('1<b c="d"></b>')
2145 ->tag, 'e:a', 'right tag';
2146is "$dom", '<e:a>1<b c="d">Test</b></e:a>', 'right result';
2147is $dom->at('a')->wrap_content('C<c><d>D</d><e>E</e></c>F')->parent->type,
2148 'root', 'right type';
2149is "$dom", '<e:a>C<c><d>D1<b c="d">Test</b></d><e>E</e></c>F</e:a>',
2150 'right result';
2151
2152# Broken "div" in "td"
2153$dom = DOM::Tiny->new(<<EOF);
2154<table>
2155 <tr>
2156 <td><div id="A"></td>
2157 <td><div id="B"></td>
2158 </tr>
2159</table>
2160EOF
2161is $dom->find('table tr td')->[0]->at('div')->{id}, 'A', 'right attribute';
2162is $dom->find('table tr td')->[1]->at('div')->{id}, 'B', 'right attribute';
2163is $dom->find('table tr td')->[2], undef, 'no result';
2164is $dom->find('table tr td')->size, 2, 'right number of elements';
2165is "$dom", <<EOF, 'right result';
2166<table>
2167 <tr>
2168 <td><div id="A"></div></td>
2169 <td><div id="B"></div></td>
2170 </tr>
2171</table>
2172EOF
2173
2174# Preformatted text
2175$dom = DOM::Tiny->new(<<EOF);
2176<div>
2177 looks
2178 <pre><code>like
2179 it
2180 really</code>
2181 </pre>
2182 works
2183</div>
2184EOF
2185is $dom->text, '', 'no text';
2186is $dom->text(0), "\n", 'right text';
2187is $dom->all_text, "looks like\n it\n really\n works", 'right text';
2188is $dom->all_text(0), "\n looks\n like\n it\n really\n \n works\n\n",
2189 'right text';
2190is $dom->at('div')->text, 'looks works', 'right text';
2191is $dom->at('div')->text(0), "\n looks\n \n works\n", 'right text';
2192is $dom->at('div')->all_text, "looks like\n it\n really\n works",
2193 'right text';
2194is $dom->at('div')->all_text(0),
2195 "\n looks\n like\n it\n really\n \n works\n", 'right text';
2196is $dom->at('div pre')->text, "\n ", 'right text';
2197is $dom->at('div pre')->text(0), "\n ", 'right text';
2198is $dom->at('div pre')->all_text, "like\n it\n really\n ", 'right text';
2199is $dom->at('div pre')->all_text(0), "like\n it\n really\n ", 'right text';
2200is $dom->at('div pre code')->text, "like\n it\n really", 'right text';
2201is $dom->at('div pre code')->text(0), "like\n it\n really", 'right text';
2202is $dom->at('div pre code')->all_text, "like\n it\n really", 'right text';
2203is $dom->at('div pre code')->all_text(0), "like\n it\n really",
2204 'right text';
2205
2206# Form values
2207$dom = DOM::Tiny->new(<<EOF);
2208<form action="/foo">
2209 <p>Test</p>
2210 <input type="text" name="a" value="A" />
2211 <input type="checkbox" checked name="b" value="B">
2212 <input type="radio" checked name="c" value="C">
2213 <select multiple name="f">
2214 <option value="F">G</option>
2215 <optgroup>
2216 <option>H</option>
2217 <option selected>I</option>
2218 </optgroup>
2219 <option value="J" selected>K</option>
2220 </select>
2221 <select name="n"><option>N</option></select>
2222 <select multiple name="q"><option>Q</option></select>
2223 <select name="d">
2224 <option selected>R</option>
2225 <option selected>D</option>
2226 </select>
2227 <textarea name="m">M</textarea>
2228 <button name="o" value="O">No!</button>
2229 <input type="submit" name="p" value="P" />
2230</form>
2231EOF
2232is $dom->at('p')->val, undef, 'no value';
2233is $dom->at('input')->val, 'A', 'right value';
2234is $dom->at('input:checked')->val, 'B', 'right value';
2235is $dom->at('input:checked[type=radio]')->val, 'C', 'right value';
2236is_deeply $dom->at('select')->val, ['I', 'J'], 'right values';
2237is $dom->at('select option')->val, 'F', 'right value';
2238is $dom->at('select optgroup option:not([selected])')->val, 'H', 'right value';
2239is $dom->find('select')->[1]->at('option')->val, 'N', 'right value';
2240is $dom->find('select')->[1]->val, undef, 'no value';
2241is_deeply $dom->find('select')->[2]->val, undef, 'no value';
2242is $dom->find('select')->[2]->at('option')->val, 'Q', 'right value';
2243is_deeply $dom->find('select')->last->val, 'D', 'right value';
2244is_deeply $dom->find('select')->last->at('option')->val, 'R', 'right value';
2245is $dom->at('textarea')->val, 'M', 'right value';
2246is $dom->at('button')->val, 'O', 'right value';
2247is $dom->find('form input')->last->val, 'P', 'right value';
2248
2249# PoCo example with whitespace sensitive text
2250$dom = DOM::Tiny->new(<<EOF);
2251<?xml version="1.0" encoding="UTF-8"?>
2252<response>
2253 <entry>
2254 <id>1286823</id>
2255 <displayName>Homer Simpson</displayName>
2256 <addresses>
2257 <type>home</type>
2258 <formatted><![CDATA[742 Evergreen Terrace
2259Springfield, VT 12345 USA]]></formatted>
2260 </addresses>
2261 </entry>
2262 <entry>
2263 <id>1286822</id>
2264 <displayName>Marge Simpson</displayName>
2265 <addresses>
2266 <type>home</type>
2267 <formatted>742 Evergreen Terrace
2268Springfield, VT 12345 USA</formatted>
2269 </addresses>
2270 </entry>
2271</response>
2272EOF
2273is $dom->find('entry')->[0]->at('displayName')->text, 'Homer Simpson',
2274 'right text';
2275is $dom->find('entry')->[0]->at('id')->text, '1286823', 'right text';
2276is $dom->find('entry')->[0]->at('addresses')->children('type')->[0]->text,
2277 'home', 'right text';
2278is $dom->find('entry')->[0]->at('addresses formatted')->text,
2279 "742 Evergreen Terrace\nSpringfield, VT 12345 USA", 'right text';
2280is $dom->find('entry')->[0]->at('addresses formatted')->text(0),
2281 "742 Evergreen Terrace\nSpringfield, VT 12345 USA", 'right text';
2282is $dom->find('entry')->[1]->at('displayName')->text, 'Marge Simpson',
2283 'right text';
2284is $dom->find('entry')->[1]->at('id')->text, '1286822', 'right text';
2285is $dom->find('entry')->[1]->at('addresses')->children('type')->[0]->text,
2286 'home', 'right text';
2287is $dom->find('entry')->[1]->at('addresses formatted')->text,
2288 '742 Evergreen Terrace Springfield, VT 12345 USA', 'right text';
2289is $dom->find('entry')->[1]->at('addresses formatted')->text(0),
2290 "742 Evergreen Terrace\nSpringfield, VT 12345 USA", 'right text';
2291is $dom->find('entry')->[2], undef, 'no result';
2292is $dom->find('entry')->size, 2, 'right number of elements';
2293
2294# Find attribute with hyphen in name and value
2295$dom = DOM::Tiny->new(<<EOF);
2296<html>
2297 <head><meta http-equiv="content-type" content="text/html"></head>
2298</html>
2299EOF
2300is $dom->find('[http-equiv]')->[0]{content}, 'text/html', 'right attribute';
2301is $dom->find('[http-equiv]')->[1], undef, 'no result';
2302is $dom->find('[http-equiv="content-type"]')->[0]{content}, 'text/html',
2303 'right attribute';
2304is $dom->find('[http-equiv="content-type"]')->[1], undef, 'no result';
2305is $dom->find('[http-equiv^="content-"]')->[0]{content}, 'text/html',
2306 'right attribute';
2307is $dom->find('[http-equiv^="content-"]')->[1], undef, 'no result';
2308is $dom->find('head > [http-equiv$="-type"]')->[0]{content}, 'text/html',
2309 'right attribute';
2310is $dom->find('head > [http-equiv$="-type"]')->[1], undef, 'no result';
2311
2312# Find "0" attribute value
2313$dom = DOM::Tiny->new(<<EOF);
2314<a accesskey="0">Zero</a>
2315<a accesskey="1">O&gTn&gt;e</a>
2316EOF
2317is $dom->find('a[accesskey]')->[0]->text, 'Zero', 'right text';
2318is $dom->find('a[accesskey]')->[1]->text, 'O&gTn>e', 'right text';
2319is $dom->find('a[accesskey]')->[2], undef, 'no result';
2320is $dom->find('a[accesskey=0]')->[0]->text, 'Zero', 'right text';
2321is $dom->find('a[accesskey=0]')->[1], undef, 'no result';
2322is $dom->find('a[accesskey^=0]')->[0]->text, 'Zero', 'right text';
2323is $dom->find('a[accesskey^=0]')->[1], undef, 'no result';
2324is $dom->find('a[accesskey$=0]')->[0]->text, 'Zero', 'right text';
2325is $dom->find('a[accesskey$=0]')->[1], undef, 'no result';
2326is $dom->find('a[accesskey~=0]')->[0]->text, 'Zero', 'right text';
2327is $dom->find('a[accesskey~=0]')->[1], undef, 'no result';
2328is $dom->find('a[accesskey*=0]')->[0]->text, 'Zero', 'right text';
2329is $dom->find('a[accesskey*=0]')->[1], undef, 'no result';
2330is $dom->find('a[accesskey=1]')->[0]->text, 'O&gTn>e', 'right text';
2331is $dom->find('a[accesskey=1]')->[1], undef, 'no result';
2332is $dom->find('a[accesskey^=1]')->[0]->text, 'O&gTn>e', 'right text';
2333is $dom->find('a[accesskey^=1]')->[1], undef, 'no result';
2334is $dom->find('a[accesskey$=1]')->[0]->text, 'O&gTn>e', 'right text';
2335is $dom->find('a[accesskey$=1]')->[1], undef, 'no result';
2336is $dom->find('a[accesskey~=1]')->[0]->text, 'O&gTn>e', 'right text';
2337is $dom->find('a[accesskey~=1]')->[1], undef, 'no result';
2338is $dom->find('a[accesskey*=1]')->[0]->text, 'O&gTn>e', 'right text';
2339is $dom->find('a[accesskey*=1]')->[1], undef, 'no result';
2340is $dom->at('a[accesskey*="."]'), undef, 'no result';
2341
2342# Empty attribute value
2343$dom = DOM::Tiny->new(<<EOF);
2344<foo bar=>
2345 test
2346</foo>
2347<bar>after</bar>
2348EOF
2349is $dom->tree->[0], 'root', 'right type';
2350is $dom->tree->[1][0], 'tag', 'right type';
2351is $dom->tree->[1][1], 'foo', 'right tag';
2352is_deeply $dom->tree->[1][2], {bar => ''}, 'right attributes';
2353is $dom->tree->[1][4][0], 'text', 'right type';
2354is $dom->tree->[1][4][1], "\n test\n", 'right text';
2355is $dom->tree->[3][0], 'tag', 'right type';
2356is $dom->tree->[3][1], 'bar', 'right tag';
2357is $dom->tree->[3][4][0], 'text', 'right type';
2358is $dom->tree->[3][4][1], 'after', 'right text';
2359is "$dom", <<EOF, 'right result';
2360<foo bar="">
2361 test
2362</foo>
2363<bar>after</bar>
2364EOF
2365
2366# Case-insensitive attribute values
2367$dom = DOM::Tiny->new(<<EOF);
2368<p class="foo">A</p>
2369<p class="foo bAr">B</p>
2370<p class="FOO">C</p>
2371EOF
2372is $dom->find('.foo')->map('text')->join(','), 'A,B', 'right result';
2373is $dom->find('.FOO')->map('text')->join(','), 'C', 'right result';
2374is $dom->find('[class=foo]')->map('text')->join(','), 'A', 'right result';
2375is $dom->find('[class=foo i]')->map('text')->join(','), 'A,C', 'right result';
2376is $dom->find('[class="foo" i]')->map('text')->join(','), 'A,C', 'right result';
2377is $dom->find('[class="foo bar"]')->size, 0, 'no results';
2378is $dom->find('[class="foo bar" i]')->map('text')->join(','), 'B',
2379 'right result';
2380is $dom->find('[class~=foo]')->map('text')->join(','), 'A,B', 'right result';
2381is $dom->find('[class~=foo i]')->map('text')->join(','), 'A,B,C',
2382 'right result';
2383is $dom->find('[class*=f]')->map('text')->join(','), 'A,B', 'right result';
2384is $dom->find('[class*=f i]')->map('text')->join(','), 'A,B,C', 'right result';
2385is $dom->find('[class^=F]')->map('text')->join(','), 'C', 'right result';
2386is $dom->find('[class^=F i]')->map('text')->join(','), 'A,B,C', 'right result';
2387is $dom->find('[class$=O]')->map('text')->join(','), 'C', 'right result';
2388is $dom->find('[class$=O i]')->map('text')->join(','), 'A,C', 'right result';
2389
2390# Nested description lists
2391$dom = DOM::Tiny->new(<<EOF);
2392<dl>
2393 <dt>A</dt>
2394 <DD>
2395 <dl>
2396 <dt>B
2397 <dd>C
2398 </dl>
2399 </dd>
2400</dl>
2401EOF
2402is $dom->find('dl > dd > dl > dt')->[0]->text, 'B', 'right text';
2403is $dom->find('dl > dd > dl > dd')->[0]->text, 'C', 'right text';
2404is $dom->find('dl > dt')->[0]->text, 'A', 'right text';
2405
2406# Nested lists
2407$dom = DOM::Tiny->new(<<EOF);
2408<div>
2409 <ul>
2410 <li>
2411 A
2412 <ul>
2413 <li>B</li>
2414 C
2415 </ul>
2416 </li>
2417 </ul>
2418</div>
2419EOF
2420is $dom->find('div > ul > li')->[0]->text, 'A', 'right text';
2421is $dom->find('div > ul > li')->[1], undef, 'no result';
2422is $dom->find('div > ul li')->[0]->text, 'A', 'right text';
2423is $dom->find('div > ul li')->[1]->text, 'B', 'right text';
2424is $dom->find('div > ul li')->[2], undef, 'no result';
2425is $dom->find('div > ul ul')->[0]->text, 'C', 'right text';
2426is $dom->find('div > ul ul')->[1], undef, 'no result';
2427
2428# Unusual order
2429$dom
2430 = DOM::Tiny->new('<a href="http://example.com" id="foo" class="bar">Ok!</a>');
2431is $dom->at('a:not([href$=foo])[href^=h]')->text, 'Ok!', 'right text';
2432is $dom->at('a:not([href$=example.com])[href^=h]'), undef, 'no result';
2433is $dom->at('a[href^=h]#foo.bar')->text, 'Ok!', 'right text';
2434is $dom->at('a[href^=h]#foo.baz'), undef, 'no result';
2435is $dom->at('a[href^=h]#foo:not(b)')->text, 'Ok!', 'right text';
2436is $dom->at('a[href^=h]#foo:not(a)'), undef, 'no result';
2437is $dom->at('[href^=h].bar:not(b)[href$=m]#foo')->text, 'Ok!', 'right text';
2438is $dom->at('[href^=h].bar:not(b)[href$=m]#bar'), undef, 'no result';
2439is $dom->at(':not(b)#foo#foo')->text, 'Ok!', 'right text';
2440is $dom->at(':not(b)#foo#bar'), undef, 'no result';
2441is $dom->at(':not([href^=h]#foo#bar)')->text, 'Ok!', 'right text';
2442is $dom->at(':not([href^=h]#foo#foo)'), undef, 'no result';
2443
2444# Slash between attributes
2445$dom = DOM::Tiny->new('<input /type=checkbox / value="/a/" checked/><br/>');
2446is_deeply $dom->at('input')->attr,
2447 {type => 'checkbox', value => '/a/', checked => undef}, 'right attributes';
2448is "$dom", '<input checked type="checkbox" value="/a/"><br>', 'right result';
2449
2450# Dot and hash in class and id attributes
2451$dom = DOM::Tiny->new('<p class="a#b.c">A</p><p id="a#b.c">B</p>');
2452is $dom->at('p.a\#b\.c')->text, 'A', 'right text';
2453is $dom->at(':not(p.a\#b\.c)')->text, 'B', 'right text';
2454is $dom->at('p#a\#b\.c')->text, 'B', 'right text';
2455is $dom->at(':not(p#a\#b\.c)')->text, 'A', 'right text';
2456
2457# Extra whitespace
2458$dom = DOM::Tiny->new('< span>a< /span><b >b</b><span >c</ span>');
2459is $dom->at('span')->text, 'a', 'right text';
2460is $dom->at('span + b')->text, 'b', 'right text';
2461is $dom->at('b + span')->text, 'c', 'right text';
2462is "$dom", '<span>a</span><b>b</b><span>c</span>', 'right result';
2463
2464# Selectors with leading and trailing whitespace
2465$dom = DOM::Tiny->new('<div id=foo><b>works</b></div>');
2466is $dom->at(' div b ')->text, 'works', 'right text';
2467is $dom->at(' :not( #foo ) ')->text, 'works', 'right text';
2468
2469# "0"
2470$dom = DOM::Tiny->new('0');
2471is "$dom", '0', 'right result';
2472$dom->append_content('☃');
2473is "$dom", '0☃', 'right result';
2474is $dom->parse('<!DOCTYPE 0>'), '<!DOCTYPE 0>', 'successful roundtrip';
2475is $dom->parse('<!--0-->'), '<!--0-->', 'successful roundtrip';
2476is $dom->parse('<![CDATA[0]]>'), '<![CDATA[0]]>', 'successful roundtrip';
2477is $dom->parse('<?0?>'), '<?0?>', 'successful roundtrip';
2478
2479# Not self-closing
2480$dom = DOM::Tiny->new('<div />< div ><pre />test</div >123');
2481is $dom->at('div > div > pre')->text, 'test', 'right text';
2482is "$dom", '<div><div><pre>test</pre></div>123</div>', 'right result';
2483$dom = DOM::Tiny->new('<p /><svg><circle /><circle /></svg>');
2484is $dom->find('p > svg > circle')->size, 2, 'two circles';
2485is "$dom", '<p><svg><circle></circle><circle></circle></svg></p>',
2486 'right result';
2487
2488# "image"
2489$dom = DOM::Tiny->new('<image src="foo.png">test');
2490is $dom->at('img')->{src}, 'foo.png', 'right attribute';
2491is "$dom", '<img src="foo.png">test', 'right result';
2492
2493# "title"
2494$dom = DOM::Tiny->new('<title> <p>test&lt;</title>');
2495is $dom->at('title')->text, ' <p>test<', 'right text';
2496is "$dom", '<title> <p>test<</title>', 'right result';
2497
2498# "textarea"
2499$dom = DOM::Tiny->new('<textarea id="a"> <p>test&lt;</textarea>');
2500is $dom->at('textarea#a')->text, ' <p>test<', 'right text';
2501is "$dom", '<textarea id="a"> <p>test<</textarea>', 'right result';
2502
2503# Comments
2504$dom = DOM::Tiny->new(<<EOF);
2505<!-- HTML5 -->
2506<!-- bad idea -- HTML5 -->
2507<!-- HTML4 -- >
2508<!-- bad idea -- HTML4 -- >
2509EOF
2510is $dom->tree->[1][1], ' HTML5 ', 'right comment';
2511is $dom->tree->[3][1], ' bad idea -- HTML5 ', 'right comment';
2512is $dom->tree->[5][1], ' HTML4 ', 'right comment';
2513is $dom->tree->[7][1], ' bad idea -- HTML4 ', 'right comment';
2514
0139fdcf 2515SKIP: {
ba909048 2516 skip 'Regex subexpression recursion causes SIGSEGV on 5.8', 1 unless $] >= 5.010000;
0139fdcf 2517 # Huge number of attributes
2518 $dom = DOM::Tiny->new('<div ' . ('a=b ' x 32768) . '>Test</div>');
2519 is $dom->at('div[a=b]')->text, 'Test', 'right text';
2520}
d6512b50 2521
2522# Huge number of nested tags
2523my $huge = ('<a>' x 100) . 'works' . ('</a>' x 100);
2524$dom = DOM::Tiny->new($huge);
2525is $dom->all_text, 'works', 'right text';
2526is "$dom", $huge, 'right result';
2527
dbff35d4 2528# TO_JSON
2529is +JSON::PP->new->convert_blessed->encode([DOM::Tiny->new('<a></a>')]), '["<a></a>"]', 'right result';
2530
d6512b50 2531done_testing();