diff options
| author | fukachan <fukachan> | 2001-10-18 08:32:16 +0000 |
|---|---|---|
| committer | fukachan <fukachan> | 2001-10-18 08:32:16 +0000 |
| commit | ee46374edfb377f57169f2b1de9afd19bf052da2 (patch) | |
| tree | 515661b27c563e1f64bef637f785dccf849adcf4 | |
| parent | ece119bc5adb2b01f8a6e5646d99870af695d9bf (diff) | |
| download | fml8-ee46374edfb377f57169f2b1de9afd19bf052da2.tar.gz fml8-ee46374edfb377f57169f2b1de9afd19bf052da2.tar.bz2 fml8-ee46374edfb377f57169f2b1de9afd19bf052da2.zip | |
Initial revision
| -rw-r--r-- | cpan/dist/HTML-FromText/FromText.pm | 736 | ||||
| -rw-r--r-- | cpan/dist/HTML-FromText/MANIFEST | 6 | ||||
| -rw-r--r-- | cpan/dist/HTML-FromText/Makefile.PL | 5 | ||||
| -rw-r--r-- | cpan/dist/HTML-FromText/README | 58 | ||||
| -rw-r--r-- | cpan/dist/HTML-FromText/TODO | 33 | ||||
| -rw-r--r-- | cpan/dist/HTML-FromText/t/text2html.t | 698 | ||||
| -rw-r--r-- | cpan/lib/HTML/FromText.pm | 736 |
7 files changed, 2272 insertions, 0 deletions
diff --git a/cpan/dist/HTML-FromText/FromText.pm b/cpan/dist/HTML-FromText/FromText.pm new file mode 100644 index 00000000..2b7f1753 --- /dev/null +++ b/cpan/dist/HTML-FromText/FromText.pm @@ -0,0 +1,736 @@ +require 5.004; +use strict; + +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; + } + + # 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; + } + + # 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; + } + + # BOLD: mark up words in *asterisks* as bold. + if ($options->{bold}) { + s#(^|\s)\*([^*]+)\*(?=\s|$)#$1<B>$2</B>#g; + } + + # UNDERLINE: mark up words in _underscores_ as underlined. + if ($options->{underline}) { + s#(^|\s)_([^_]+?)_(?=\s|$)#$1<U>$2</U>#g; + } + } + + return $_[0]; +} + +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; + } + + 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; + } + } + } + + 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 $_; +} + +1; + +__END__ + +=head1 NAME + +HTML::FromText - mark up text as HTML + +=head1 SYNOPSIS + + use HTML::FromText; + print text2html($text, urls => 1, paras => 1, headings => 1); + +=head1 DESCRIPTION + +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. + +=head1 SUMMARY OF OPTIONS + +These options always apply: + + 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 + +You can then choose to treat the text according to one of these options: + + pre Treat text as preformatted + lines Treat text as line-oriented + paras Treat text as paragraph-oriented + +(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: + + spaces Preserve spaces from the original text + +The following options apply when the C<paras> option is specified: + + 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 + +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. + +=head1 OPTIONS + +=over 4 + +=item blockparas + +=item blockquotes + +=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. + +The C<blockparas> option marks up the paragraph as a block quote with no +other changes. For example, + + Turing wrote, + + I propose to consider the question, + "Can machines think?" + +becomes + + <P>Turing wrote,</P> + <BLOCKQUOTE>I propose to consider the question, + "Can machines think?"</BLOCKQUOTE> + +The C<blockquotes> option preserves line breaks in the original text. +For example, + + From "The Waste Land": + + Phlebas the Phoenecian, a fortnight dead, + Forgot the cry of gulls, and the deep sea swell + +becomes + + <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> + +The C<blockcode> option preserves line breaks and spaces in the original +text and renders the paragraph in a fixed-width font. For example: + + Here's how to output numbers with commas: + + sub commify { + local $_ = shift; + 1 while s/^(-?\d+)(\d{3})/$1,$2/; + $_; + } + +becomes + + <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> + +=item bold + +Words surrounded with asterisks are marked up in bold, so C<*abc*> +becomes C<E<lt>BE<gt>abcE<lt>/BE<gt>>. + +=item bullets + +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, + + Shopping list: + + * apples + * pears + +becomes + + <P>Shopping list:</P> + <UL><LI><P>apples</P> + <LI><P>pears</P> + </UL> + +This option is ignored unless the C<paras> option is set. + +=item email + +Spots email addresses in the text and converts them to links. For example + + Mail me at web@perl.com. + +becomes + + Mail me at <TT><A HREF="mailto:web@perl.com">web@perl.com</A></TT>. + +=item headings + +Spots headings (paragraphs starting with numbers) and marks them up as +headings of the appropriate level. For example, + + 1. Introduction + + 1.1 Background + + 1.1.1 Previous work + + 2. Conclusion + +becomes + + <H1>1. Introduction</H1> + <H2>1.1 Background</H2> + <H3>1.1.1 Previous work</H3> + <H1>2. Conclusion</H1> + +This option is ignored unless the C<paras> option is set. + +=item lines + +Formats the text so as to preserve line breaks. For example, + + Line 1 + Line 2 + +becomes + + Line 1<BR> + Line 2 + +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>. + +=item metachars + +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. + +=item numbers + +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, + + To do: + + 1. Write thesis + 2. Submit it + 3. Celebrate + +becomes + + <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> + +This option is ignored unless the C<paras> option is set. + +=item paras + +Format the text into paragraphs. Paragraphs are separated by one or +more blank lines. For example, + + Paragraph 1 + + Paragraph 2 + +becomes + + <P>Paragraph 1</P> + <P>Paragraph 2</P> + +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>. + +=item pre + +Wrap the whole input in a C<E<lt>PREE<gt>> element. For example, + + preformatted + text + +becomes + + <PRE>preformatted + text</PRE> + +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>. + +=item spaces + +Preserves spaces throughout the text. For example, + + Line 1 + Line 2 + Line 3 + +becomes + + Line 1<BR> + Line 2<BR> + Line 3 + +This option is ignored unless the C<lines> option is set. + +=item 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 + + -e File exists. + -z File has zero size. + -s File has nonzero size (returns size). + +becomes + + <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> + +C<text2html> guesses for each column whether it is intended to be left, +centre or right aligned. + +This option is ignored unless the C<paras> option is set. + +=item title + +Formats the first paragraph of the text as a first-level heading. +For example, + + Paragraph 1 + + Paragraph 2 + +becomes + + <H1>Paragraph 1</H1> + <P>Paragraph 2</P> + +This option is ignored unless the C<paras> option is set. + +=item underline + +Words surrounded with underscores are marked up with underline, so C<_abc_> +becomes C<E<lt>UE<gt>abcE<lt>/UE<gt>>. + +=item urls + +Spots Uniform Resource Locators (URLs) in the text and converts them +to links. For example + + See https://perl.com/. + +becomes + + See <TT><A HREF="https://perl.com/">https://perl.com/</A></TT>. + +=back + +=head1 SEE ALSO + +The C<HTML::Entities> module (part of the LWP package) provides +functions for encoding and decoding HTML entities. + +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>. + +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/>. + +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>. + +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>. + +=head1 AUTHOR + +Gareth Rees C<E<lt>garethr@cre.canon.co.ukE<gt>>. + +=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. + +=cut diff --git a/cpan/dist/HTML-FromText/MANIFEST b/cpan/dist/HTML-FromText/MANIFEST new file mode 100644 index 00000000..1d625b62 --- /dev/null +++ b/cpan/dist/HTML-FromText/MANIFEST @@ -0,0 +1,6 @@ +FromText.pm +MANIFEST +Makefile.PL +README +TODO +t/text2html.t diff --git a/cpan/dist/HTML-FromText/Makefile.PL b/cpan/dist/HTML-FromText/Makefile.PL new file mode 100644 index 00000000..e73edc44 --- /dev/null +++ b/cpan/dist/HTML-FromText/Makefile.PL @@ -0,0 +1,5 @@ +use ExtUtils::MakeMaker; +WriteMakefile( NAME => 'HTML::FromText', + VERSION_FROM => 'FromText.pm', + dist => { COMPRESS => 'gzip', SUFFIX => 'gz' }, + ); diff --git a/cpan/dist/HTML-FromText/README b/cpan/dist/HTML-FromText/README new file mode 100644 index 00000000..cf740136 --- /dev/null +++ b/cpan/dist/HTML-FromText/README @@ -0,0 +1,58 @@ +NAME + HTML::FromText - mark up text as HTML + +SYNOPSIS + use HTML::FromText; + print text2html($text, urls => 1, paras => 1, headings => 1); + +DESCRIPTION + The text2html function marks up plain text as HTML. By + default it converts HTML metacharacters into the + corresponding entities. More sophisticated transformations, + such as splitting the text into paragraphs or marking up + bulleted lists, can be carried out by setting the + appropriate options. + +INSTALLATION + perl Makefile.PL && make && make test && make install + +HISTORY + 1.005 Options 'bold' and 'underline' are more aggressive in + recognising markup. + + 1.004 New options 'blockparas' (mark up block quotes as ordinary + paragraphs) and 'blockcode' (mark up block quotes in + fixed-width font while preserving line breaks and spaces). + + Tabs are expanded throughout the text (it was a bug not to + do so in earlier versions because alignment could be lost, + block quotes not recognised, etc). + + New option 'tables'. + + 1.003 Recognize '&' in email addresses, as specified by RFC822. + + 1.002 Much improved recognition of e-mail addresses with special + characters, as specified by RFC822. + + When 'urls' is supplied, the prefix mailto: on email + addresses is preserved. + + New option 'pre' wraps text in <PRE>...</PRE>. + + When anchor text is in fixed-width font, the <A> element is + inside the <TT> element, as required by the HTML DTD. + + 1.001 Original CPAN release. + +BUGS + There are transformations it doesn't do. + +AUTHOR + Gareth Rees <garethr@cre.canon.co.uk>. + +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. diff --git a/cpan/dist/HTML-FromText/TODO b/cpan/dist/HTML-FromText/TODO new file mode 100644 index 00000000..6ebf6838 --- /dev/null +++ b/cpan/dist/HTML-FromText/TODO @@ -0,0 +1,33 @@ +---------------------------------IDEAS---------------------------------- + +1. Have a more general means of representing paragraph transformations + and applying them so that text2html can be extended. Each + transformation would have (a) an option name, (b) a sub for testing + whether the transformation is applicable to the paragraph and (c) a + sub for applying the option. (There must be a means of passing + information from step (b) to step (c) as for the table + transformation.) + + (But are there that many other transformations that can be applied on + a paragraph basis? Other kinds of transformations would be harder to + generalise like this. Think about the need to have a different idea + about where paragraph boundaries are depending on the options.) + +2. The current approach is to test the transformations for applicability + one by one and apply the first one that matches. It might be better + to test all the transformations and score them, then apply the + best scoring. This would allow heuristics to be applied. + +3. The current approach goes through the paragraphs one by one with no + lookahead. Multiple passes through the paragraphs could allow + heuristics to be applied, e.g., to distinguish nested lists from + adjacent lists. + +4. These transformations could be implemented: + + * lettered paragraphs a) b) c) etc + + * underlined headings + + * quoted text in e-mail message (impossible to accurately given the + enormous diversity of quoting styles -- would 60% be good enough?) diff --git a/cpan/dist/HTML-FromText/t/text2html.t b/cpan/dist/HTML-FromText/t/text2html.t new file mode 100644 index 00000000..0e78a21c --- /dev/null +++ b/cpan/dist/HTML-FromText/t/text2html.t @@ -0,0 +1,698 @@ +# HTML::FromText test suite (-*- cperl -*-) + +use strict; +use HTML::FromText; +$^W = 1; + +# Each test is represented by three data chunks, separated by a line +# containing only a form-feed character (0x0c). The first chunk is +# the options to pass to text2html(), the second is the input, and the +# third the expected output. + +$/ = "\n\f\n"; +my @tests = (); +while (<DATA>) { + chomp; + push @tests, $_; +} +my $n = @tests / 3; +print "1..$n\n"; +foreach my $i (1..$n) { + my $j = 3 * ($i - 1); + my $input = $tests[$j + 1]; + my $expected = $tests[$j + 2]; + my @options = eval $tests[$j]; + my $output = text2html($input, @options); + unless ($output eq $expected) { + print STDERR + "\n",'--expected','-'x60, + "\n",$expected, + "\n",'--but-found','-'x59, + "\n",$output, + "\n",'-'x70, + "\n"; + print "not "; + } + print "ok $i\n"; +} + +__DATA__ + +() + +<B><&></B> + +<B>&lt;&amp;&gt;</B> + + +(metachars => 0) + +<B><&></B> + +<B><&></B> + + +(email => 1) + +real@email.address, real2@email.addresss. +fake@:email.address, another@[fake].address +mailto:me@foo.bar.com +<tricky@subdomain.domain> +#$%=strange!?@characters.=+=in=+=_.address + +<TT><A HREF="mailto:real@email.address">real@email.address</A></TT>, <TT><A HREF="mailto:real2@email.addresss">real2@email.addresss</A></TT>. +fake@:email.address, another@[fake].address +<TT><A HREF="mailto:me@foo.bar.com">mailto:me@foo.bar.com</A></TT> +<<TT><A HREF="mailto:tricky@subdomain.domain">tricky@subdomain.domain</A></TT>> +<TT><A HREF="mailto:#$%=strange!?@characters.=+=in=+=_.address">#$%=strange!?@characters.=+=in=+=_.address</A></TT> + + +(metachars => 1, email => 1) + +An email address with an & in it: fred&barney@stonehenge.com. + +An email address with an & in it: <TT><A HREF="mailto:fred&barney@stonehenge.com">fred&barney@stonehenge.com</A></TT>. + + +(metachars => 0, email => 1) + +An email address with an & in it: fred&barney@stonehenge.com. Generates +non-legal HTML, but that was what was asked for! + +An email address with an & in it: <TT><A HREF="mailto:fred&barney@stonehenge.com">fred&barney@stonehenge.com</A></TT>. Generates +non-legal HTML, but that was what was asked for! + + +(urls => 1) + +See http://foo.bar.com. +What about http://foo.com/bar/baz? +http://foo.com/bar/baz?quux. +ftp://spong.gov/a/b/c/d.e/f.g/h/ should have trailing / +...gopher://x.y.z/foo... +mailto:mail@address.com is translated +but mail@address.com on its own is not + +See <TT><A HREF="http://foo.bar.com">http://foo.bar.com</A></TT>. +What about <TT><A HREF="http://foo.com/bar/baz">http://foo.com/bar/baz</A></TT>? +<TT><A HREF="http://foo.com/bar/baz?quux">http://foo.com/bar/baz?quux</A></TT>. +<TT><A HREF="ftp://spong.gov/a/b/c/d.e/f.g/h/">ftp://spong.gov/a/b/c/d.e/f.g/h/</A></TT> should have trailing / +...<TT><A HREF="gopher://x.y.z/foo">gopher://x.y.z/foo</A></TT>... +<TT><A HREF="mailto:mail@address.com">mailto:mail@address.com</A></TT> is translated +but mail@address.com on its own is not + + +(bold => 1, underline => 1) + +*Words* in *bold* _underline_ and *bold* again, but 5*4, 3_1 unaffected; +_underline_ *more* *bold* +_more_ _underline_ +Now *several words in bold* and _several in underline_ but +equations like 5*x + 5*y or zeta_i + phi_i are not marked up. +Here's a *phrase in bold +crossing a newline* and an _underlined phrase +crossing a newline_ +Single letter words: *a* _b_ *c* _d_ + +<B>Words</B> in <B>bold</B> <U>underline</U> and <B>bold</B> again, but 5*4, 3_1 unaffected; +<U>underline</U> <B>more</B> <B>bold</B> +<U>more</U> <U>underline</U> +Now <B>several words in bold</B> and <U>several in underline</U> but +equations like 5*x + 5*y or zeta_i + phi_i are not marked up. +Here's a <B>phrase in bold +crossing a newline</B> and an <U>underlined phrase +crossing a newline</U> +Single letter words: <B>a</B> <U>b</U> <B>c</B> <U>d</U> + + +(paras => 1, bold => 1, underline => 1) + +*Bold* works OK in a paragraph context +and so does _underline_ + +_Underline works OK_ in a paragraph context +and *so does bold* + +<P><B>Bold</B> works OK in a paragraph context +and so does <U>underline</U></P> +<P><U>Underline works OK</U> in a paragraph context +and <B>so does bold</B></P> + + +(lines => 1) + +line 1 +line 2 +line 3 +line 4 + +line 1<BR> +line 2<BR> +line 3<BR> +line 4 + + +(lines => 1, spaces => 1) + +line 1 + line 2 + line 3 + line 4 + tab 1 + tab 2 + +line 1<BR> + line 2<BR> + line 3<BR> + line 4<BR> + tab 1<BR> + tab 2 + + +(paras => 1) + +paragraph +one + +paragraph +two + +a +long +paragraph +three + +<P>paragraph +one</P> +<P>paragraph +two</P> +<P>a +long +paragraph +three</P> + + +(paras => 1, title => 1) + +this +is the +title + +and this +the text + +<H1>this +is the +title</H1> +<P>and this +the text</P> + + +(paras => 1, headings => 1) + +1. Chapter one + +2. Chapter two + +2.1 Section two point one + +2.1.1 Subsection two point one point one + +2.1.1.3 Subsubsection two point one point one point three + +2.1.1.3.7 Heading level 5 + +2.1.1.3.7.1 Heading level 6 (a long heading across +two lines) + +paragraph text + +2.1.1.3.7.1.2 There are no more than 6 heading levels. + +paragraph text + +3. Chapter three + +<H1>1. Chapter one</H1> +<H1>2. Chapter two</H1> +<H2>2.1 Section two point one</H2> +<H3>2.1.1 Subsection two point one point one</H3> +<H4>2.1.1.3 Subsubsection two point one point one point three</H4> +<H5>2.1.1.3.7 Heading level 5</H5> +<H6>2.1.1.3.7.1 Heading level 6 (a long heading across +two lines)</H6> +<P>paragraph text</P> +<H6>2.1.1.3.7.1.2 There are no more than 6 heading levels.</H6> +<P>paragraph text</P> +<H1>3. Chapter three</H1> + + +(paras => 1, bullets => 1) + +Ordinary text + + * bulleted paragraph + + * another bulleted paragarph + with two lines + +ordinary text + +* bullet flush left + + * bullet with tabs + + - bullet with hyphen + +<P>Ordinary text</P> +<UL><LI><P>bulleted paragraph</P> +<LI><P>another bulleted paragarph + with two lines</P> +</UL><P>ordinary text</P> +<UL><LI><P>bullet flush left</P> +<LI><P>bullet with tabs</P> +<LI><P>bullet with hyphen</P> +</UL> + + +(paras => 1, headings => 1, numbers => 1) + +1. This is a heading, not a numbered paragraph + + 1. number one + + 2. number two + + 31. number thirty-one + +ordinary text + + 1. another number one + + * bulleted paragraph not recognised + + 3. number three + +<H1>1. This is a heading, not a numbered paragraph</H1> +<OL><LI VALUE="1"><P>number one</P> +<LI VALUE="2"><P>number two</P> +<LI VALUE="31"><P>number thirty-one</P> +</OL><P>ordinary text</P> +<OL><LI VALUE="1"><P>another number one</P> +</OL><P> * bulleted paragraph not recognised</P> +<OL><LI VALUE="3"><P>number three</P> +</OL> + + +(paras => 1, numbers => 1, bullets => 1) + + 1. a numbered item + + 2. and another + + * switching to bullets starts a new list + + 3. as does switching back + +<OL><LI VALUE="1"><P>a numbered item</P> +<LI VALUE="2"><P>and another</P> +</OL><UL><LI><P>switching to bullets starts a new list</P> +</UL><OL><LI VALUE="3"><P>as does switching back</P> +</OL> + + +(paras => 1, numbers => 1, bullets => 1) + +* a bulleted list +* with all the bullets next to each other +* blah +* blah + +<UL><LI><P>a bulleted list</P> +<LI><P>with all the bullets next to each other</P> +<LI><P>blah</P> +<LI><P>blah</P> +</UL> + + +(paras => 1, numbers => 1, bullets => 1) + +1. a numbered list +2. with all the numbers next to each other +3. blah +4. blah + +<OL><LI VALUE="1"><P>a numbered list</P> +<LI VALUE="2"><P>with all the numbers next to each other</P> +<LI VALUE="3"><P>blah</P> +<LI VALUE="4"><P>blah</P> +</OL> + + +(paras => 1, numbers => 1, bullets => 1) + +* switching between +111 numbers +* and +222 bullets + +<UL><LI><P>switching between</P> +</UL><OL><LI VALUE="111"><P>numbers</P> +</OL><UL><LI><P>and</P> +</UL><OL><LI VALUE="222"><P>bullets</P> +</OL> + + +(paras => 1, numbers => 1, bullets => 1) + +Ordinary paragraphs +* mixed up with +numbers +789 and +bullets + +<P>Ordinary paragraphs</P> +<UL><LI><P>mixed up with +numbers</P> +</UL><OL><LI VALUE="789"><P>and +bullets</P> +</OL> + + +(paras => 1, numbers => 1, bullets => 1) + +000 different +002. kinds +003) of +004] numbered +001 list + +<OL><LI VALUE="000"><P>different</P> +<LI VALUE="002"><P>kinds</P> +<LI VALUE="003"><P>of</P> +<LI VALUE="004"><P>numbered</P> +<LI VALUE="001"><P>list</P> +</OL> + + +(paras => 1, blockquotes => 1) + +Here's a block quote: + + line 1 + line 2 + line 3 + line 4 + +end of block quote + +<P>Here's a block quote:</P> +<BLOCKQUOTE>line 1<BR> +line 2<BR> +line 3<BR> +line 4</BLOCKQUOTE> +<P>end of block quote</P> + + +(paras => 1, blockquotes => 1) + +A block quote with variable spacing: + + line 1 + line 2 + line 3 + +end of block quote + +<P>A block quote with variable spacing:</P> +<BLOCKQUOTE>line 1<BR> + line 2<BR> + line 3</BLOCKQUOTE> +<P>end of block quote</P> + + +(paras => 1, blockquotes => 1) + +Ditto, spacing goes the other way: + + line 1 + line 2 + line 3 + +end of block quote + +<P>Ditto, spacing goes the other way:</P> +<BLOCKQUOTE> line 1<BR> + line 2<BR> +line 3</BLOCKQUOTE> +<P>end of block quote</P> + + +(paras => 1, blockquotes => 1) + +This shouldn't be recognized as blockquote: + + despite the spaces on this line, + and this one, +this is just an ordinary paragraph? + +<P>This shouldn't be recognized as blockquote:</P> +<P> despite the spaces on this line, + and this one, +this is just an ordinary paragraph?</P> + + +(paras => 1, bullets => 1, numbers => 1, blockquotes => 1) + + +This is not a blockquote, despite initial and final blank lines. + + +<P>This is not a blockquote, despite initial and final blank lines.</P> + + +(pre => 1) + +preformatted +text + +<PRE>preformatted +text</PRE> + + +(paras => 1, blockparas => 1) + +Turing wrote, + + I propose to consider the question, "Can machines think?" + This should begin with definitions of the meaning of the + terms "machine" and "think". + +<P>Turing wrote,</P> +<BLOCKQUOTE><P>I propose to consider the question, "Can machines think?" +This should begin with definitions of the meaning of the +terms "machine" and "think".</P></BLOCKQUOTE> + + +(paras => 1, blockquotes => 1) + +From "The Waste Land": + + Phlebas the Phoenecian, a fortnight dead, + Forgot the cry of gulls, and the deep sea swell + +<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> + + +(paras => 1, blockcode => 1) + +Here's how to output numbers with commas (from perlfaq4): + + sub commify { + local $_ = shift; + 1 while s/^(-?\d+)(\d{3})/$1,$2/; + $_; + } + +<P>Here's how to output numbers with commas (from perlfaq4):</P> +<BLOCKQUOTE><TT>sub commify {<BR> + local $_ = shift;<BR> + 1 while s/^(-?\d+)(\d{3})/$1,$2/;<BR> + $_;<BR> +}</TT></BLOCKQUOTE> + + +() + +Line mixing tabs and metachars: + &&& <> + +Line mixing tabs and metachars: + &&& <> + + +(paras => 1, tables => 1) + + 1, 1 1, 2 1, 3 + 2, 1 2, 2 2, 3 + 3, 1 3, 2 3, 3 + +<BLOCKQUOTE><TABLE> +<TR><TD>1, 1</TD><TD>1, 2</TD><TD>1, 3</TD></TR> +<TR><TD>2, 1</TD><TD>2, 2</TD><TD>2, 3</TD></TR> +<TR><TD>3, 1</TD><TD>3, 2</TD><TD>3, 3</TD></TR> +</TABLE></BLOCKQUOTE> + + +(paras => 1, tables => 1) + +Tables can be left-aligned: + +1, 1 1, 2 1, 3 +2, 1 2, 2 2, 3 +3, 1 3, 2 3, 3 + +<P>Tables can be left-aligned:</P> +<P><TABLE> +<TR><TD>1, 1</TD><TD>1, 2</TD><TD>1, 3</TD></TR> +<TR><TD>2, 1</TD><TD>2, 2</TD><TD>2, 3</TD></TR> +<TR><TD>3, 1</TD><TD>3, 2</TD><TD>3, 3</TD></TR> +</TABLE></P> + + +(paras => 1, tables => 1) + + despite its appearance + this table has + only two columns + +<BLOCKQUOTE><TABLE> +<TR><TD>despite its</TD><TD>appearance</TD></TR> +<TR><TD>this table</TD><TD>has</TD></TR> +<TR><TD>only two</TD><TD>columns</TD></TR> +</TABLE></BLOCKQUOTE> + + +(paras => 1, tables => 1) + + tables + must + have + two + columns + +<P> tables + must + have + two + columns</P> + + +(paras => 1, tables => 1) + + tables can + have only + one space + at the left + +<BLOCKQUOTE><TABLE> +<TR><TD>tables</TD><TD>can</TD></TR> +<TR><TD>have</TD><TD>only</TD></TR> +<TR><TD>one</TD><TD>space</TD></TR> +<TR><TD>at the</TD><TD>left</TD></TR> +</TABLE></BLOCKQUOTE> + + +(paras => 1, tables => 1) + + tables must have two rows + +<P> tables must have two rows</P> + + +(paras => 1, tables => 1) + + this table has varying lengths of column + at the right + +<BLOCKQUOTE><TABLE> +<TR><TD>this</TD><TD>table</TD><TD>has varying lengths of column</TD></TR> +<TR><TD>at</TD><TD>the</TD><TD>right</TD></TR> +</TABLE></BLOCKQUOTE> + + +(paras => 1, tables => 1) + +This table contains right-aligned cells: + + p p^2 p^3 p^4 + 2 4 8 16 + 3 9 27 81 + 5 25 125 625 + 7 49 343 2401 + +<P>This table contains right-aligned cells:</P> +<BLOCKQUOTE><TABLE> +<TR><TD>p</TD><TD ALIGN="RIGHT">p^2</TD><TD ALIGN="RIGHT">p^3</TD><TD ALIGN="RIGHT">p^4</TD></TR> +<TR><TD>2</TD><TD ALIGN="RIGHT">4</TD><TD ALIGN="RIGHT">8</TD><TD ALIGN="RIGHT">16</TD></TR> +<TR><TD>3</TD><TD ALIGN="RIGHT">9</TD><TD ALIGN="RIGHT">27</TD><TD ALIGN="RIGHT">81</TD></TR> +<TR><TD>5</TD><TD ALIGN="RIGHT">25</TD><TD ALIGN="RIGHT">125</TD><TD ALIGN="RIGHT">625</TD></TR> +<TR><TD>7</TD><TD ALIGN="RIGHT">49</TD><TD ALIGN="RIGHT">343</TD><TD ALIGN="RIGHT">2401</TD></TR> +</TABLE></BLOCKQUOTE> + + +(paras => 1, tables => 1) + +This table contains metacharacters: + + & & + < < + > > + +<P>This table contains metacharacters:</P> +<BLOCKQUOTE><TABLE> +<TR><TD>&</TD><TD>&amp;</TD></TR> +<TR><TD><</TD><TD>&lt;</TD></TR> +<TR><TD>></TD><TD>&gt;</TD></TR> +</TABLE></BLOCKQUOTE> + + +(paras => 1, tables => 1) + +Here's a table with centre-aligned columns: + +1 1 +2 1 1 +3 1 2 1 +4 1 3 3 1 +5 1 4 6 4 1 + +<P>Here's a table with centre-aligned columns:</P> +<P><TABLE> +<TR><TD>1</TD><TD ALIGN="CENTER">1</TD></TR> +<TR><TD>2</TD><TD ALIGN="CENTER">1 1</TD></TR> +<TR><TD>3</TD><TD ALIGN="CENTER">1 2 1</TD></TR> +<TR><TD>4</TD><TD ALIGN="CENTER">1 3 3 1</TD></TR> +<TR><TD>5</TD><TD ALIGN="CENTER">1 4 6 4 1</TD></TR> +</TABLE></P> + + +(paras => 1, blockparas => 1, tables => 1) + + This should get recognised + as a blockquote despite + the unorthodox spacing. + + But this is a table + XXX XXXX XX X XXXXX + +<BLOCKQUOTE><P>This should get recognised +as a blockquote despite +the unorthodox spacing.</P></BLOCKQUOTE> +<BLOCKQUOTE><TABLE> +<TR><TD>But</TD><TD>this</TD><TD>is</TD><TD>a</TD><TD>table</TD></TR> +<TR><TD>XXX</TD><TD>XXXX</TD><TD>XX</TD><TD>X</TD><TD>XXXXX</TD></TR> +</TABLE></BLOCKQUOTE> + diff --git a/cpan/lib/HTML/FromText.pm b/cpan/lib/HTML/FromText.pm new file mode 100644 index 00000000..2b7f1753 --- /dev/null +++ b/cpan/lib/HTML/FromText.pm @@ -0,0 +1,736 @@ +require 5.004; +use strict; + +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; + } + + # 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; + } + + # 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; + } + + # BOLD: mark up words in *asterisks* as bold. + if ($options->{bold}) { + s#(^|\s)\*([^*]+)\*(?=\s|$)#$1<B>$2</B>#g; + } + + # UNDERLINE: mark up words in _underscores_ as underlined. + if ($options->{underline}) { + s#(^|\s)_([^_]+?)_(?=\s|$)#$1<U>$2</U>#g; + } + } + + return $_[0]; +} + +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; + } + + 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; + } + } + } + + 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 $_; +} + +1; + +__END__ + +=head1 NAME + +HTML::FromText - mark up text as HTML + +=head1 SYNOPSIS + + use HTML::FromText; + print text2html($text, urls => 1, paras => 1, headings => 1); + +=head1 DESCRIPTION + +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. + +=head1 SUMMARY OF OPTIONS + +These options always apply: + + 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 + +You can then choose to treat the text according to one of these options: + + pre Treat text as preformatted + lines Treat text as line-oriented + paras Treat text as paragraph-oriented + +(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: + + spaces Preserve spaces from the original text + +The following options apply when the C<paras> option is specified: + + 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 + +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. + +=head1 OPTIONS + +=over 4 + +=item blockparas + +=item blockquotes + +=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. + +The C<blockparas> option marks up the paragraph as a block quote with no +other changes. For example, + + Turing wrote, + + I propose to consider the question, + "Can machines think?" + +becomes + + <P>Turing wrote,</P> + <BLOCKQUOTE>I propose to consider the question, + "Can machines think?"</BLOCKQUOTE> + +The C<blockquotes> option preserves line breaks in the original text. +For example, + + From "The Waste Land": + + Phlebas the Phoenecian, a fortnight dead, + Forgot the cry of gulls, and the deep sea swell + +becomes + + <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> + +The C<blockcode> option preserves line breaks and spaces in the original +text and renders the paragraph in a fixed-width font. For example: + + Here's how to output numbers with commas: + + sub commify { + local $_ = shift; + 1 while s/^(-?\d+)(\d{3})/$1,$2/; + $_; + } + +becomes + + <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> + +=item bold + +Words surrounded with asterisks are marked up in bold, so C<*abc*> +becomes C<E<lt>BE<gt>abcE<lt>/BE<gt>>. + +=item bullets + +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, + + Shopping list: + + * apples + * pears + +becomes + + <P>Shopping list:</P> + <UL><LI><P>apples</P> + <LI><P>pears</P> + </UL> + +This option is ignored unless the C<paras> option is set. + +=item email + +Spots email addresses in the text and converts them to links. For example + + Mail me at web@perl.com. + +becomes + + Mail me at <TT><A HREF="mailto:web@perl.com">web@perl.com</A></TT>. + +=item headings + +Spots headings (paragraphs starting with numbers) and marks them up as +headings of the appropriate level. For example, + + 1. Introduction + + 1.1 Background + + 1.1.1 Previous work + + 2. Conclusion + +becomes + + <H1>1. Introduction</H1> + <H2>1.1 Background</H2> + <H3>1.1.1 Previous work</H3> + <H1>2. Conclusion</H1> + +This option is ignored unless the C<paras> option is set. + +=item lines + +Formats the text so as to preserve line breaks. For example, + + Line 1 + Line 2 + +becomes + + Line 1<BR> + Line 2 + +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>. + +=item metachars + +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. + +=item numbers + +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, + + To do: + + 1. Write thesis + 2. Submit it + 3. Celebrate + +becomes + + <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> + +This option is ignored unless the C<paras> option is set. + +=item paras + +Format the text into paragraphs. Paragraphs are separated by one or +more blank lines. For example, + + Paragraph 1 + + Paragraph 2 + +becomes + + <P>Paragraph 1</P> + <P>Paragraph 2</P> + +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>. + +=item pre + +Wrap the whole input in a C<E<lt>PREE<gt>> element. For example, + + preformatted + text + +becomes + + <PRE>preformatted + text</PRE> + +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>. + +=item spaces + +Preserves spaces throughout the text. For example, + + Line 1 + Line 2 + Line 3 + +becomes + + Line 1<BR> + Line 2<BR> + Line 3 + +This option is ignored unless the C<lines> option is set. + +=item 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 + + -e File exists. + -z File has zero size. + -s File has nonzero size (returns size). + +becomes + + <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> + +C<text2html> guesses for each column whether it is intended to be left, +centre or right aligned. + +This option is ignored unless the C<paras> option is set. + +=item title + +Formats the first paragraph of the text as a first-level heading. +For example, + + Paragraph 1 + + Paragraph 2 + +becomes + + <H1>Paragraph 1</H1> + <P>Paragraph 2</P> + +This option is ignored unless the C<paras> option is set. + +=item underline + +Words surrounded with underscores are marked up with underline, so C<_abc_> +becomes C<E<lt>UE<gt>abcE<lt>/UE<gt>>. + +=item urls + +Spots Uniform Resource Locators (URLs) in the text and converts them +to links. For example + + See https://perl.com/. + +becomes + + See <TT><A HREF="https://perl.com/">https://perl.com/</A></TT>. + +=back + +=head1 SEE ALSO + +The C<HTML::Entities> module (part of the LWP package) provides +functions for encoding and decoding HTML entities. + +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>. + +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/>. + +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>. + +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>. + +=head1 AUTHOR + +Gareth Rees C<E<lt>garethr@cre.canon.co.ukE<gt>>. + +=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. + +=cut |
