File Coverage

blib/lib/WWW/Noss/TextToHtml.pm
Criterion Covered Total %
statement 75 83 90.3
branch 37 44 84.0
condition 5 9 55.5
subroutine 10 10 100.0
pod 4 4 100.0
total 131 150 87.3


line stmt bran cond sub pod time code
1             package WWW::Noss::TextToHtml;
2 6     6   88778 use 5.016;
  6         22  
3 6     6   30 use strict;
  6         62  
  6         139  
4 6     6   29 use warnings;
  6         9  
  6         432  
5             our $VERSION = '2.02';
6              
7 6     6   41 use Exporter 'import';
  6         14  
  6         445  
8             our @EXPORT_OK = qw(text2html escape_html unescape_html strip_tags);
9              
10             use constant {
11 6         10169 STRIP_TEXT => 0,
12             STRIP_TAG => 1,
13             STRIP_COMMENT => 2,
14             STRIP_CDATA => 3,
15             STRIP_QUOTE => 4,
16 6     6   49 };
  6         37  
17              
18             # Copypasted from HTML::Parser's HTML::Entities
19             my %ENTITY2CHAR = (
20             # Some normal chars that have special meaning in SGML context
21             amp => '&', # ampersand
22             'gt' => '>', # greater than
23             'lt' => '<', # less than
24             quot => '"', # double quote
25             apos => "'", # single quote
26             # PUBLIC ISO 8879-1986//ENTITIES Added Latin 1//EN//HTML
27             AElig => chr(198), # capital AE diphthong (ligature)
28             Aacute => chr(193), # capital A, acute accent
29             Acirc => chr(194), # capital A, circumflex accent
30             Agrave => chr(192), # capital A, grave accent
31             Aring => chr(197), # capital A, ring
32             Atilde => chr(195), # capital A, tilde
33             Auml => chr(196), # capital A, dieresis or umlaut mark
34             Ccedil => chr(199), # capital C, cedilla
35             ETH => chr(208), # capital Eth, Icelandic
36             Eacute => chr(201), # capital E, acute accent
37             Ecirc => chr(202), # capital E, circumflex accent
38             Egrave => chr(200), # capital E, grave accent
39             Euml => chr(203), # capital E, dieresis or umlaut mark
40             Iacute => chr(205), # capital I, acute accent
41             Icirc => chr(206), # capital I, circumflex accent
42             Igrave => chr(204), # capital I, grave accent
43             Iuml => chr(207), # capital I, dieresis or umlaut mark
44             Ntilde => chr(209), # capital N, tilde
45             Oacute => chr(211), # capital O, acute accent
46             Ocirc => chr(212), # capital O, circumflex accent
47             Ograve => chr(210), # capital O, grave accent
48             Oslash => chr(216), # capital O, slash
49             Otilde => chr(213), # capital O, tilde
50             Ouml => chr(214), # capital O, dieresis or umlaut mark
51             THORN => chr(222), # capital THORN, Icelandic
52             Uacute => chr(218), # capital U, acute accent
53             Ucirc => chr(219), # capital U, circumflex accent
54             Ugrave => chr(217), # capital U, grave accent
55             Uuml => chr(220), # capital U, dieresis or umlaut mark
56             Yacute => chr(221), # capital Y, acute accent
57             aacute => chr(225), # small a, acute accent
58             acirc => chr(226), # small a, circumflex accent
59             aelig => chr(230), # small ae diphthong (ligature)
60             agrave => chr(224), # small a, grave accent
61             aring => chr(229), # small a, ring
62             atilde => chr(227), # small a, tilde
63             auml => chr(228), # small a, dieresis or umlaut mark
64             ccedil => chr(231), # small c, cedilla
65             eacute => chr(233), # small e, acute accent
66             ecirc => chr(234), # small e, circumflex accent
67             egrave => chr(232), # small e, grave accent
68             eth => chr(240), # small eth, Icelandic
69             euml => chr(235), # small e, dieresis or umlaut mark
70             iacute => chr(237), # small i, acute accent
71             icirc => chr(238), # small i, circumflex accent
72             igrave => chr(236), # small i, grave accent
73             iuml => chr(239), # small i, dieresis or umlaut mark
74             ntilde => chr(241), # small n, tilde
75             oacute => chr(243), # small o, acute accent
76             ocirc => chr(244), # small o, circumflex accent
77             ograve => chr(242), # small o, grave accent
78             oslash => chr(248), # small o, slash
79             otilde => chr(245), # small o, tilde
80             ouml => chr(246), # small o, dieresis or umlaut mark
81             szlig => chr(223), # small sharp s, German (sz ligature)
82             thorn => chr(254), # small thorn, Icelandic
83             uacute => chr(250), # small u, acute accent
84             ucirc => chr(251), # small u, circumflex accent
85             ugrave => chr(249), # small u, grave accent
86             uuml => chr(252), # small u, dieresis or umlaut mark
87             yacute => chr(253), # small y, acute accent
88             yuml => chr(255), # small y, dieresis or umlaut mark
89             # Some extra Latin 1 chars that are listed in the HTML3.2 draft (21-May-96)
90             copy => chr(169), # copyright sign
91             reg => chr(174), # registered sign
92             nbsp => chr(160), # non breaking space
93             # Additional ISO-8859/1 entities listed in rfc1866 (section 14)
94             iexcl => chr(161),
95             cent => chr(162),
96             pound => chr(163),
97             curren => chr(164),
98             yen => chr(165),
99             brvbar => chr(166),
100             sect => chr(167),
101             uml => chr(168),
102             ordf => chr(170),
103             laquo => chr(171),
104             'not' => chr(172), # not is a keyword in perl
105             shy => chr(173),
106             macr => chr(175),
107             deg => chr(176),
108             plusmn => chr(177),
109             sup1 => chr(185),
110             sup2 => chr(178),
111             sup3 => chr(179),
112             acute => chr(180),
113             micro => chr(181),
114             para => chr(182),
115             middot => chr(183),
116             cedil => chr(184),
117             ordm => chr(186),
118             raquo => chr(187),
119             frac14 => chr(188),
120             frac12 => chr(189),
121             frac34 => chr(190),
122             iquest => chr(191),
123             'times' => chr(215), # times is a keyword in perl
124             divide => chr(247),
125             'OElig' => chr(338),
126             'oelig' => chr(339),
127             'Scaron' => chr(352),
128             'scaron' => chr(353),
129             'Yuml' => chr(376),
130             'fnof' => chr(402),
131             'circ' => chr(710),
132             'tilde' => chr(732),
133             'Alpha' => chr(913),
134             'Beta' => chr(914),
135             'Gamma' => chr(915),
136             'Delta' => chr(916),
137             'Epsilon' => chr(917),
138             'Zeta' => chr(918),
139             'Eta' => chr(919),
140             'Theta' => chr(920),
141             'Iota' => chr(921),
142             'Kappa' => chr(922),
143             'Lambda' => chr(923),
144             'Mu' => chr(924),
145             'Nu' => chr(925),
146             'Xi' => chr(926),
147             'Omicron' => chr(927),
148             'Pi' => chr(928),
149             'Rho' => chr(929),
150             'Sigma' => chr(931),
151             'Tau' => chr(932),
152             'Upsilon' => chr(933),
153             'Phi' => chr(934),
154             'Chi' => chr(935),
155             'Psi' => chr(936),
156             'Omega' => chr(937),
157             'alpha' => chr(945),
158             'beta' => chr(946),
159             'gamma' => chr(947),
160             'delta' => chr(948),
161             'epsilon' => chr(949),
162             'zeta' => chr(950),
163             'eta' => chr(951),
164             'theta' => chr(952),
165             'iota' => chr(953),
166             'kappa' => chr(954),
167             'lambda' => chr(955),
168             'mu' => chr(956),
169             'nu' => chr(957),
170             'xi' => chr(958),
171             'omicron' => chr(959),
172             'pi' => chr(960),
173             'rho' => chr(961),
174             'sigmaf' => chr(962),
175             'sigma' => chr(963),
176             'tau' => chr(964),
177             'upsilon' => chr(965),
178             'phi' => chr(966),
179             'chi' => chr(967),
180             'psi' => chr(968),
181             'omega' => chr(969),
182             'thetasym' => chr(977),
183             'upsih' => chr(978),
184             'piv' => chr(982),
185             'ensp' => chr(8194),
186             'emsp' => chr(8195),
187             'thinsp' => chr(8201),
188             'zwnj' => chr(8204),
189             'zwj' => chr(8205),
190             'lrm' => chr(8206),
191             'rlm' => chr(8207),
192             'ndash' => chr(8211),
193             'mdash' => chr(8212),
194             'lsquo' => chr(8216),
195             'rsquo' => chr(8217),
196             'sbquo' => chr(8218),
197             'ldquo' => chr(8220),
198             'rdquo' => chr(8221),
199             'bdquo' => chr(8222),
200             'dagger' => chr(8224),
201             'Dagger' => chr(8225),
202             'bull' => chr(8226),
203             'hellip' => chr(8230),
204             'permil' => chr(8240),
205             'prime' => chr(8242),
206             'Prime' => chr(8243),
207             'lsaquo' => chr(8249),
208             'rsaquo' => chr(8250),
209             'oline' => chr(8254),
210             'frasl' => chr(8260),
211             'euro' => chr(8364),
212             'image' => chr(8465),
213             'weierp' => chr(8472),
214             'real' => chr(8476),
215             'trade' => chr(8482),
216             'alefsym' => chr(8501),
217             'larr' => chr(8592),
218             'uarr' => chr(8593),
219             'rarr' => chr(8594),
220             'darr' => chr(8595),
221             'harr' => chr(8596),
222             'crarr' => chr(8629),
223             'lArr' => chr(8656),
224             'uArr' => chr(8657),
225             'rArr' => chr(8658),
226             'dArr' => chr(8659),
227             'hArr' => chr(8660),
228             'forall' => chr(8704),
229             'part' => chr(8706),
230             'exist' => chr(8707),
231             'empty' => chr(8709),
232             'nabla' => chr(8711),
233             'isin' => chr(8712),
234             'notin' => chr(8713),
235             'ni' => chr(8715),
236             'prod' => chr(8719),
237             'sum' => chr(8721),
238             'minus' => chr(8722),
239             'lowast' => chr(8727),
240             'radic' => chr(8730),
241             'prop' => chr(8733),
242             'infin' => chr(8734),
243             'ang' => chr(8736),
244             'and' => chr(8743),
245             'or' => chr(8744),
246             'cap' => chr(8745),
247             'cup' => chr(8746),
248             'int' => chr(8747),
249             'there4' => chr(8756),
250             'sim' => chr(8764),
251             'cong' => chr(8773),
252             'asymp' => chr(8776),
253             'ne' => chr(8800),
254             'equiv' => chr(8801),
255             'le' => chr(8804),
256             'ge' => chr(8805),
257             'sub' => chr(8834),
258             'sup' => chr(8835),
259             'nsub' => chr(8836),
260             'sube' => chr(8838),
261             'supe' => chr(8839),
262             'oplus' => chr(8853),
263             'otimes' => chr(8855),
264             'perp' => chr(8869),
265             'sdot' => chr(8901),
266             'lceil' => chr(8968),
267             'rceil' => chr(8969),
268             'lfloor' => chr(8970),
269             'rfloor' => chr(8971),
270             'lang' => chr(9001),
271             'rang' => chr(9002),
272             'loz' => chr(9674),
273             'spades' => chr(9824),
274             'clubs' => chr(9827),
275             'hearts' => chr(9829),
276             'diams' => chr(9830),
277             );
278              
279             sub escape_html {
280              
281 31     31 1 3491 my $text = shift;
282              
283 31         104 $text =~ s/&/&/g;
284 31         86 $text =~ s/
285 31         56 $text =~ s/>/>/g;
286              
287 31         52 return $text;
288              
289             }
290              
291             sub unescape_html {
292              
293 6     6 1 2735 my $text = shift;
294              
295 6         45 $text =~ s{&(\S+?);}{
296 17         84 my $e = $1;
297 17 100       120 if ($e =~ /^#x([[:xdigit:]]+)$/) {
    100          
    50          
298 7         42 chr hex $1;
299             } elsif ($e =~ /^#(\d+)$/) {
300 5         25 chr $1;
301             } elsif (exists $ENTITY2CHAR{ $1 }) {
302 5         20 $ENTITY2CHAR{ $1 };
303             } else {
304 0         0 '&' . $1 . ';';
305             }
306             }ge;
307              
308 6         35 return $text;
309              
310             }
311              
312             sub text2html {
313              
314 30     30 1 235401 my $text = shift;
315              
316 30         64 $text = escape_html($text);
317              
318 30         127 my @paras = split /(\s*\n){2,}/, $text;
319              
320             my $html = join '',
321 32         119 map { "

" . $_ . "

\n" }
322 30         83 grep { /\S/ }
  34         168  
323             @paras;
324              
325 30         116 return $html;
326              
327             }
328              
329             sub strip_tags {
330              
331 6     6 1 1432 my $text = shift;
332              
333 6     6   60 no warnings 'substr';
  6         9  
  6         2815  
334              
335 6         30 my $out = '';
336              
337 6         12 my $state = STRIP_TEXT;
338 6         12 my $prev;
339 6         25 my $start = 0;
340 6         14 my $quot;
341              
342 6         28 for (my $i = 0; $i < length($text); $i++) {
343 202         358 my $c = substr $text, $i, 1;
344 202 100       447 if ($state == STRIP_TEXT) {
    100          
    100          
    100          
    50          
345 77 100       197 next if $c ne '<';
346 21 50       73 if (substr($text, $i, 4) eq '') {
375 2         5 $state = $prev;
376 2 50       5 if ($state == STRIP_TEXT) {
377 0         0 $start = $i + 3;
378             }
379 2         5 $i += 2;
380             }
381             } elsif ($state == STRIP_CDATA) {
382 14 100       41 if (substr($text, $i, 3) eq ']]>') {
383 1         2 $state = STRIP_TEXT;
384 1         2 $start = $i + 3;
385 1         4 $i += 2;
386             }
387             } elsif ($state == STRIP_QUOTE) {
388 11 100       34 if ($c eq $quot) {
    100          
389 3         7 $state = STRIP_TAG;
390             } elsif ($c eq '\\') {
391 2         7 $i += 1;
392             }
393             }
394             }
395              
396 6 50 33     55 if ($state == STRIP_TEXT and $start < length $text) {
    50 33        
397 0         0 $out .= substr $text, $start;
398             # Invalid HTML
399             } elsif ($state != STRIP_COMMENT and $state != STRIP_TEXT) {
400 0         0 return $text;
401             }
402              
403 6         39 return $out;
404              
405             }
406              
407             1;
408              
409             =head1 NAME
410              
411             WWW::Noss::TextToHtml - Convert text to HTML
412              
413             =head1 USAGE
414              
415             use WWW::Noss::TextToHtml(text2html);
416              
417             my $html = text2html($text);
418              
419             =head1 DESCRIPTION
420              
421             B is a module that provides subroutines for converting
422             plain text to HTML. This is a private module, please consult the L
423             manual for user documentation.
424              
425             =head1 SUBROUTINES
426              
427             Subroutines are not exported automatically.
428              
429             =over 4
430              
431             =item $html = text2html($text)
432              
433             Converts the given string C<$text> to HTML.
434              
435             =item $escaped = escape_html($text)
436              
437             Escapes the given text by converting special HTML characters (C>,
438             C>, and C<&>) into their entity equivalents.
439              
440             =item $unescaped = unescape_html($text)
441              
442             Unescapes the given text by converting HTML entities to their string
443             equivalents.
444              
445             =item $stripped = strip_tags($text)
446              
447             Strips HTML tags from the given text.
448              
449             =back
450              
451             =head1 AUTHOR
452              
453             Written by Samuel Young, Esamyoung12788@gmail.comE.
454              
455             This project's source can be found on its
456             L. Comments and pull
457             requests are welcome!
458              
459             =head1 COPYRIGHT
460              
461             Copyright (C) 2025-2026 Samuel Young
462              
463             This program is free software: you can redistribute it and/or modify
464             it under the terms of the GNU General Public License as published by
465             the Free Software Foundation, either version 3 of the License, or
466             (at your option) any later version.
467              
468             =head1 SEE ALSO
469              
470             L
471              
472             =cut
473              
474             # vim: expandtab shiftwidth=4