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   368696 use 5.022;
  2         8  
5              
6 2     2   1120 use exact;
  2         84891  
  2         8  
7 2     2   5924 use exact::class;
  2         26867  
  2         6  
8 2     2   1987 use Mojo::DOM;
  2         574576  
  2         288  
9 2     2   27 use Mojo::Util 'html_unescape';
  2         4  
  2         180  
10 2     2   1350 use Text::Wrap 'wrap';
  2         7981  
  2         225  
11 2     2   1626 use Bible::Reference;
  2         52969  
  2         27  
12              
13             $Text::Wrap::unexpand = 0;
14              
15             our $VERSION = '2.11'; # 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   15829 sub __ocd_tree ($node) {
  209         250  
  209         297  
32 209         283 my $new_node;
33              
34 209 100       385 if ( 'tag' eq shift @$node ) {
35 91         197 $new_node->{tag} = shift @$node;
36              
37 91         137 my $attr = shift @$node;
38 91 100       229 $new_node->{attr} = $attr if (%$attr);
39              
40 91         159 shift @$node;
41              
42 91         167 my $children = [ grep { defined } map { __ocd_tree($_) } @$node ];
  205         375  
  205         324  
43 91 100       261 $new_node->{children} = $children if (@$children);
44             }
45             else {
46 118 100       349 $new_node->{text} = $node->[0] if ( $node->[0] ne "\n\n" );
47             }
48              
49 209         423 return $new_node;
50             }
51              
52 189     189   222 sub __html_tree ($node) {
  189         234  
  189         231  
53 189 100       315 if ( $node->{tag} ) {
54 91 100       199 if ( $node->{children} ) {
55             my $attr = ( $node->{attr} )
56 80 100       149 ? ' ' . join( ' ', map { $_ . '="' . $node->{attr}{$_} . '"' } keys %{ $node->{attr} } )
  15         49  
  15         38  
57             : '';
58              
59             return join( '',
60             '<', $node->{tag}, $attr, '>',
61             (
62             ( $node->{children} )
63 185         299 ? ( map { __html_tree($_) } @{ $node->{children} } )
  80         131  
64             : ()
65             ),
66 80 50       161 '{tag}, '>',
67             );
68             }
69             else {
70 11         31 return '<' . $node->{tag} . '>';
71             }
72             }
73             else {
74 98         468 return $node->{text};
75             }
76             }
77              
78 8     8   100 sub __cleanup_html ($html) {
  8         19  
  8         13  
79             # spacing cleanup
80 8         351 $html =~ s/\s+/ /g;
81 8         1166 $html =~ s/(?:^\s+|\s+$)//mg;
82 8         30 $html =~ s/^[ ]+//mg;
83              
84             # protect against inadvertent OBML
85 8         23 $html =~ s/~/-/g;
86 8         25 $html =~ s/`/'/g;
87 8         21 $html =~ s/\|//g;
88 8         55 $html =~ s/\\/ /g;
89 8         22 $html =~ s/\*//g;
90 8         19 $html =~ s/\{/(/g;
91 8         19 $html =~ s/\}/)/g;
92 8         20 $html =~ s/\[/(/g;
93 8         20 $html =~ s/\]/)/g;
94              
95 8         77 $html =~ s|

|\n\n

|g;

96 8         51 $html =~ s||\n\n|g;
97 8         48 $html =~ s|
|\n\n
|g;
98 8         95 $html =~ s|
\s*|
\n|g;
99 8         35 $html =~ s|[ ]+

|

|g;
100 8         52 $html =~ s|[ ]+||;
101              
102             # trim spaces at line ends
103 8         221 $html =~ s/[ ]+$//mg;
104              
105 8         60 return $html;
106             }
107              
108 4     4   50 sub __clean_html_to_data ($clean_html) {
  4         7  
  4         7  
109 4         44 return __ocd_tree( Mojo::DOM->new($clean_html)->at('obml')->tree );
110             }
111              
112 4     4   43 sub __data_to_clean_html ($data) {
  4         9  
  4         6  
113 4         17 return __cleanup_html( __html_tree($data) );
114             }
115              
116 3     3   46 sub _clean_html_to_obml ( $self, $html ) {
  3         6  
  3         9  
  3         5  
117 3         34 my $dom = Mojo::DOM->new($html);
118              
119             # append a trailing
inside any

with a
for later wrapping reasons

120 3     12   11642 $dom->find('p')->grep( sub { $_->find('br')->size } )->each( sub { $_->append_content('
') } );
  12         7631  
  3         829  
121              
122 3         838 my $obml = html_unescape( $dom->to_string );
123              
124             # de-XML
125 3         4051 $obml =~ s|||g;
126 3         35 $obml =~ s|

|

|g;
127 3         75 $obml =~ s|||g;
128 3         45 $obml =~ s||\*|g;
129 3         34 $obml =~ s||\^|g;
130 3         27 $obml =~ s||\\|g;
131 3         22 $obml =~ s|\s*|~ |g;
132 3         52 $obml =~ s|\s*| ~|g;
133 3         40 $obml =~ s!\s*!|!g;
134 3         156 $obml =~ s!\s*!| !g;
135 3         24 $obml =~ s|\s*|== |g;
136 3         134 $obml =~ s|\s*| ==|g;
137 3         23 $obml =~ s|
\s*|= |g;
138 3         161 $obml =~ s|\s*| =|g;
139 3         25 $obml =~ s|\s*|\{|g;
140 3         49 $obml =~ s|\s*|\}|g;
141 3         18 $obml =~ s|\s*|\[|g;
142 3         70 $obml =~ s|\s*|\]|g;
143 3         55 $obml =~ s|^| ' ' x ( $self->indent_width * $1 ) |mge;
  12         211  
144 3         59 $obml =~ s|||g;
145 3         23 $obml =~ s|||g;
146              
147 3 50       22 if ( $self->wrap_at ) {
148             # wrap lines that don't end in
149             $obml = join( "\n", map {
150 3 100       67 unless ( s|
|| ) {
  48         4898  
151 36         91 s/^(\s+)//;
152 36   50     192 my $header = $1 || '';
153 36         133 $Text::Wrap::columns = $self->wrap_at - length($header);
154 36         472 wrap( $header, $header, $_ );
155             }
156             else {
157 12         25 $_;
158             }
159             } split( /\n/, $obml ) ) . "\n";
160             }
161 3         460 $obml =~ s|
||g;
162 3         102 $obml =~ s|[ ]+$||mg;
163 3         13 $obml =~ s/\n{3,}/\n\n/g;
164 3         18 $obml =~ s/^[ ]([^ ])/$1/mg;
165              
166 3         12 chomp $obml;
167 3         204 return $obml;
168             }
169              
170 4     4   47 sub _obml_to_clean_html ( $self, $obml ) {
  4         8  
  4         10  
  4         7  
171             # spacing cleanup
172 4         109 $obml =~ s/\r?\n/\n/g;
173 4         12 $obml =~ s/\t/ /g;
174 4         36 $obml =~ s/\n[ \t]+\n/\n\n/mg;
175 4         10 $obml =~ s/^\n+//g;
176 4         13 $obml =~ /^(\s+)/;
177 4 50       16 $obml =~ s/^$1//mg if ($1);
178 4         72 $obml =~ s/\s+$//g;
179              
180             # remove comments
181 4         70 $obml =~ s/^\s*#.*?(?>\r?\n)//msg;
182              
183             # "unwrap" wrapped lines
184 4         9 my @obml;
185 4         31 for my $line ( split( /\n/, $obml ) ) {
186 58 100 100     208 if ( not @obml or not length $line or not length $obml[-1] ) {
187 44         81 push( @obml, $line );
188             }
189             else {
190 14         47 my ($last_line_indent) = $obml[-1] =~ /^([ ]*)/;
191 14         43 my ($this_line_indent) = $line =~ /^([ ]*)/;
192              
193 14 100 66     42 if ( length $last_line_indent == 0 and length $this_line_indent == 0 ) {
194 3         8 $line =~ s/^[ ]+//;
195 3         12 $obml[-1] .= ' ' . $line;
196             }
197             else {
198 11         25 push( @obml, $line );
199             }
200             }
201             }
202 4         27 $obml = join( "\n", @obml );
203              
204 4         64 $obml =~ s|~+[ ]*([^~]+?)[ ]*~+|$1|g;
205 4         36 $obml =~ s|={2,}[ ]*([^=]+?)[ ]*={2,}|$1|g;
206 4         33 $obml =~ s|=[ ]*([^=]+?)[ ]*=|
$1
|g;
207              
208 4         23 $obml =~ s|^([ ]+)(\S.*)$|
209 15         276 ' 210             . int( ( length($1) + $self->indent_width * 0.5 ) / $self->indent_width )
211             . '">'
212             . $2
213             . ''
214             |mge;
215              
216 4         461 $obml =~ s|(\S)(?=\n\S)|$1
|g;
217              
218 4         442 $obml =~ s`(?:^|(?<=\n\n))(?!<(?:reference|sub_header|header)\b)`

`g;

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

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

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

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

``g;
223              
224 4         67 $obml =~ s!\|(\d+)\|\s*!$1!g;
225              
226 4         35 $obml =~ s|\*([^\*]+)\*|$1|g;
227 4         31 $obml =~ s|\^([^\^]+)\^|$1|g;
228 4         22 $obml =~ s|\\([^\\]+)\\|$1|g;
229              
230 4         17 $obml =~ s|\{||g;
231 4         18 $obml =~ s|\}||g;
232              
233 4         17 $obml =~ s|\[||g;
234 4         16 $obml =~ s|\]||g;
235              
236 4         41 return "$obml";
237             }
238              
239 22     22   62 sub _accessor ( $self, $input = undef ) {
  22         44  
  22         84  
  22         45  
240 22         455 my $want = ( split( '::', ( caller(1) )[3] ) )[-1];
241              
242 22 100       141 if ($input) {
243 11 100       45 if ( ref $input ) {
244 3         7 my $data_refs_ocd;
245 171     171   239 $data_refs_ocd = sub ($node) {
  171         270  
  171         238  
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         15 for ( grep { $_->{text} } @{ $node->{children} } ) {
  9         35  
  6         21  
251             $_->{text} = $self->reference->acronyms(
252             $self->fnxref_acronym
253 6         46 )->clear->in( $_->{text} )->as_text;
254             }
255             }
256 171 100       109207 if ( $node->{children} ) {
257 72         111 $data_refs_ocd->($_) for ( @{ $node->{children} } );
  72         258  
258             }
259 171         363 return;
260 3         27 };
261 3         13 $data_refs_ocd->($input);
262              
263 3         8 my $reference = ( grep { $_->{tag} eq 'reference' } @{ $input->{children} } )[0]{children}[0];
  21         60  
  3         14  
264             my $runs = $self->reference->acronyms(
265             $self->reference_acronym
266 3         19 )->clear->in( $reference->{text} )->as_runs;
267              
268 3         5572 $reference->{text} = $runs->[0];
269             }
270             else {
271 17     17   268 my $ref_ocd = sub ( $text, $acronyms ) {
  17         82  
  17         76  
  17         34  
272 17         78 return $self->reference->acronyms($acronyms)->clear->in($text)->as_text;
273 8         58 };
274              
275 8         175 $input =~ s!
276             ((?:<(?:footnote|crossref)>|\{|\[)\s*.+?\s*(?:|\}|\]))
277             !
278 9         96228 $ref_ocd->( $1, $self->fnxref_acronym )
279             !gex;
280              
281 8         604152 $input =~ s!
282             ((?:|~)\s*.+?\s*(?:|~))
283             !
284 8         55 $ref_ocd->( $1, $self->reference_acronym )
285             !gex;
286             }
287              
288 11         21874 return $self->_load({ $want => $input });
289             }
290              
291 11 100 100     64 return $self->_load->{data} if ( $want eq 'data' and $self->_load->{data} );
292              
293 10 50       65 unless ( $self->_load->{canonical}{$want} ) {
294 10 100       147 if ( $self->_load->{html} ) {
    100          
    50          
295 4   33     47 $self->_load->{clean_html} //= __cleanup_html( $self->_load->{html} );
296              
297 4 100 66     31 if ( $want eq 'obml' ) {
    50          
298 1         8 $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         11 $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       109 if ( $want eq 'html' );
305             }
306             }
307             elsif ( $self->_load->{data} ) {
308 2         49 $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       45 if ( $want eq 'obml' );
312             }
313             elsif ( $self->_load->{obml} ) {
314 4         120 $self->_load->{canonical}{html} = $self->_obml_to_clean_html( $self->_load->{obml} );
315              
316 4 100       76 if ( $want eq 'obml' ) {
    100          
317             $self->_load->{canonical}{obml} = $self->_clean_html_to_obml(
318             $self->_load->{canonical}{html}
319 1         6 );
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       226 return ( $want eq 'data' ) ? $self->_load->{$want} : $self->_load->{canonical}{$want};
328             }
329              
330 6     6 1 63754 sub data { shift->_accessor(@_) }
331 9     9 1 2165 sub html { shift->_accessor(@_) }
332 7     7 1 294508 sub obml { shift->_accessor(@_) }
333              
334             1;
335              
336             __END__