如何在C#中将HTML转换为文本?


71

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

我不是在寻找简单的标记剥离方法,而是会输出纯文本并合理保留原始布局的东西。

输出应如下所示:

W3C的Html2Txt

我看过HTML Agility Pack,但我认为这不是我所需要的。还有其他建议吗?

编辑:我只是从CodePlex下载HTML Agility Pack ,然后运行Html2Txt项目。多么令人失望(至少是将html转换为文本的模块)!它所做的只是剥离标签,展平表等。输出看起来不像生成的Html2Txt @ W3C。不幸的是,该来源似乎不可用。我一直在寻找是否有更多的“罐头”解决方案可用。

编辑2:谢谢大家的建议。 FlySwat向我提示了我要走的方向。我可以使用System.Diagnostics.Process类的“突降”开关运行lynx.exe将文本发送到标准输出,并与捕获标准输出ProcessStartInfo.UseShellExecute = falseProcessStartInfo.RedirectStandardOutput = true。我将所有这些都包装在C#类中。只会偶尔调用此代码,因此与在代码中进行操作相比,我不太担心生成新进程。另外,山猫快!


4
HTML Agility Pack为什么不能满足您的需求?可能会帮助您将人们引向您的特定要求。
09年

我没有详细研究它,也许行得通吗?您能指出我某个地方的代码示例吗?
马特·克劳奇

马特(Matt)您曾经写过这段代码吗?希望看到结果。
马修2010年

我会尽快发布(本周请假,这并不难)。足够多的人喜欢这个问题,我很高兴!
马特·克劳奇

嗨,马特,您是否成功将lynx包装在ac#类中-我面临着同样的要求,并且不想像现在那样重新发明轮子。
HeavenCore

Answers:


11

您正在寻找的是文本模式DOM渲染器,该输出器可以输出文本,就像Lynx或其他Text浏览器一样……这比您预期的要难得多。


不,实际上可以更轻松!!(请参阅问题编辑)。再次感谢!
马特·克劳奇

3
@MattCrouch如何使其变得更容易?原始问题中的Edit 2答案仅是一种破解-我完全无法接受,我怀疑几乎任何人的情况-您会承认吗?
PandaWood

48

只是有关HtmlAgilityPack的注释,以供后代使用。该项目包含一个将文本解析为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空格以改善表中的格式。
rboy

您可以张贴代码以返回格式化的输出字符串,即等量的换行符等吗?
路西法

尽管它不处理人力资源,但看起来很有趣。
伊恩·沃伯顿

35

您可以使用此:

 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;
        }

更新

感谢您的评论,我已更新以改进此功能


3
这是不完整的...例如,它不考虑&nbsp; 等等...
Riko 2012年

2
太棒了,与HtmlDecoded结合使用会更好,我的意思是:“ HTMLText = HttpUtility.HtmlDecode(HTMLText);”
索伦2012年

4
这实际上是一个很好的例子!我在Web应用程序中使用了它。我们所有的内容都以HT​​ML格式存储在数据库中。一个更直接的示例就是像这样使用它。字符串测试= HttpUtility.HtmlDecode(StripHTML(htmlText));
meanbunny 2012年

2
如果不在Web项目中,您还可以尝试System.Net.WebUtiltiy.HtmlDecode()
Roman Gudkov

2
如果要在可移植类库中使用WebUtility,则可以使用此nuget包。 nuget.org/packages/PCLWebUtility
chenk'Nov




3

假设您的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);
    }
}

3

我在HtmlAgility上遇到一些解码问题,并且我不想花时间研究它。

相反,我使用了Microsoft Team Foundation API中的该实用程序

var text = HtmlFilter.ConvertToPlainText(htmlContent);

2

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


好思想,做一个粗糙的版本实际上很容易。
尽管有

好吧,这取决于HTML。我在CMS中使用php编写了这种方法的快速版本,该版本每周通过纯文本电子邮件发送帖子摘要。在这种情况下,帖子的编辑器仅允许某些HTML元素。如果允许完整的HTML过渡,则要困难得多。
EricSchaefer

0

另一篇文章提出了HTML敏捷包

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



0

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

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 />");
    }   
}

0

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

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

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


-1

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


这更接近我要寻找的内容,但这仍使html表“变平”。:(
马特·克劳奇


-1

尝试简单易用的方法: 只需致电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 "";

        }

-2

在Genexus中,您可以使用Regex制作

&pattern ='<[^>] +>'

&TSTRPNOT =&TSTRPNOT.ReplaceRegEx(&pattern,“”)

在Genexus possiamo gestirlo con Regex中,



-3

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

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

-4

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

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

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


6
最多,请注意,这是您推荐的产品。您的所有答案IIRC都在建议该产品。SO社区非常保护并且对垃圾邮件/垃圾邮件很敏感。如果您不清楚,并且您在这里所做的只是建议人们购买您的软件,那么您最终将遭受的伤害大于好处。

1
嗨,威尔!是的,这是我的产品-是的,对不起,这篇文章看起来像是广告。我现在将其更改为不包含任何广告。
Maxim
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.