diff options
| author | fukachan <fukachan> | 2012-12-13 13:52:24 +0000 |
|---|---|---|
| committer | fukachan <fukachan> | 2012-12-13 13:52:24 +0000 |
| commit | 44e12ff7a8c3c479e16486a4c8422bd78212832e (patch) | |
| tree | 0b1be5c4701e7c00bb8365d17e256dd7ea4e9fd5 | |
| parent | 8704fc7e183127d138a11a2236eddb492ade8849 (diff) | |
| download | fml8-44e12ff7a8c3c479e16486a4c8422bd78212832e.tar.gz fml8-44e12ff7a8c3c479e16486a4c8422bd78212832e.tar.bz2 fml8-44e12ff7a8c3c479e16486a4c8422bd78212832e.zip | |
create HTML::EntitiesLite, defined from HTML-Parser-3.69.
modified HTML::FromText to use it.
| -rw-r--r-- | cpan/MANIFEST | 11 | ||||
| -rw-r--r-- | cpan/lib/HTML/EntitiesLite.pm | 500 | ||||
| -rw-r--r-- | cpan/lib/HTML/FromText.pm | 1292 |
3 files changed, 1211 insertions, 592 deletions
diff --git a/cpan/MANIFEST b/cpan/MANIFEST index 8c307b08..e5763887 100644 --- a/cpan/MANIFEST +++ b/cpan/MANIFEST @@ -31,4 +31,13 @@ Time-modules 2011.0517 unknown (your own risk) Unicode-Japanese 0.47 perl itself -$FML: MANIFEST,v 1.19 2012/02/25 13:08:13 fukachan Exp $ + +# +# below, hacked by fukachan@fml.org. +# + +# defined from HTML-Parser-3.69. +HTML-EntitiesLite 3.69p1 perl itself + + +$FML: MANIFEST,v 1.20 2012/04/15 07:24:03 fukachan Exp $ diff --git a/cpan/lib/HTML/EntitiesLite.pm b/cpan/lib/HTML/EntitiesLite.pm new file mode 100644 index 00000000..4423b02b --- /dev/null +++ b/cpan/lib/HTML/EntitiesLite.pm @@ -0,0 +1,500 @@ +# +# modified from package HTML::Entities in HTML-Parser-3.69; +# +# $FML$ +# This library is free software; you can redistribute it and/or modify +# it under the same terms as Perl itself. +# +# HTML-Parser-3.69 COPYRIGHT +# (C) 1995-2009 Gisle Aas. All rights reserved. +# (C) 1999-2000 Michael A. Chase. All rights reserved. +# This library is free software; you can redistribute it and/or modify +# it under the same terms as Perl itself. + +# modified from package HTML::Entities; +package HTML::EntitiesLite; + +=encoding utf8 + +=head1 NAME + +HTML::EntitiesLite - Encode or decode strings with HTML entities + +=head1 SYNOPSIS + + use HTML::EntitiesLite; + + $a = "Våre norske tegn bør æres"; + decode_entities($a); + encode_entities($a, "\200-\377"); + +For example, this: + + $input = "vis-à-vis Beyoncé's naïve\npapier-mâché résumé"; + print encode_entities($input), "\n" + +Prints this out: + + vis-à-vis Beyoncé's naïve + papier-mâché résumé + +=head1 DESCRIPTION + +This module deals with encoding and decoding of strings with HTML +character entities. The module provides the following functions: + +=over 4 + +=item decode_entities( $string, ... ) + +This routine replaces HTML entities found in the $string with the +corresponding Unicode character. Unrecognized entities are left alone. + +If multiple strings are provided as argument they are each decoded +separately and the same number of strings are returned. + +If called in void context the arguments are decoded in-place. + +This routine is exported by default. + +=item _decode_entities( $string, \%entity2char ) + +=item _decode_entities( $string, \%entity2char, $expand_prefix ) + +This will in-place replace HTML entities in $string. The %entity2char +hash must be provided. Named entities not found in the %entity2char +hash are left alone. Numeric entities are expanded unless their value +overflow. + +The keys in %entity2char are the entity names to be expanded and their +values are what they should expand into. The values do not have to be +single character strings. If a key has ";" as suffix, +then occurrences in $string are only expanded if properly terminated +with ";". Entities without ";" will be expanded regardless of how +they are terminated for compatibility with how common browsers treat +entities in the Latin-1 range. + +If $expand_prefix is TRUE then entities without trailing ";" in +%entity2char will even be expanded as a prefix of a longer +unrecognized name. The longest matching name in %entity2char will be +used. This is mainly present for compatibility with an MSIE +misfeature. + + $string = "foo bar"; + _decode_entities($string, { nb => "@", nbsp => "\xA0" }, 1); + print $string; # will print "foo bar" + +This routine is exported by default. + +=item encode_entities( $string ) + +=item encode_entities( $string, $unsafe_chars ) + +This routine replaces unsafe characters in $string with their entity +representation. A second argument can be given to specify which characters to +consider unsafe. The unsafe characters is specified using the regular +expression character class syntax (what you find within brackets in regular +expressions). + +The default set of characters to encode are control chars, high-bit chars, and +the C<< < >>, C<< & >>, C<< > >>, C<< ' >> and C<< " >> characters. But this, +for example, would encode I<just> the C<< < >>, C<< & >>, C<< > >>, and C<< " +>> characters: + + $encoded = encode_entities($input, '<>&"'); + +and this would only encode non-plain ascii: + + $encoded = encode_entities($input, '^\n\x20-\x25\x27-\x7e'); + +This routine is exported by default. + +=item encode_entities_numeric( $string ) + +=item encode_entities_numeric( $string, $unsafe_chars ) + +This routine works just like encode_entities, except that the replacement +entities are always C<&#xI<hexnum>;> and never C<&I<entname>;>. For +example, C<encode_entities("r\xF4le")> returns "rôle", but +C<encode_entities_numeric("r\xF4le")> returns "rôle". + +This routine is I<not> exported by default. But you can always +export it with C<use HTML::EntitiesLite qw(encode_entities_numeric);> +or even C<use HTML::EntitiesLite qw(:DEFAULT encode_entities_numeric);> + +=back + +All these routines modify the string passed as the first argument, if +called in a void context. In scalar and array contexts, the encoded or +decoded string is returned (without changing the input string). + +If you prefer not to import these routines into your namespace, you can +call them as: + + use HTML::EntitiesLite (); + $decoded = HTML::EntitiesLite::decode($a); + $encoded = HTML::EntitiesLite::encode($a); + $encoded = HTML::EntitiesLite::encode_numeric($a); + +The module can also export the %char2entity and the %entity2char +hashes, which contain the mapping from all characters to the +corresponding entities (and vice versa, respectively). + +=head1 COPYRIGHT + +Copyright 1995-2006 Gisle Aas. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the same terms as Perl itself. + +=cut + +use strict; +use vars qw(@ISA @EXPORT @EXPORT_OK $VERSION); +use vars qw(%entity2char %char2entity); + +require 5.004; +require Exporter; +@ISA = qw(Exporter); + +# XXX modified: disable here, we not use decode_entities(). +# @EXPORT = qw(encode_entities decode_entities _decode_entities); +@EXPORT = qw(encode_entities); +@EXPORT_OK = qw(%entity2char %char2entity encode_entities_numeric); + +$VERSION = "3.69"; +sub Version { $VERSION; } + +# XXX modified: disable here, we not use decode_entities(). +# require HTML::Parser; # for fast XS implemented decode_entities + + +%entity2char = ( + # Some normal chars that have special meaning in SGML context + amp => '&', # ampersand +'gt' => '>', # greater than +'lt' => '<', # less than + quot => '"', # double quote + apos => "'", # single quote + + # PUBLIC ISO 8879-1986//ENTITIES Added Latin 1//EN//HTML + AElig => chr(198), # capital AE diphthong (ligature) + Aacute => chr(193), # capital A, acute accent + Acirc => chr(194), # capital A, circumflex accent + Agrave => chr(192), # capital A, grave accent + Aring => chr(197), # capital A, ring + Atilde => chr(195), # capital A, tilde + Auml => chr(196), # capital A, dieresis or umlaut mark + Ccedil => chr(199), # capital C, cedilla + ETH => chr(208), # capital Eth, Icelandic + Eacute => chr(201), # capital E, acute accent + Ecirc => chr(202), # capital E, circumflex accent + Egrave => chr(200), # capital E, grave accent + Euml => chr(203), # capital E, dieresis or umlaut mark + Iacute => chr(205), # capital I, acute accent + Icirc => chr(206), # capital I, circumflex accent + Igrave => chr(204), # capital I, grave accent + Iuml => chr(207), # capital I, dieresis or umlaut mark + Ntilde => chr(209), # capital N, tilde + Oacute => chr(211), # capital O, acute accent + Ocirc => chr(212), # capital O, circumflex accent + Ograve => chr(210), # capital O, grave accent + Oslash => chr(216), # capital O, slash + Otilde => chr(213), # capital O, tilde + Ouml => chr(214), # capital O, dieresis or umlaut mark + THORN => chr(222), # capital THORN, Icelandic + Uacute => chr(218), # capital U, acute accent + Ucirc => chr(219), # capital U, circumflex accent + Ugrave => chr(217), # capital U, grave accent + Uuml => chr(220), # capital U, dieresis or umlaut mark + Yacute => chr(221), # capital Y, acute accent + aacute => chr(225), # small a, acute accent + acirc => chr(226), # small a, circumflex accent + aelig => chr(230), # small ae diphthong (ligature) + agrave => chr(224), # small a, grave accent + aring => chr(229), # small a, ring + atilde => chr(227), # small a, tilde + auml => chr(228), # small a, dieresis or umlaut mark + ccedil => chr(231), # small c, cedilla + eacute => chr(233), # small e, acute accent + ecirc => chr(234), # small e, circumflex accent + egrave => chr(232), # small e, grave accent + eth => chr(240), # small eth, Icelandic + euml => chr(235), # small e, dieresis or umlaut mark + iacute => chr(237), # small i, acute accent + icirc => chr(238), # small i, circumflex accent + igrave => chr(236), # small i, grave accent + iuml => chr(239), # small i, dieresis or umlaut mark + ntilde => chr(241), # small n, tilde + oacute => chr(243), # small o, acute accent + ocirc => chr(244), # small o, circumflex accent + ograve => chr(242), # small o, grave accent + oslash => chr(248), # small o, slash + otilde => chr(245), # small o, tilde + ouml => chr(246), # small o, dieresis or umlaut mark + szlig => chr(223), # small sharp s, German (sz ligature) + thorn => chr(254), # small thorn, Icelandic + uacute => chr(250), # small u, acute accent + ucirc => chr(251), # small u, circumflex accent + ugrave => chr(249), # small u, grave accent + uuml => chr(252), # small u, dieresis or umlaut mark + yacute => chr(253), # small y, acute accent + yuml => chr(255), # small y, dieresis or umlaut mark + + # Some extra Latin 1 chars that are listed in the HTML3.2 draft (21-May-96) + copy => chr(169), # copyright sign + reg => chr(174), # registered sign + nbsp => chr(160), # non breaking space + + # Additional ISO-8859/1 entities listed in rfc1866 (section 14) + iexcl => chr(161), + cent => chr(162), + pound => chr(163), + curren => chr(164), + yen => chr(165), + brvbar => chr(166), + sect => chr(167), + uml => chr(168), + ordf => chr(170), + laquo => chr(171), +'not' => chr(172), # not is a keyword in perl + shy => chr(173), + macr => chr(175), + deg => chr(176), + plusmn => chr(177), + sup1 => chr(185), + sup2 => chr(178), + sup3 => chr(179), + acute => chr(180), + micro => chr(181), + para => chr(182), + middot => chr(183), + cedil => chr(184), + ordm => chr(186), + raquo => chr(187), + frac14 => chr(188), + frac12 => chr(189), + frac34 => chr(190), + iquest => chr(191), +'times' => chr(215), # times is a keyword in perl + divide => chr(247), + + ( $] > 5.007 ? ( + 'OElig;' => chr(338), + 'oelig;' => chr(339), + 'Scaron;' => chr(352), + 'scaron;' => chr(353), + 'Yuml;' => chr(376), + 'fnof;' => chr(402), + 'circ;' => chr(710), + 'tilde;' => chr(732), + 'Alpha;' => chr(913), + 'Beta;' => chr(914), + 'Gamma;' => chr(915), + 'Delta;' => chr(916), + 'Epsilon;' => chr(917), + 'Zeta;' => chr(918), + 'Eta;' => chr(919), + 'Theta;' => chr(920), + 'Iota;' => chr(921), + 'Kappa;' => chr(922), + 'Lambda;' => chr(923), + 'Mu;' => chr(924), + 'Nu;' => chr(925), + 'Xi;' => chr(926), + 'Omicron;' => chr(927), + 'Pi;' => chr(928), + 'Rho;' => chr(929), + 'Sigma;' => chr(931), + 'Tau;' => chr(932), + 'Upsilon;' => chr(933), + 'Phi;' => chr(934), + 'Chi;' => chr(935), + 'Psi;' => chr(936), + 'Omega;' => chr(937), + 'alpha;' => chr(945), + 'beta;' => chr(946), + 'gamma;' => chr(947), + 'delta;' => chr(948), + 'epsilon;' => chr(949), + 'zeta;' => chr(950), + 'eta;' => chr(951), + 'theta;' => chr(952), + 'iota;' => chr(953), + 'kappa;' => chr(954), + 'lambda;' => chr(955), + 'mu;' => chr(956), + 'nu;' => chr(957), + 'xi;' => chr(958), + 'omicron;' => chr(959), + 'pi;' => chr(960), + 'rho;' => chr(961), + 'sigmaf;' => chr(962), + 'sigma;' => chr(963), + 'tau;' => chr(964), + 'upsilon;' => chr(965), + 'phi;' => chr(966), + 'chi;' => chr(967), + 'psi;' => chr(968), + 'omega;' => chr(969), + 'thetasym;' => chr(977), + 'upsih;' => chr(978), + 'piv;' => chr(982), + 'ensp;' => chr(8194), + 'emsp;' => chr(8195), + 'thinsp;' => chr(8201), + 'zwnj;' => chr(8204), + 'zwj;' => chr(8205), + 'lrm;' => chr(8206), + 'rlm;' => chr(8207), + 'ndash;' => chr(8211), + 'mdash;' => chr(8212), + 'lsquo;' => chr(8216), + 'rsquo;' => chr(8217), + 'sbquo;' => chr(8218), + 'ldquo;' => chr(8220), + 'rdquo;' => chr(8221), + 'bdquo;' => chr(8222), + 'dagger;' => chr(8224), + 'Dagger;' => chr(8225), + 'bull;' => chr(8226), + 'hellip;' => chr(8230), + 'permil;' => chr(8240), + 'prime;' => chr(8242), + 'Prime;' => chr(8243), + 'lsaquo;' => chr(8249), + 'rsaquo;' => chr(8250), + 'oline;' => chr(8254), + 'frasl;' => chr(8260), + 'euro;' => chr(8364), + 'image;' => chr(8465), + 'weierp;' => chr(8472), + 'real;' => chr(8476), + 'trade;' => chr(8482), + 'alefsym;' => chr(8501), + 'larr;' => chr(8592), + 'uarr;' => chr(8593), + 'rarr;' => chr(8594), + 'darr;' => chr(8595), + 'harr;' => chr(8596), + 'crarr;' => chr(8629), + 'lArr;' => chr(8656), + 'uArr;' => chr(8657), + 'rArr;' => chr(8658), + 'dArr;' => chr(8659), + 'hArr;' => chr(8660), + 'forall;' => chr(8704), + 'part;' => chr(8706), + 'exist;' => chr(8707), + 'empty;' => chr(8709), + 'nabla;' => chr(8711), + 'isin;' => chr(8712), + 'notin;' => chr(8713), + 'ni;' => chr(8715), + 'prod;' => chr(8719), + 'sum;' => chr(8721), + 'minus;' => chr(8722), + 'lowast;' => chr(8727), + 'radic;' => chr(8730), + 'prop;' => chr(8733), + 'infin;' => chr(8734), + 'ang;' => chr(8736), + 'and;' => chr(8743), + 'or;' => chr(8744), + 'cap;' => chr(8745), + 'cup;' => chr(8746), + 'int;' => chr(8747), + 'there4;' => chr(8756), + 'sim;' => chr(8764), + 'cong;' => chr(8773), + 'asymp;' => chr(8776), + 'ne;' => chr(8800), + 'equiv;' => chr(8801), + 'le;' => chr(8804), + 'ge;' => chr(8805), + 'sub;' => chr(8834), + 'sup;' => chr(8835), + 'nsub;' => chr(8836), + 'sube;' => chr(8838), + 'supe;' => chr(8839), + 'oplus;' => chr(8853), + 'otimes;' => chr(8855), + 'perp;' => chr(8869), + 'sdot;' => chr(8901), + 'lceil;' => chr(8968), + 'rceil;' => chr(8969), + 'lfloor;' => chr(8970), + 'rfloor;' => chr(8971), + 'lang;' => chr(9001), + 'rang;' => chr(9002), + 'loz;' => chr(9674), + 'spades;' => chr(9824), + 'clubs;' => chr(9827), + 'hearts;' => chr(9829), + 'diams;' => chr(9830), + ) : ()) +); + + +# Make the opposite mapping +while (my($entity, $char) = each(%entity2char)) { + $entity =~ s/;\z//; + $char2entity{$char} = "&$entity;"; +} +delete $char2entity{"'"}; # only one-way decoding + +# Fill in missing entities +for (0 .. 255) { + next if exists $char2entity{chr($_)}; + $char2entity{chr($_)} = "&#$_;"; +} + +my %subst; # compiled encoding regexps + +sub encode_entities +{ + return undef unless defined $_[0]; + my $ref; + if (defined wantarray) { + my $x = $_[0]; + $ref = \$x; # copy + } else { + $ref = \$_[0]; # modify in-place + } + if (defined $_[1] and length $_[1]) { + unless (exists $subst{$_[1]}) { + # Because we can't compile regex we fake it with a cached sub + my $chars = $_[1]; + $chars =~ s,(?<!\\)([]/]),\\$1,g; + $chars =~ s,(?<!\\)\\\z,\\\\,; + my $code = "sub {\$_[0] =~ s/([$chars])/\$char2entity{\$1} || num_entity(\$1)/ge; }"; + $subst{$_[1]} = eval $code; + die( $@ . " while trying to turn range: \"$_[1]\"\n " + . "into code: $code\n " + ) if $@; + } + &{$subst{$_[1]}}($$ref); + } else { + # Encode control chars, high bit chars and '<', '&', '>', ''' and '"' + $$ref =~ s/([^\n\r\t !\#\$%\(-;=?-~])/$char2entity{$1} || num_entity($1)/ge; + } + $$ref; +} + +sub encode_entities_numeric { + local %char2entity; + return &encode_entities; # a goto &encode_entities wouldn't work +} + + +sub num_entity { + sprintf "&#x%X;", ord($_[0]); +} + +# Set up aliases +*encode = \&encode_entities; +*encode_numeric = \&encode_entities_numeric; +*encode_numerically = \&encode_entities_numeric; +# *decode = \&decode_entities; + +1; diff --git a/cpan/lib/HTML/FromText.pm b/cpan/lib/HTML/FromText.pm index 2b7f1753..29089819 100644 --- a/cpan/lib/HTML/FromText.pm +++ b/cpan/lib/HTML/FromText.pm @@ -1,736 +1,846 @@ -require 5.004; +package HTML::FromText; + +=head1 NAME + +HTML::FromText - Convert plain text to HTML. + +=head1 SYNOPSIS + + use HTML::FromText; + text2html( $text, %options ); + + # or + + use HTML::FromText (); + my $t2h = HTML::FromText->new( \%options ); + my $html = $t2h->parse( $html ); + +=cut + use strict; +use Scalar::Util qw[blessed]; +use HTML::EntitiesLite qw[encode_entities]; +use Text::Tabs qw[expand]; +use Email::Find::addrspec qw[$Addr_spec_re]; +use Exporter::Lite; + +use vars qw[$VERSION @EXPORT @DECORATORS $PROTOCOLS]; + +$VERSION = '2.05'; +@EXPORT = qw[text2html]; +@DECORATORS = qw[urls email bold underline]; +$PROTOCOLS = qr/ + afs | cid | ftp | gopher | + http | https | mid | news | + nntp | prospero | telnet | wais + /x; -package HTML::FromText; -use Carp; -use Exporter; -use Text::Tabs 'expand'; -use vars qw($RCSID $VERSION $QUIET @EXPORT @ISA); - -@ISA = qw(Exporter); -@EXPORT = qw(text2html); -$RCSID = q$Id: FromText.pm,v 1.14 1999/10/06 10:53:37 garethr Exp $; -$VERSION = '1.005'; -$QUIET = 0; - -# This list of protocols is taken from RFC 1630: "Universal Resource -# Identifiers in WWW". The protocol "file" is omitted because -# experience suggests that it results in many false positives; "https" -# postdates RFC 1630. The protocol "mailto" is handled separately, by -# the email address matching code. - -my $protocol = join '|', - qw(afs cid ftp gopher http https mid news nntp prospero telnet wais); - -# The regular expressions matching email addresses use the following -# syntax elements from RFC 822. I can't use the full details of -# structured field bodies, because that would give too many false -# positives. (See Tom Christiansen's ckaddr.gz for a full -# implementation of the RFC 822.) -# -# addr-spec = local-part "@" domain -# local-part = word *("." word) -# word = atom -# domain = sub-domain *("." sub-domain) -# sub-domain = domain-ref -# domain-ref = atom -# atom = 1*<any CHAR except specials, SPACE and CTLs> -# specials = "(" / ")" / "<" / ">" / "@" / "," / ";" / ":" / "\" -# / <"> / "." / "[" / "]" -# -# I have ignored quoting, domain literals and comments. -# -# Note that '&' can legally appear in email addresses (for example, -# 'fred&barney@stonehenge.com'). If the 'metachars' option is passed to -# text2html then I must use '&' to recognize '&'. Thus the regular -# expression $atom[0] recognizes an atom in the case where the option -# 'metachars' is false; $atom[1] recognizes an atom in the case where -# 'metachars' is true. Similarly for the regular expressions $email[0] -# and $email[1], which recognize email addresses. - -my @atom = - ( '[!#$%&\'*+\\-/0123456789=?ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz{|}~]+', - '(?:&|[!#$%\'*+\\-/0123456789=?ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz{|}~])+' ); - -my @email = ( "$atom[0](\\.$atom[0])*\@$atom[0](\\.$atom[0])*", - "$atom[1](\\.$atom[1])*\@$atom[1](\\.$atom[1])*" ); - -my @alignments = ( '', '', ' ALIGN="RIGHT"', ' ALIGN="CENTER"' ); - -sub string2html ($$) { - my $options = $_[1]; - for ($_[0]) { # Modify in-place. - - # METACHARS: mark up HTML metacharacters as corresponding entities. - if ($options->{metachars}) { - s/&/&/g; - s/</</g; - s/>/>/g; - s/\"/"/g; - } +=head1 DESCRIPTION - # EMAIL, URLS: spot electronic mail addresses and turn them into - # links. Note (1) if `urls' is set but not `email', then only - # addresses prefixed by `mailto:' will be marked up; (2) that we leave - # the `mailto:' prefix in the anchor text. - if ($options->{email} or $options->{urls}) { - s|((?:mailto:)?)($email[$options->{metachars}?1:0])| - ($options->{email} or $1) - ? "<TT><A HREF=\"mailto:$2\">$1$2</A></TT>" : $2|gex; - } +C<HTML::FromText> converts plain text to HTML. There are a handfull of +options that shape the conversion. There is a utility function, +C<text2html>, that's exported by default. This function is simply a short- +cut to the Object Oriented interface described in detail below. - # URLS: mark up URLs as links (note that `mailto' links are handled - # above). - if ($options->{urls}) { - s|\b((?:$protocol):\S+[\w/])|<TT><A HREF="$1">$1</A></TT>|g; - } +=head2 Methods - # BOLD: mark up words in *asterisks* as bold. - if ($options->{bold}) { - s#(^|\s)\*([^*]+)\*(?=\s|$)#$1<B>$2</B>#g; - } +The following methods may be used as the public interface. - # UNDERLINE: mark up words in _underscores_ as underlined. - if ($options->{underline}) { - s#(^|\s)_([^_]+?)_(?=\s|$)#$1<U>$2</U>#g; - } - } +=head3 new - return $_[0]; -} + my $t2h = HTML::FromText->new({ + paras => 1, + blockcode => 1, + tables => 1, + bullets => 1, + numbers => 1, + urls => 1, + email => 1, + bold => 1, + underline => 1, + }); -sub text2html { - local $_ = shift; # Take a copy; don't modify in-place. - return $_ unless $_; - - my %options = ( metachars => 1, @_ ); - - # Check options for sanity. - unless ($QUIET) { - carp "text2html: `spaces' will be ignored since `lines' is not specified" - if $options{spaces} and not $options{lines}; - if ($options{paras}) { - if ($options{blockparas}) { - foreach my $o (qw(blockquotes blockcode)) { - carp "text2html: `$o' will be ignored since `blockparas' is specified" if $options{$o}; - } - } elsif ($options{blockcode} and $options{blockquotes}) { - carp "text2html: `blockquotes' will be ignored since `blockcode' is specified"; - } - } else { - foreach my $o (qw(bullets numbers blockquotes blockparas blockcode - title headings tables)) { - carp "text2html: `$o' will be ignored since `paras' is not specified" - if $options{$o}; - } - } - } - - # Expand tabs. - $_ = join "\n", expand(split /\r?\n/); - - # PRE: put text in <PRE> element. - if ($options{pre}) { - string2html($_, \%options); - s|^|<PRE>|; - s|$|</PRE>|; - } - - # LINES: preserve line breaks from original text. - elsif ($options{lines}) { - string2html($_, \%options); - s/\n/<BR>\n/gm; - - # SPACES: preserve spaces from original text. - s/ / /g if $options{spaces}; - } - - # PARAS: treat text as sequence of paragraphs. - elsif ($options{paras}) { - my @paras; - - # Remove initial and final blank lines. - s/^(?:\s*?\n)+//; - s/(?:\n\s*?)+$//; - - # Split on a different regexp depending on what kinds of paragraphs - # will be recognised later. The idea is that bulleted lists like - # this: - # - # * item 1 - # * item 2 - # - # will be recognised as multiple paragraphs if the 'bullets' option - # is supplied, but as a single paragraph otherwise. (Similarly for - # numbered lists). - if ($options{bullets} and $options{numbers}) { - @paras = split - /(?:\s*\n)+ # (0 or more blank lines, followed by LF) - (?:\s*\n # Either 1 or more blank lines, or - |(?=\s*[*-]\s+ # bulleted item follows, or - |\s*(?:\d+)[.\)\]]?\s+)) # numbered item follows - /x; - } elsif ($options{bullets}) { - @paras = split - /(?:\s*\n)+ # (0 or more blank lines, followed by LF) - (?:\s*\n # Either 1 or more blank lines, or - |(?=\s*[*-]\s+)) # bulleted item follows. - /x; - } elsif ($options{numbers}) { - @paras = split - /(?:\s*\n)+ # (0 or more blank lines, followed by LF) - (?:\s*\n # Either 1 or more blank lines, or - |(?=\s*(?:\d+)[.\)\]]?\s+)) # numbered item follows. - /x; - } else { - @paras = split - /\s*\n(?:\s*\n)+ # 1 or more blank lines. - /x; - } +Constructs a new C<HTML::FromText> object using the given +configuration. The resulting object can parse lots of objects using the +C<parse> method. - my $last = ''; # List type (OL/UL) of last paragraph - my $this; # List type (OL/UL) of this paragraph - my $first = 1; # True if this is first paragraph - - foreach (@paras) { - my (@rows,@starts,@ends); - $this = ''; - - # TITLE: mark up first paragraph as level-1 heading. - if ($options{title} and $first) { - string2html($_,\%options); - s|^|<H1>|; - s|$|</H1>|; - } - - # HEADINGS: mark up paragraphs with numbers at the start of the - # first line as headings. - elsif ($options{headings} and /^(\d+(\.\d+)*)\.?\s/) { - my $number = $1; - my $level = 1 + ($number =~ tr/././); - $level = 6 if $level > 6; - string2html($_,\%options); - s|^|<H$level>|; - s|$|</H$level>|; - } - - # BULLETS: mark up paragraphs starting with bullets as items in an - # unnumbered list. - elsif ($options{bullets} and /^\s*[*-]\s+/) { - string2html($_,\%options); - s/^\s*[*-]\s+/<LI><P>/; - s|$|</P>|; - $this = 'UL'; - } - - # NUMBERS: mark up paragraphs starting with numbers as items in a - # numbered list. - elsif ($options{numbers} and /^\s*(\d+)[.\)\]]?\s+/) { - string2html($_,\%options); - s/^\s*(\d+)[.\)\]]?\s+/<LI VALUE="$1"><P>/; - s|$|</P>|; - $this = 'OL'; - } - - # TABLES: spot and mark up tables. We combine the lines of the - # paragraph using the string bitwise or (|) operator, the result - # being in $spaces. A character in $spaces is a space only if - # there was a space at that position in every line of the - # paragraph. $space can be used to search for contiguous spaces - # that occur on all lines of the paragraph. If this results in at - # least two columns, the paragraph is identified as a table. - # - # Note that this option appears before the various 'blockquotes' - # options because a table may well have whitespace to the left, in - # which case it must not be incorrectly recognised as a - # blockquote. - elsif ($options{tables} and do { - @rows = split /\n/, $_; - my $spaces; - my $max = 0; - my $min = length; - foreach my $row (@rows) { - ($spaces |= $row) =~ tr/ /\xff/c; - $min = length $row if length $row < $min; - $max = length $row if $max < length $row; - } - $spaces = substr $spaces, 0, $min; - push(@starts, 0) unless $spaces =~ /^ /; - while ($spaces =~ /((?:^| ) +)(?=[^ ])/g) { - push @ends, pos($spaces) - length $1; - push @starts, pos($spaces); - } - shift(@ends) if $spaces =~ /^ /; - push(@ends, $max); - - # Two or more rows and two or more columns indicate a table. - 2 <= @rows and 2 <= @starts - }) { - # For each column, guess whether it should be left, centre or - # right aligned by examining all cells in that column for space - # to the left or the right. A simple majority among those cells - # that actually have space to one side or another decides (if no - # alignment gets a majority, left alignment wins by default). - - my @align; - foreach my $col (0 .. $#starts) { - my @count = (0, 0, 0, 0); - foreach my $row (@rows) { - my $width = $ends[$col] - $starts[$col]; - my $cell = substr $row, $starts[$col], $width; - ++ $count[($cell =~ /^ / ? 2 : 0) - + ($cell =~ / $/ || length($cell) < $width ? 1 : 0)]; - } - $align[$col] = 0; - my $population = $count[1] + $count[2] + $count[3]; - foreach (1 .. 3) { - if ($count[$_] * 2 > $population) { - $align[$col] = $_; - last; - } - } - } +Options to C<new> are passed by name, with the value being either true +or false. If true, the option will be turned on. If false, it will be +turned off. The following outlines all the options. - foreach my $row (@rows) { - $row = join '', '<TR>', (map { - my $cell = substr $row, $starts[$_], $ends[$_] - $starts[$_]; - $cell =~ s/^ +//; - $cell =~ s/ +$//; - string2html($cell,\%options); - ('<TD', $alignments[$align[$_]], '>', $cell, '</TD>') - } 0 .. $#starts), '</TR>'; - } - my $tag = $starts[0] == 0 ? 'P' : 'BLOCKQUOTE'; - $_ = join "\n", "<$tag><TABLE>", @rows, "</TABLE></$tag>"; - } - - # BLOCKPARAS, BLOCKCODE, BLOCKQUOTES: mark up indented paragraphs - # as block quotes of various kinds. - elsif (($options{blockparas} or $options{blockquotes} - or $options{blockcode}) and /^(\s+).*(?:\n\1.*)*$/) { - string2html($_,\%options); - - # Every line in the paragraph starts with at white space, the common - # whitespace being in $1. Remove the common initial whitespace, - s/^$1//gm; - - # BLOCKPARAS: treat as a paragraph. - if ($options{blockparas}) { - s|^|<P>|; - s|$|</P>|; - } - - # BLOCKCODE, BLOCKQUOTES: preserve line breaks. - else { - s/\n/<BR>\n/gm; - - # BLOCKCODE: preserve spaces, use fixed-width font. - if ($options{blockcode}) { - s| | |g; - s|^|<TT>|; - s|$|</TT>|; - } - } - s|^|<BLOCKQUOTE>|; - s|$|</BLOCKQUOTE>|; - } - - # Didn't match any of the above, so just an ordinary paragraph. - else { - string2html($_,\%options); - s|^|<P>|; - s|$|</P>|; - } - - # Insert <UL>, </UL>, <OL> or </OL> if this paragraph belongs to a - # different list type than the previous one. - if ($this ne $last) { - s|^|<$this>| if ($this ne ''); - s|^|</$last>| if ($last ne ''); - } - $last = $this; - $first = 0; - } - if ($this ne '') { - push @paras, "</$this>"; - } - $_ = join "\n", @paras; - } - - # None of PRE, LINES, PARAS specified: apply basic transformations. - else { - string2html($_,\%options); - } - return $_; -} +=head4 Decorators -1; +=over 5 -__END__ +=item metachars -=head1 NAME +This option is on by default. -HTML::FromText - mark up text as HTML +All characters that are unsafe for HTML display will be encoded using +C<HTML::Entities::encode_entities()>. -=head1 SYNOPSIS +=item urls - use HTML::FromText; - print text2html($text, urls => 1, paras => 1, headings => 1); +This option is off by default. -=head1 DESCRIPTION +Replaces URLs with links. -The C<text2html> function marks up plain text as HTML. By default it -expands tabs and converts HTML metacharacters into the corresponding -entities. More complicated transformations, such as splitting the text -into paragraphs or marking up bulleted lists, can be carried out by -setting the appropriate options. +=item email -=head1 SUMMARY OF OPTIONS +This option is off by default. -These options always apply: +Replaces email addresses with C<mailto:> links. - metachars Convert HTML metacharacters to entity references - urls Convert URLs to links - email Convert email addresses to links - bold Mark up words with *asterisks* in bold - underline Mark up words with _underscores_ as underlined +=item bold -You can then choose to treat the text according to one of these options: +This option is off by default. - pre Treat text as preformatted - lines Treat text as line-oriented - paras Treat text as paragraph-oriented +Replaces text surrounded by asterisks (C<*>) with the same text +surrounded by C<strong> tags. -(If more than one of these is specified, C<pre> takes precedence over -C<lines> which takes precedence over C<paras>.) The following option -applies when the C<lines> option is specified: +=item underline + +This option is off by default. - spaces Preserve spaces from the original text +Replaces text surrownded by underscores (C<_>) with the same text +surrounded by C<span> tags with an underline style. -The following options apply when the C<paras> option is specified: +=back - blockparas Mark up indented paragraphs as block quote - blockquotes Ditto, also preserve lines from original - blockcode Ditto, also preserve spaces from original - bullets Mark up bulleted paragraphs as unordered list - headings Mark up headings - numbers Mark up numbered paragraphs as ordered list - tables Mark up tables - title Mark up first paragraph as level 1 heading +=head4 Output Modes -C<text2html> will issue a warning if it is passed nonsensical options, -for example C<headings> but not C<paras>. These warnings can be -supressed by setting $HTML::FromText::QUIET to true. +The following are three output modes and the options associated with +them. They are listed in order of precidence. If none of these modes are +supplied, the basic decorators are applied to the text in whole. -=head1 OPTIONS +=over 5 -=over 4 +=item B<pre> + +This option is off by default. + +Wraps the entire text in C<pre> tags. + +=item B<lines> + +This option is off by default. + +Preserves line breaks by inserting C<br> tags at the end of each line. + +This mode has further options. + +=over 5 + +=item spaces + +This option is off by default. + +All spaces are HTML encoded. + +=back + +=item B<paras> + +This option is off by default. + +Preserves paragraphs by wrapping them in C<p> tags. + +This mode has further options. + +=over 5 + +=item bullets + +This option is off by default. + +Convert bulleted lists into unordered lists (C<ul>). Bullets can be +either an asterisk (C<*>) or a hyphen (C<->). Lists can be nested. + +=item numbers + +This option is off by default. + +Convert numbered lists into ordered lists (C<ol>). Numbered lists are +identified by numerals. Lists may be nested. + +=item headings + +This option is off by default. + +Convert paragraphs identified as headings into HTML headings at +the appropriate level. The heading C<1. Top> would be heading +level one (C<h1>). The heading C<2.5.1. Blah> would be heading +level three (C<h3>). + +=item title + +This option is off by default. + +Convert the first paragraph to a heading level one (C<h1>). + +=item tables + +This option is off by default. + +Convert paragraphs identified as tables to HTML tables. Tables are two +or more rows and two or more columns. Columns should be separated by two +or more spaces. + +=back + +The following options apply specifically to indented paragraphs. They +are listed in order of precidence. + +=over 5 =item blockparas +This option is off by default. + +Convert indented paragraphs to block quotes using the C<blockquote> tag. + =item blockquotes +Convert indented paragraphs as C<blockparas> would, but also preserving +line breaks. + =item blockcode -These options cause to C<text2html> to spot paragraphs where every line -begins with whitespace, and mark them up as block quotes. If more than -one of these options is specified, C<blockparas> takes precedence over -C<blockcode>, which takes precedence over C<blockquotes>. All three -options are ignored unless the C<paras> option is also set. +Convert indented paragraphs as C<blockquotes> would, but also preserving +spaces using C<pre> tags. -The C<blockparas> option marks up the paragraph as a block quote with no -other changes. For example, +=back - Turing wrote, +=back - I propose to consider the question, - "Can machines think?" +=cut -becomes +sub new { + my ($class, $options) = @_; + $options ||= {}; + $class->_croak("Options must be a hash reference") + if ref($options) ne 'HASH'; + + my %options = ( + metachars => 1, + urls => 0, + email => 0, + bold => 0, + underline => 0, + + pre => 0, + + lines => 0, + spaces => 0, + + paras => 0, + bullets => 0, + numbers => 0, + headings => 0, + title => 0, + blockparas => 0, + blockquotes => 0, + blockcode => 0, + tables => 0, + + %{ $options }, + ); + + my %self = ( + options => \%options, + text => '', + html => '', + ); + + return bless \%self, blessed($class) || $class; +} - <P>Turing wrote,</P> - <BLOCKQUOTE>I propose to consider the question, - "Can machines think?"</BLOCKQUOTE> +=head3 parse -The C<blockquotes> option preserves line breaks in the original text. -For example, + my $html = $t2h->parse( $text ); - From "The Waste Land": +Parses text supplied as a single scalar string and returns the HTML as a +single scalar string. All the tabs in your text will be expanded using +C<Text::Tabs::expand()>. - Phlebas the Phoenecian, a fortnight dead, - Forgot the cry of gulls, and the deep sea swell +=cut -becomes +sub parse { + my ($self, $text) = @_; - <P>From "The Waste Land":</P> - <BLOCKQUOTE>Phlebas the Phoenecian, a fortnight dead,<BR> - Forgot the cry of gulls, and the deep sea swell</BLOCKQUOTE> + $text = join "\n", expand( split /\n/, $text ); -The C<blockcode> option preserves line breaks and spaces in the original -text and renders the paragraph in a fixed-width font. For example: + $self->{text} = $text; + $self->{html} = $text; + $self->{paras} = undef; - Here's how to output numbers with commas: + my $options = $self->{options}; - sub commify { - local $_ = shift; - 1 while s/^(-?\d+)(\d{3})/$1,$2/; - $_; - } + $self->metachars if $options->{metachars}; -becomes + if ( $options->{pre} ) { $self->pre } + elsif ( $options->{lines} ) { $self->lines } + elsif ( $options->{paras} ) { $self->paras } - <P>Here's how to output numbers with commas:</P> - <BLOCKQUOTE><TT>sub commify {<BR> - local $_ = shift;<BR> - 1 while s/^(-?\d+)(\d{3})/$1,$2/;<BR> - $_;<BR> - }</TT></BLOCKQUOTE> + $options->{$_} and $self->$_ foreach @DECORATORS; -=item bold + return $self->{html}; +} -Words surrounded with asterisks are marked up in bold, so C<*abc*> -becomes C<E<lt>BE<gt>abcE<lt>/BE<gt>>. +=head2 Functions -=item bullets +=head3 text2html -Spots bulleted paragraphs (beginning with optional whitespace, an -asterisk or hyphen, and whitespace) and marks them up as an unordered -list. Bulleted paragraphs don't have to be separated by blank lines. -For example, + my $html = text2html( + $text, + urls => 1, + email => 1, + ); - Shopping list: +Functional interface that just wraps the OO interface. This function is +exported by default. If you don't want it you can C<require> the module +or C<use> it with an empty list. - * apples - * pears + require HTML::FromText; + # or ... + use HTML::FromText (); -becomes +=cut - <P>Shopping list:</P> - <UL><LI><P>apples</P> - <LI><P>pears</P> - </UL> +sub text2html { + my ($text, %options) = @_; + HTML::FromText->new(\%options)->parse($text); +} -This option is ignored unless the C<paras> option is set. +=head2 Subclassing + +B<Note:> At the time of this release, the internals of C<HTML::FromText> +are in a state of development and cannot be expected to stay the same +from release to release. I expect that release version B<3.00> will be +analogous to a C<1.00> release of other software. This is because the +current maintainer has rewritten this distribution from the ground up +for the C<2.x> series. You have been warned. + +The following methods may be used for subclassing C<HTML::FromText> +to create your own text to HTML conversions. Each of these methods +is passed just one argument, the object (C<$self>), unless +otherwise stated. + +The structure of C<$self> is as follows for this release. + + { + options => { + option_name => $value, + ... + }, + text => $text, # as passed to parse(), with tabs expanded + html => $html, # the HTML that will be returned from parse() + } -=item email +=head3 pre -Spots email addresses in the text and converts them to links. For example +Used when C<pre> mode is specified. - Mail me at web@perl.com. +Should set C<< $self->{html} >>. -becomes +Return value is ignored. - Mail me at <TT><A HREF="mailto:web@perl.com">web@perl.com</A></TT>. +=cut -=item headings +sub pre { + my ($self) = @_; + $self->{html} = join $self->{html}, '<pre class="hft-pre">', '</pre>'; +} -Spots headings (paragraphs starting with numbers) and marks them up as -headings of the appropriate level. For example, +=head3 lines - 1. Introduction +Used when C<lines> mode is specified. - 1.1 Background +Implements the C<spaces> option internally when the option is set to a +true value. - 1.1.1 Previous work +Should set C<< $self->{html} >>. - 2. Conclusion +Return value is ignored. -becomes +=cut - <H1>1. Introduction</H1> - <H2>1.1 Background</H2> - <H3>1.1.1 Previous work</H3> - <H1>2. Conclusion</H1> +sub lines { + my ($self) = @_; + $self->{html} =~ s[ ][ ]g if $self->{options}->{spaces}; + $self->{html} =~ s[$][<br />]gm; + $self->{html} =~ s[^][<div class="hft-lines">]; + $self->{html} =~ s[$][</div>]; +} -This option is ignored unless the C<paras> option is set. +=head3 paras -=item lines +Used when the C<paras> mode is specified. -Formats the text so as to preserve line breaks. For example, +Splits C<< $self->{text} >> into paragraphs internally and sets up +C<< $self->{paras} >> as follows. - Line 1 - Line 2 + paras => { + 0 => { + text => $text, # paragraph text + html => $html, # paragraph html + }, + ... # and so on for all paragraphs + }, -becomes +Implements the C<title> option internally when the option is turned on. - Line 1<BR> - Line 2 +Converts any normal paragraphs to HTML paragraphs (surrounded by C<p> +tags) internally. -If two or more of the options C<pre>, C<lines> and C<paras> are set, -then C<pre> takes precedence over C<lines>, which takes precedence over -C<paras>. +Should set C<< $self->{html} >>. -=item metachars +Return value is ignored. -Converts HTML metacharacters into their corresponding entity references. -Ampersand (C<E<amp>>) becomes C<E<amp>amp;>, less than (C<E<lt>>) -becomes C<E<amp>lt;>, greater than (C<E<gt>>) becomes C<E<amp>gt;>, and -quote (") becomes C<E<amp>quot;>. This option is 1 by default. +=cut -=item numbers +sub paras { + my ($self) = @_; -Spots numbered paragraphs (beginning with whitespace, digits, an -optional period/parenthesis/bracket, and whitespace) and marks them up -as an ordered list. Numbered paragraphs don't have to be separated by -blank lines. For example, + my $options = $self->{options}; + my @paras = split /\n{2,}/, $self->{html}; + my %paras = map { $_, { text => $paras[$_], html => undef } } 0 .. $#paras; + $self->{paras} = \%paras; - To do: + $self->{paras}->{0}->{html} = join( + $self->{paras}->{0}->{text}, + q[<h1 class="hft-title">], "</h1>\n" + ) if $options->{title}; - 1. Write thesis - 2. Submit it - 3. Celebrate + $self->headings if $options->{headings}; + $self->bullets if $options->{bullets}; + $self->numbers if $options->{numbers}; -becomes + $self->tables if $options->{tables}; - <P>To do:</P> - <OL><LI VALUE="1"><P>Write thesis</P> - <LI VALUE="2"><P>Submit it</P> - <LI VALUE="3"><P>Celebrate</P> - </OL> + if ( $options->{blockparas} ) { $self->blockparas } + elsif ( $options->{blockquotes} ) { $self->blockquotes } + elsif ( $options->{blockcode} ) { $self->blockcode } -This option is ignored unless the C<paras> option is set. + $self->_manipulate_paras(sub { qq[<p class="hft-paras">$_[0]</p>\n] }); -=item paras + $self->{html} = join "\n", map $paras{$_}->{html}, + sort { $a <=> $b } keys %paras; +} -Format the text into paragraphs. Paragraphs are separated by one or -more blank lines. For example, +=head3 headings - Paragraph 1 +Used to format headings when the C<headings> option is turned on. - Paragraph 2 +Return value is ignored. -becomes +=cut - <P>Paragraph 1</P> - <P>Paragraph 2</P> +sub headings { + my ($self) = @_; + my $heading = qr/\d+\./; -If two or more of the options C<pre>, C<lines> and C<paras> are set, -then C<pre> takes precedence over C<lines>, which takes precedence over -C<paras>. + $self->_manipulate_paras(sub{ + my ($text) = @_; + return unless $text =~ m[^((?:$heading)+)\s+]; -=item pre + my $depth; $depth++ for split /\./, $1; -Wrap the whole input in a C<E<lt>PREE<gt>> element. For example, + qq[<h$depth class="hft-headings">$text</h$depth>\n]; + }); +} - preformatted - text +=head3 bullets -becomes +Format bulleted lists when the C<bullets> option is turned on. - <PRE>preformatted - text</PRE> +Return value is ignored. -If two or more of the options C<pre>, C<lines> and C<paras> are set, -then C<pre> takes precedence over C<lines>, which takes precedence over -C<paras>. +=cut -=item spaces +sub bullets { + my ($self) = @_; + $self->_format_list( qr/[*]/, 'ul', 'hft-bullets' ); + $self->_format_list( qr/[-]/, 'ul', 'hft-bullets' ); +} -Preserves spaces throughout the text. For example, +=head3 numbers - Line 1 - Line 2 - Line 3 +Format numbered lists when the C<numbers> option is turned on. -becomes +Return value is ignored. - Line 1<BR> - Line 2<BR> - Line 3 +=cut -This option is ignored unless the C<lines> option is set. +sub numbers { + my ($self) = @_; + $self->_format_list( qr/[0-9]/, 'ol', 'hft-numbers'); +} -=item tables +=head3 tables -Spots tables and marks them up appropriately. Columns must be separated -by two or more spaces (this prevents accidental incorrect recognition of -a paragraph where interword spaces happen to line up). If there are two -or more rows in a paragraph and all rows share the same set of (two or -more) columns, the paragraph is assumed to be a table. For example +Format tables when the C<tables> option is turned on. - -e File exists. - -z File has zero size. - -s File has nonzero size (returns size). +Return value is ignored. -becomes +=cut - <P><TABLE> - <TR><TD>-e</TD><TD>File exists.</TD></TR> - <TR><TD>-z</TD><TD>File has zero size.</TD></TR> - <TR><TD>-s</TD><TD>File has nonzero size (returns size).</TD></TR> - </TABLE></P> +sub tables { + my ($self) = @_; -C<text2html> guesses for each column whether it is intended to be left, -centre or right aligned. + $self->_manipulate_paras(sub{ + my ($text) = $self->_remove_indent( $_[0] ); -This option is ignored unless the C<paras> option is set. + my @lines = split /\n/, $text; + my $columns = $self->_table_find_columns( + $self->_table_initial_spaces( split //, $lines[0] ), + [ @lines[1 .. $#lines] ], + ); -=item title + return unless $columns; + $self->_table_create( $columns, \@lines ); + }); +} -Formats the first paragraph of the text as a first-level heading. -For example, +=head3 blockparas - Paragraph 1 +Used when the C<blockparas> option is turned on. - Paragraph 2 +Return value is ignored. -becomes +=cut - <H1>Paragraph 1</H1> - <P>Paragraph 2</P> +sub blockparas { + my ($self) = @_; + my $paras = $self->{paras}; + + $self->_manipulate_paras(sub{ + my ($text) = $self->_remove_indent( $_[0], 1 ); + my ($pnum, $paras) = @_[1,2]; + return unless $text; + + $self->_consolidate_blocks( + ( exists $paras->{$pnum - 1} ? $paras->{$pnum -1} : undef ), + 'blockparas', 1, + qq[<blockquote class="hft-blockparas"><p>$text</p></blockquote>\n], + ); + }); +} -This option is ignored unless the C<paras> option is set. +=head3 blockquotes -=item underline +Used when the C<blockquotes> option is turned on. -Words surrounded with underscores are marked up with underline, so C<_abc_> -becomes C<E<lt>UE<gt>abcE<lt>/UE<gt>>. +Return value is ignored. -=item urls +=cut -Spots Uniform Resource Locators (URLs) in the text and converts them -to links. For example +sub blockquotes { + my ($self) = @_; + my $paras = $self->{paras}; - See https://perl.com/. + $self->_manipulate_paras(sub { + my ($text) = $self->_remove_indent( $_[0], 1 ); + return unless $text; -becomes + $text =~ s[\n|$][<br />\n]g; - See <TT><A HREF="https://perl.com/">https://perl.com/</A></TT>. + qq[<blockquote class="hft-blockquotes"><div>$text</div></blockquote>\n]; + }); +} -=back +=head3 blockcode -=head1 SEE ALSO +Used when the C<blockcode> option is turned on. + +Return value is ignored. + +=cut + +sub blockcode { + my ($self) = @_; + my $paras = $self->{paras}; + + $self->_manipulate_paras(sub { + my ($text) = $self->_remove_indent( $_[0], 1 ); + my ($pnum, $paras) = @_[1,2]; + return unless $text; + + $text =~ s[^][<pre>]; + $text =~ s[$][</pre>]; + $self->_consolidate_blocks( + ( exists $paras->{$pnum - 1} ? $paras->{$pnum -1} : undef ), + 'blockcode', 0, + qq[<blockquote class="hft-blockcode">$text</blockquote>\n], + ); + }); +} + +=head3 urls + +Turn urls into links when C<urls> option is turned on. + +Should operate on C<< $self->{html} >>. + +Return value is ignored. + +=cut + +sub urls { + my ($self) = @_; + $self->{html} =~ s[\b((?:$PROTOCOLS):[^\s<]+[\w/])] + [<a href="$1" class="hft-urls">$1</a>]og; +} + +=head3 email + +Turn email addresses into C<mailto:> links when C<email> option is +turned on. + +Should operate on C<< $self->{html} >>. + +Return value is ignored. + +=cut + +sub email { + my ($self) = @_; + $self->{html} =~ s[($Addr_spec_re)] + [<a href="mailto:$1" class="hft-email">$1</a>]og; +} + +=head3 underline + +Underline things between _underscores_ when C<underline> option is +turned on. + +Should operate on C<< $self->{html} >>. + +Return value is ignored. + +=cut + +sub underline { + my ($self) = @_; + $self->{html} =~ s[(?:^|(?<=\W))((_)([^\\_\n]*(?:\\.[^\\_\n]*)*)(_))(?:(?=\W)|$)] + [<span class="hft-underline" style="text-decoration: underline">$3</span>]g; +} + +=head3 bold + +Bold things between *asterisks* when C<bold> option is turned on. + +Should operate on C<< $self->{html} >>. -The C<HTML::Entities> module (part of the LWP package) provides -functions for encoding and decoding HTML entities. +Return value is ignored. -Tom Christiansen has a complete implementation of RFC 822 structured -field bodies. See -C<http://www.perl.com/CPAN/authors/Tom_Christiansen/scripts/ckaddr.gz>. +=cut + +sub bold { + my ($self) = @_; + $self->{html} =~ s[(?:^|(?<=\W))((\*)([^\\\*\n]*(?:\\.[^\\\*\n]*)*)(\*))(?:(?=\W)|$)] + [<strong class="hft-bold">$3</strong>]g; +} + +=head3 metachars + +Encode meta characters when C<metachars> option is turned on. + +Should operate on C<< $self->{html} >>. + +Return value is ignored. -Seth Golub's C<txt2html> utility does everything that C<HTML::FromText> -does, and a few things that it would like to do. See -C<http://www.thehouse.org/txt2html/>. +=cut + +sub metachars { + my ($self) = @_; + $self->{html} = encode_entities( $self->{html} ); +} -RFC 822: "Standard for the Format of ARPA Internet Text Messages" -describes the syntax of email addresses (the more esoteric features of -structured field bodies, in particular quoted-strings, domain literals -and comments, are not recognized by C<HTML::FromText>). See -C<ftp://src.doc.ic.ac.uk/rfc/rfc822.txt>. +# private -RFC 1630: "Universal Resource Identifiers in WWW" lists the protocols -that may appear in URLs. C<HTML::FromText> also recognizes "https:", -but ignores "file:" because experience suggests that it results in too -many false positives. See C<ftp://src.doc.ic.ac.uk/rfc/rfc1630.txt>. +sub _croak { + my ($class, @error) = @_; + require Carp; + Carp::croak(@error); +} + +sub _carp { + my ($class, @error) = @_; + require Carp; + Carp::carp(@error); +} + +sub _format_list { + my ($self, $identifier, $parent, $class) = @_; + + $self->_manipulate_paras(sub { + my ($text) = @_; + return unless $text =~ m[^\s*($identifier)\s+]; + + my ($pos, $html, @open) = (-1, ''); + foreach my $line ( split /\n(?=\s*$identifier)/, $text ) { + $line =~ s[(\s*)$identifier][]; + my $line_pos = length $1; + if ($line_pos > $pos) { + $html .= (' ' x $line_pos) . qq[<$parent class="$class">\n]; + push @open, $line_pos; + } elsif ($line_pos < $pos) { + until ( $open[-1] <= $line_pos ) { + $html .= (' ' x pop @open) . "</$parent>\n"; + } + } + $html .= (' ' x ($pos = $line_pos)) . "<li>$line</li>\n"; + } + $html .= "</$parent>\n"x@open; + }); +} + +sub _manipulate_paras { + my ($self, $action) = @_; + + my $paras = $self->{paras}; + + foreach my $pnum ( sort { $a <=> $b } keys %{$paras}) { + my $para = $paras->{$pnum}; + $para->{html} = $action->($para->{text}, $pnum, $paras) + unless $para->{html}; + } +} + +sub _table_initial_spaces { + my ($self, @chars) = @_; + + my %spaces; + foreach ( 0 .. $#chars ) { + my ($open_space) = grep { !defined( $_->{end} ) } values %spaces; + if ( $chars[$_] eq ' ' ) { + $spaces{$_} = {start => $_, end => undef} unless $open_space; + } else { + if ( $open_space && $_ - $open_space->{start} > 1 ) { + $open_space->{end} = $_ - 1; + } else { + delete $spaces{$open_space->{start}} if $open_space; + } + } + } + return \%spaces; +} + +sub _table_find_columns { + my ($self, $spaces, $lines) = @_; + return unless keys %{$spaces}; + my %spots; + foreach my $line ( @{$lines} ) { + foreach my $pos ( sort { $a <=> $b } keys %{$spaces} ) { + my $key; + $key = $spaces->{$pos}->{start} + if substr( $line, $spaces->{$pos}->{start}, 1 ) eq ' '; + $key = $spaces->{$pos}->{end} + if substr( $line, $spaces->{$pos}->{end}, 1 ) eq ' ' && ! $key; + if ( $key ) { + $spots{$key}++; + $spots{$spaces->{$pos}->{start}}++ + if $spots{$spaces->{$pos}->{start}} && $key ne $spaces->{$pos}->{start}; + $spots{$spaces->{$pos}->{end}}++ + if $key ne $spaces->{$pos}->{end}; + } else { + delete $spaces->{$pos}; + } + } + foreach my $spot (sort {$b <=> $a} keys %spots) { + if ( substr( $line, $spot, 1 ) ne ' ' ) { + delete $spots{$spot}; + } + if ( exists $spaces->{$spot}) { + my $space = $spaces->{$spot}; + if ( exists $spots{$space->{start}} && $spots{$space->{end}}) { + delete $spots{$spot}; + } + } + } + } + + + my @spots = grep { $spots{$_} == @{$lines} } sort { $a <=> $b } keys %spots; + return @spots ? join( '', ( + map { + my $ret = 'A' . ( $spots[$_] - ( $_ == 0 ? 0 : $spots[$_ - 1] ) ); + $ret eq 'A0' ? () : $ret; + } 0 .. $#spots + ), 'A*' ) : undef; +} + +sub _table_create { + my ($self, $columns, $lines) = @_; + + my $table = qq[<table class="hft-tables">\n]; + foreach my $line ( @{$lines} ) { + $table .= join( '', + ' <tr><td>', + join( + '</td><td>', + map { s/^\s+//; s/\s$//; $_ } unpack $columns, $line + ), + "</td></tr>\n", + ); + } + $table .= "</table>\n"; +} + +sub _remove_indent { + my ($self, $text, $strict) = @_; + return if $text !~ m[^(\s+).+(?:\n\1.+)*$] && $strict; + $text =~ s[^$1][]mg if $1; + return $text; +} + +sub _consolidate_blocks { + my ($self, $prev_para, $class, $keep_inner, $html) = @_; + if ( $prev_para && $prev_para->{html} =~ m[<blockquote class="hft-$class"><(\w+)>] ) { + my $inner_tag = $keep_inner ? '' : qr[</?$1>]; + $prev_para->{html} =~ s[$inner_tag</blockquote>][]; + $html =~ s[<blockquote class="hft-$class">$inner_tag][]; + } + return $html; +} + +1; + +__END__ + +=head2 Output + +The output from C<HTML::FromText> has been updated to pass XHTML 1.1 +validation. Every HTML tag that should have a CSS class name does. They +are prefixed with C<hft-> and correspond to the names of the options to +C<new()> (or C<text2html()>). For example C<hft-lines>, C<hft-paras>, +and C<hft-urls>. + +One important note is the output for C<underline>. Because the <u> tag +is deprecated in this specification a C<span> is used with a style +attribute of C<text-decoration: underline>. The class is C<hft- +underline>. If you want to override the C<text-decoration> style in the +CSS class you'll need to do so like this. + + text-decoration: none !important; + +=head1 SEE ALSO + +L<text2html(1)>. =head1 AUTHOR -Gareth Rees C<E<lt>garethr@cre.canon.co.ukE<gt>>. +Casey West <F<casey@geeknest.com>>. + +=head1 AUTHOR EMERITUS + +Gareth Rees <F<garethr@cre.canon.co.uk>>. =head1 COPYRIGHT -Copyright (c) 1999 Canon Research Centre Europe. All rights reserved. -This module is free software; you can redistribute it and/or modify it -under the same terms as Perl itself. + Copyright (c) 2003 Casey West. All rights reserved. + This module is free software; you can redistribute it and/or modify it + under the same terms as Perl itself. =cut |
