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