简体   繁体   中英

HTML to plain text (for email)

Do you know any good HTML to plain text conversion class written in PHP?

I need it for converting HTML mail body to plain text mail body.

I wrote simple function, but I need more features like converting tables, adding links at the end, converting nested lists…

-- regards
takeshin

A particular mail sending implementation around here simply spawns lynx with the HTML and uses its output for the text version. It's not perfect but works. You might also use links or elinks .

Using lynx is an option only if you have permission to run executables on the server. Doing so, however, is not considered a good practice. Furthermore, in secure hosts the php process is limited to be unable to spawn bash sessions, which are required for running lynx.

The most complete solution written entirely in PHP I was able to find is the Horde_Text_Filter_Html2text class. It is a part from the Horde framework .

Other solutions I've tried include:

If someone got the perfect solution, please, post it back for further reference!

As the question is about PHP and I found Dharmesh Hadiyal's c# code quite useful, I have converted it to PHP.

(can't comment, not enough reputation)

//From https://stackoverflow.com/questions/1930297/html-to-plain-text-for-email/23988241#23988241
//converted from c# to PHP
class HtmlToText
{
    public static function stripHTML($source)
    {
        // Remove HTML Development formatting
        // Replace line breaks with space
        // because browsers inserts space
        $result = str_replace("\r",  " ",$source );
        // Replace line breaks with space
        // because browsers inserts space
        $result = str_replace("\n",  " ",$result );
        // Remove step-formatting
        $result = str_replace("\t",  "",$result );
        // Remove repeating spaces because browsers ignore them
        $result = preg_replace("/( )+/im",  " ", $result);


        // Remove html-Tag (prepare first by clearing attributes)
        $result = preg_replace("/<( )*html([^>])*>\s*/im",  "<html>", $result);

        $result = preg_replace("/(<( )*(\/)( )*html( )*>)/im",  "</html>", $result);

        $result = preg_replace("/(<html>)|(<\/html>)/im",  "", $result);

        // Remove the header (prepare first by clearing attributes)
        $result = preg_replace("/<( )*head([^>])*>/im",  "<head>", $result);

        $result = preg_replace("/(<( )*(\/)( )*head( )*>)/im",  "</head>", $result);

        $result = preg_replace("/(<head>).*(<\/head>)/im",  "", $result);


        // remove all scripts (prepare first by clearing attributes)
        $result = preg_replace("/<( )*script([^>])*>/im",  "<script>", $result);

        $result = preg_replace("/(<( )*(\/)( )*script( )*>)/im",  "</script>", $result);

        //$result = System.Text.RegularExpressions.Regex.Replace($result,
        //         "(<script>)([^(<script>\.</script>)])*(</script>)",
        //         "",
        //         System.Text.RegularExpressions.RegexOptions.IgnoreCase);
        $result = preg_replace("/(<script>).*(<\/script>)/im",  "", $result);


        // remove all styles (prepare first by clearing attributes)
        $result = preg_replace("/<( )*style([^>])*>/im",  "<style>", $result);

        $result = preg_replace("/(<( )*(\/)( )*style( )*>)/im",  "</style>", $result);

        $result = preg_replace("/(<style>).*(<\/style>)/im",  "", $result);


        // insert tabs in spaces of <td> tags
        $result = preg_replace("/<( )*td([^>])*>/im",  "\t", $result);


        // insert line breaks in places of <BR> and <LI> tags
        $result = preg_replace("/<( )*br( )*\/?>/im",  "\r", $result);

        $result = preg_replace("/<( )*li( )*>/im",  "\r", $result);


        // insert line paragraphs (double line breaks) in place
        // if <P>, <DIV> and <TR> tags
        $result = preg_replace("/<( )*div([^>])*>/im",  "\r\r", $result);

        $result = preg_replace("/<( )*tr([^>])*>/im",  "\r\r", $result);

        $result = preg_replace("/<( )*p([^>])*>/im",  "\r\r", $result);


        // Remove remaining tags like <a>, links, images,
        // comments etc - anything that's enclosed inside < >
        $result = preg_replace("/<[^>]*>/im",  "", $result);


        // replace special characters:
        $result = preg_replace("/ /im",  " ", $result);


        $result = preg_replace("/&bull;/im",  " * ", $result);

        $result = preg_replace("/&lsaquo;/im",  "<", $result);

        $result = preg_replace("/&rsaquo;/im",  ">", $result);

        $result = preg_replace("/&trade;/im",  "(tm)", $result);

        $result = preg_replace("/&frasl;/im",  "/", $result);

        $result = preg_replace("/&lt;/im",  "<", $result);

        $result = preg_replace("/&gt;/im",  ">", $result);

        $result = preg_replace("/&copy;/im",  "(c)", $result);

        $result = preg_replace("/&reg;/im",  "(r)", $result);

        // Remove all others. More can be added, see
        // http://hotwired.lycos.com/webmonkey/reference/special_characters/
        $result = preg_replace("/&(.{2,6});/im",  "", $result);


        // for testing
        //System.Text.RegularExpressions.Regex.Replace($result,
        //       this.txtRegex.Text,"",
            //       System.Text.RegularExpressions.RegexOptions.IgnoreCase);

        // make line breaking consistent
        $result = str_replace("\n",  "\r",$result );

        // Remove extra line breaks and tabs:
        // replace over 2 breaks with 2 and over 4 tabs with 4.
        // Prepare first to remove any whitespaces in between
        // the escaped characters and remove redundant tabs in between line breaks
        $result = preg_replace("/(\r)( )+(\r)/im",  "\r\r", $result);

        $result = preg_replace("/(\t)( )+(\t)/im",  "\t\t", $result);

        $result = preg_replace("/(\t)( )+(\r)/im",  "\t\r", $result);

        $result = preg_replace("/(\r)( )+(\t)/im",  "\r\t", $result);

        // Remove redundant tabs
        $result = preg_replace("/(\r)(\t)+(\r)/im",  "\r\r", $result);

        // Remove multiple tabs following a line break with just one tab
        $result = preg_replace("/(\r)(\t)+/im",  "\r\t", $result);

        // Initial replacement target string for line breaks
        $breaks = "\r\r\r";
        // Initial replacement target string for tabs
        $tabs = "\t\t\t\t\t";
        for ($index = 0; $index < strlen($result); $index++)
        {
            $result = str_replace($breaks,  "\r\r",$result );
            $result = str_replace($tabs,  "\t\t\t\t",$result );
            $breaks = $breaks . "\r";
            $tabs = $tabs . "\t";
        }

        //remove spaces at the beginning of a line
        $result = preg_replace("/^ +/im",  "", $result);

        //line breaks at the beginning/end is probably unwanted. Coluld be left over by removing <html>/<head>/<body>
        $result = trim($result);

        // That's it.
        return $result;
    }
}

You can use lynx with -stdin and -dump options to achieve that:

<?php
$descriptorspec = array(
   0 => array("pipe", "r"),  // stdin is a pipe that the child will read from
   1 => array("pipe", "w"),  // stdout is a pipe that the child will write to
   2 => array("file", "/tmp/htmp2txt.log", "a") // stderr is a file to write to
);

$process = proc_open('lynx -stdin -dump 2>&1', $descriptorspec, $pipes, '/tmp', NULL);

if (is_resource($process)) {
    // $pipes now looks like this:
    // 0 => writeable handle connected to child stdin
    // 1 => readable handle connected to child stdout
    // Any error output will be appended to htmp2txt.log

    $stdin = $pipes[0];
    fwrite($stdin,  <<<'EOT'
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
 <title>TEST</title>
</head>
<body>
<h1><span>Lorem Ipsum</span></h1>

<h4>"Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..."</h4>
<h5>"There is no one who loves pain itself, who seeks after it and wants to have it, simply because it is pain..."</h5>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque et sapien ut erat porttitor suscipit id nec dui. Nam rhoncus mauris ac dui tristique bibendum. Aliquam molestie placerat gravida. Duis vitae tortor gravida libero semper cursus eu ut tortor. Nunc id orci orci. Suspendisse potenti. Phasellus vehicula leo sed erat rutrum sed blandit purus convallis.
</p>
<p>
Aliquam feugiat, neque a tempus rhoncus, neque dolor vulputate eros, non pellentesque elit lacus ut nunc. Pellentesque vel purus libero, ultrices condimentum lorem. Nam dictum faucibus mollis. Praesent adipiscing nunc sed dui ultricies molestie. Quisque facilisis purus quis felis molestie ut accumsan felis ultricies. Curabitur euismod est id est pretium accumsan. Praesent a mi in dolor feugiat vehicula quis at elit. Mauris lacus mauris, laoreet non molestie nec, adipiscing a nulla. Nullam rutrum, libero id pellentesque tempus, erat nibh ornare dolor, id accumsan est risus at leo. In convallis felis at eros condimentum adipiscing aliquam nisi faucibus. Integer arcu ligula, porttitor in fermentum vitae, lacinia nec dui.
</p>
</body>
</html>
EOT
    );
    fclose($stdin);

    echo stream_get_contents($pipes[1]);
    fclose($pipes[1]);

    // It is important that you close any pipes before calling
    // proc_close in order to avoid a deadlock
    $return_value = proc_close($process);

    echo "command returned $return_value\n";
}

In c# :

private string StripHTML(string source)
{
    try
    {
        string result;

        // Remove HTML Development formatting
        // Replace line breaks with space
        // because browsers inserts space
        result = source.Replace("\r", " ");
        // Replace line breaks with space
        // because browsers inserts space
        result = result.Replace("\n", " ");
        // Remove step-formatting
        result = result.Replace("\t", string.Empty);
        // Remove repeating spaces because browsers ignore them
        result = System.Text.RegularExpressions.Regex.Replace(result,
                                                              @"( )+", " ");

        // Remove the header (prepare first by clearing attributes)
        result = System.Text.RegularExpressions.Regex.Replace(result,
                 @"<( )*head([^>])*>", "<head>",
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);
        result = System.Text.RegularExpressions.Regex.Replace(result,
                 @"(<( )*(/)( )*head( )*>)", "</head>",
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);
        result = System.Text.RegularExpressions.Regex.Replace(result,
                 "(<head>).*(</head>)", string.Empty,
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);

        // remove all scripts (prepare first by clearing attributes)
        result = System.Text.RegularExpressions.Regex.Replace(result,
                 @"<( )*script([^>])*>", "<script>",
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);
        result = System.Text.RegularExpressions.Regex.Replace(result,
                 @"(<( )*(/)( )*script( )*>)", "</script>",
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);
        //result = System.Text.RegularExpressions.Regex.Replace(result,
        //         @"(<script>)([^(<script>\.</script>)])*(</script>)",
        //         string.Empty,
        //         System.Text.RegularExpressions.RegexOptions.IgnoreCase);
        result = System.Text.RegularExpressions.Regex.Replace(result,
                 @"(<script>).*(</script>)", string.Empty,
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);

        // remove all styles (prepare first by clearing attributes)
        result = System.Text.RegularExpressions.Regex.Replace(result,
                 @"<( )*style([^>])*>", "<style>",
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);
        result = System.Text.RegularExpressions.Regex.Replace(result,
                 @"(<( )*(/)( )*style( )*>)", "</style>",
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);
        result = System.Text.RegularExpressions.Regex.Replace(result,
                 "(<style>).*(</style>)", string.Empty,
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);

        // insert tabs in spaces of <td> tags
        result = System.Text.RegularExpressions.Regex.Replace(result,
                 @"<( )*td([^>])*>", "\t",
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);

        // insert line breaks in places of <BR> and <LI> tags
        result = System.Text.RegularExpressions.Regex.Replace(result,
                 @"<( )*br( )*>", "\r",
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);
        result = System.Text.RegularExpressions.Regex.Replace(result,
                 @"<( )*li( )*>", "\r",
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);

        // insert line paragraphs (double line breaks) in place
        // if <P>, <DIV> and <TR> tags
        result = System.Text.RegularExpressions.Regex.Replace(result,
                 @"<( )*div([^>])*>", "\r\r",
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);
        result = System.Text.RegularExpressions.Regex.Replace(result,
                 @"<( )*tr([^>])*>", "\r\r",
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);
        result = System.Text.RegularExpressions.Regex.Replace(result,
                 @"<( )*p([^>])*>", "\r\r",
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);

        // Remove remaining tags like <a>, links, images,
        // comments etc - anything that's enclosed inside < >
        result = System.Text.RegularExpressions.Regex.Replace(result,
                 @"<[^>]*>", string.Empty,
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);

        // replace special characters:
        result = System.Text.RegularExpressions.Regex.Replace(result,
                 @" ", " ",
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);

        result = System.Text.RegularExpressions.Regex.Replace(result,
                 @"&bull;", " * ",
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);
        result = System.Text.RegularExpressions.Regex.Replace(result,
                 @"&lsaquo;", "<",
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);
        result = System.Text.RegularExpressions.Regex.Replace(result,
                 @"&rsaquo;", ">",
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);
        result = System.Text.RegularExpressions.Regex.Replace(result,
                 @"&trade;", "(tm)",
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);
        result = System.Text.RegularExpressions.Regex.Replace(result,
                 @"&frasl;", "/",
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);
        result = System.Text.RegularExpressions.Regex.Replace(result,
                 @"&lt;", "<",
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);
        result = System.Text.RegularExpressions.Regex.Replace(result,
                 @"&gt;", ">",
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);
        result = System.Text.RegularExpressions.Regex.Replace(result,
                 @"&copy;", "(c)",
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);
        result = System.Text.RegularExpressions.Regex.Replace(result,
                 @"&reg;", "(r)",
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);
        // Remove all others. More can be added, see
        // http://hotwired.lycos.com/webmonkey/reference/special_characters/
        result = System.Text.RegularExpressions.Regex.Replace(result,
                 @"&(.{2,6});", string.Empty,
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);

        // for testing
        //System.Text.RegularExpressions.Regex.Replace(result,
        //       this.txtRegex.Text,string.Empty,
        //       System.Text.RegularExpressions.RegexOptions.IgnoreCase);

        // make line breaking consistent
        result = result.Replace("\n", "\r");

        // Remove extra line breaks and tabs:
        // replace over 2 breaks with 2 and over 4 tabs with 4.
        // Prepare first to remove any whitespaces in between
        // the escaped characters and remove redundant tabs in between line breaks
        result = System.Text.RegularExpressions.Regex.Replace(result,
                 "(\r)( )+(\r)", "\r\r",
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);
        result = System.Text.RegularExpressions.Regex.Replace(result,
                 "(\t)( )+(\t)", "\t\t",
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);
        result = System.Text.RegularExpressions.Regex.Replace(result,
                 "(\t)( )+(\r)", "\t\r",
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);
        result = System.Text.RegularExpressions.Regex.Replace(result,
                 "(\r)( )+(\t)", "\r\t",
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);
        // Remove redundant tabs
        result = System.Text.RegularExpressions.Regex.Replace(result,
                 "(\r)(\t)+(\r)", "\r\r",
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);
        // Remove multiple tabs following a line break with just one tab
        result = System.Text.RegularExpressions.Regex.Replace(result,
                 "(\r)(\t)+", "\r\t",
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);
        // Initial replacement target string for line breaks
        string breaks = "\r\r\r";
        // Initial replacement target string for tabs
        string tabs = "\t\t\t\t\t";
        for (int index = 0; index < result.Length; index++)
        {
            result = result.Replace(breaks, "\r\r");
            result = result.Replace(tabs, "\t\t\t\t");
            breaks = breaks + "\r";
            tabs = tabs + "\t";
        }

        // That's it.
        return result;
    }
    catch
    {
        MessageBox.Show("Error");
        return source;
    }
}

I know the question is about PHP, but I used the lynx idea to make this Perl subroutine to convert HTML to text:

use File::Temp;

sub html2Txt {
    my $html = shift;
    my $htmlF = File::Temp->new(SUFFIX => '.html');
    print $htmlF $html;
    close $htmlF;
    return scalar `/usr/bin/lynx -dump $htmlF 2> /dev/null`;
}

print html2Txt '<b>Hi there</b> Testing';

prints: Hi there Testing

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM