Advertisement
uwekeim

Convert HTML to plain text

May 10th, 2013 (edited)
2,378
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 12.12 KB | None | 0 0
  1. namespace ZetaUploader.ServerRuntime.Sys
  2. {
  3.     using System;
  4.     using System.Linq;
  5.     using System.Text.RegularExpressions;
  6.     using ZetaLongPaths;
  7.  
  8.     /// <summary>
  9.     /// Function to convert HTML source into readable plain text, intended to use in e-mail messages.
  10.     /// </summary>
  11.     /// <remarks>
  12.     /// Changelog:
  13.     ///
  14.     /// - 2024-07-01, Uwe Keim: Doing an HTML-decode at start to avoid e.g. German Umlauts
  15.     ///                         to be removed.
  16.     /// - 2018-09-23, Uwe Keim: Added ability to keep links visible and not remove them.
  17.     /// - 2018-09-23, Uwe Keim: Added ability to keep HRs as "-------...".
  18.     /// - 2013-05-10, Uwe Keim: Initial release.
  19.     ///
  20.     /// Use it for whatever you want. Some of our tools that use this function include:
  21.     ///
  22.     /// - https://www.zeta-producer.com
  23.     /// - https://www.zeta-uploader.com
  24.     /// - https://www.zeta-test.com
  25.     /// </remarks>
  26.     public static class HtmlToText2
  27.     {
  28.         /// <summary>
  29.         /// Convert a given HTML source code to readable plain text, intented to use in e-mail message.
  30.         /// </summary>
  31.         public static string ConvertHtmlToPlainText(string html)
  32.         {
  33.             if (string.IsNullOrWhiteSpace(html)) return string.Empty;
  34.  
  35.             // 2024-07-01, Uwe Keim: Umlaute werden geschluckt, deshalb in die literalen Zeichen umwandeln.
  36.             html = System.Net.WebUtility.HtmlDecode(html);
  37.  
  38.             // http://pastebin.com/NswerNkQ
  39.             // http://stackoverflow.com/questions/8419517/convert-html-to-plain-text-while-preserving-p-br-ul-ol
  40.             // http://www.codeproject.com/KB/HTML/HTML_to_Plain_Text.aspx
  41.             // https://github.com/soundasleep/html2text/blob/master/src/Html2Text.php for keeping links usable.
  42.  
  43.             // Remove HTML Development formatting
  44.             // Replace line breaks with space
  45.             // because browsers inserts space
  46.             // ReSharper disable LocalizableElement
  47.             var result = html.Replace("\r", @" ");
  48.             // Replace line breaks with space
  49.             // because browsers inserts space
  50.             result = result.Replace("\n", @" ");
  51.             // Remove step-formatting
  52.             result = result.Replace("\t", string.Empty);
  53.             // ReSharper restore LocalizableElement
  54.             // Remove repeating spaces because browsers ignore them
  55.             result = Regex.Replace(result, @"(\s)+", " ", RegexOptions.Singleline);
  56.  
  57.             // Remove the header (prepare first by clearing attributes)
  58.             result = Regex.Replace(result,
  59.                         @"<( )*head([^>])*>", @"<head>",
  60.                         RegexOptions.IgnoreCase);
  61.             result = Regex.Replace(result,
  62.                         @"(<( )*(/)( )*head( )*>)", @"</head>",
  63.                         RegexOptions.IgnoreCase);
  64.             result = Regex.Replace(result,
  65.                         @"(<head>).*(</head>)", string.Empty,
  66.                         RegexOptions.IgnoreCase);
  67.  
  68.             // remove all scripts (prepare first by clearing attributes)
  69.             result = Regex.Replace(result,
  70.                         @"<( )*script([^>])*>", @"<script>",
  71.                         RegexOptions.IgnoreCase);
  72.             result = Regex.Replace(result,
  73.                         @"(<( )*(/)( )*script( )*>)", @"</script>",
  74.                         RegexOptions.IgnoreCase);
  75.             //result = Regex.Replace(result,
  76.             //         @"(<script>)([^(<script>\.</script>)])*(</script>)",
  77.             //         string.Empty,
  78.             //         RegexOptions.IgnoreCase);
  79.             result = Regex.Replace(result,
  80.                         @"(<script>).*(</script>)", string.Empty,
  81.                         RegexOptions.IgnoreCase);
  82.  
  83.             // remove all styles (prepare first by clearing attributes)
  84.             result = Regex.Replace(result,
  85.                         @"<( )*style([^>])*>", @"<style>",
  86.                         RegexOptions.IgnoreCase);
  87.             result = Regex.Replace(result,
  88.                         @"(<( )*(/)( )*style( )*>)", @"</style>",
  89.                         RegexOptions.IgnoreCase);
  90.             result = Regex.Replace(result,
  91.                         @"(<style>).*(</style>)", string.Empty,
  92.                         RegexOptions.IgnoreCase);
  93.  
  94.             // insert tabs in spaces of <td> tags
  95.             result = Regex.Replace(result,
  96.                         @"<( )*td([^>])*>", "\t",
  97.                         RegexOptions.IgnoreCase);
  98.  
  99.             // insert line breaks in places of <BR> and <LI> tags
  100.             result = Regex.Replace(result,
  101.                         @"<( )*br( )*>", "\r",
  102.                         RegexOptions.IgnoreCase);
  103.             result = Regex.Replace(result,
  104.                         @"<( )*li( )*>", "\r- ",
  105.                         RegexOptions.IgnoreCase);
  106.  
  107.             // --
  108.             // Keep HRs.
  109.  
  110.             var hrs = Regex.Matches(
  111.                     result, @"<hr.*?>",
  112.                     RegexOptions.IgnoreCase)
  113.                 .Cast<Match>()
  114.                 .Where(m => m.Success);
  115.  
  116.             foreach (var hr in hrs)
  117.             {
  118.                 // Insert inside "<p>" tags to let the following code add
  119.                 // the correct number of empty lines.
  120.                 result = result.Replace(hr.Value, "<p>---------------------------------------------------------------</p>");
  121.             }
  122.  
  123.             // --
  124.  
  125.             // insert line paragraphs (double line breaks) in place
  126.             // if <P>, <DIV> and <TR> tags
  127.             result = Regex.Replace(result,
  128.                         @"<( )*div([^>])*>", "\r\r",
  129.                         RegexOptions.IgnoreCase);
  130.             result = Regex.Replace(result,
  131.                         @"<( )*tr([^>])*>", "\r\r",
  132.                         RegexOptions.IgnoreCase);
  133.             result = Regex.Replace(result,
  134.                         @"<( )*p([^>])*>", "\r\r",
  135.                         RegexOptions.IgnoreCase);
  136.             result = Regex.Replace(result,
  137.                         @"<( )*ol([^>])*>", "\r\r",
  138.                         RegexOptions.IgnoreCase);
  139.             result = Regex.Replace(result,
  140.                         @"<( )*ul([^>])*>", "\r\r",
  141.                         RegexOptions.IgnoreCase);
  142.  
  143.             // --
  144.             // Extract from Anchors.
  145.  
  146.             var anchors = Regex.Matches(
  147.                     result,
  148.                     @"<a.*?href\s*=\s*(?:['""])(.+?)(?:['""])>(.*?)</a>",
  149.                     RegexOptions.IgnoreCase | RegexOptions.Singleline)
  150.                 .Cast<Match>()
  151.                 .Where(m => m.Success);
  152.  
  153.             foreach (var anchor in anchors)
  154.             {
  155.                 var link = anchor.Groups[1].Value;
  156.                 var text = anchor.Groups[2].Value;
  157.  
  158.                 var coreLink = link;
  159.                 var coreText = text;
  160.  
  161.                 var isSame =
  162.                     coreLink.EqualsNoCase(coreText) ||
  163.                     coreLink.EqualsNoCase($@"mailto:{coreText}") ||
  164.                     coreLink.EqualsNoCase($@"http://{coreText}") ||
  165.                     coreLink.EqualsNoCase($@"https://{coreText}");
  166.  
  167.                 var replacement = isSame || string.IsNullOrEmpty(coreText) ? coreLink : $@"[{coreText}]({coreLink})";
  168.  
  169.                 result = result.Replace(anchor.Value, replacement);
  170.             }
  171.  
  172.             // --
  173.  
  174.             // Remove remaining tags like <a>, links, images,
  175.             // comments etc - anything that's enclosed inside < >
  176.             result = Regex.Replace(result,
  177.                     @"<[^>]*>", string.Empty,
  178.                     RegexOptions.IgnoreCase);
  179.  
  180.             // replace special characters:
  181.             result = Regex.Replace(result,
  182.                         @" ", " ",
  183.                         RegexOptions.IgnoreCase);
  184.  
  185.             result = Regex.Replace(result,
  186.                         @"&bull;", " * ",
  187.                         RegexOptions.IgnoreCase);
  188.             result = Regex.Replace(result,
  189.                         @"&lsaquo;", "<",
  190.                         RegexOptions.IgnoreCase);
  191.             result = Regex.Replace(result,
  192.                         @"&rsaquo;", ">",
  193.                         RegexOptions.IgnoreCase);
  194.             result = Regex.Replace(result,
  195.                         @"&trade;", "(tm)",
  196.                         RegexOptions.IgnoreCase);
  197.             result = Regex.Replace(result,
  198.                         @"&frasl;", "/",
  199.                         RegexOptions.IgnoreCase);
  200.             result = Regex.Replace(result,
  201.                         @"&lt;", "<",
  202.                         RegexOptions.IgnoreCase);
  203.             result = Regex.Replace(result,
  204.                         @"&gt;", ">",
  205.                         RegexOptions.IgnoreCase);
  206.             result = Regex.Replace(result,
  207.                         @"&copy;", "(c)",
  208.                         RegexOptions.IgnoreCase);
  209.             result = Regex.Replace(result,
  210.                         @"&reg;", "(r)",
  211.                         RegexOptions.IgnoreCase);
  212.             // Remove all others. More can be added, see
  213.             // http://hotwired.lycos.com/webmonkey/reference/special_characters/
  214.             result = Regex.Replace(result,
  215.                         @"&(.{2,6});", string.Empty,
  216.                         RegexOptions.IgnoreCase);
  217.  
  218.             // for testing
  219.             //Regex.Replace(result,
  220.             //       this.txtRegex.Text,string.Empty,
  221.             //       RegexOptions.IgnoreCase);
  222.  
  223.             // make line breaking consistent
  224.             // ReSharper disable LocalizableElement
  225.             result = result.Replace("\n", "\r");
  226.             // ReSharper restore LocalizableElement
  227.  
  228.             // Remove extra line breaks and tabs:
  229.             // replace over 2 breaks with 2 and over 4 tabs with 4.
  230.             // Prepare first to remove any whitespaces in between
  231.             // the escaped characters and remove redundant tabs in between line breaks
  232.             result = Regex.Replace(result,
  233.                         "(\r)( )+(\r)", "\r\r",
  234.                         RegexOptions.IgnoreCase);
  235.             result = Regex.Replace(result,
  236.                         "(\t)( )+(\t)", "\t\t",
  237.                         RegexOptions.IgnoreCase);
  238.             result = Regex.Replace(result,
  239.                         "(\t)( )+(\r)", "\t\r",
  240.                         RegexOptions.IgnoreCase);
  241.             result = Regex.Replace(result,
  242.                         "(\r)( )+(\t)", "\r\t",
  243.                         RegexOptions.IgnoreCase);
  244.             // Remove redundant tabs
  245.             result = Regex.Replace(result,
  246.                         "(\r)(\t)+(\r)", "\r\r",
  247.                         RegexOptions.IgnoreCase);
  248.             // Remove multiple tabs following a line break with just one tab
  249.             result = Regex.Replace(result,
  250.                         "(\r)(\t)+", "\r\t",
  251.                         RegexOptions.IgnoreCase);
  252.             // Initial replacement target string for line breaks
  253.             // ReSharper disable LocalizableElement
  254.             var breaks = "\r\r\r";
  255.             // ReSharper restore LocalizableElement
  256.             // Initial replacement target string for tabs
  257.             // ReSharper disable LocalizableElement
  258.             var tabs = "\t\t\t\t\t";
  259.             // ReSharper restore LocalizableElement
  260.             for (var index = 0; index < result.Length; index++)
  261.             {
  262.                 // ReSharper disable LocalizableElement
  263.                 result = result.Replace(breaks, "\r\r");
  264.                 result = result.Replace(tabs, "\t\t\t\t");
  265.                 breaks = breaks + "\r";
  266.                 tabs = tabs + "\t";
  267.                 // ReSharper restore LocalizableElement
  268.             }
  269.  
  270.             // UK: Space at the beginning.
  271.             // ReSharper disable LocalizableElement
  272.             result = result.Replace("\r ", "\r");
  273.             // ReSharper restore LocalizableElement
  274.  
  275.             // UK: Normalize.
  276.             // ReSharper disable LocalizableElement
  277.             result = result.Replace("\r", Environment.NewLine);
  278.             // ReSharper restore LocalizableElement
  279.  
  280.             // That's it.
  281.             return result.Trim();
  282.         }
  283.     }
  284. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement