File Coverage

blib/lib/Bible/OBML.pm
Criterion Covered Total %
statement 220 220 100.0
branch 52 58 89.6
condition 21 26 80.7
subroutine 21 21 100.0
pod 3 3 100.0
total 317 328 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   225052 use 5.020;
  1         10  
5              
6 1     1   450 use exact;
  1         36219  
  1         4  
7 1     1   2916 use exact::class;
  1         11466  
  1         11  
8 1     1   1149 use Mojo::DOM;
  1         175980  
  1         43  
9 1     1   11 use Mojo::Util 'html_unescape';
  1         3  
  1         52  
10 1     1   526 use Text::Wrap 'wrap';
  1         2748  
  1         60  
11 1     1   635 use Bible::Reference;
  1         15334  
  1         7  
12              
13             our $VERSION = '2.06'; # 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             require_chapter_match => 1,
24             require_book_ucfirst => 1,
25             );
26              
27 209     209   11327 sub __ocd_tree ($node) {
  209         245  
  209         232  
28 209         234 my $new_node;
29              
30 209 100       330 if ( 'tag' eq shift @$node ) {
31 91         166 $new_node->{tag} = shift @$node;
32              
33 91         122 my $attr = shift @$node;
34 91 100       173 $new_node->{attr} = $attr if (%$attr);
35              
36 91         134 shift @$node;
37              
38 91         140 my $children = [ grep { defined } map { __ocd_tree($_) } @$node ];
  205         342  
  205         302  
39 91 100       199 $new_node->{children} = $children if (@$children);
40             }
41             else {
42 118 100       277 $new_node->{text} = $node->[0] if ( $node->[0] ne "\n\n" );
43             }
44              
45 209         364 return $new_node;
46             }
47              
48 189     189   203 sub __html_tree ($node) {
  189         232  
  189         203  
49 189 100       285 if ( $node->{tag} ) {
50 91 100       132 if ( $node->{children} ) {
51             my $attr = ( $node->{attr} )
52 80 100       135 ? ' ' . join( ' ', map { $_ . '="' . $node->{attr}{$_} . '"' } keys %{ $node->{attr} } )
  15         43  
  15         37  
53             : '';
54              
55             return join( '',
56             '<', $node->{tag}, $attr, '>',
57             (
58             ( $node->{children} )
59 185         279 ? ( map { __html_tree($_) } @{ $node->{children} } )
  80         124  
60             : ()
61             ),
62 80 50       131 '{tag}, '>',
63             );
64             }
65             else {
66 11         28 return '<' . $node->{tag} . '>';
67             }
68             }
69             else {
70 98         345 return $node->{text};
71             }
72             }
73              
74 8     8   91 sub __cleanup_html ($html) {
  8         16  
  8         13  
75             # spacing cleanup
76 8         241 $html =~ s/\s+/ /g;
77 8         703 $html =~ s/(?:^\s+|\s+$)//mg;
78 8         22 $html =~ s/^[ ]+//mg;
79              
80             # protect against inadvertent OBML
81 8         18 $html =~ s/~/-/g;
82 8         16 $html =~ s/`/'/g;
83 8         16 $html =~ s/\|//g;
84 8         15 $html =~ s/\\/ /g;
85 8         12 $html =~ s/\*//g;
86 8         18 $html =~ s/\{/(/g;
87 8         13 $html =~ s/\}/)/g;
88 8         14 $html =~ s/\[/(/g;
89 8         15 $html =~ s/\]/)/g;
90              
91 8         57 $html =~ s|

|\n\n

|g;

92 8         46 $html =~ s||\n\n|g;
93 8         39 $html =~ s|
|\n\n
|g;
94 8         57 $html =~ s|
\s*|
\n|g;
95 8         29 $html =~ s|[ ]+

|

|g;
96 8         24 $html =~ s|[ ]+||;
97              
98             # trim spaces at line ends
99 8         93 $html =~ s/[ ]+$//mg;
100              
101 8         53 return $html;
102             }
103              
104 4     4   44 sub __clean_html_to_data ($clean_html) {
  4         8  
  4         7  
105 4         24 return __ocd_tree( Mojo::DOM->new($clean_html)->at('obml')->tree );
106             }
107              
108 4     4   49 sub __data_to_clean_html ($data) {
  4         7  
  4         7  
109 4         12 return __cleanup_html( __html_tree($data) );
110             }
111              
112 3     3   40 sub _clean_html_to_obml ( $self, $html ) {
  3         6  
  3         7  
  3         22  
113 3         19 my $dom = Mojo::DOM->new($html);
114              
115             # append a trailing
inside any

with a
for later wrapping reasons

116 3     12   8635 $dom->find('p')->grep( sub { $_->find('br')->size } )->each( sub { $_->append_content('
') } );
  12         6046  
  3         481  
117              
118 3         501 my $obml = html_unescape( $dom->to_string );
119              
120             # de-XML
121 3         3203 $obml =~ s|||g;
122 3         23 $obml =~ s|

|

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

`g;

212 4         333 $obml =~ s`(?:$|(?=\n\n))`

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

``g;
214 4         34 $obml =~ s`(?<=)

``g;
215 4         22 $obml =~ s`(?<=)

``g;
216              
217 4         55 $obml =~ s!\|(\d+)\|\s*!$1!g;
218              
219 4         34 $obml =~ s|\*([^\*]+)\*|$1|g;
220 4         31 $obml =~ s|\^([^\^]+)\^|$1|g;
221 4         31 $obml =~ s|\\([^\\]+)\\|$1|g;
222              
223 4         13 $obml =~ s|\{||g;
224 4         16 $obml =~ s|\}||g;
225              
226 4         22 $obml =~ s|\[||g;
227 4         14 $obml =~ s|\]||g;
228              
229 4         31 return "$obml";
230             }
231              
232 22     22   44 sub _accessor ( $self, $input = undef ) {
  22         35  
  22         43  
  22         26  
233 22         276 my $want = ( split( '::', ( caller(1) )[3] ) )[-1];
234              
235 22 100       111 if ($input) {
236 11 100       32 if ( ref $input ) {
237 3         6 my $data_refs_ocd;
238 171     171   178 $data_refs_ocd = sub ($node) {
  171         191  
  171         212  
239 171 100 100     533 if (
      100        
      100        
240             $node->{tag} and $node->{children} and
241             ( $node->{tag} eq 'crossref' or $node->{tag} eq 'footnote' )
242             ) {
243 6         13 for ( grep { $_->{text} } @{ $node->{children} } ) {
  9         33  
  6         13  
244             $_->{text} = $self->reference->acronyms(
245             $self->fnxref_acronym
246 6         30 )->clear->in( $_->{text} )->as_text;
247             }
248             }
249 171 100       52746 if ( $node->{children} ) {
250 72         88 $data_refs_ocd->($_) for ( @{ $node->{children} } );
  72         163  
251             }
252 171         276 return;
253 3         21 };
254 3         10 $data_refs_ocd->($input);
255              
256 3         6 my $reference = ( grep { $_->{tag} eq 'reference' } @{ $input->{children} } )[0]{children}[0];
  21         41  
  3         9  
257             my $runs = $self->reference->acronyms(
258             $self->reference_acronym
259 3         13 )->clear->in( $reference->{text} )->as_runs;
260              
261 3         3058 $reference->{text} = $runs->[0];
262             }
263             else {
264 17     17   23 my $ref_ocd = sub ( $text, $acronyms ) {
  17         179  
  17         45  
  17         23  
265 17         42 return $self->reference->acronyms($acronyms)->clear->in($text)->as_text;
266 8         46 };
267              
268 8         113 $input =~ s!
269             ((?:<(?:footnote|crossref)>|\{|\[)\s*.+?\s*(?:|\}|\]))
270             !
271 9         54091 $ref_ocd->( $1, $self->fnxref_acronym )
272             !gex;
273              
274 8         13465 $input =~ s!
275             ((?:|~)\s*.+?\s*(?:|~))
276             !
277 8         30 $ref_ocd->( $1, $self->reference_acronym )
278             !gex;
279             }
280              
281 11         12384 return $self->_load({ $want => $input });
282             }
283              
284 11 100 100     39 return $self->_load->{data} if ( $want eq 'data' and $self->_load->{data} );
285              
286 10 50       47 unless ( $self->_load->{canonical}{$want} ) {
287 10 100       122 if ( $self->_load->{html} ) {
    100          
    50          
288 4   33     47 $self->_load->{clean_html} //= __cleanup_html( $self->_load->{html} );
289              
290 4 100 66     42 if ( $want eq 'obml' ) {
    50          
291 1         45 $self->_load->{canonical}{obml} = $self->_clean_html_to_obml( $self->_load->{clean_html} );
292             }
293             elsif ( $want eq 'data' or $want eq 'html' ) {
294 3         13 $self->_load->{data} = __clean_html_to_data( $self->_load->{clean_html} );
295              
296             $self->_load->{canonical}{html} = __data_to_clean_html( $self->_load->{data} )
297 3 100       82 if ( $want eq 'html' );
298             }
299             }
300             elsif ( $self->_load->{data} ) {
301 2         45 $self->_load->{canonical}{html} = __data_to_clean_html( $self->_load->{data} );
302              
303             $self->_load->{canonical}{obml} = $self->_clean_html_to_obml( $self->_load->{canonical}{html} )
304 2 100       34 if ( $want eq 'obml' );
305             }
306             elsif ( $self->_load->{obml} ) {
307 4         118 $self->_load->{canonical}{html} = $self->_obml_to_clean_html( $self->_load->{obml} );
308              
309 4 100       55 if ( $want eq 'obml' ) {
    100          
310             $self->_load->{canonical}{obml} = $self->_clean_html_to_obml(
311             $self->_load->{canonical}{html}
312 1         4 );
313             }
314             elsif ( $want eq 'data' ) {
315 1         4 $self->_load->{data} = __clean_html_to_data( $self->_load->{canonical}{html} );
316             }
317             }
318             }
319              
320 10 100       115 return ( $want eq 'data' ) ? $self->_load->{$want} : $self->_load->{canonical}{$want};
321             }
322              
323 6     6 1 46119 sub data { shift->_accessor(@_) }
324 9     9 1 1647 sub html { shift->_accessor(@_) }
325 7     7 1 11545 sub obml { shift->_accessor(@_) }
326              
327             1;
328              
329             __END__