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   186883 use 5.020;
  1         7  
5              
6 1     1   362 use exact;
  1         29477  
  1         4  
7 1     1   2378 use exact::class;
  1         9388  
  1         5  
8 1     1   713 use Mojo::DOM;
  1         139781  
  1         39  
9 1     1   7 use Mojo::Util 'html_unescape';
  1         2  
  1         43  
10 1     1   429 use Text::Wrap 'wrap';
  1         2258  
  1         48  
11 1     1   568 use Bible::Reference;
  1         10671  
  1         8  
12              
13             our $VERSION = '2.04'; # 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   9380 sub __ocd_tree ($node) {
  209         195  
  209         185  
26 209         187 my $new_node;
27              
28 209 100       260 if ( 'tag' eq shift @$node ) {
29 91         139 $new_node->{tag} = shift @$node;
30              
31 91         115 my $attr = shift @$node;
32 91 100       143 $new_node->{attr} = $attr if (%$attr);
33              
34 91         86 shift @$node;
35              
36 91         121 my $children = [ grep { defined } map { __ocd_tree($_) } @$node ];
  205         283  
  205         238  
37 91 100       167 $new_node->{children} = $children if (@$children);
38             }
39             else {
40 118 100       244 $new_node->{text} = $node->[0] if ( $node->[0] ne "\n\n" );
41             }
42              
43 209         302 return $new_node;
44             }
45              
46 189     189   164 sub __html_tree ($node) {
  189         171  
  189         170  
47 189 100       251 if ( $node->{tag} ) {
48 91 100       108 if ( $node->{children} ) {
49             my $attr = ( $node->{attr} )
50 80 100       101 ? ' ' . join( ' ', map { $_ . '="' . $node->{attr}{$_} . '"' } keys %{ $node->{attr} } )
  15         40  
  15         28  
51             : '';
52              
53             return join( '',
54             '<', $node->{tag}, $attr, '>',
55             (
56             ( $node->{children} )
57 185         228 ? ( map { __html_tree($_) } @{ $node->{children} } )
  80         111  
58             : ()
59             ),
60 80 50       111 '{tag}, '>',
61             );
62             }
63             else {
64 11         20 return '<' . $node->{tag} . '>';
65             }
66             }
67             else {
68 98         277 return $node->{text};
69             }
70             }
71              
72 8     8   77 sub __cleanup_html ($html) {
  8         10  
  8         9  
73             # spacing cleanup
74 8         194 $html =~ s/\s+/ /g;
75 8         538 $html =~ s/(?:^\s+|\s+$)//mg;
76 8         24 $html =~ s/^[ ]+//mg;
77              
78             # protect against inadvertent OBML
79 8         12 $html =~ s/~/-/g;
80 8         13 $html =~ s/`/'/g;
81 8         12 $html =~ s/\|//g;
82 8         13 $html =~ s/\\/ /g;
83 8         10 $html =~ s/\*//g;
84 8         12 $html =~ s/\{/(/g;
85 8         9 $html =~ s/\}/)/g;
86 8         14 $html =~ s/\[/(/g;
87 8         10 $html =~ s/\]/)/g;
88              
89 8         45 $html =~ s|

|\n\n

|g;

90 8         40 $html =~ s||\n\n|g;
91 8         36 $html =~ s|
|\n\n
|g;
92 8         51 $html =~ s|
\s*|
\n|g;
93 8         21 $html =~ s|[ ]+

|

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

with a
for later wrapping reasons

114 3     3   7047 $dom->find('p')->grep( sub { $_->find('br')->size } )->each( sub { $_->append_content('
') } );
  12         4896  
  3         392  
115              
116 3         489 my $obml = html_unescape( $dom->to_string );
117              
118             # de-XML
119 3         2525 $obml =~ s|||g;
120 3         26 $obml =~ s|||g;
121 3         24 $obml =~ s||\*|g;
122 3         18 $obml =~ s||\^|g;
123 3         21 $obml =~ s||\\|g;
124 3         17 $obml =~ s|\s*|~ |g;
125 3         18 $obml =~ s|\s*| ~|g;
126 3         22 $obml =~ s!\s*!|!g;
127 3         56 $obml =~ s!\s*!| !g;
128 3         20 $obml =~ s|\s*|== |g;
129 3         47 $obml =~ s|\s*| ==|g;
130 3         14 $obml =~ s|
\s*|= |g;
131 3         39 $obml =~ s|\s*| =|g;
132 3         13 $obml =~ s|\s*|\{|g;
133 3         20 $obml =~ s|\s*|\}|g;
134 3         20 $obml =~ s|\s*|\[|g;
135 3         26 $obml =~ s|\s*|\]|g;
136 3         15 $obml =~ s|^| ' ' x ( $self->indent_width * $1 ) |mge;
  12         111  
137 3         29 $obml =~ s|||g;
138 3         13 $obml =~ s|||g;
139              
140 3 50       10 if ( $self->wrap_at ) {
141             # wrap lines that don't end in
142             $obml = join( "\n", map {
143 3 100       39 unless ( s|
|| ) {
  48         3398  
144 36         59 s/^(\s+)//;
145 36   50     71 $Text::Wrap::columns = $self->wrap_at - length( $1 || '' );
146 36         351 wrap( $1, $1, $_ );
147             }
148             else {
149 12         20 $_;
150             }
151             } split( /\n/, $obml ) ) . "\n";
152             }
153 3         361 $obml =~ s|
||g;
154              
155 3         7 chomp $obml;
156 3         69 return $obml;
157             }
158              
159 4     4   34 sub _obml_to_clean_html ( $self, $obml ) {
  4         7  
  4         6  
  4         5  
160             # spacing cleanup
161 4         57 $obml =~ s/\r?\n/\n/g;
162 4         12 $obml =~ s/\t/ /g;
163 4         48 $obml =~ s/\n[ \t]+\n/\n\n/mg;
164 4         11 $obml =~ s/^\n+//g;
165 4         8 $obml =~ /^(\s+)/;
166 4 50       14 $obml =~ s/^$1//mg if ($1);
167 4         43 $obml =~ s/\s+$//g;
168              
169             # remove comments
170 4         35 $obml =~ s/^\s*#.*?(?>\r?\n)//msg;
171              
172             # "unwrap" wrapped lines
173 4         6 my @obml;
174 4         32 for my $line ( split( /\n/, $obml ) ) {
175 58 100 100     166 if ( not @obml or not length $line or not length $obml[-1] ) {
176 44         73 push( @obml, $line );
177             }
178             else {
179 14         37 my ($last_line_indent) = $obml[-1] =~ /^([ ]*)/;
180 14         31 my ($this_line_indent) = $line =~ /^([ ]*)/;
181              
182 14 100 66     55 if ( length $last_line_indent == 0 and length $this_line_indent == 0 ) {
183 3         6 $line =~ s/^[ ]+//;
184 3         9 $obml[-1] .= ' ' . $line;
185             }
186             else {
187 11         48 push( @obml, $line );
188             }
189             }
190             }
191 4         15 $obml = join( "\n", @obml );
192              
193 4         46 $obml =~ s|~+[ ]*([^~]+?)[ ]*~+|$1|g;
194 4         27 $obml =~ s|={2,}[ ]*([^=]+?)[ ]*={2,}|$1|g;
195 4         22 $obml =~ s|=[ ]*([^=]+?)[ ]*=|
$1
|g;
196              
197 4         17 $obml =~ s|^([ ]+)(\S.*)$|
198 15         209 ' 199             . int( ( length($1) + $self->indent_width * 0.5 ) / $self->indent_width )
200             . '">'
201             . $2
202             . ''
203             |mge;
204              
205 4         221 $obml =~ s|(\S)(?=\n\S)|$1
|g;
206              
207 4         282 $obml =~ s`(?:^|(?<=\n\n))(?!<(?:reference|sub_header|header)\b)`

`g;

208 4         269 $obml =~ s`(?:$|(?=\n\n))`

`g;
209 4         24 $obml =~ s`(?<=)

``g;
210 4         19 $obml =~ s`(?<=)

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

``g;
212              
213 4         47 $obml =~ s!\|(\d+)\|\s*!$1!g;
214              
215 4         27 $obml =~ s|\*([^\*]+)\*|$1|g;
216 4         26 $obml =~ s|\^([^\^]+)\^|$1|g;
217 4         19 $obml =~ s|\\([^\\]+)\\|$1|g;
218              
219 4         20 $obml =~ s|\{||g;
220 4         13 $obml =~ s|\}||g;
221              
222 4         11 $obml =~ s|\[||g;
223 4         11 $obml =~ s|\]||g;
224              
225 4         30 return "$obml";
226             }
227              
228 22     22   34 sub _accessor ( $self, $input = undef ) {
  22         31  
  22         40  
  22         25  
229 22         224 my $want = ( split( '::', ( caller(1) )[3] ) )[-1];
230              
231 22 100       75 if ($input) {
232 11 100       24 if ( ref $input ) {
233 3         6 my $data_refs_ocd;
234 171     171   154 $data_refs_ocd = sub ($node) {
  171         155  
  171         163  
235 171 100 100     456 if (
      100        
      100        
236             $node->{tag} and $node->{children} and
237             ( $node->{tag} eq 'crossref' or $node->{tag} eq 'footnote' )
238             ) {
239 6         10 for ( grep { $_->{text} } @{ $node->{children} } ) {
  9         24  
  6         16  
240             $_->{text} = $self->reference->acronyms(
241             $self->fnxref_acronym
242 6         27 )->clear->in( $_->{text} )->as_text;
243             }
244             }
245 171 100       48444 if ( $node->{children} ) {
246 72         73 $data_refs_ocd->($_) for ( @{ $node->{children} } );
  72         134  
247             }
248 171         219 return;
249 3         20 };
250 3         8 $data_refs_ocd->($input);
251              
252 3         6 my $reference = ( grep { $_->{tag} eq 'reference' } @{ $input->{children} } )[0]{children}[0];
  21         36  
  3         7  
253             my $runs = $self->reference->acronyms(
254             $self->reference_acronym
255 3         9 )->clear->in( $reference->{text} )->as_runs;
256              
257 3         2695 $reference->{text} = $runs->[0];
258             }
259             else {
260 17     17   17 my $ref_ocd = sub ( $text, $acronyms ) {
  17         164  
  17         60  
  17         24  
261 17         39 return $self->reference->acronyms($acronyms)->clear->in($text)->as_text;
262 8         62 };
263              
264 8         96 $input =~ s!
265             ((?:<(?:footnote|crossref)>|\{|\[)\s*.+?\s*(?:|\}|\]))
266             !
267 9         48510 $ref_ocd->( $1, $self->fnxref_acronym )
268             !gex;
269              
270 8         10985 $input =~ s!
271             ((?:|~)\s*.+?\s*(?:|~))
272             !
273 8         31 $ref_ocd->( $1, $self->reference_acronym )
274             !gex;
275             }
276              
277 11         10134 return $self->_load({ $want => $input });
278             }
279              
280 11 100 100     41 return $self->_load->{data} if ( $want eq 'data' and $self->_load->{data} );
281              
282 10 50       42 unless ( $self->_load->{canonical}{$want} ) {
283 10 100       109 if ( $self->_load->{html} ) {
    100          
    50          
284 4   33     37 $self->_load->{clean_html} //= __cleanup_html( $self->_load->{html} );
285              
286 4 100 66     25 if ( $want eq 'obml' ) {
    50          
287 1         46 $self->_load->{canonical}{obml} = $self->_clean_html_to_obml( $self->_load->{clean_html} );
288             }
289             elsif ( $want eq 'data' or $want eq 'html' ) {
290 3         13 $self->_load->{data} = __clean_html_to_data( $self->_load->{clean_html} );
291              
292             $self->_load->{canonical}{html} = __data_to_clean_html( $self->_load->{data} )
293 3 100       71 if ( $want eq 'html' );
294             }
295             }
296             elsif ( $self->_load->{data} ) {
297 2         38 $self->_load->{canonical}{html} = __data_to_clean_html( $self->_load->{data} );
298              
299             $self->_load->{canonical}{obml} = $self->_clean_html_to_obml( $self->_load->{canonical}{html} )
300 2 100       29 if ( $want eq 'obml' );
301             }
302             elsif ( $self->_load->{obml} ) {
303 4         96 $self->_load->{canonical}{html} = $self->_obml_to_clean_html( $self->_load->{obml} );
304              
305 4 100       52 if ( $want eq 'obml' ) {
    100          
306             $self->_load->{canonical}{obml} = $self->_clean_html_to_obml(
307             $self->_load->{canonical}{html}
308 1         4 );
309             }
310             elsif ( $want eq 'data' ) {
311 1         3 $self->_load->{data} = __clean_html_to_data( $self->_load->{canonical}{html} );
312             }
313             }
314             }
315              
316 10 100       98 return ( $want eq 'data' ) ? $self->_load->{$want} : $self->_load->{canonical}{$want};
317             }
318              
319 6     6 1 37914 sub data { shift->_accessor(@_) }
320 9     9 1 1456 sub html { shift->_accessor(@_) }
321 7     7 1 9765 sub obml { shift->_accessor(@_) }
322              
323             1;
324              
325             __END__