web-dev-qa-db-fra.com

Comment puis-je convertir du HTML en texte en C #?

Je cherche du code C # pour convertir un document HTML en texte brut. 

Je ne cherche pas une simple suppression de balise, mais quelque chose qui produira du texte brut avec un raisonnable préservation de la présentation d'origine.

Le résultat devrait ressembler à ceci:

Html2Txt au W3C

J'ai consulté le pack d'agilité HTML, mais je ne pense pas que ce soit ce dont j'ai besoin. Quelqu'un a-t-il d'autres suggestions?

EDIT: Je viens de télécharger le pack d'agilité HTML à partir de CodePlex et d'exécuter le projet Html2Txt. Quelle déception (au moins le module qui convertit du HTML en texte)! Tout ce qu'il a fait est de supprimer les balises, d'aplatir les tables, etc. Le résultat ne ressemble en rien au format Html2Txt @ W3C. Dommage que cette source ne semble pas être disponible ... Je cherchais une solution plus "en boîte".

EDIT 2: Merci à tous pour vos suggestions. FlySwat m'a orienté dans la direction dans laquelle je voulais aller. Je peux utiliser la classe System.Diagnostics.Process pour exécuter lynx.exe avec le commutateur "-dump" afin d’envoyer le texte à la sortie standard et de capturer la sortie standard avec ProcessStartInfo.UseShellExecute = false et ProcessStartInfo.RedirectStandardOutput = true. Je vais envelopper tout cela dans une classe C #. Ce code sera appelé seulement de temps en temps, donc je ne suis pas trop préoccupé par la création d'un nouveau processus par opposition à le faire dans le code. De plus, Lynx est rapide !!

67
Matt Crouch

Ce que vous recherchez est un moteur de rendu DOM en mode texte qui génère du texte, un peu comme Lynx ou d’autres navigateurs texte ... C’est beaucoup plus difficile à faire que ce à quoi vous vous attendiez.

10
FlySwat

Juste une note sur le HtmlAgilityPack pour la postérité. Le projet contient un exemple d’analyse de texte en HTML , qui, comme l’a noté le PO, ne gère pas du tout les espaces comme le voudrait toute personne écrivant en HTML. Il existe des solutions de rendu de texte intégral, soulignées par d'autres personnes à cette question, mais ce n'est pas le cas (il ne peut même pas gérer les tableaux dans sa forme actuelle), mais il est léger et rapide, ce que je voulais pour créer un texte simple version des emails 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 };
    }
}

Par exemple, le code HTML suivant ...

<!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>

... sera transformé en:

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 

... par opposition à:

        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
38
Brent

Vous pouvez utiliser ceci:

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

Mis à jour

Merci pour les commentaires que j'ai mis à jour pour améliorer cette fonction

30
Richard

Une source fiable nous a dit que si vous analysez du HTML dans .Net, vous devriez consulter à nouveau le pack d'agilité HTML. 

http://www.codeplex.com/htmlagilitypack

Quelques échantillons sur SO .. 

Pack d'agilité HTML - Analyse de tables

17
madcolor

Parce que je voulais une conversion en texte brut avec LF et des puces, j'ai trouvé cette jolie solution sur codeproject, qui couvre de nombreux cas de conversion:

Convertir du HTML en texte brut

Oui, ça a l'air si gros, mais ça fonctionne bien.

4
Vaclav Svara

Avez-vous essayé http://www.aaronsw.com/2002/html2text/ c'est Python, mais open source.

3
inspite

En supposant que le langage HTML soit bien formé, vous pouvez également essayer une transformation XSL.

Voici un exemple:

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
Dennis George

Le plus simple serait probablement de supprimer les balises, en remplaçant certaines balises par des éléments de disposition de texte, tels que des tirets pour les éléments de liste (li) et des sauts de ligne pour br et p . Il ne devrait pas être trop difficile d’étendre cela aux tableaux.

2
EricSchaefer

J'ai eu quelques problèmes de décodage avec HtmlAgility et je ne voulais pas investir du temps à l'explorer.

Au lieu de cela, j'ai utilisé cet utilitaire à partir de l'API Microsoft Team Foundation:

var text = HtmlFilter.ConvertToPlainText(htmlContent);
2
Nir

Un autre article suggère le pack d'agilité HTML :

Ceci est un analyseur HTML agile qui construit un DOM en lecture/écriture et prend en charge XPATH ou XSLT simple (vous ne devez pas comprendre XPATH ni XSLT pour l'utiliser, ne vous inquiétez pas ...). Il est une bibliothèque de code .NET qui vous permet de analyser les fichiers HTML "hors du Web". Le L’analyseur est très tolérant avec du HTML malformé "real world". L'object modèle est très similaire à ce que propose System.Xml, mais pour les documents HTML (ou les flux ).

0
crb

J'ai utilisé Detagger dans le passé. Il fait un très bon travail de formatage du code HTML en tant que texte et n’est pas seulement un dissolvant de balises.

0
Brian Genisio

Cette fonction convertit "Ce que vous voyez dans le navigateur" en texte brut avec des sauts de ligne. (Si vous voulez voir le résultat dans le navigateur, utilisez simplement la valeur de retour commentée)

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
EminST