【问题标题】:How can I Convert HTML to Text in C#?如何在 C# 中将 HTML 转换为文本?
【发布时间】:2010-10-18 09:56:58
【问题描述】:

我正在寻找将 HTML 文档转换为纯文本的 C# 代码。

我不是在寻找简单的标签剥离,而是在合理保留原始布局的情况下输出纯文本。

输出应如下所示:

Html2Txt at W3C

我查看了 HTML Agility Pack,但我认为这不是我需要的。有人有其他建议吗?

编辑:我刚刚从CodePlex 下载了 HTML Agility Pack,然后运行了 Html2Txt 项目。多么令人失望(至少是进行 html 到文本转换的模块)!它所做的只是剥离标签、展平表格等。输出看起来与 W3C 生成的 Html2Txt 完全不同。太糟糕了,该来源似乎不可用。 我正在寻找是否有更“罐装”的解决方案可用。

编辑 2:感谢大家的建议。 FlySwat 将我引向了我想去的方向。我可以使用System.Diagnostics.Process 类来运行带有“-dump”开关的lynx.exe,以将文本发送到标准输出,并使用ProcessStartInfo.UseShellExecute = falseProcessStartInfo.RedirectStandardOutput = true 捕获标准输出。我将把所有这些包装在一个 C# 类中。这段代码只会偶尔被调用,所以我不太担心产生一个新进程而不是在代码中执行它。另外,Lynx 速度很快!!

【问题讨论】:

  • 为什么 Html Agility Pack 不能满足您的需求?可能有助于引导人们满足您的特定要求。
  • 我没有仔细看,也许它会起作用?你能指出我某处的代码示例吗?
  • Matt 你写过这段代码吗?很想看看结果。
  • 我很快就会发布它(这周放了一天假,这不是太难)。有足够多的人喜欢这个问题,我很高兴!
  • 嗨,马特,您是否设法将 lynx 包装在一个 c# 类中 - 我面临着同样的要求并且不想像以前那样重新发明轮子。

标签: c# html .net parsing text


【解决方案1】:

只是关于 HtmlAgilityPack 的注释,供后人参考。该项目包含一个example of parsing text to html,正如 OP 所指出的,它根本不像任何编写 HTML 的人所设想的那样处理空格。那里有全文渲染解决方案,其他人对此问题提出了意见,但事实并非如此(它甚至无法处理当前形式的表格),但它轻巧且快速,这是我创建简单文本所需的全部HTML 电子邮件的版本。

using System.IO;
using System.Text.RegularExpressions;
using HtmlAgilityPack;

//small but important modification to class https://github.com/zzzprojects/html-agility-pack/blob/master/src/Samples/Html2Txt/HtmlConvert.cs
public static class HtmlToText
{

    public static string Convert(string path)
    {
        HtmlDocument doc = new HtmlDocument();
        doc.Load(path);
        return ConvertDoc(doc);
    }

    public static string ConvertHtml(string html)
    {
        HtmlDocument doc = new HtmlDocument();
        doc.LoadHtml(html);
        return ConvertDoc(doc);
    }

    public static string ConvertDoc (HtmlDocument doc)
    {
        using (StringWriter sw = new StringWriter())
        {
            ConvertTo(doc.DocumentNode, sw);
            sw.Flush();
            return sw.ToString();
        }
    }

    internal static void ConvertContentTo(HtmlNode node, TextWriter outText, PreceedingDomTextInfo textInfo)
    {
        foreach (HtmlNode subnode in node.ChildNodes)
        {
            ConvertTo(subnode, outText, textInfo);
        }
    }
    public static void ConvertTo(HtmlNode node, TextWriter outText)
    {
        ConvertTo(node, outText, new PreceedingDomTextInfo(false));
    }
    internal static void ConvertTo(HtmlNode node, TextWriter outText, PreceedingDomTextInfo textInfo)
    {
        string html;
        switch (node.NodeType)
        {
            case HtmlNodeType.Comment:
                // don't output comments
                break;
            case HtmlNodeType.Document:
                ConvertContentTo(node, outText, textInfo);
                break;
            case HtmlNodeType.Text:
                // script and style must not be output
                string parentName = node.ParentNode.Name;
                if ((parentName == "script") || (parentName == "style"))
                {
                    break;
                }
                // get text
                html = ((HtmlTextNode)node).Text;
                // is it in fact a special closing node output as text?
                if (HtmlNode.IsOverlappedClosingElement(html))
                {
                    break;
                }
                // check the text is meaningful and not a bunch of whitespaces
                if (html.Length == 0)
                {
                    break;
                }
                if (!textInfo.WritePrecedingWhiteSpace || textInfo.LastCharWasSpace)
                {
                    html= html.TrimStart();
                    if (html.Length == 0) { break; }
                    textInfo.IsFirstTextOfDocWritten.Value = textInfo.WritePrecedingWhiteSpace = true;
                }
                outText.Write(HtmlEntity.DeEntitize(Regex.Replace(html.TrimEnd(), @"\s{2,}", " ")));
                if (textInfo.LastCharWasSpace = char.IsWhiteSpace(html[html.Length - 1]))
                {
                    outText.Write(' ');
                }
                    break;
            case HtmlNodeType.Element:
                string endElementString = null;
                bool isInline;
                bool skip = false;
                int listIndex = 0;
                switch (node.Name)
                {
                    case "nav":
                        skip = true;
                        isInline = false;
                        break;
                    case "body":
                    case "section":
                    case "article":
                    case "aside":
                    case "h1":
                    case "h2":
                    case "header":
                    case "footer":
                    case "address":
                    case "main":
                    case "div":
                    case "p": // stylistic - adjust as you tend to use
                        if (textInfo.IsFirstTextOfDocWritten)
                        {
                            outText.Write("\r\n");
                        }
                        endElementString = "\r\n";
                        isInline = false;
                        break;
                    case "br":
                        outText.Write("\r\n");
                        skip = true;
                        textInfo.WritePrecedingWhiteSpace = false;
                        isInline = true;
                        break;
                    case "a":
                        if (node.Attributes.Contains("href"))
                        {
                            string href = node.Attributes["href"].Value.Trim();
                            if (node.InnerText.IndexOf(href, StringComparison.InvariantCultureIgnoreCase)==-1)
                            {
                                endElementString =  "<" + href + ">";
                            }  
                        }
                        isInline = true;
                        break;
                    case "li": 
                        if(textInfo.ListIndex>0)
                        {
                            outText.Write("\r\n{0}.\t", textInfo.ListIndex++); 
                        }
                        else
                        {
                            outText.Write("\r\n*\t"); //using '*' as bullet char, with tab after, but whatever you want eg "\t->", if utf-8 0x2022
                        }
                        isInline = false;
                        break;
                    case "ol": 
                        listIndex = 1;
                        goto case "ul";
                    case "ul": //not handling nested lists any differently at this stage - that is getting close to rendering problems
                        endElementString = "\r\n";
                        isInline = false;
                        break;
                    case "img": //inline-block in reality
                        if (node.Attributes.Contains("alt"))
                        {
                            outText.Write('[' + node.Attributes["alt"].Value);
                            endElementString = "]";
                        }
                        if (node.Attributes.Contains("src"))
                        {
                            outText.Write('<' + node.Attributes["src"].Value + '>');
                        }
                        isInline = true;
                        break;
                    default:
                        isInline = true;
                        break;
                }
                if (!skip && node.HasChildNodes)
                {
                    ConvertContentTo(node, outText, isInline ? textInfo : new PreceedingDomTextInfo(textInfo.IsFirstTextOfDocWritten){ ListIndex = listIndex });
                }
                if (endElementString != null)
                {
                    outText.Write(endElementString);
                }
                break;
        }
    }
}
internal class PreceedingDomTextInfo
{
    public PreceedingDomTextInfo(BoolWrapper isFirstTextOfDocWritten)
    {
        IsFirstTextOfDocWritten = isFirstTextOfDocWritten;
    }
    public bool WritePrecedingWhiteSpace {get;set;}
    public bool LastCharWasSpace { get; set; }
    public readonly BoolWrapper IsFirstTextOfDocWritten;
    public int ListIndex { get; set; }
}
internal class BoolWrapper
{
    public BoolWrapper() { }
    public bool Value { get; set; }
    public static implicit operator bool(BoolWrapper boolWrapper)
    {
        return boolWrapper.Value;
    }
    public static implicit operator BoolWrapper(bool boolWrapper)
    {
        return new BoolWrapper{ Value = boolWrapper };
    }
}

例如,下面的 HTML 代码...

<!DOCTYPE HTML>
<html>
    <head>
    </head>
    <body>
        <header>
            Whatever Inc.
        </header>
        <main>
            <p>
                Thanks for your enquiry. As this is the 1<sup>st</sup> time you have contacted us, we would like to clarify a few things:
            </p>
            <ol>
                <li>
                    Please confirm this is your email by replying.
                </li>
                <li>
                    Then perform this step.
                </li>
            </ol>
            <p>
                Please solve this <img alt="complex equation" src="http://upload.wikimedia.org/wikipedia/commons/8/8d/First_Equation_Ever.png"/>. Then, in any order, could you please:
            </p>
            <ul>
                <li>
                    a point.
                </li>
                <li>
                    another point, with a <a href="http://en.wikipedia.org/wiki/Hyperlink">hyperlink</a>.
                </li>
            </ul>
            <p>
                Sincerely,
            </p>
            <p>
                The whatever.com team
            </p>
        </main>
        <footer>
            Ph: 000 000 000<br/>
            mail: whatever st
        </footer>
    </body>
</html>

...将转化为:

Whatever Inc. 


Thanks for your enquiry. As this is the 1st time you have contacted us, we would like to clarify a few things: 

1.  Please confirm this is your email by replying. 
2.  Then perform this step. 

Please solve this [complex equation<http://upload.wikimedia.org/wikipedia/commons/8/8d/First_Equation_Ever.png>]. Then, in any order, could you please: 

*   a point. 
*   another point, with a hyperlink<http://en.wikipedia.org/wiki/Hyperlink>. 

Sincerely, 

The whatever.com team 


Ph: 000 000 000
mail: whatever st 

...相对于:

        Whatever Inc.


            Thanks for your enquiry. As this is the 1st time you have contacted us, we would like to clarify a few things:

                Please confirm this is your email by replying.

                Then perform this step.


            Please solve this . Then, in any order, could you please:

                a point.

                another point, with a hyperlink.


            Sincerely,


            The whatever.com team

        Ph: 000 000 000
        mail: whatever st

【讨论】:

  • 您还可以处理node.name tr 换行和td 空格来改进表格内的格式。
  • 你能发布一个返回格式化输出字符串的代码,即相同数量的新行空间等
  • 看起来很有趣,虽然它不处理 HR。
  • 当前显示的代码中有一个粗略的位——if(textInfo.LastCharWasSpace = char.IsWhiteSpace(html[html.Length - 1]))(注意单个=)似乎对if() 语句中的赋值产生了副作用。这是一种代码异味,很难推断出意图与错字。
【解决方案2】:

你可以用这个:

 public static string StripHTML(string HTMLText, bool decode = true)
        {
            Regex reg = new Regex("<[^>]+>", RegexOptions.IgnoreCase);
            var stripped = reg.Replace(HTMLText, "");
            return decode ? HttpUtility.HtmlDecode(stripped) : stripped;
        }

更新

感谢我更新的 cmets 以改进此功能

【讨论】:

  • 这是不完整的......例如,它不考虑像   这样的实体。等等……
  • 太棒了,如果结合HtmlDecoded会更好,我的意思是:'HTMLText = HttpUtility.HtmlDecode(HTMLText);'
  • 这实际上是一个很好的例子!我在我的网络应用程序中使用了它。我们所有的内容都以 HTML 格式存储在数据库中。一个更直接的例子是像这样使用它。字符串测试 = HttpUtility.HtmlDecode(StripHTML(htmlText));
  • 如果不在网络项目中,您也可以尝试 System.Net.WebUtiltiy.HtmlDecode()
  • 如果想在Portable类库中使用WebUtility,可以使用这个nuget包。 nuget.org/packages/PCLWebUtility
【解决方案3】:

我从可靠消息来源获悉,如果您在 .Net 中进行 HTML 解析,您应该再次查看 HTML 敏捷包。

http://www.codeplex.com/htmlagilitypack

SO 上的一些示例..

HTML Agility pack - parsing tables

【讨论】:

  • 祝你好运..我不假装它会很容易..但我认为这是正确的道路。
【解决方案4】:

您正在寻找的是一个输出文本的文本模式 DOM 渲染器,很像 Lynx 或其他文本浏览器...这比您预期的要难得多。

【讨论】:

  • 不,它实际上使它更容易! (见问题编辑)。再次感谢!
  • @MattCrouch 如何让它变得更容易?原始问题中的 Edit 2 答案只不过是一个 hack - 我完全不能接受,我怀疑几乎任何人的情况 - 你会承认这一点吗?
【解决方案5】:

您是否尝试过http://www.aaronsw.com/2002/html2text/ 它是 Python,但开源。

【讨论】:

    【解决方案6】:

    假设您的 html 格式良好,您也可以尝试 XSL 转换。

    这是一个例子:

    using System;
    using System.IO;
    using System.Xml.Linq;
    using System.Xml.XPath;
    using System.Xml.Xsl;
    
    class Html2TextExample
    {
        public static string Html2Text(XDocument source)
        {
            var writer = new StringWriter();
            Html2Text(source, writer);
            return writer.ToString();
        }
    
        public static void Html2Text(XDocument source, TextWriter output)
        {
            Transformer.Transform(source.CreateReader(), null, output);
        }
    
        public static XslCompiledTransform _transformer;
        public static XslCompiledTransform Transformer
        {
            get
            {
                if (_transformer == null)
                {
                    _transformer = new XslCompiledTransform();
                    var xsl = XDocument.Parse(@"<?xml version='1.0'?><xsl:stylesheet version=""1.0"" xmlns:xsl=""http://www.w3.org/1999/XSL/Transform"" exclude-result-prefixes=""xsl""><xsl:output method=""html"" indent=""yes"" version=""4.0"" omit-xml-declaration=""yes"" encoding=""UTF-8"" /><xsl:template match=""/""><xsl:value-of select=""."" /></xsl:template></xsl:stylesheet>");
                    _transformer.Load(xsl.CreateNavigator());
                }
                return _transformer;
            }
        }
    
        static void Main(string[] args)
        {
            var html = XDocument.Parse("<html><body><div>Hello world!</div></body></html>");
            var text = Html2Text(html);
            Console.WriteLine(text);
        }
    }
    

    【讨论】:

      【解决方案7】:

      因为我想用 LF 和项目符号转换为纯文本,所以我在 codeproject 上找到了这个漂亮的解决方案,它涵盖了许多转换用例:

      Convert HTML to Plain Text

      是的,看起来很大,但效果很好。

      【讨论】:

        【解决方案8】:

        我在使用 HtmlAgility 时遇到了一些解码问题,我不想花时间调查它。

        我使用了来自 Microsoft Team Foundation API 的 that utility

        var text = HtmlFilter.ConvertToPlainText(htmlContent);
        

        【讨论】:

          【解决方案9】:

          最简单的可能是标签剥离结合使用文本布局元素替换一些标签,例如列表元素 (li) 的破折号和 br 和 p 的换行符。 将其扩展到表格应该不会太难。

          【讨论】:

          • 好主意,做一个粗略的版本其实很容易。
          • 这取决于 HTML。我在 php 中为一个通过纯文本电子邮件发送帖子每周摘要的 CMS 编写了一个快速而肮脏的版本。在这种情况下,帖子的编辑器只允许某些 HTML 元素。如果允许完整的 HTML 过渡,这应该会更难。
          【解决方案10】:

          Another post 建议HTML agility pack

          这是一个敏捷的 HTML 解析器, 构建一个读/写 DOM 并支持 普通的 XPATH 或 XSLT(你实际上 不必了解 XPATH 也不必 XSLT 使用它,不用担心...)。它是 一个 .NET 代码库,可让您 解析“网络之外”的 HTML 文件。这 解析器对“真实的 世界”格式错误的 HTML。对象 模型与提议的非常相似 System.Xml,但用于 HTML 文档(或 流)。

          【讨论】:

            【解决方案11】:

            我过去使用过Detagger。它在将 HTML 格式化为文本方面做得非常好,而且不仅仅是一个标签删除器。

            【讨论】:

              【解决方案12】:

              此功能将“您在浏览器中看到的内容”转换为带有换行符的纯文本。 (如果您想在浏览器中查看结果,只需使用注释返回值)

              public string HtmlFileToText(string filePath)
              {
                  using (var browser = new WebBrowser())
                  {
                      string text = File.ReadAllText(filePath);
                      browser.ScriptErrorsSuppressed = true;
                      browser.Navigate("about:blank");
                      browser?.Document?.OpenNew(false);
                      browser?.Document?.Write(text);
                      return browser.Document?.Body?.InnerText;
                      //return browser.Document?.Body?.InnerText.Replace(Environment.NewLine, "<br />");
                  }   
              }
              

              【讨论】:

                【解决方案13】:

                这是使用 HtmlAgilityPack 的简短而甜蜜的回答。您可以在 LinqPad 中运行它。

                var html = "<div>..whatever html</div>";
                var doc = new HtmlAgilityPack.HtmlDocument();
                doc.LoadHtml(html);
                var plainText = doc.DocumentNode.InnerText;
                

                我只是在任何需要 HTML 解析的 .NET 项目中使用 HtmlAgilityPack。它简单、可靠且快速。

                【讨论】:

                  【解决方案14】:

                  我不懂 C#,但这里有一个相当小且易于阅读的 python html2txt 脚本:http://www.aaronsw.com/2002/html2text/

                  【讨论】:

                  • 这更接近我正在寻找的,但这仍然“扁平化”了 html 表格。 :(
                  【解决方案15】:

                  我有recently blogged on a solution,它通过使用 Markdown XSLT 文件来转换 HTML 源代码。 HTML 源代码当然首先需要是有效的 XML

                  【讨论】:

                    【解决方案16】:

                    试试简单实用的方法:只需拨打StripHTML(WebBrowserControl_name);

                     public string StripHTML(WebBrowser webp)
                            {
                                try
                                {
                                    doc.execCommand("SelectAll", true, null);
                                    IHTMLSelectionObject currentSelection = doc.selection;
                    
                                    if (currentSelection != null)
                                    {
                                        IHTMLTxtRange range = currentSelection.createRange() as IHTMLTxtRange;
                                        if (range != null)
                                        {
                                            currentSelection.empty();
                                            return range.text;
                                        }
                                    }
                                }
                                catch (Exception ep)
                                {
                                    //MessageBox.Show(ep.Message);
                                }
                                return "";
                    
                            }
                    

                    【讨论】:

                      【解决方案17】:

                      在 Genexus 中你可以用正则表达式制作

                      &pattern = ']+>'

                      &TSTRPNOT=&TSTRPNOT.ReplaceRegEx(&pattern,"")

                      在 Genexus possiamo gestirlo con 正则表达式中,

                      【讨论】:

                        【解决方案18】:

                        如果您使用的是 .NET 框架 4.5,则可以使用 System.Net.WebUtility.HtmlDecode(),它采用 HTML 编码字符串并返回解码字符串。

                        记录在 MSDN 上:http://msdn.microsoft.com/en-us/library/system.net.webutility.htmldecode(v=vs.110).aspx

                        您也可以在 Windows 应用商店应用中使用它。

                        【讨论】:

                        • 这不是将 HTML 转换为文本,而是用于将 HTML 编码的字符串转换为纯 HTML(标签)。
                        【解决方案19】:

                        您可以使用 WebBrowser 控件在内存中呈现您的 html 内容。 LoadCompleted 事件触发后...

                        IHTMLDocument2 htmlDoc = (IHTMLDocument2)webBrowser.Document;
                        string innerHTML = htmlDoc.body.innerHTML;
                        string innerText = htmlDoc.body.innerText;
                        

                        【讨论】:

                          【解决方案20】:

                          这是在 C# 中将 HTML 转换为 Text 或 RTF 的另一种解决方案:

                              SautinSoft.HtmlToRtf h = new SautinSoft.HtmlToRtf();
                              h.OutputFormat = HtmlToRtf.eOutputFormat.TextUnicode;
                              string text = h.ConvertString(htmlString);
                          

                          这个库不是免费的,这是商业产品,是我自己的产品。

                          【讨论】:

                          • Max,请清楚这是您推荐的产品。你所有的答案 IIRC 都是你推荐这个产品。 SO 社区对垃圾邮件/人造草皮非常保护和敏感。如果您不清楚,并且您在这里所做的只是建议人们购买您的软件,那么您最终将弊大于利。
                          • 您好!是的,这是我的产品——你是对的,很抱歉这篇文章看起来像广告。我现在就改一下,让它没有任何广告。
                          猜你喜欢
                          • 2016-11-10
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 2015-04-15
                          • 2016-02-04
                          • 1970-01-01
                          • 2010-09-22
                          相关资源
                          最近更新 更多