File Coverage

blib/lib/XML/Grammar/Screenplay/FromProto/Parser/QnD.pm
Criterion Covered Total %
statement 27 176 15.3
branch 0 46 0.0
condition 0 33 0.0
subroutine 9 44 20.4
pod n/a
total 36 299 12.0


line stmt bran cond sub pod time code
1             package XML::Grammar::Screenplay::FromProto::Parser::QnD;
2              
3 1     1   22064 use strict;
  1         2  
  1         33  
4 1     1   6 use warnings;
  1         1  
  1         28  
5              
6 1     1   790 use MooX 'late';
  1         21060  
  1         9  
7              
8             extends( 'XML::Grammar::FictionBase::FromProto::Parser::XmlIterator' );
9              
10 1     1   42066 use XML::Grammar::Fiction::Struct::Tag;
  1         3  
  1         49  
11 1     1   715 use XML::Grammar::FictionBase::Event;
  1         3  
  1         29  
12              
13 1     1   6 use List::Util ();
  1         2  
  1         15  
14 1     1   5 use List::MoreUtils ();
  1         2  
  1         2888  
15              
16             our $VERSION = '0.14.10';
17              
18             has "_in_saying" => (isa => "Bool", is => "rw");
19             has "_prev_line_is_empty" => (isa => "Bool", is => "rw", default => 1);
20             has '_is_start' => (isa => 'Bool', is => 'rw');
21              
22             before 'next_line_ref' => sub {
23             my $self = shift;
24              
25             $self->_prev_line_is_empty(scalar(${$self->curr_line_ref()} =~ m{\A\s*\z}));
26              
27             return;
28             };
29              
30             sub _top_is_para
31             {
32 0     0   0 my $self = shift;
33              
34 0   0     0 return $self->_in_para() && ($self->_top_is('p'));
35             }
36              
37              
38             sub _top_is_saying
39             {
40 0     0   0 my $self = shift;
41              
42 0   0     0 return $self->_in_saying() && ($self->_top_is('saying'));
43             }
44              
45             sub _top_is
46             {
47 0     0   0 my ($self, $want_name) = @_;
48              
49 0         0 return ($self->_top_tag->name eq $want_name);
50             }
51              
52             sub _top_is_desc
53             {
54 0     0   0 my $self = shift;
55              
56 0         0 return $self->_top_is('desc');
57             }
58              
59             around '_pop_tag' => sub {
60             my ($orig, $self) = @_;
61              
62             my $open = $self->$orig();
63              
64             if ($open->name() eq "saying")
65             {
66             $self->_in_saying(0);
67             }
68              
69             return $open;
70             };
71              
72             sub _count_tags_in_stack
73             {
74 0     0   0 my $self = shift;
75 0         0 my $name = shift;
76              
77 0     0   0 my @tags = $self->_grep_tags_stack(sub { $_->name() eq $name; });
  0         0  
78              
79 0         0 return scalar(@tags);
80             }
81              
82             after '_push_tag' => sub {
83             my $self = shift;
84              
85             # This is an assert - it must never happen.
86             if ($self->_count_tags_in_stack("p") == 2)
87             {
88             Carp::confess (qq{Two paragraphs in the tags stack.});
89             }
90              
91             # This is an assert - it must never happen.
92             if ($self->_count_tags_in_stack("saying") == 2)
93             {
94             Carp::confess (qq{Two sayings in the tags stack at the same time.});
95             }
96              
97             return;
98             };
99              
100             sub _new_saying
101             {
102 0     0   0 my $self = shift;
103 0         0 my $sayer = shift;
104 0         0 my $contents = shift;
105              
106 0         0 return $self->_new_node(
107             {
108             t => "Saying",
109             character => $sayer,
110             children => $self->_new_list($contents),
111             }
112             );
113             }
114              
115             sub _get_desc_name
116             {
117 0     0   0 my $self = shift;
118              
119 0 0       0 return ($self->_in_para() ? "innerdesc" : "desc");
120             }
121              
122             sub _create_closing_desc_tag {
123 0     0   0 my $self = shift;
124              
125 0         0 return XML::Grammar::Fiction::Struct::Tag->new(
126             name => $self->_get_desc_name(),
127             line => $self->line_num(),
128             );
129             }
130              
131             sub _detect_closing_desc_tag
132             {
133 0     0   0 my $self = shift;
134              
135 0         0 return (${ $self->curr_line_ref() } =~ m{\G\]}cg);
  0         0  
136             }
137              
138             around '_parse_closing_tag' => sub {
139             my ($orig, $self) = @_;
140              
141             return
142             $self->_detect_closing_desc_tag
143             ? $self->_create_closing_desc_tag
144             : $self->$orig();
145             };
146              
147             sub _detect_open_desc_tag
148             {
149 1     1   3 my $self = shift;
150              
151 1         3 return (${ $self->curr_line_ref } =~ m{\G\[}cg);
  1         5  
152             }
153              
154             sub _create_open_desc_tag
155             {
156 0     0   0 my ($self) = @_;
157              
158 0         0 my $not_inline = 0;
159 0 0 0     0 if ($self->_is_start && $self->_prev_line_is_empty())
160             {
161 0         0 $self->_close_top_tags();
162 0         0 $not_inline = 1;
163             }
164              
165 0 0       0 return XML::Grammar::Fiction::Struct::Tag->new(
166             name => $not_inline ? "desc" : $self->_get_desc_name(),
167             line => $self->line_num(),
168             attrs => [],
169             );
170             }
171              
172             sub _set_is_start
173             {
174 1     1   2 my $self = shift;
175              
176 1         17 $self->_is_start($self->at_line_start);
177              
178 1         947 return;
179             }
180              
181             around '_parse_opening_tag' => sub {
182             my ($orig, $self) = @_;
183              
184             $self->_set_is_start;
185              
186             return
187             $self->_detect_open_desc_tag
188             ? $self->_create_open_desc_tag
189             : $self->$orig();
190             };
191              
192             sub _parse_speech_unit
193             {
194 0     0     my $self = shift;
195              
196 0 0         if (${$self->curr_line_ref()} !~ /\G([^:\n]+): /cgms)
  0            
197             {
198 0           Carp::confess("Cannot match addressing at line " . $self->line_num());
199             }
200              
201 0           my $sayer = $1;
202              
203 0 0         if ($sayer =~ m{[\[\]]})
204             {
205 0           Carp::confess("Tried to put an inner-desc inside an addressing at line " . $self->line_num());
206             }
207              
208             # All pluses
209 0 0         if ($sayer =~ m{\A\++\z})
210             {
211 0           return { elem => $self->_new_para([]), para_end => 0 };
212             }
213             else
214             {
215 0           return { elem => $self->_new_saying($sayer, []), sayer => $sayer, para_end => 0};
216             }
217             }
218              
219             sub _non_tag_text_unit_consume_regex
220             {
221 0     0     return qr{(?:[\<\[\]]|^\n?$)}ms;
222             }
223              
224             sub _is_there_a_speech_unit
225             {
226 0     0     my $self = shift;
227              
228 0           my $l = $self->curr_line_ref();
229              
230             return
231             (
232 0   0       $self->at_line_start()
233             && (! $self->_top_is_desc())
234             && ($$l =~ m{\A[^\[<][^:]*:})
235             );
236             }
237              
238             around '_parse_non_tag_text_unit' => sub {
239             my ($orig, $self) = @_;
240              
241             return
242             (
243             $self->_is_there_a_speech_unit()
244             ? $self->_parse_speech_unit()
245             : $self->$orig()
246             );
247             };
248              
249             sub _look_for_tag_opener
250             {
251 0     0     my $self = shift;
252              
253 0           my $l = $self->curr_line_ref();
254              
255 0 0         if ($$l =~ m{\G([<\[\]])})
256             {
257 0           return $1;
258             }
259             else
260             {
261 0           return;
262             }
263             }
264              
265              
266             sub _is_closing_tag {
267 0     0     my $self = shift;
268 0           my $tag_start = shift;
269              
270 0           return (${$self->curr_line_ref()} =~ m{\G(
  0            
271             }
272              
273             sub _generate_non_tag_text_event
274             {
275 0     0     my $self = shift;
276 0           my $status = $self->_parse_non_tag_text_unit();
277              
278 0 0         if (!defined($status))
279             {
280 0           return;
281             }
282              
283 0           my $elem = $status->{'elem'};
284 0           my $is_para_end = $status->{'para_end'};
285 0           my $is_saying = $elem->isa("XML::Grammar::Fiction::FromProto::Node::Saying");
286 0           my $is_para = $elem->isa("XML::Grammar::Fiction::FromProto::Node::Paragraph");
287              
288 0           my $in_para = $self->_in_para();
289 0           my $was_already_enqueued = 0;
290              
291 0 0 0       if ( ($is_saying || $is_para) && $in_para)
      0        
292             {
293 0           $self->_enqueue_event(
294             XML::Grammar::FictionBase::Event->new(
295             {type => "close", tag => "para"}
296             )
297             );
298 0           $in_para = 0;
299             }
300              
301 0 0 0       if ( $is_saying && $self->_in_saying())
302             {
303 0           $self->_enqueue_event(
304             XML::Grammar::FictionBase::Event->new(
305             {type => "close", tag => "saying"}
306             )
307             );
308             }
309              
310 0 0 0       if ($is_saying)
    0          
311             {
312 0           $self->_enqueue_event(
313             XML::Grammar::FictionBase::Event->new(
314             {type => "open", tag => "saying", tag_elem => $elem, },
315             ),
316             );
317 0           $was_already_enqueued = 1;
318              
319 0           $self->_enqueue_event(
320             XML::Grammar::FictionBase::Event->new(
321             {type => "open", tag => "para"}
322             )
323             );
324 0           $in_para = 1;
325             }
326             elsif ($is_para && !$in_para)
327             {
328 0           $self->_enqueue_event(
329             XML::Grammar::FictionBase::Event->new(
330             {type => "open", tag => "para"}
331             ),
332             );
333 0           $in_para = 1;
334             }
335              
336 0 0 0       if ($elem->isa("XML::Grammar::Fiction::FromProto::Node::Text") &&
337             !$was_already_enqueued)
338             {
339 0 0         if (!$in_para)
340             {
341 0           $self->_enqueue_event(
342             XML::Grammar::FictionBase::Event->new(
343             {type => "open", tag => "para"},
344             )
345             );
346 0           $in_para = 1;
347             }
348             $self->_enqueue_event(
349 0           XML::Grammar::FictionBase::Event->new(
350             {type => "elem", elem => $elem, }
351             )
352             );
353 0           $was_already_enqueued = 1;
354             }
355              
356 0           return;
357             }
358              
359             sub _handle_close_saying
360             {
361 0     0     my $self = shift;
362 0           my $open = $self->_pop_tag();
363              
364             # This is an assert.
365 0 0         if ($open->name() ne "saying")
366             {
367 0           Carp::confess (qq{Not a saying tag.});
368             }
369              
370             my $new_elem =
371             $self->_new_saying(
372             (List::Util::first
373 0     0     { $_->{key} eq "character"}
374 0           @{$open->attrs()}
  0            
375             )->{value},
376             $open->detach_children(),
377             );
378              
379 0           $self->_add_to_top_tag($new_elem);
380              
381 0           return;
382             }
383              
384             sub _assert_top_is_para
385             {
386 0     0     my ($self, $open) = @_;
387              
388 0 0         if ($open->name() ne "p")
389             {
390 0           Carp::confess (qq{Not a para tag.});
391             }
392              
393 0           return;
394             }
395              
396             sub _process_closed_para
397             {
398 0     0     my ($self, $open) = @_;
399              
400 0           my $children = $open->detach_children();
401              
402             # Filter away empty paragraphs.
403 0 0 0       if (defined($children) && @$children)
404             {
405 0           $self->_add_to_top_tag(
406             $self->_new_para(
407             $children
408             )
409             );
410             }
411              
412 0           return;
413             }
414              
415             sub _close_para
416             {
417 0     0     my $self = shift;
418              
419 0           my $open = $self->_pop_tag();
420              
421 0           $self->_assert_top_is_para($open);
422              
423 0           $self->_process_closed_para($open);
424              
425 0           $self->_in_para(0);
426              
427 0           return;
428             }
429              
430             sub _create_start_para
431             {
432 0     0     my $self = shift;
433              
434             return
435 0           XML::Grammar::Fiction::Struct::Tag::Para->new(
436             name => "p",
437             is_standalone => 0,
438             line => $self->line_num(),
439             attrs => [],
440             children => [],
441             );
442             }
443              
444             sub _start_para
445             {
446 0     0     my $self = shift;
447              
448 0           $self->_push_tag($self->_create_start_para());
449              
450 0           $self->_in_para(1);
451              
452 0           return;
453             }
454              
455             sub _close_top_tags
456             {
457 0     0     my $self = shift;
458              
459 0 0         if ($self->_top_is_para())
460             {
461 0           $self->_close_para();
462             }
463              
464 0 0         if ($self->_top_is_saying())
465             {
466 0           $self->_handle_close_saying();
467             }
468              
469 0           return;
470             }
471              
472             sub _handle_close_para
473             {
474 0     0     my ($self, $event) = @_;
475              
476 0           return $self->_close_para();
477             }
478              
479             sub _handle_open_para
480             {
481 0     0     my ($self, $event) = @_;
482              
483 0           return $self->_start_para();
484             }
485              
486             sub _create_open_saying_tag
487             {
488 0     0     my $self = shift;
489 0           my $event = shift;
490              
491             return
492 0           XML::Grammar::Fiction::Struct::Tag->new(
493             {
494             name => "saying",
495             is_standalone => 0,
496             # TODO : propagate the correct line_num
497             # from the called-to layers.
498             line => $self->line_num(),
499             attrs => [{key => "character", value => $event->tag_elem->character()}],
500             children => [],
501             }
502             );
503             }
504              
505             sub _handle_open_saying
506             {
507 0     0     my ($self, $event) = @_;
508              
509 0           $self->_push_tag($self->_create_open_saying_tag($event));
510              
511 0           $self->_in_saying(1);
512              
513 0           return;
514             }
515              
516             sub _handle_saying_event
517             {
518 0     0     my ($self, $event) = @_;
519              
520             return
521 0 0         $event->is_open()
522             ? $self->_handle_open_saying($event)
523             : $self->_handle_close_saying();
524             }
525              
526             sub _list_valid_tag_events
527             {
528 0     0     return [qw(para saying)];
529             }
530              
531             after '_handle_open_tag' => sub {
532             my $self = shift;
533              
534             if ($self->_top_is_desc)
535             {
536             $self->_start_para();
537             }
538              
539             return;
540             };
541              
542             before '_handle_close_tag' => sub {
543             my $self = shift;
544              
545             $self->_close_top_tags();
546             };
547              
548             sub _look_ahead_for_tag
549             {
550 0     0     my $self = shift;
551              
552 0           my $l = $self->curr_line_copy();
553              
554 0           my $is_tag_cond = ($$l =~ m{\G([<\[\]])});
555              
556 0   0       my $is_close = $is_tag_cond && ($$l =~ m{\G(?:
557              
558 0           return ($is_tag_cond, $is_close);
559             }
560              
561             sub _main_loop_iter_on_empty_line
562             {
563 0     0     my $self = shift;
564              
565 0 0         if ($self->_top_is_para())
566             {
567 0           $self->_close_para();
568             }
569              
570 0           $self->next_line_ref();
571              
572 0           return;
573             }
574              
575             sub _main_loop_iter_on_whitepsace
576             {
577 0     0     my $self = shift;
578              
579 0           $self->_add_to_top_tag( $self->_new_text([" "]) );
580              
581 0           $self->next_line_ref();
582              
583 0           return;
584             }
585              
586             sub _main_loop_iter_body_prelude
587             {
588 0     0     my $self = shift;
589              
590 0           my $l = $self->curr_line_ref();
591              
592             return
593             (
594 0 0         ($$l eq "\n")
    0          
595             ? $self->_main_loop_iter_on_empty_line
596             : ($$l =~ m{\G[ \t]+\n?\z})
597             ? $self->_main_loop_iter_on_whitepsace
598             : 1
599             );
600             }
601              
602              
603              
604             1;
605              
606             __END__