Как преобразовать HTML в текст в С#?

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

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

Результат должен выглядеть следующим образом:

Html2Txt в W3C

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

EDIT: Я просто загружаю пакет Agility Pack из CodePlex и запускал проект Html2Txt. Какое разочарование (по крайней мере модуль, который делает html для преобразования текста)! Все, что было сделано, это разбить теги, свернуть таблицы и т.д. Выход не выглядел так, как Html2Txt @W3C. Слишком плохо, что источник кажется недоступным. Я смотрел, есть ли доступ к более "консервированным" решениям.

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

Ответ 1

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

Ответ 2

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

Ответ 3

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

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

Обновление

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

Ответ 5

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

Преобразование HTML в обычный текст

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

Ответ 7

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

Ответ 8

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

Ответ 9

У меня были некоторые проблемы с расшифровкой с помощью HtmlAgility, и я не хотел тратить время на его исследование.

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

var text = HtmlFilter.ConvertToPlainText(htmlContent);

Ответ 10

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

Это гибкий 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

Я не знаю С#, но есть довольно маленький и удобный для чтения python html2txt script здесь: http://www.aaronsw.com/2002/html2text/

Ответ 14

У меня недавно появилось сообщение о решении, которое сработало для меня, используя файл Markdown XSLT для преобразования источника HTML. Источник HTML, конечно же, должен быть корректным XML сначала

Ответ 15

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

        }

Ответ 16

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

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

& TSTRPNOT = & TSTRPNOT.ReplaceRegEx(& шаблон "")

В Genexus possiamo gestirlo con Regex,

Ответ 17

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

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

Ответ 18

Если вы используете .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 Store.

Ответ 19

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

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

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