File Coverage

blib/lib/HTML/FormatData.pm
Criterion Covered Total %
statement 165 213 77.4
branch 31 46 67.3
condition n/a
subroutine 31 35 88.5
pod 25 26 96.1
total 252 320 78.7


line stmt bran cond sub pod time code
1             package HTML::FormatData;
2              
3             =pod
4              
5             =head1 NAME
6              
7             HTML::FormatData - formats strings and dates for web display/storage
8              
9             =head1 SYNOPSIS
10              
11             use HTML::FormatData;
12              
13             my $f = HTML::FormatData->new();
14              
15             my $string = "bolded";
16             my $formatted = $f->format_text( $string, strip_html=>1 );
17             # $string eq 'bolded'
18              
19             my $dt = $f->parse_date( $dt_string, '%Y%m%d%H%M%S' );
20             my $yrmoday = $f->format_date( $dt, '%Y%m%d' );
21             $yrmoday = $f->reformat_date( $dt_string, '%Y%m%d%H%M%S', '%Y%m%d' ); # shortcut
22              
23             =head1 DESCRIPTION
24              
25             HTML::FormatData contains utility functions to format strings and dates.
26             These utilities are useful for formatting data to be displayed on webpages,
27             or for cleaning and date data during server-side validation before storage
28             in a database or file.
29              
30             While doing web development work in the past, I noticed that I was having
31             to do the same operations time and again: strip HTML from form submissions,
32             truncate strings for display as table data, URI-encode strings for use in
33             links, translate Unix timestamps into mm/dd/yyyy format, etc. Rather than
34             try to keep straight the different modules and functions used, I decided to
35             write a wrapper with a single, consistent interface.
36              
37             =head1 METHODS
38              
39             =cut
40              
41 1     1   24220 use 5.006;
  1         3  
  1         41  
42 1     1   6 use strict;
  1         3  
  1         41  
43 1     1   11 use warnings;
  1         8  
  1         31  
44              
45 1     1   5 use Carp qw( croak );
  1         2  
  1         120  
46 1     1   1422 use DateTime;
  1         296657  
  1         50  
47 1     1   1426 use DateTime::Format::Strptime;
  1         33722  
  1         92  
48 1     1   1498 use HTML::Entities;
  1         12028  
  1         137  
49 1     1   12 use HTML::Parser;
  1         2  
  1         26  
50 1     1   1099 use URI::Escape;
  1         1705  
  1         2497  
51              
52             our $VERSION = '0.10';
53              
54             =pod
55              
56             =head2 new()
57              
58             This method creates a new HTML::FormatData object.
59             Returns the blessed object.
60              
61             =cut
62              
63             sub new {
64 1     1 1 38 my $class = shift;
65 1         2 my $config = shift;
66              
67 1         5 bless {}, $class;
68             }
69              
70             =pod
71              
72             =head2 format_text( $string, %args )>
73              
74             Wrapper function for the text formatting routines below. Formats a string
75             according to parameters passed in. While the functions this routine calls
76             can be called directly, it will usually be best to always go thru this function.
77              
78             Returns the formatted string.
79              
80             =cut
81              
82             sub format_text {
83 208     208 1 156135 my $self = shift;
84 208         448 my $string = shift;
85 208 50       957 croak( "Odd number of parameters passed to format_text." ) if @_ % 2;
86 208         627 my %args = @_;
87              
88 208 100       463 return unless defined $string;
89 192 100       424 return '' if $string eq '';
90              
91 176         990 my @jobs = qw(
92             decode_xml decode_html decode_uri
93             strip_html strip_whitespace
94             clean_high_ascii clean_encoded_html clean_encoded_text
95             clean_whitespace clean_whitespace_keep_full_breaks clean_whitespace_keep_all_breaks
96             force_lc force_uc
97             truncate truncate_with_ellipses
98             encode_xml encode_html encode_uri
99             );
100              
101 176         963 foreach my $job ( @jobs ) {
102 3168 100       7154 next unless exists $args{$job};
103 165 100       1075 if ( $job =~ /^truncate/ ) {
104 22         66 $string = $self->$job( $string, $args{$job} );
105             } else {
106 143         426 $string = $self->$job( $string );
107             }
108             }
109              
110 176         703 return $string;
111             }
112            
113             =pod
114              
115             =head2 decode_xml( $string )
116              
117             A copy of XML::Comma::Util::XML_basic_unescape. Returns
118             an XML-unescaped string.
119              
120             =cut
121              
122             sub decode_xml {
123 11     11 1 20 my $self = shift;
124 11         14 my $string = shift;
125            
126 11         25 $string =~ s/\&/&/g ;
127 11         20 $string =~ s/\</
128 11         12 $string =~ s/\>/>/g ;
129            
130 11         32 return $string;
131             }
132            
133             =pod
134              
135             =head2 decode_html( $string )
136              
137             Returns an HTML-unescaped string.
138              
139             =cut
140              
141             sub decode_html {
142 11     11 1 15 my $self = shift;
143 11         12 my $string = shift;
144              
145 11         92 return HTML::Entities::decode( $string );
146             }
147              
148             =pod
149              
150             =head2 decode_uri( $string )
151              
152             Returns an URI-unescaped string.
153              
154             =cut
155              
156             sub decode_uri {
157 0     0 1 0 my $self = shift;
158 0         0 my $string = shift;
159              
160 0         0 return URI::Escape::uri_unescape( $string );
161             }
162              
163             =pod
164              
165             =head2 strip_html( $string )
166              
167             Strips all HTML tags from string. Returns string.
168              
169             =cut
170              
171             sub strip_html {
172 11     11 1 14 my $self = shift;
173 11         15 my $string = shift;
174            
175 11         12 our $output;
176 11         16 $output = '';
177              
178             sub default_handler {
179 22     22 0 157 $output .= shift;
180             }
181              
182 11         43 my $p = HTML::Parser->new( api_version => 3 );
183 11         313 $p->handler( default => \&default_handler, "text" );
184 11         32 $p->handler( start => "" );
185 11         30 $p->handler( end => "" );
186 11         31 $p->handler( comment => '' );
187 11         26 $p->handler( declaration => '' );
188 11         27 $p->handler( process => '' );
189              
190 11         43 $p->ignore_elements( qw( script style ) );
191              
192 11         69 $p->parse( "$string " );
193              
194 11         79 return $output;
195             }
196              
197             =pod
198              
199             =head2 strip_whitespace( $string )
200              
201             Strips all whitespace ( \s ) characters from string.
202             Returns string.
203              
204             =cut
205              
206             sub strip_whitespace {
207 11     11 1 17 my $self = shift;
208 11         18 $_ = shift;
209 11         53 s/\s+//g;
210 11         30 return $_;
211             }
212              
213             =pod
214              
215             =head2 clean_high_ascii( $string )
216              
217             Converts 8-bit ascii characters to their 7-bit counterparts.
218             Tested with MS-Word documents; might not work right with high-ascii
219             text from other sources. Returns string.
220              
221             =cut
222              
223             sub clean_high_ascii {
224 0     0 1 0 my $self = shift;
225 0         0 $_ = shift;
226              
227 0         0 my ( $high, $low );
228              
229             ### single quotes
230 0         0 $high = chr(145); $high = qr{$high};
  0         0  
231 0         0 $low = qr{'};
232 0         0 s/$high/$low/g;
233              
234 0         0 $high = chr(146); $high = qr{$high};
  0         0  
235 0         0 s/$high/$low/g;
236              
237             ### double quotes
238 0         0 $high = chr(147); $high = qr{$high};
  0         0  
239 0         0 $low = qr{"};
240 0         0 s/$high/$low/g;
241              
242 0         0 $high = chr(148); $high = qr{$high};
  0         0  
243 0         0 s/$high/$low/g;
244              
245             ### endash
246 0         0 $high = chr(150); $high = qr{$high};
  0         0  
247 0         0 $low = qr{-};
248 0         0 s/$high/$low/g;
249            
250             ### emdash
251 0         0 $high = chr(151); $high = qr{$high};
  0         0  
252 0         0 $low = qr{--};
253 0         0 s/$high/$low/g;
254            
255             ### ellipsis
256 0         0 $high = chr(133); $high = qr{$high};
  0         0  
257 0         0 $low = qr{...};
258 0         0 s/$high/$low/g;
259            
260             ### unknown
261 0         0 $high = chr(194); $high = qr{$high};
  0         0  
262 0         0 s/$high//g;
263              
264 0         0 return $_;
265             }
266              
267             =pod
268              
269             =head2 clean_html_encoded_text( $string )
270              
271             Properly encodes some entities skipped by HTML::Entities::encode.
272             Returns the modified string.
273              
274             =cut
275              
276             sub clean_html_encoded_text {
277 22     22 1 32 my $self = shift;
278 22         32 $_ = shift;
279            
280             ### properly encode m-dashes
281 22         37 s/\—/\—/g;
282 22         38 s/--/\—/g;
283              
284             ### properly encode ellipses
285 22         170 s/\.\.\./\…/g;
286              
287             ### encode apostrophes
288             #s/'/’/g;
289              
290 22         43 return $_;
291             }
292              
293             =pod
294              
295             =head2 decode_select_entities( $string )
296              
297             Takes HTML::Entities::encoded HTML and selectively unencodes certain entities
298             for display on webpage. Returns modified string.
299              
300             =cut
301              
302             sub decode_select_entities {
303 11     11 1 18 my $self = shift;
304 11         21 $_ = shift;
305              
306             ### restore angle brackets
307 11         27 s/\</
308 11         21 s/\>/>/g;
309              
310             ### restore quotes inside angle brackets
311 11         37 1 while s/(<[^>]*)(\")/$1\"/gs;
312              
313 11         31 return $_;
314             }
315              
316             =pod
317              
318             =head2 clean_encoded_html( $string )
319              
320             Formats HTML-encoded HTML for display on webpage. Returns modified string.
321              
322             =cut
323              
324             sub clean_encoded_html {
325 11     11 1 23 my $self = shift;
326 11         47 my $string = shift;
327              
328 11         30 $string = $self->decode_select_entities( $string );
329 11         39 $string = $self->clean_html_encoded_text( $string );
330              
331 11         29 return $string;
332             }
333              
334             =pod
335              
336             =head2 clean_encoded_text( $string )
337              
338             Formats HTML-encoded text for display on webpage. Returns modified string.
339              
340             =cut
341              
342             sub clean_encoded_text {
343 11     11 1 19 my $self = shift;
344 11         29 my $string = shift;
345              
346 11         45 $string = $self->clean_html_encoded_text( $string );
347              
348 11         30 return $string;
349             }
350              
351             =pod
352              
353             =head2 clean_whitespace( $string [keep_full_breaks => 1 | keep_all_breaks => 1] )
354              
355             Cleans up whitespace in HTML and plain text. If passed an argument for handling
356             line breaks, it will either keep full breaks (\n\n) or all breaks (any \n). Otherwise,
357             all line breaks will be converted to spaces. Returns the modified string.
358              
359             =cut
360              
361             sub clean_whitespace {
362 33     33 1 41 my $self = shift;
363 33         45 $_ = shift;
364 33 50       79 croak( "Odd number of parameters passed to format_text." ) if @_ % 2;
365 33         69 my %args = @_;
366              
367 33         53 s/\r\n/\n/g;
368 33         36 s/\r/\n/g;
369 33         256 1 while s/\n\n\n/\n\n/g;
370 33         76 s/^[ \t\f]+//g;
371 33         63 s/[ \t\f]+$//g;
372              
373 33 100       85 if ( $args{keep_all_breaks} ) {
    100          
374 11         24 1 while s/ / /g;
375             } elsif ( $args{keep_full_breaks} ) {
376 11         17 s/\n\n/\$\$\$/g;
377 11         20 s/\n/ /g;
378 11         28 1 while s/ / /g;
379 11         17 s/\$\$\$/\n\n/g;
380             } else {
381 11         30 s/\n/ /g;
382 11         35 1 while s/ / /g;
383             }
384              
385 33         113 return $_;
386             }
387              
388             =pod
389              
390             =head2 clean_whitespace_keep_full_breaks( $string )
391              
392             Cleans up whitespace in HTML and plain text while preserving all full breaks (\n\n).
393             Returns the modified string.
394              
395             =cut
396              
397             sub clean_whitespace_keep_full_breaks {
398 11     11 1 18 my $self = shift;
399 11         15 my $string = shift;
400              
401 11         31 return $self->clean_whitespace( $string, keep_full_breaks => 1 );
402             }
403              
404             =pod
405              
406             =head2 clean_whitespace_keep_all_breaks( $string )
407              
408             Cleans up whitespace in HTML and plain text while preserving all line breaks (\n).
409             Returns the modified string.
410              
411             =cut
412              
413             sub clean_whitespace_keep_all_breaks {
414 11     11 1 13 my $self = shift;
415 11         11 my $string = shift;
416              
417 11         28 return $self->clean_whitespace( $string, keep_all_breaks => 1 );
418             }
419              
420             =pod
421              
422             =head2 force_lc( $string )
423              
424             Returns lc( $string ).
425              
426             =cut
427              
428             sub force_lc {
429 11     11 1 18 my $self = shift;
430 11         12 my $string = shift;
431              
432 11         32 return lc $string;
433             }
434              
435             =pod
436              
437             =head2 force_uc( $string )
438              
439             Returns uc( $string ).
440              
441             =cut
442              
443             sub force_uc {
444 11     11 1 13 my $self = shift;
445 11         14 my $string = shift;
446              
447 11         35 return uc $string;
448             }
449              
450             =pod
451              
452             =head2 truncate( $string, $count )
453              
454             Returns the first $count characters of string.
455              
456             =cut
457              
458             sub truncate {
459 11     11 1 18 my $self = shift;
460 11         13 my $string = shift;
461 11         10 my $count = shift;
462              
463 11 100       27 if ( length( $string ) > $count ) {
464 9         17 $string = substr( $string, 0, $count );
465             }
466            
467 11         26 return $string;
468             }
469              
470             =pod
471              
472             =head2 truncate_with_ellipses( $string, $count )
473              
474             Returns the first $count - 3 characters of string followed by '...'.
475              
476             =cut
477              
478             sub truncate_with_ellipses {
479 11     11 1 16 my $self = shift;
480 11         14 my $string = shift;
481 11         12 my $count = shift;
482              
483 11 50       21 if ( $count > 3 ) {
484 11 100       25 if ( length( $string ) > $count ) {
485 9         20 $string = substr( $string, 0, ( $count - 3 ) ) . '...';
486             }
487             }
488            
489 11         36 return $string;
490             }
491              
492             =pod
493              
494             =head2 encode_xml( $string )
495              
496             A copy of XML::Comma::Util::XML_basic_escape. Returns
497             an XML-escaped string.
498              
499             =cut
500              
501             sub encode_xml {
502 11     11 1 17 my $self = shift;
503 11         50 my $string = shift;
504            
505             # escape &
506 11         33 $string =~ s/\&/&/g;
507            
508             # escape < >
509 11         65 $string =~ s/
510 11         28 $string =~ s/>/\>/g ;
511            
512 11         34 return $string;
513             }
514            
515             =pod
516              
517             =head2 encode_html( $string )
518              
519             Returns an HTML-escaped string.
520              
521             =cut
522              
523             sub encode_html {
524 11     11 1 16 my $self = shift;
525 11         17 my $string = shift;
526              
527 11         35 return HTML::Entities::encode( $string );
528             }
529              
530             =pod
531              
532             =head2 encode_uri( $string )
533              
534             Returns an URI-escaped string.
535              
536             =cut
537              
538             sub encode_uri {
539 0     0 1 0 my $self = shift;
540 0         0 my $string = shift;
541              
542 0         0 return URI::Escape::uri_escape( $string );
543             }
544              
545             =pod
546              
547             =head2 reformat_date( $string, $oldformat, $newformat )
548              
549             Takes a date string in $oldformat and returns a new string in
550             $new_format.
551              
552             =cut
553              
554             sub reformat_date {
555 0     0 1 0 my $self = shift;
556 0         0 my $string = shift;
557 0         0 my $oldformat = shift;
558 0         0 my $newformat = shift;
559              
560 0         0 my $dt = $self->parse_date( $string, $oldformat );
561 0         0 return $self->format_date( $dt, $newformat );
562             }
563              
564              
565             =pod
566              
567             =head2 parse_date( $string [, $format] )
568              
569             Takes a $string representing a date and time, and tries to
570             produce a valid DateTime object. Returns the object upon success,
571             otherwise undef.
572              
573             Setting $string to 'now' creates a DateTime object of the current
574             date and time. Setting $string to 'today' creates a DateTime object
575             of today's date and time set to midnight.
576              
577             Otherwise, you must pass a $format to parse the string correctly.
578             $format can be set to one of the following "shortcuts": 'date8',
579             'date14', or 'rfc822'.
580              
581             =cut
582              
583             sub parse_date {
584 2     2 1 5793 my $self = shift;
585 2         4 my $string = shift;
586 2         4 my $format = shift;
587              
588 2 50       10 return unless $string;
589            
590 2 50       10 if ( $string eq 'now' ) {
591 0         0 return DateTime->now( time_zone => 'local' );
592             }
593              
594 2 50       10 if ( $string eq 'today' ) {
595 0         0 return DateTime->today( time_zone => 'local' );
596             }
597              
598 2 50       6 return unless $format;
599            
600 2 50       7 $format = '%Y%m%d' if $format eq 'date8';
601 2 50       8 $format = '%Y%m%d%H%M%S' if $format eq 'date14';
602 2 50       9 $format = '%a, %d %b %Y %H:%M:%S %z' if $format eq 'rfc822';
603              
604 2 50       7 if ( $format eq '%s' ) {
605 0         0 return DateTime->from_epoch( epoch => $string, time_zone => 'local' );
606             } else {
607 2         29 my $parser = DateTime::Format::Strptime->new(
608             pattern => $format,
609             on_error => 'undef',
610             time_zone => 'local'
611             );
612 2         49161 return $parser->parse_datetime( $string );
613             }
614              
615             }
616              
617             =pod
618              
619             =head2 format_date( $dt, $format )
620              
621             Takes a DateTime object ($dt) and a $format, and
622             returns the formatted string.
623              
624             $format is a DateTime 'strftime' format string. $format can be
625             set to one of the following "shortcuts": 'date8', 'date14',
626             and 'rfc822'.
627              
628             =cut
629              
630             sub format_date {
631 5     5 1 5889 my $self = shift;
632 5         8 my $dt = shift;
633 5         9 my $format = shift;
634            
635 5 50       24 return unless ref $dt eq 'DateTime';
636              
637 5 50       15 $format = '%Y%m%d' if $format eq 'date8';
638 5 50       15 $format = '%Y%m%d%H%M%S' if $format eq 'date14';
639 5 50       11 $format = '%a, %d %b %Y %H:%M:%S %z' if $format eq 'rfc822';
640            
641 5         23 return $dt->strftime( $format );
642             }
643              
644             =pod
645              
646             =head1 AUTHOR
647              
648             Eric Folley, Eeric@folley.netE
649              
650             =head1 COPYRIGHT AND LICENSE
651              
652             Copyright 2004-2005 by Eric Folley
653              
654             This library is free software; you can redistribute it and/or modify
655             it under the same terms as Perl itself.
656              
657             =cut
658              
659             1;