File Coverage

blib/lib/Bible/OBML.pm
Criterion Covered Total %
statement 223 223 100.0
branch 52 58 89.6
condition 21 26 80.7
subroutine 21 21 100.0
pod 3 3 100.0
total 320 331 96.6


line stmt bran cond sub pod time code
1             package Bible::OBML;
2             # ABSTRACT: Open Bible Markup Language parser and renderer
3              
4 2     2   396968 use 5.022;
  2         7  
5              
6 2     2   1095 use exact;
  2         91871  
  2         13  
7 2     2   8748 use exact::class;
  2         30268  
  2         7  
8 2     2   1906 use Mojo::DOM;
  2         591880  
  2         294  
9 2     2   20 use Mojo::Util 'html_unescape';
  2         4  
  2         138  
10 2     2   1172 use Text::Wrap 'wrap';
  2         7256  
  2         253  
11 2     2   1399 use Bible::Reference;
  2         42867  
  2         21  
12              
13             $Text::Wrap::unexpand = 0;
14              
15             our $VERSION = '2.10'; # VERSION
16              
17             has _load => sub { {} };
18             has indent_width => 4;
19             has reference_acronym => 0;
20             has fnxref_acronym => 1;
21             has wrap_at => 80;
22             has reference => sub {
23             Bible::Reference->new(
24             bible => 'Protestant',
25             sorting => 1,
26             require_chapter_match => 1,
27             require_book_ucfirst => 1,
28             );
29             };
30              
31 209     209   13157 sub __ocd_tree ($node) {
  209         270  
  209         281  
32 209         305 my $new_node;
33              
34 209 100       434 if ( 'tag' eq shift @$node ) {
35 91         201 $new_node->{tag} = shift @$node;
36              
37 91         150 my $attr = shift @$node;
38 91 100       261 $new_node->{attr} = $attr if (%$attr);
39              
40 91         139 shift @$node;
41              
42 91         173 my $children = [ grep { defined } map { __ocd_tree($_) } @$node ];
  205         389  
  205         336  
43 91 100       245 $new_node->{children} = $children if (@$children);
44             }
45             else {
46 118 100       360 $new_node->{text} = $node->[0] if ( $node->[0] ne "\n\n" );
47             }
48              
49 209         422 return $new_node;
50             }
51              
52 189     189   217 sub __html_tree ($node) {
  189         218  
  189         203  
53 189 100       298 if ( $node->{tag} ) {
54 91 100       141 if ( $node->{children} ) {
55             my $attr = ( $node->{attr} )
56 80 100       135 ? ' ' . join( ' ', map { $_ . '="' . $node->{attr}{$_} . '"' } keys %{ $node->{attr} } )
  15         54  
  15         37  
57             : '';
58              
59             return join( '',
60             '<', $node->{tag}, $attr, '>',
61             (
62             ( $node->{children} )
63 185         269 ? ( map { __html_tree($_) } @{ $node->{children} } )
  80         126  
64             : ()
65             ),
66 80 50       150 '{tag}, '>',
67             );
68             }
69             else {
70 11         28 return '<' . $node->{tag} . '>';
71             }
72             }
73             else {
74 98         445 return $node->{text};
75             }
76             }
77              
78 8     8   86 sub __cleanup_html ($html) {
  8         17  
  8         11  
79             # spacing cleanup
80 8         278 $html =~ s/\s+/ /g;
81 8         907 $html =~ s/(?:^\s+|\s+$)//mg;
82 8         25 $html =~ s/^[ ]+//mg;
83              
84             # protect against inadvertent OBML
85 8         21 $html =~ s/~/-/g;
86 8         16 $html =~ s/`/'/g;
87 8         14 $html =~ s/\|//g;
88 8         17 $html =~ s/\\/ /g;
89 8         16 $html =~ s/\*//g;
90 8         17 $html =~ s/\{/(/g;
91 8         16 $html =~ s/\}/)/g;
92 8         23 $html =~ s/\[/(/g;
93 8         17 $html =~ s/\]/)/g;
94              
95 8         65 $html =~ s|

|\n\n

|g;

96 8         42 $html =~ s||\n\n|g;
97 8         52 $html =~ s|
|\n\n
|g;
98 8         66 $html =~ s|
\s*|
\n|g;
99 8         30 $html =~ s|[ ]+

|

|g;
100 8         30 $html =~ s|[ ]+||;
101              
102             # trim spaces at line ends
103 8         172 $html =~ s/[ ]+$//mg;
104              
105 8         61 return $html;
106             }
107              
108 4     4   44 sub __clean_html_to_data ($clean_html) {
  4         10  
  4         4  
109 4         54 return __ocd_tree( Mojo::DOM->new($clean_html)->at('obml')->tree );
110             }
111              
112 4     4   41 sub __data_to_clean_html ($data) {
  4         8  
  4         8  
113 4         15 return __cleanup_html( __html_tree($data) );
114             }
115              
116 3     3   37 sub _clean_html_to_obml ( $self, $html ) {
  3         6  
  3         6  
  3         8  
117 3         26 my $dom = Mojo::DOM->new($html);
118              
119             # append a trailing
inside any

with a
for later wrapping reasons

120 3     3   7682 $dom->find('p')->grep( sub { $_->find('br')->size } )->each( sub { $_->append_content('
') } );
  12         6725  
  3         646  
121              
122 3         643 my $obml = html_unescape( $dom->to_string );
123              
124             # de-XML
125 3         3262 $obml =~ s|||g;
126 3         32 $obml =~ s|

|

|g;
127 3         42 $obml =~ s|||g;
128 3         62 $obml =~ s||\*|g;
129 3         27 $obml =~ s||\^|g;
130 3         23 $obml =~ s||\\|g;
131 3         19 $obml =~ s|\s*|~ |g;
132 3         29 $obml =~ s|\s*| ~|g;
133 3         57 $obml =~ s!\s*!|!g;
134 3         140 $obml =~ s!\s*!| !g;
135 3         20 $obml =~ s|\s*|== |g;
136 3         110 $obml =~ s|\s*| ==|g;
137 3         25 $obml =~ s|
\s*|= |g;
138 3         99 $obml =~ s|\s*| =|g;
139 3         18 $obml =~ s|\s*|\{|g;
140 3         40 $obml =~ s|\s*|\}|g;
141 3         16 $obml =~ s|\s*|\[|g;
142 3         50 $obml =~ s|\s*|\]|g;
143 3         20 $obml =~ s|^| ' ' x ( $self->indent_width * $1 ) |mge;
  12         133  
144 3         30 $obml =~ s|||g;
145 3         20 $obml =~ s|||g;
146              
147 3 50       13 if ( $self->wrap_at ) {
148             # wrap lines that don't end in
149             $obml = join( "\n", map {
150 3 100       48 unless ( s|
|| ) {
  48         4089  
151 36         84 s/^(\s+)//;
152 36   50     142 my $header = $1 || '';
153 36         107 $Text::Wrap::columns = $self->wrap_at - length($header);
154 36         388 wrap( $header, $header, $_ );
155             }
156             else {
157 12         40 $_;
158             }
159             } split( /\n/, $obml ) ) . "\n";
160             }
161 3         384 $obml =~ s|
||g;
162 3         89 $obml =~ s|[ ]+$||mg;
163 3         12 $obml =~ s/\n{3,}/\n\n/g;
164 3         16 $obml =~ s/^[ ]([^ ])/$1/mg;
165              
166 3         9 chomp $obml;
167 3         140 return $obml;
168             }
169              
170 4     4   43 sub _obml_to_clean_html ( $self, $obml ) {
  4         8  
  4         8  
  4         6  
171             # spacing cleanup
172 4         106 $obml =~ s/\r?\n/\n/g;
173 4         14 $obml =~ s/\t/ /g;
174 4         42 $obml =~ s/\n[ \t]+\n/\n\n/mg;
175 4         12 $obml =~ s/^\n+//g;
176 4         14 $obml =~ /^(\s+)/;
177 4 50       19 $obml =~ s/^$1//mg if ($1);
178 4         82 $obml =~ s/\s+$//g;
179              
180             # remove comments
181 4         77 $obml =~ s/^\s*#.*?(?>\r?\n)//msg;
182              
183             # "unwrap" wrapped lines
184 4         10 my @obml;
185 4         31 for my $line ( split( /\n/, $obml ) ) {
186 58 100 100     207 if ( not @obml or not length $line or not length $obml[-1] ) {
187 44         112 push( @obml, $line );
188             }
189             else {
190 14         53 my ($last_line_indent) = $obml[-1] =~ /^([ ]*)/;
191 14         39 my ($this_line_indent) = $line =~ /^([ ]*)/;
192              
193 14 100 66     63 if ( length $last_line_indent == 0 and length $this_line_indent == 0 ) {
194 3         9 $line =~ s/^[ ]+//;
195 3         13 $obml[-1] .= ' ' . $line;
196             }
197             else {
198 11         22 push( @obml, $line );
199             }
200             }
201             }
202 4         36 $obml = join( "\n", @obml );
203              
204 4         83 $obml =~ s|~+[ ]*([^~]+?)[ ]*~+|$1|g;
205 4         41 $obml =~ s|={2,}[ ]*([^=]+?)[ ]*={2,}|$1|g;
206 4         35 $obml =~ s|=[ ]*([^=]+?)[ ]*=|
$1
|g;
207              
208 4         23 $obml =~ s|^([ ]+)(\S.*)$|
209 15         286 ' 210             . int( ( length($1) + $self->indent_width * 0.5 ) / $self->indent_width )
211             . '">'
212             . $2
213             . ''
214             |mge;
215              
216 4         364 $obml =~ s|(\S)(?=\n\S)|$1
|g;
217              
218 4         480 $obml =~ s`(?:^|(?<=\n\n))(?!<(?:reference|sub_header|header)\b)`

`g;

219 4         399 $obml =~ s`(?:$|(?=\n\n))`

`g;
220 4         65 $obml =~ s`(?<=)

``g;
221 4         31 $obml =~ s`(?<=)

``g;
222 4         25 $obml =~ s`(?<=)

``g;
223              
224 4         69 $obml =~ s!\|(\d+)\|\s*!$1!g;
225              
226 4         31 $obml =~ s|\*([^\*]+)\*|$1|g;
227 4         32 $obml =~ s|\^([^\^]+)\^|$1|g;
228 4         25 $obml =~ s|\\([^\\]+)\\|$1|g;
229              
230 4         15 $obml =~ s|\{||g;
231 4         17 $obml =~ s|\}||g;
232              
233 4         15 $obml =~ s|\[||g;
234 4         16 $obml =~ s|\]||g;
235              
236 4         48 return "$obml";
237             }
238              
239 22     22   48 sub _accessor ( $self, $input = undef ) {
  22         41  
  22         44  
  22         31  
240 22         397 my $want = ( split( '::', ( caller(1) )[3] ) )[-1];
241              
242 22 100       133 if ($input) {
243 11 100       40 if ( ref $input ) {
244 3         6 my $data_refs_ocd;
245 171     171   263 $data_refs_ocd = sub ($node) {
  171         266  
  171         237  
246 171 100 100     782 if (
      100        
      100        
247             $node->{tag} and $node->{children} and
248             ( $node->{tag} eq 'crossref' or $node->{tag} eq 'footnote' )
249             ) {
250 6         13 for ( grep { $_->{text} } @{ $node->{children} } ) {
  9         49  
  6         19  
251             $_->{text} = $self->reference->acronyms(
252             $self->fnxref_acronym
253 6         47 )->clear->in( $_->{text} )->as_text;
254             }
255             }
256 171 100       106301 if ( $node->{children} ) {
257 72         138 $data_refs_ocd->($_) for ( @{ $node->{children} } );
  72         242  
258             }
259 171         397 return;
260 3         28 };
261 3         14 $data_refs_ocd->($input);
262              
263 3         9 my $reference = ( grep { $_->{tag} eq 'reference' } @{ $input->{children} } )[0]{children}[0];
  21         64  
  3         15  
264             my $runs = $self->reference->acronyms(
265             $self->reference_acronym
266 3         19 )->clear->in( $reference->{text} )->as_runs;
267              
268 3         5096 $reference->{text} = $runs->[0];
269             }
270             else {
271 17     17   245 my $ref_ocd = sub ( $text, $acronyms ) {
  17         64  
  17         30  
  17         25  
272 17         69 return $self->reference->acronyms($acronyms)->clear->in($text)->as_text;
273 8         39 };
274              
275 8         133 $input =~ s!
276             ((?:<(?:footnote|crossref)>|\{|\[)\s*.+?\s*(?:|\}|\]))
277             !
278 9         94279 $ref_ocd->( $1, $self->fnxref_acronym )
279             !gex;
280              
281 8         528826 $input =~ s!
282             ((?:|~)\s*.+?\s*(?:|~))
283             !
284 8         52 $ref_ocd->( $1, $self->reference_acronym )
285             !gex;
286             }
287              
288 11         17619 return $self->_load({ $want => $input });
289             }
290              
291 11 100 100     56 return $self->_load->{data} if ( $want eq 'data' and $self->_load->{data} );
292              
293 10 50       66 unless ( $self->_load->{canonical}{$want} ) {
294 10 100       137 if ( $self->_load->{html} ) {
    100          
    50          
295 4   33     37 $self->_load->{clean_html} //= __cleanup_html( $self->_load->{html} );
296              
297 4 100 66     27 if ( $want eq 'obml' ) {
    50          
298 1         4 $self->_load->{canonical}{obml} = $self->_clean_html_to_obml( $self->_load->{clean_html} );
299             }
300             elsif ( $want eq 'data' or $want eq 'html' ) {
301 3         10 $self->_load->{data} = __clean_html_to_data( $self->_load->{clean_html} );
302              
303             $self->_load->{canonical}{html} = __data_to_clean_html( $self->_load->{data} )
304 3 100       119 if ( $want eq 'html' );
305             }
306             }
307             elsif ( $self->_load->{data} ) {
308 2         40 $self->_load->{canonical}{html} = __data_to_clean_html( $self->_load->{data} );
309              
310             $self->_load->{canonical}{obml} = $self->_clean_html_to_obml( $self->_load->{canonical}{html} )
311 2 100       47 if ( $want eq 'obml' );
312             }
313             elsif ( $self->_load->{obml} ) {
314 4         125 $self->_load->{canonical}{html} = $self->_obml_to_clean_html( $self->_load->{obml} );
315              
316 4 100       74 if ( $want eq 'obml' ) {
    100          
317             $self->_load->{canonical}{obml} = $self->_clean_html_to_obml(
318             $self->_load->{canonical}{html}
319 1         5 );
320             }
321             elsif ( $want eq 'data' ) {
322 1         5 $self->_load->{data} = __clean_html_to_data( $self->_load->{canonical}{html} );
323             }
324             }
325             }
326              
327 10 100       175 return ( $want eq 'data' ) ? $self->_load->{$want} : $self->_load->{canonical}{$want};
328             }
329              
330 6     6 1 57729 sub data { shift->_accessor(@_) }
331 9     9 1 1879 sub html { shift->_accessor(@_) }
332 7     7 1 275049 sub obml { shift->_accessor(@_) }
333              
334             1;
335              
336             __END__