Commit | Line | Data |
b9c27a5a |
1 | use strict; |
2 | use warnings FATAL => 'all'; |
3 | |
4 | use HTML::Zoom; |
5 | use Test::More; |
6 | |
7 | ok my $zoom = HTML::Zoom->from_html(<<HTML); |
8 | <html> |
9 | <head> |
10 | <title>Hi!</title> |
11 | </head> |
12 | <body id="content-area"> |
13 | <h1>Test</h1> |
14 | <div> |
15 | <p class="first-para">Some Stuff</p> |
16 | <p class="body-para">More Stuff</p> |
17 | <p class="body-para">Even More Stuff</p> |
18 | <ol> |
19 | <li class="first-item odd">First</li> |
20 | <li class="body-items even">Stuff A</li> |
21 | <li class="body-items odd">Stuff B</li> |
22 | <li class="body-items even">Stuff C</li> |
23 | <li class="last-item odd">Last</li> |
24 | </ol> |
25 | <ul class="items"> |
26 | <li class="first">space</li> |
27 | <li class="second">space</li> |
28 | </ul> |
29 | <p class="body-para">Even More stuff</p> |
30 | <p class="last-para">Some Stuff</p> |
31 | </div> |
32 | <div id="blocks"> |
33 | <h2 class="h2-item">Sub Item</h2> |
34 | </div> |
35 | <ul id="people"> |
36 | <li class="name">name</li> |
37 | <li class="email">email</li> |
38 | </ul> |
39 | <ul id="people2"> |
40 | <li class="name">name</li> |
41 | <li class="email">email</li> |
42 | </ul> |
43 | <ul id="object"> |
44 | <li class="aa">AA</li> |
45 | <li class="bb">BB</li> |
46 | </ul> |
47 | <p id="footer">Copyright 2222</p> |
48 | </body> |
49 | </html> |
50 | HTML |
51 | |
52 | ## Stub for testing the fill method to be |
53 | |
54 | ok my $title = sub { |
55 | my ($z, $content) = @_; |
56 | $z = $z->select('title')->replace_content($content); |
57 | return $z; |
58 | }; |
59 | |
5802b80a |
60 | { |
61 | ok my $z = HTML::Zoom |
62 | ->from_html(q[<ul><li>Test</li></ul>]) |
63 | ->select('ul') |
64 | ->repeat_content( |
65 | [ |
66 | sub { $_->select('li')->replace_content('Real Life1') }, |
67 | sub { $_->select('li')->replace_content('Real Life2') }, |
68 | sub { $_->select('li')->replace_content('Real Life3') }, |
69 | ], |
70 | ) |
71 | ->to_html; |
72 | |
73 | is $z, '<ul><li>Real Life1</li><li>Real Life2</li><li>Real Life3</li></ul>', |
74 | 'Got correct from repeat_content'; |
75 | } |
76 | |
77 | { |
78 | ok my $z = HTML::Zoom |
79 | ->from_html(q[<ul><li>Test</li></ul>]) |
80 | ->select('ul') |
81 | ->repeat_content([ |
82 | map { my $i = $_; +sub {$_->select('li')->replace_content("Real Life$i")} } (1,2,3) |
83 | ]) |
84 | ->to_html; |
85 | |
86 | is $z, '<ul><li>Real Life1</li><li>Real Life2</li><li>Real Life3</li></ul>', |
87 | 'Got correct from repeat_content'; |
88 | } |
89 | |
90 | |
91 | use HTML::Zoom::CodeStream; |
92 | sub code_stream (&) { |
93 | my $code = shift; |
94 | return sub { |
95 | HTML::Zoom::CodeStream->new({ |
96 | code => $code, |
97 | }); |
98 | } |
99 | } |
100 | |
101 | { |
102 | my @list = qw(foo bar baz); |
103 | ok my $z = HTML::Zoom |
104 | ->from_html(q[<ul><li>Test</li></ul>]) |
105 | ->select('ul') |
106 | ->repeat_content(code_stream { |
107 | if (my $name = shift @list) { |
108 | return sub { $_->select('li')->replace_content($name) }; |
109 | } else { |
110 | return |
111 | } |
112 | }) |
113 | ->to_html; |
114 | |
115 | is $z, '<ul><li>foo</li><li>bar</li><li>baz</li></ul>', |
116 | 'Got correct from repeat_content'; |
117 | } |
118 | |
119 | { |
120 | my @list = qw(foo bar baz); |
121 | ok my $z = HTML::Zoom |
122 | ->from_html(q[<ul><li>Test</li></ul>]) |
123 | ->select('ul') |
124 | ->repeat_content(sub { |
125 | HTML::Zoom::CodeStream->new({ |
126 | code => sub { |
127 | if (my $name = shift @list) { |
128 | return sub { $_->select('li')->replace_content($name) }; |
129 | } else { |
130 | return |
131 | } |
132 | } |
133 | }); |
134 | }) |
135 | ->to_html; |
136 | |
137 | is $z, '<ul><li>foo</li><li>bar</li><li>baz</li></ul>', |
138 | 'Got correct from repeat_content'; |
139 | } |
140 | |
141 | { |
142 | use Scalar::Util (); |
143 | my $next_item_from_array = sub { |
144 | my @items = @_; |
145 | return sub { shift @items }; |
146 | }; |
147 | |
148 | my $next_item_from_proto = sub { |
149 | my $proto = shift; |
150 | if ( |
151 | ref $proto eq 'ARRAY' || |
152 | ref $proto eq 'HASH' || |
153 | Scalar::Util::blessed($proto) |
154 | ) { |
155 | return $next_item_from_array->($proto, @_); |
156 | } elsif(ref $proto eq 'CODE' ) { |
157 | return $proto; |
158 | } else { |
159 | die "Don't know what to do with $proto, it's a ". ref($proto); |
160 | } |
161 | }; |
162 | |
163 | my $normalize_targets = sub { |
164 | my $targets = shift; |
165 | my $targets_type = ref $targets; |
166 | return $targets_type eq 'ARRAY' ? $targets |
167 | : $targets_type eq 'HASH' ? [ map { +{$_=>$targets->{$_}} } keys %$targets ] |
168 | : die "targets data structure ". ref($targets). " not understood"; |
169 | }; |
170 | |
171 | my $replace_from_hash_or_object = sub { |
172 | my ($datum, $value) = @_; |
173 | return ref($datum) eq 'HASH' ? |
174 | $datum->{$value} : $datum->$value; |
175 | }; |
176 | |
177 | my $fill = sub { |
178 | my ($zoom, $targets, @rest) = @_; |
179 | $zoom->repeat_content(sub { |
180 | my $itr = $next_item_from_proto->(@rest); |
181 | $targets = $normalize_targets->($targets); |
182 | HTML::Zoom::CodeStream->new({ |
183 | code => sub { |
184 | my $cnt = 0; |
185 | if(my $datum = $itr->($zoom, $cnt)) { |
186 | $cnt++; |
187 | return sub { |
188 | for my $idx(0..$#{$targets}) { |
189 | my $target = $targets->[$idx]; |
190 | my ($match, $replace) = do { |
191 | my $type = ref $target; |
192 | $type ? ($type eq 'HASH' ? do { |
193 | my ($match, $value) = %$target; |
194 | ref $value eq 'CODE' ? |
195 | ($match, $value->($datum, $idx, $_)) |
196 | : ($match, $replace_from_hash_or_object->($datum, $value)); |
197 | } : die "What?") |
198 | : do { |
199 | ref($datum) eq 'ARRAY' ? |
200 | ($target, $datum->[$idx]) : |
201 | ( '.'.$target, $replace_from_hash_or_object->($datum, $target)); |
202 | }; |
203 | }; |
204 | $_ = $_->select($match) |
205 | ->replace_content($replace); |
206 | } $_; |
207 | }; |
208 | } else { |
209 | return; |
210 | } |
211 | }, |
212 | }); |
213 | }); |
214 | }; |
870c22a9 |
215 | |
216 | ## fill a selection from an array of targets, using a list. Syntax is a bit |
217 | ## like DBIC->populate |
5802b80a |
218 | |
219 | { |
220 | ok my $z = HTML::Zoom |
221 | ->from_html(q[<ul><li class="even">Even</li><li class="odd">Odd</li></ul>]) |
222 | ->select('ul') |
223 | ->$fill( |
224 | ['.even','.odd'], |
225 | [0,1], |
226 | [2,3], |
227 | ), 'Made Zoom object from array'; |
228 | |
229 | is( |
230 | $z->to_html, |
231 | '<ul><li class="even">0</li><li class="odd">1</li><li class="even">2</li><li class="odd">3</li></ul>', |
232 | 'Got correct from repeat_content' |
233 | ); |
234 | } |
870c22a9 |
235 | |
236 | ## Same as above but using an iterator coderef instead of a pregenerated list |
5802b80a |
237 | { |
238 | my @items = ( [0,1], [2,3] ); |
239 | ok my $z = HTML::Zoom |
240 | ->from_html(q[<ul><li class="even">Even</li><li class="odd">Odd</li></ul>]) |
241 | ->select('ul') |
242 | ->$fill( |
243 | ['.even','.odd'], |
244 | sub { shift @items } |
245 | ), 'Made Zoom object from itr'; |
246 | |
247 | for my $cnt (1..2) { |
248 | is( |
249 | $z->to_html, |
250 | '<ul><li class="even">0</li><li class="odd">1</li><li class="even">2</li><li class="odd">3</li></ul>', |
251 | 'Got correct from repeat_content: '.$cnt |
252 | ); |
253 | ok( |
254 | (@items = ( [0,1], [2,3] )), |
255 | 'Reset the items array' |
256 | ); |
257 | } |
258 | } |
870c22a9 |
259 | |
260 | ## Target has a value of a coderef that is expected to take the item from the |
261 | ## list of data and provide the actual value that goes into the target |
5802b80a |
262 | |
263 | { |
264 | ok my $z = HTML::Zoom |
265 | ->from_html(q[<ul><li class="even">Even</li><li class="odd">Odd</li></ul>]) |
266 | ->select('ul') |
267 | ->$fill( |
268 | [ |
269 | {'.even' => sub { my ($i,$cnt,$z) = @_; return $i->[0]; } }, |
270 | {'.odd' => sub { my ($i,$cnt,$z) = @_; return $i->[1]; } }, |
271 | ], |
272 | [0,1], |
273 | [2,3], |
274 | ), 'Made Zoom object from code style'; |
275 | |
276 | for my $cnt (1..2) { |
277 | is( |
278 | $z->to_html, |
279 | '<ul><li class="even">0</li><li class="odd">1</li><li class="even">2</li><li class="odd">3</li></ul>', |
280 | 'Got correct from repeat_content: '.$cnt |
281 | ); |
282 | } |
283 | } |
870c22a9 |
284 | |
285 | ## Like above but showing how this can be used to get a value from any type of |
286 | ## list item. |
5802b80a |
287 | |
288 | { |
289 | ok my $z = HTML::Zoom |
290 | ->from_html(q[<ul><li class="even">Even</li><li class="odd">Odd</li></ul>]) |
291 | ->select('ul') |
292 | ->$fill( |
293 | [ |
294 | {'.even' => sub { my ($i,$cnt,$z) = @_; return $i->{even}; } }, |
295 | {'.odd' => sub { my ($i,$cnt,$z) = @_; return $i->{odd}; } }, |
296 | ], |
297 | { even => 0, odd => 1}, |
298 | { even => 2, odd => 3}, |
299 | ), 'Made Zoom object from code style'; |
300 | |
301 | for my $cnt (1..2) { |
302 | is( |
303 | $z->to_html, |
304 | '<ul><li class="even">0</li><li class="odd">1</li><li class="even">2</li><li class="odd">3</li></ul>', |
305 | 'Got correct from repeat_content: '.$cnt |
306 | ); |
307 | } |
308 | } |
309 | |
310 | { |
311 | ok my $even_or_odd = sub { |
312 | my ($i,$cnt,$z) = @_; |
313 | return $cnt % 2 ? $i->{odd} : $i->{even}; |
314 | }; |
315 | |
316 | ok my $z = HTML::Zoom |
317 | ->from_html(q[<ul><li class="even">Even</li><li class="odd">Odd</li></ul>]) |
318 | ->select('ul') |
319 | ->$fill( |
320 | [ |
321 | {'.even' => $even_or_odd }, |
322 | {'.odd' => $even_or_odd }, |
323 | ], |
324 | { even => 0, odd => 1}, |
325 | { even => 2, odd => 3}, |
326 | ), 'Made Zoom object from code style'; |
327 | |
328 | for my $cnt (1..2) { |
329 | is( |
330 | $z->to_html, |
331 | '<ul><li class="even">0</li><li class="odd">1</li><li class="even">2</li><li class="odd">3</li></ul>', |
332 | 'Got correct from repeat_content: '.$cnt |
333 | ); |
334 | } |
335 | } |
870c22a9 |
336 | |
337 | ## Just an example of above showing off how you can use Perl to generate the |
338 | ## needed structures. |
5802b80a |
339 | |
340 | { |
341 | ok my $even_or_odd = sub { |
342 | my ($i,$cnt,$z) = @_; |
343 | return $cnt % 2 ? $i->{odd} : $i->{even}; |
344 | }; |
345 | |
346 | ok my $z = HTML::Zoom |
347 | ->from_html(q[<ul><li class="even">Even</li><li class="odd">Odd</li></ul>]) |
348 | ->select('ul') |
349 | ->$fill( |
350 | [ |
351 | map { +{$_ => $even_or_odd} } qw(.even .odd), |
352 | ], |
353 | { even => 0, odd => 1}, |
354 | { even => 2, odd => 3}, |
355 | ), 'Made Zoom object from code style'; |
356 | |
357 | for my $cnt (1..2) { |
358 | is( |
359 | $z->to_html, |
360 | '<ul><li class="even">0</li><li class="odd">1</li><li class="even">2</li><li class="odd">3</li></ul>', |
361 | 'Got correct from repeat_content: '.$cnt |
362 | ); |
363 | } |
364 | } |
870c22a9 |
365 | |
366 | ## using a list of objects, we get replace values. |
5802b80a |
367 | |
368 | { |
369 | { |
370 | package Test::HTML::Zoom::EvenOdd; |
371 | |
372 | sub new { |
373 | my $class = shift; |
374 | bless { _e=>$_[0], _o=>$_[1] }, $class; |
375 | } |
376 | |
377 | sub even { shift->{_e} } |
378 | sub odd { shift->{_o} } |
379 | } |
380 | |
381 | ok my $even_or_odd = sub { |
382 | my ($i,$cnt,$z) = @_; |
383 | return $cnt % 2 ? $i->odd : $i->even; |
384 | }; |
385 | |
386 | ok my $z = HTML::Zoom |
387 | ->from_html(q[<ul><li class="even">Even</li><li class="odd">Odd</li></ul>]) |
388 | ->select('ul') |
389 | ->$fill( |
390 | [ |
391 | map { +{$_ => $even_or_odd} } qw(.even .odd), |
392 | ], |
393 | Test::HTML::Zoom::EvenOdd->new(0,1), |
394 | Test::HTML::Zoom::EvenOdd->new(2,3), |
395 | ), 'Made Zoom object from code style'; |
396 | |
397 | for my $cnt (1..2) { |
398 | is( |
399 | $z->to_html, |
400 | '<ul><li class="even">0</li><li class="odd">1</li><li class="even">2</li><li class="odd">3</li></ul>', |
401 | 'Got correct from repeat_content: '.$cnt |
402 | ); |
403 | } |
404 | } |
405 | |
870c22a9 |
406 | ## Looks a lot like how pure.js uses 'directives' to map a selector to a value |
407 | ## from each item in the data list. |
408 | |
5802b80a |
409 | { |
410 | ok my $z = HTML::Zoom |
411 | ->from_html(q[<ul><li class="even">Even</li><li class="odd">Odd</li></ul>]) |
412 | ->select('ul') |
413 | ->$fill( |
414 | [ |
415 | { '.even' => 'even'}, |
416 | { '.odd' => 'odd'}, |
417 | ], |
418 | { even => 0, odd => 1}, |
419 | { even => 2, odd => 3}, |
420 | ), 'Made Zoom object from declare accessors style'; |
421 | |
422 | for my $cnt (1..2) { |
423 | is( |
424 | $z->to_html, |
425 | '<ul><li class="even">0</li><li class="odd">1</li><li class="even">2</li><li class="odd">3</li></ul>', |
426 | 'Got correct from repeat_content: '.$cnt |
427 | ); |
428 | } |
429 | } |
430 | |
431 | { |
432 | ok my $z = HTML::Zoom |
433 | ->from_html(q[<ul><li class="even">Even</li><li class="odd">Odd</li></ul>]) |
434 | ->select('ul') |
435 | ->$fill( |
436 | [ |
437 | { '.even' => 'even'}, |
438 | { '.odd' => 'odd'}, |
439 | ], |
440 | Test::HTML::Zoom::EvenOdd->new(0,1), |
441 | Test::HTML::Zoom::EvenOdd->new(2,3), |
442 | ), 'Made Zoom object from declare accessors style'; |
443 | |
444 | for my $cnt (1..2) { |
445 | is( |
446 | $z->to_html, |
447 | '<ul><li class="even">0</li><li class="odd">1</li><li class="even">2</li><li class="odd">3</li></ul>', |
448 | 'Got correct from repeat_content: '.$cnt |
449 | ); |
450 | } |
451 | } |
452 | |
870c22a9 |
453 | ## Ideally the targets are an array so that you properly control the order that |
454 | ## the replaces happen, but if you don't care you can take advantage of the |
455 | ## shorthand target structure of a hashref |
456 | |
5802b80a |
457 | { |
458 | ok my $z = HTML::Zoom |
459 | ->from_html(q[<ul><li class="even">Even</li><li class="odd">Odd</li></ul>]) |
460 | ->select('ul') |
461 | ->$fill( |
462 | { |
463 | '.even' => 'even', |
464 | '.odd' => 'odd' |
465 | }, |
466 | Test::HTML::Zoom::EvenOdd->new(0,1), |
467 | Test::HTML::Zoom::EvenOdd->new(2,3), |
468 | ), 'Made Zoom object from declare accessors style'; |
469 | |
470 | for my $cnt (1..2) { |
471 | is( |
472 | $z->to_html, |
473 | '<ul><li class="even">0</li><li class="odd">1</li><li class="even">2</li><li class="odd">3</li></ul>', |
474 | 'Got correct from repeat_content: '.$cnt |
475 | ); |
476 | } |
477 | } |
478 | |
479 | ## Helper for when you have a list of object or hashs and you just want to map |
480 | ## target classes to accessors |
481 | |
482 | { |
483 | ok my $z = HTML::Zoom |
484 | ->from_html(q[<ul><li class="even">Even</li><li class="odd">Odd</li></ul>]) |
485 | ->select('ul') |
486 | ->$fill( |
487 | { map { ".".$_ => $_ } qw(even odd) }, |
488 | Test::HTML::Zoom::EvenOdd->new(0,1), |
489 | Test::HTML::Zoom::EvenOdd->new(2,3), |
490 | ), 'Made Zoom object from declare accessors style'; |
491 | |
492 | for my $cnt (1..2) { |
493 | is( |
494 | $z->to_html, |
495 | '<ul><li class="even">0</li><li class="odd">1</li><li class="even">2</li><li class="odd">3</li></ul>', |
496 | 'Got correct from repeat_content: '.$cnt |
497 | ); |
498 | } |
499 | } |
500 | |
501 | ## if the target is arrayref but the data is a list of objects or hashes, we |
502 | ## guess the target is a class form of the target items, but use the value of |
503 | ## the item as the hash or object accessor |
504 | |
505 | { |
506 | ok my $z = HTML::Zoom |
507 | ->from_html(q[<ul><li class="even">Even</li><li class="odd">Odd</li></ul>]) |
508 | ->select('ul') |
509 | ->$fill( |
510 | [qw(even odd)], |
511 | Test::HTML::Zoom::EvenOdd->new(0,1), |
512 | Test::HTML::Zoom::EvenOdd->new(2,3), |
513 | ), 'Made Zoom object from declare accessors style'; |
514 | |
515 | for my $cnt (1..2) { |
516 | is( |
517 | $z->to_html, |
518 | '<ul><li class="even">0</li><li class="odd">1</li><li class="even">2</li><li class="odd">3</li></ul>', |
519 | 'Got correct from repeat_content: '.$cnt |
520 | ); |
521 | } |
522 | } |
870c22a9 |
523 | |
524 | ## Using above shortcut with iterator instead of fixed list |
525 | |
526 | { |
527 | my @list = ( |
528 | Test::HTML::Zoom::EvenOdd->new(0,1), |
529 | Test::HTML::Zoom::EvenOdd->new(2,3), |
530 | ); |
531 | |
532 | ok my $z = HTML::Zoom |
533 | ->from_html(q[<ul><li class="even">Even</li><li class="odd">Odd</li></ul>]) |
534 | ->select('ul') |
535 | ->$fill( |
536 | [qw(even odd)], |
537 | sub { shift @list }, |
538 | ), 'Made Zoom object from declare accessors style'; |
539 | |
540 | is( |
541 | $z->to_html, |
542 | '<ul><li class="even">0</li><li class="odd">1</li><li class="even">2</li><li class="odd">3</li></ul>', |
543 | 'Got correct from repeat_content', |
544 | ); |
545 | } |
5802b80a |
546 | } |
547 | |
b9c27a5a |
548 | done_testing; |