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