Как я могу конвертировать HTML в текст в C#?

Я ищу код C# для преобразования HTML-документа в обычный текст.

Я не ищу простое удаление тегов, но что-то, что будет выводить простой текст с разумным сохранением оригинального макета.

Вывод должен выглядеть так:

Html2Txt на W3C

Я посмотрел на HTML Agility Pack, но не думаю, что это то, что мне нужно. У кого-нибудь есть другие предложения?

РЕДАКТИРОВАТЬ: Я просто скачал пакет Agility HTML из CodePlex и запустил проект Html2Txt. Какое разочарование (по крайней мере, модуль, который делает преобразование HTML в текст)! Все, что он делал, это вырезал теги, выравнивал таблицы и т. Д. Вывод не выглядел так, как Html2Txt @ W3C. Жаль, что источник, кажется, не доступен. Я искал, есть ли более "консервированное" решение.

РЕДАКТИРОВАТЬ 2: Спасибо всем за ваши предложения. FlySwat наклонил меня в ту сторону, куда я хотел идти. Я могу использовать System.Diagnostics.Process класс для запуска lynx.exe с ключом "-dump" для отправки текста на стандартный вывод и захвата стандартного вывода с помощью ProcessStartInfo.UseShellExecute = false а также ProcessStartInfo.RedirectStandardOutput = true, Я оберну все это в класс C#. Этот код будет вызываться только изредка, поэтому я не слишком озабочен порождением нового процесса по сравнению с выполнением его в коде. Плюс, рысь это быстро!

19 ответов

Решение

То, что вы ищете, это текстовый режим DOM-рендеринга, который выводит текст, очень похожий на Lynx или другие текстовые браузеры... Это гораздо сложнее, чем вы ожидаете.

Просто заметка о пакете 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

Вы можете использовать это:

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

обновленный

Спасибо за комментарии, которые я обновил, чтобы улучшить эту функцию

Из надежного источника я узнал, что, если вы выполняете анализ HTML в.Net, вам следует снова взглянуть на пакет гибкости HTML.

http://www.codeplex.com/htmlagilitypack

Некоторый образец на ТАК..

HTML Agility Pack - парсинг таблиц

Поскольку я хотел преобразовать в простой текст с помощью LF и маркеров, я нашел это симпатичное решение для codeproject, которое охватывает многие варианты использования преобразования:

Конвертировать HTML в обычный текст

Да, выглядит такой большой, но отлично работает.

У меня были некоторые проблемы с декодированием HtmlAgility, и я не хотел тратить время на его изучение.

Вместо этого я использовал эту утилиту из Microsoft Team Foundation API:

var text = HtmlFilter.ConvertToPlainText(htmlContent);

Вы пробовали http://www.aaronsw.com/2002/html2text/ это Python, но с открытым исходным кодом.

Предполагая, что у вас хорошо сформированный 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);
    }
}

Самым простым будет, вероятно, разметка тегов в сочетании с заменой некоторых тегов элементами текстового макета, такими как тире для элементов списка (li) и разрывы строк для br и p. Не должно быть слишком сложно распространить это на таблицы.

Вот краткий сладкий ответ с использованием HtmlAgilityPack. Вы можете запустить это в LinqPad.

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

Я просто использую HtmlAgilityPack в любом проекте.NET, который требует синтаксического анализа HTML. Это просто, надежно и быстро.

Другой пост предлагает пакет гибкости HTML:

Это гибкий HTML-парсер, который создает DOM для чтения / записи и поддерживает простой XPATH или XSLT (вам на самом деле не нужно понимать XPATH или XSLT, чтобы его использовать, не беспокойтесь...). Это библиотека кода.NET, которая позволяет анализировать HTML-файлы "вне сети". Синтаксический анализатор очень терпим с искаженным HTML "реального мира". Объектная модель очень похожа на то, что предлагает System.Xml, но для документов HTML (или потоков).

Я использовал Detagger в прошлом. Он отлично справляется с форматированием HTML как текста и представляет собой нечто большее, чем просто средство удаления тегов.

Эта функция конвертирует "Что вы видите в браузере" в простой текст с переносами строк. (Если вы хотите увидеть результат в браузере, просто используйте возвращаемое значение с комментариями)

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

Я не знаю C#, но здесь есть довольно маленький и легкий для чтения скрипт на python html2txt: http://www.aaronsw.com/2002/html2text/

Попробуйте простой и удобный способ: просто позвоните 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 "";

        }

Недавно я писал в блоге о решении, которое работало для меня с использованием XSLT-файла Markdown для преобразования исходного кода HTML. Разумеется, исходный код HTML должен быть в первую очередь действительным XML

В Genexus Вы можете сделать с помощью Regex

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

& TSTRPNOT = & TSTRPNOT.ReplaceRegEx (& узор "")

В Genexus possiamo gestirlo con Regex,

Если вы используете.NET Framework 4.5, вы можете использовать System.Net.WebUtility.HtmlDecode(), который принимает строку в кодировке HTML и возвращает декодированную строку.

Документация по MSDN по адресу: http://msdn.microsoft.com/en-us/library/system.net.webutility.htmldecode(v=vs.110).aspx

Вы также можете использовать это в приложении Магазина Windows.

Вы можете использовать элемент управления WebBrowser для отображения в памяти вашего HTML-контента. После запуска события LoadCompleted...

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

Это еще одно решение для преобразования HTML в текст или RTF в C#:

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

Эта библиотека не бесплатная, это коммерческий продукт, и это мой собственный продукт.

Другие вопросы по тегам