File Coverage

blib/lib/Bible/OBML.pm
Criterion Covered Total %
statement 218 218 100.0
branch 52 58 89.6
condition 21 26 80.7
subroutine 21 21 100.0
pod 3 3 100.0
total 315 326 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 1     1   229356 use 5.020;
  1         8  
5              
6 1     1   448 use exact;
  1         36188  
  1         3  
7 1     1   3001 use exact::class;
  1         11525  
  1         13  
8 1     1   878 use Mojo::DOM;
  1         171259  
  1         42  
9 1     1   8 use Mojo::Util 'html_unescape';
  1         3  
  1         50  
10 1     1   470 use Text::Wrap 'wrap';
  1         2621  
  1         63  
11 1     1   591 use Bible::Reference;
  1         14407  
  1         11  
12              
13             our $VERSION = '2.05'; # VERSION
14              
15             has _load => {};
16             has indent_width => 4;
17             has reference_acronym => 0;
18             has fnxref_acronym => 1;
19             has wrap_at => 80;
20             has reference => Bible::Reference->new(
21             bible => 'Protestant',
22             sorting => 1,
23             );
24              
25 209     209   12185 sub __ocd_tree ($node) {
  209         258  
  209         246  
26 209         296 my $new_node;
27              
28 209 100       357 if ( 'tag' eq shift @$node ) {
29 91         181 $new_node->{tag} = shift @$node;
30              
31 91         153 my $attr = shift @$node;
32 91 100       183 $new_node->{attr} = $attr if (%$attr);
33              
34 91         116 shift @$node;
35              
36 91         162 my $children = [ grep { defined } map { __ocd_tree($_) } @$node ];
  205         367  
  205         313  
37 91 100       226 $new_node->{children} = $children if (@$children);
38             }
39             else {
40 118 100       341 $new_node->{text} = $node->[0] if ( $node->[0] ne "\n\n" );
41             }
42              
43 209         407 return $new_node;
44             }
45              
46 189     189   219 sub __html_tree ($node) {
  189         229  
  189         211  
47 189 100       305 if ( $node->{tag} ) {
48 91 100       142 if ( $node->{children} ) {
49             my $attr = ( $node->{attr} )
50 80 100       135 ? ' ' . join( ' ', map { $_ . '="' . $node->{attr}{$_} . '"' } keys %{ $node->{attr} } )
  15         44  
  15         37  
51             : '';
52              
53             return join( '',
54             '<', $node->{tag}, $attr, '>',
55             (
56             ( $node->{children} )
57 185         291 ? ( map { __html_tree($_) } @{ $node->{children} } )
  80         128  
58             : ()
59             ),
60 80 50       150 '{tag}, '>',
61             );
62             }
63             else {
64 11         35 return '<' . $node->{tag} . '>';
65             }
66             }
67             else {
68 98         361 return $node->{text};
69             }
70             }
71              
72 8     8   94 sub __cleanup_html ($html) {
  8         18  
  8         10  
73             # spacing cleanup
74 8         248 $html =~ s/\s+/ /g;
75 8         677 $html =~ s/(?:^\s+|\s+$)//mg;
76 8         24 $html =~ s/^[ ]+//mg;
77              
78             # protect against inadvertent OBML
79 8         15 $html =~ s/~/-/g;
80 8         18 $html =~ s/`/'/g;
81 8         15 $html =~ s/\|//g;
82 8         16 $html =~ s/\\/ /g;
83 8         16 $html =~ s/\*//g;
84 8         14 $html =~ s/\{/(/g;
85 8         14 $html =~ s/\}/)/g;
86 8         13 $html =~ s/\[/(/g;
87 8         14 $html =~ s/\]/)/g;
88              
89 8         67 $html =~ s|

|\n\n

|g;

90 8         39 $html =~ s||\n\n|g;
91 8         35 $html =~ s|
|\n\n
|g;
92 8         65 $html =~ s|
\s*|
\n|g;
93 8         30 $html =~ s|[ ]+

|

|g;
94 8         25 $html =~ s|[ ]+||;
95              
96             # trim spaces at line ends
97 8         101 $html =~ s/[ ]+$//mg;
98              
99 8         46 return $html;
100             }
101              
102 4     4   59 sub __clean_html_to_data ($clean_html) {
  4         8  
  4         8  
103 4         23 return __ocd_tree( Mojo::DOM->new($clean_html)->at('obml')->tree );
104             }
105              
106 4     4   47 sub __data_to_clean_html ($data) {
  4         7  
  4         7  
107 4         10 return __cleanup_html( __html_tree($data) );
108             }
109              
110 3     3   39 sub _clean_html_to_obml ( $self, $html ) {
  3         6  
  3         5  
  3         25  
111 3         17 my $dom = Mojo::DOM->new($html);
112              
113             # append a trailing
inside any

with a
for later wrapping reasons

114 3     3   9171 $dom->find('p')->grep( sub { $_->find('br')->size } )->each( sub { $_->append_content('
') } );
  12         6246  
  3         504  
115              
116 3         517 my $obml = html_unescape( $dom->to_string );
117              
118             # de-XML
119 3         3396 $obml =~ s|||g;
120             # $obml =~ s|

|

|g;
121 3         36 $obml =~ s|||g;
122 3         36 $obml =~ s||\*|g;
123 3         28 $obml =~ s||\^|g;
124 3         26 $obml =~ s||\\|g;
125 3         17 $obml =~ s|\s*|~ |g;
126 3         18 $obml =~ s|\s*| ~|g;
127 3         36 $obml =~ s!\s*!|!g;
128 3         71 $obml =~ s!\s*!| !g;
129 3         18 $obml =~ s|\s*|== |g;
130 3         50 $obml =~ s|\s*| ==|g;
131 3         15 $obml =~ s|
\s*|= |g;
132 3         53 $obml =~ s|\s*| =|g;
133 3         15 $obml =~ s|\s*|\{|g;
134 3         22 $obml =~ s|\s*|\}|g;
135 3         16 $obml =~ s|\s*|\[|g;
136 3         30 $obml =~ s|\s*|\]|g;
137 3         19 $obml =~ s|^| ' ' x ( $self->indent_width * $1 ) |mge;
  12         158  
138 3         44 $obml =~ s|||g;
139 3         17 $obml =~ s|||g;
140              
141 3 50       14 if ( $self->wrap_at ) {
142             # wrap lines that don't end in
143             $obml = join( "\n", map {
144 3 100       52 unless ( s|
|| ) {
  48         4145  
145 36         63 s/^(\s+)//;
146 36   50     87 $Text::Wrap::columns = $self->wrap_at - length( $1 || '' );
147 36         469 wrap( $1, $1, $_ );
148             }
149             else {
150 12         22 $_;
151             }
152             } split( /\n/, $obml ) ) . "\n";
153             }
154 3         468 $obml =~ s|
||g;
155             # $obml =~ s|[ ]+$||mg;
156              
157 3         9 chomp $obml;
158 3         87 return $obml;
159             }
160              
161 4     4   52 sub _obml_to_clean_html ( $self, $obml ) {
  4         9  
  4         16  
  4         7  
162             # spacing cleanup
163 4         88 $obml =~ s/\r?\n/\n/g;
164 4         17 $obml =~ s/\t/ /g;
165 4         24 $obml =~ s/\n[ \t]+\n/\n\n/mg;
166 4         12 $obml =~ s/^\n+//g;
167 4         12 $obml =~ /^(\s+)/;
168 4 50       17 $obml =~ s/^$1//mg if ($1);
169 4         55 $obml =~ s/\s+$//g;
170              
171             # remove comments
172 4         45 $obml =~ s/^\s*#.*?(?>\r?\n)//msg;
173              
174             # "unwrap" wrapped lines
175 4         7 my @obml;
176 4         26 for my $line ( split( /\n/, $obml ) ) {
177 58 100 100     229 if ( not @obml or not length $line or not length $obml[-1] ) {
178 44         90 push( @obml, $line );
179             }
180             else {
181 14         52 my ($last_line_indent) = $obml[-1] =~ /^([ ]*)/;
182 14         43 my ($this_line_indent) = $line =~ /^([ ]*)/;
183              
184 14 100 66     51 if ( length $last_line_indent == 0 and length $this_line_indent == 0 ) {
185 3         12 $line =~ s/^[ ]+//;
186 3         11 $obml[-1] .= ' ' . $line;
187             }
188             else {
189 11         26 push( @obml, $line );
190             }
191             }
192             }
193 4         20 $obml = join( "\n", @obml );
194              
195 4         47 $obml =~ s|~+[ ]*([^~]+?)[ ]*~+|$1|g;
196 4         33 $obml =~ s|={2,}[ ]*([^=]+?)[ ]*={2,}|$1|g;
197 4         25 $obml =~ s|=[ ]*([^=]+?)[ ]*=|
$1
|g;
198              
199 4         27 $obml =~ s|^([ ]+)(\S.*)$|
200 15         275 ' 201             . int( ( length($1) + $self->indent_width * 0.5 ) / $self->indent_width )
202             . '">'
203             . $2
204             . ''
205             |mge;
206              
207 4         278 $obml =~ s|(\S)(?=\n\S)|$1
|g;
208              
209 4         334 $obml =~ s`(?:^|(?<=\n\n))(?!<(?:reference|sub_header|header)\b)`

`g;

210 4         330 $obml =~ s`(?:$|(?=\n\n))`

`g;
211 4         33 $obml =~ s`(?<=)

``g;
212 4         30 $obml =~ s`(?<=)

``g;
213 4         21 $obml =~ s`(?<=)

``g;
214              
215 4         50 $obml =~ s!\|(\d+)\|\s*!$1!g;
216              
217 4         34 $obml =~ s|\*([^\*]+)\*|$1|g;
218 4         31 $obml =~ s|\^([^\^]+)\^|$1|g;
219 4         25 $obml =~ s|\\([^\\]+)\\|$1|g;
220              
221 4         16 $obml =~ s|\{||g;
222 4         15 $obml =~ s|\}||g;
223              
224 4         18 $obml =~ s|\[||g;
225 4         14 $obml =~ s|\]||g;
226              
227 4         31 return "$obml";
228             }
229              
230 22     22   40 sub _accessor ( $self, $input = undef ) {
  22         40  
  22         36  
  22         31  
231 22         245 my $want = ( split( '::', ( caller(1) )[3] ) )[-1];
232              
233 22 100       78 if ($input) {
234 11 100       35 if ( ref $input ) {
235 3         5 my $data_refs_ocd;
236 171     171   201 $data_refs_ocd = sub ($node) {
  171         218  
  171         220  
237 171 100 100     615 if (
      100        
      100        
238             $node->{tag} and $node->{children} and
239             ( $node->{tag} eq 'crossref' or $node->{tag} eq 'footnote' )
240             ) {
241 6         11 for ( grep { $_->{text} } @{ $node->{children} } ) {
  9         23  
  6         13  
242             $_->{text} = $self->reference->acronyms(
243             $self->fnxref_acronym
244 6         21 )->clear->in( $_->{text} )->as_text;
245             }
246             }
247 171 100       56915 if ( $node->{children} ) {
248 72         93 $data_refs_ocd->($_) for ( @{ $node->{children} } );
  72         159  
249             }
250 171         278 return;
251 3         17 };
252 3         9 $data_refs_ocd->($input);
253              
254 3         12 my $reference = ( grep { $_->{tag} eq 'reference' } @{ $input->{children} } )[0]{children}[0];
  21         79  
  3         11  
255             my $runs = $self->reference->acronyms(
256             $self->reference_acronym
257 3         14 )->clear->in( $reference->{text} )->as_runs;
258              
259 3         3557 $reference->{text} = $runs->[0];
260             }
261             else {
262 17     17   22 my $ref_ocd = sub ( $text, $acronyms ) {
  17         187  
  17         46  
  17         30  
263 17         47 return $self->reference->acronyms($acronyms)->clear->in($text)->as_text;
264 8         61 };
265              
266 8         133 $input =~ s!
267             ((?:<(?:footnote|crossref)>|\{|\[)\s*.+?\s*(?:|\}|\]))
268             !
269 9         58654 $ref_ocd->( $1, $self->fnxref_acronym )
270             !gex;
271              
272 8         13707 $input =~ s!
273             ((?:|~)\s*.+?\s*(?:|~))
274             !
275 8         34 $ref_ocd->( $1, $self->reference_acronym )
276             !gex;
277             }
278              
279 11         13450 return $self->_load({ $want => $input });
280             }
281              
282 11 100 100     47 return $self->_load->{data} if ( $want eq 'data' and $self->_load->{data} );
283              
284 10 50       52 unless ( $self->_load->{canonical}{$want} ) {
285 10 100       126 if ( $self->_load->{html} ) {
    100          
    50          
286 4   33     47 $self->_load->{clean_html} //= __cleanup_html( $self->_load->{html} );
287              
288 4 100 66     30 if ( $want eq 'obml' ) {
    50          
289 1         44 $self->_load->{canonical}{obml} = $self->_clean_html_to_obml( $self->_load->{clean_html} );
290             }
291             elsif ( $want eq 'data' or $want eq 'html' ) {
292 3         14 $self->_load->{data} = __clean_html_to_data( $self->_load->{clean_html} );
293              
294             $self->_load->{canonical}{html} = __data_to_clean_html( $self->_load->{data} )
295 3 100       86 if ( $want eq 'html' );
296             }
297             }
298             elsif ( $self->_load->{data} ) {
299 2         44 $self->_load->{canonical}{html} = __data_to_clean_html( $self->_load->{data} );
300              
301             $self->_load->{canonical}{obml} = $self->_clean_html_to_obml( $self->_load->{canonical}{html} )
302 2 100       40 if ( $want eq 'obml' );
303             }
304             elsif ( $self->_load->{obml} ) {
305 4         134 $self->_load->{canonical}{html} = $self->_obml_to_clean_html( $self->_load->{obml} );
306              
307 4 100       69 if ( $want eq 'obml' ) {
    100          
308             $self->_load->{canonical}{obml} = $self->_clean_html_to_obml(
309             $self->_load->{canonical}{html}
310 1         5 );
311             }
312             elsif ( $want eq 'data' ) {
313 1         3 $self->_load->{data} = __clean_html_to_data( $self->_load->{canonical}{html} );
314             }
315             }
316             }
317              
318 10 100       121 return ( $want eq 'data' ) ? $self->_load->{$want} : $self->_load->{canonical}{$want};
319             }
320              
321 6     6 1 48488 sub data { shift->_accessor(@_) }
322 9     9 1 1735 sub html { shift->_accessor(@_) }
323 7     7 1 11822 sub obml { shift->_accessor(@_) }
324              
325             1;
326              
327             __END__