Comment conserver les sauts de ligne lors de l'utilisation de jsoup pour convertir du HTML en texte brut?


101

J'ai le code suivant:

 public class NewClass {
     public String noTags(String str){
         return Jsoup.parse(str).text();
     }


     public static void main(String args[]) {
         String strings="<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN \">" +
         "<HTML> <HEAD> <TITLE></TITLE> <style>body{ font-size: 12px;font-family: verdana, arial, helvetica, sans-serif;}</style> </HEAD> <BODY><p><b>hello world</b></p><p><br><b>yo</b> <a href=\"http://google.com\">googlez</a></p></BODY> </HTML> ";

         NewClass text = new NewClass();
         System.out.println((text.noTags(strings)));
}

Et j'ai le résultat:

hello world yo googlez

Mais je veux briser la ligne:

hello world
yo googlez

J'ai regardé TextNode # getWholeText () de jsoup mais je ne peux pas comprendre comment l'utiliser.

S'il y a un <br>dans le balisage que j'analyse, comment puis-je obtenir un saut de ligne dans ma sortie résultante?


modifiez votre texte - il n'y a pas de saut de ligne dans votre question. En général, veuillez lire l'aperçu de votre question avant de la publier, pour vérifier que tout s'affiche correctement.
Robin Green

J'ai posé la même question (sans l'exigence de jsoup) mais je n'ai toujours pas de bonne solution: stackoverflow.com/questions/2513707/...
Eduardo

voir la réponse de @zeenosaur.
Jang-Ho Bae

Réponses:


102

La vraie solution qui préserve les sauts de ligne devrait être comme ceci:

public static String br2nl(String html) {
    if(html==null)
        return html;
    Document document = Jsoup.parse(html);
    document.outputSettings(new Document.OutputSettings().prettyPrint(false));//makes html() preserve linebreaks and spacing
    document.select("br").append("\\n");
    document.select("p").prepend("\\n\\n");
    String s = document.html().replaceAll("\\\\n", "\n");
    return Jsoup.clean(s, "", Whitelist.none(), new Document.OutputSettings().prettyPrint(false));
}

Il satisfait aux exigences suivantes:

  1. si le html d'origine contient une nouvelle ligne (\ n), il est conservé
  2. si le html d'origine contient des balises br ou p, elles sont traduites en nouvelle ligne (\ n).

5
Cela devrait être la réponse choisie
Duy

2
br2nl n'est pas le nom de méthode le plus utile ou le plus précis
DD.

2
C'est la meilleure réponse. Mais que diriez-vous d' for (Element e : document.select("br")) e.after(new TextNode("\n", ""));ajouter une vraie nouvelle ligne et non la séquence \ n? Voir Node :: after () et Elements :: append () pour la différence. Le replaceAll()n'est pas nécessaire dans ce cas. Similaire pour p et d'autres éléments de bloc.
user2043553

1
La réponse de @ user121196 doit être la réponse choisie. Si vous avez encore des entités HTML après avoir nettoyé le code HTML d'entrée, appliquez StringEscapeUtils.unescapeHtml (...) Apache commons à la sortie du Jsoup clean.
karth500

6
Voir github.com/jhy/jsoup/blob/master/src/main/java/org/jsoup/… pour une réponse complète à ce problème.
Malcolm Smith

44
Jsoup.clean(unsafeString, "", Whitelist.none(), new OutputSettings().prettyPrint(false));

Nous utilisons cette méthode ici:

public static String clean(String bodyHtml,
                       String baseUri,
                       Whitelist whitelist,
                       Document.OutputSettings outputSettings)

En le passant, Whitelist.none()nous nous assurons que tout le HTML est supprimé.

En passant, new OutputSettings().prettyPrint(false)nous nous assurons que la sortie n'est pas reformatée et que les sauts de ligne sont préservés.


Cela devrait être la seule bonne réponse. Tous les autres supposent que seules les brbalises produisent de nouvelles lignes. Qu'en est- il de tout autre élément de bloc HTML tels que div, p, uletc? Tous introduisent également de nouvelles lignes.
adarshr

7
Avec cette solution, le html "<html> <body> <div> ligne 1 </div> <div> ligne 2 </div> <div> ligne 3 </div> </body> </html>" produit la sortie: "ligne 1 ligne 2 ligne 3" sans nouvelles lignes.
JohnC

2
Cela ne fonctionne pas pour moi; Les <br> ne créent pas de sauts de ligne.
JoshuaD

43

Avec

Jsoup.parse("A\nB").text();

vous avez une sortie

"A B" 

et pas

A

B

Pour cela, j'utilise:

descrizione = Jsoup.parse(html.replaceAll("(?i)<br[^>]*>", "br2n")).text();
text = descrizione.replaceAll("br2n", "\n");

2
En effet, c'est un palliatif facile, mais à mon humble avis, cela devrait être entièrement géré par la bibliothèque Jsoup elle-même (qui a pour le moment quelques comportements dérangeants comme celui-ci - sinon c'est une excellente bibliothèque!).
SRG

5
JSoup ne vous donne-t-il pas un DOM? Pourquoi ne pas simplement remplacer tous les <br>éléments par des nœuds de texte contenant de nouvelles lignes, puis appeler .text()au lieu de faire une transformation regex qui entraînera une sortie incorrecte pour certaines chaînes comme<div title=<br>'not an attribute'></div>
Mike Samuel

5
Bien, mais d'où vient cette "descrizione"?
Steve Waters

"descrizione" représente la variable à laquelle le texte brut est affecté
enigma969

23

Essayez ceci en utilisant jsoup:

public static String cleanPreserveLineBreaks(String bodyHtml) {

    // get pretty printed html with preserved br and p tags
    String prettyPrintedBodyFragment = Jsoup.clean(bodyHtml, "", Whitelist.none().addTags("br", "p"), new OutputSettings().prettyPrint(true));
    // get plain text with preserved line breaks by disabled prettyPrint
    return Jsoup.clean(prettyPrintedBodyFragment, "", Whitelist.none(), new OutputSettings().prettyPrint(false));
}

gentil ça me marche avec un petit changement new Document.OutputSettings().prettyPrint(true)
Ashu

Cette solution laisse "& nbsp;" sous forme de texte au lieu de les analyser dans un espace.
Andrei Volgin

13

Sur Jsoup v1.11.2, nous pouvons désormais utiliser Element.wholeText().

Exemple de code:

String cleanString = Jsoup.parse(htmlString).wholeText();

user121196's La réponse fonctionne toujours. Mais wholeText()préserve l'alignement des textes.


Fonctionnalité super sympa!
Denis Kulagin

8

Pour du HTML plus complexe, aucune des solutions ci-dessus ne fonctionnait parfaitement; J'ai pu réussir la conversion tout en préservant les sauts de ligne avec:

Document document = Jsoup.parse(myHtml);
String text = new HtmlToPlainText().getPlainText(document);

(version 1.10.3)


1
Le meilleur de toutes les réponses! Merci Andy Res!
Bharath Nadukatla le

6

Vous pouvez parcourir un élément donné

public String convertNodeToText(Element element)
{
    final StringBuilder buffer = new StringBuilder();

    new NodeTraversor(new NodeVisitor() {
        boolean isNewline = true;

        @Override
        public void head(Node node, int depth) {
            if (node instanceof TextNode) {
                TextNode textNode = (TextNode) node;
                String text = textNode.text().replace('\u00A0', ' ').trim();                    
                if(!text.isEmpty())
                {                        
                    buffer.append(text);
                    isNewline = false;
                }
            } else if (node instanceof Element) {
                Element element = (Element) node;
                if (!isNewline)
                {
                    if((element.isBlock() || element.tagName().equals("br")))
                    {
                        buffer.append("\n");
                        isNewline = true;
                    }
                }
            }                
        }

        @Override
        public void tail(Node node, int depth) {                
        }                        
    }).traverse(element);        

    return buffer.toString();               
}

Et pour votre code

String result = convertNodeToText(JSoup.parse(html))

Je pense que vous devriez tester si isBlockà la tail(node, depth)place et ajouter \nlorsque vous quittez le bloc plutôt que lorsque vous y entrez? Je fais cela (c'est-à-dire en utilisant tail) et cela fonctionne très bien. Cependant, si j'utilise headcomme vous le faites, alors ceci: <p>line one<p>line twose termine comme une seule ligne.
KajMagnus

4
text = Jsoup.parse(html.replaceAll("(?i)<br[^>]*>", "br2n")).text();
text = descrizione.replaceAll("br2n", "\n");

fonctionne si le html lui-même ne contient pas "br2n"

Alors,

text = Jsoup.parse(html.replaceAll("(?i)<br[^>]*>", "<pre>\n</pre>")).text();

fonctionne plus fiable et plus facile.


4

Essayez ceci en utilisant jsoup:

    doc.outputSettings(new OutputSettings().prettyPrint(false));

    //select all <br> tags and append \n after that
    doc.select("br").after("\\n");

    //select all <p> tags and prepend \n before that
    doc.select("p").before("\\n");

    //get the HTML from the document, and retaining original new lines
    String str = doc.html().replaceAll("\\\\n", "\n");

3

Utilisez textNodes()pour obtenir une liste des nœuds de texte. Puis concaténez-les avec \ncomme séparateur. Voici un code scala que j'utilise pour cela, le port java devrait être simple:

val rawTxt = doc.body().getElementsByTag("div").first.textNodes()
                    .asScala.mkString("<br />\n")

3

Sur la base des autres réponses et des commentaires sur cette question, il semble que la plupart des gens qui viennent ici recherchent vraiment une solution générale qui fournira une représentation en texte brut bien formatée d'un document HTML. Je sais que je l'étais.

Heureusement, JSoup fournit déjà un exemple assez complet de la façon d'y parvenir: HtmlToPlainText.java

L'exemple FormattingVisitorpeut facilement être modifié selon vos préférences et traite de la plupart des éléments de bloc et de l'habillage de ligne.

Pour éviter la pourriture des liens, voici la solution de Jonathan Hedley dans son intégralité:

package org.jsoup.examples;

import org.jsoup.Jsoup;
import org.jsoup.helper.StringUtil;
import org.jsoup.helper.Validate;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.nodes.TextNode;
import org.jsoup.select.Elements;
import org.jsoup.select.NodeTraversor;
import org.jsoup.select.NodeVisitor;

import java.io.IOException;

/**
 * HTML to plain-text. This example program demonstrates the use of jsoup to convert HTML input to lightly-formatted
 * plain-text. That is divergent from the general goal of jsoup's .text() methods, which is to get clean data from a
 * scrape.
 * <p>
 * Note that this is a fairly simplistic formatter -- for real world use you'll want to embrace and extend.
 * </p>
 * <p>
 * To invoke from the command line, assuming you've downloaded the jsoup jar to your current directory:</p>
 * <p><code>java -cp jsoup.jar org.jsoup.examples.HtmlToPlainText url [selector]</code></p>
 * where <i>url</i> is the URL to fetch, and <i>selector</i> is an optional CSS selector.
 * 
 * @author Jonathan Hedley, jonathan@hedley.net
 */
public class HtmlToPlainText {
    private static final String userAgent = "Mozilla/5.0 (jsoup)";
    private static final int timeout = 5 * 1000;

    public static void main(String... args) throws IOException {
        Validate.isTrue(args.length == 1 || args.length == 2, "usage: java -cp jsoup.jar org.jsoup.examples.HtmlToPlainText url [selector]");
        final String url = args[0];
        final String selector = args.length == 2 ? args[1] : null;

        // fetch the specified URL and parse to a HTML DOM
        Document doc = Jsoup.connect(url).userAgent(userAgent).timeout(timeout).get();

        HtmlToPlainText formatter = new HtmlToPlainText();

        if (selector != null) {
            Elements elements = doc.select(selector); // get each element that matches the CSS selector
            for (Element element : elements) {
                String plainText = formatter.getPlainText(element); // format that element to plain text
                System.out.println(plainText);
            }
        } else { // format the whole doc
            String plainText = formatter.getPlainText(doc);
            System.out.println(plainText);
        }
    }

    /**
     * Format an Element to plain-text
     * @param element the root element to format
     * @return formatted text
     */
    public String getPlainText(Element element) {
        FormattingVisitor formatter = new FormattingVisitor();
        NodeTraversor traversor = new NodeTraversor(formatter);
        traversor.traverse(element); // walk the DOM, and call .head() and .tail() for each node

        return formatter.toString();
    }

    // the formatting rules, implemented in a breadth-first DOM traverse
    private class FormattingVisitor implements NodeVisitor {
        private static final int maxWidth = 80;
        private int width = 0;
        private StringBuilder accum = new StringBuilder(); // holds the accumulated text

        // hit when the node is first seen
        public void head(Node node, int depth) {
            String name = node.nodeName();
            if (node instanceof TextNode)
                append(((TextNode) node).text()); // TextNodes carry all user-readable text in the DOM.
            else if (name.equals("li"))
                append("\n * ");
            else if (name.equals("dt"))
                append("  ");
            else if (StringUtil.in(name, "p", "h1", "h2", "h3", "h4", "h5", "tr"))
                append("\n");
        }

        // hit when all of the node's children (if any) have been visited
        public void tail(Node node, int depth) {
            String name = node.nodeName();
            if (StringUtil.in(name, "br", "dd", "dt", "p", "h1", "h2", "h3", "h4", "h5"))
                append("\n");
            else if (name.equals("a"))
                append(String.format(" <%s>", node.absUrl("href")));
        }

        // appends text to the string builder with a simple word wrap method
        private void append(String text) {
            if (text.startsWith("\n"))
                width = 0; // reset counter if starts with a newline. only from formats above, not in natural text
            if (text.equals(" ") &&
                    (accum.length() == 0 || StringUtil.in(accum.substring(accum.length() - 1), " ", "\n")))
                return; // don't accumulate long runs of empty spaces

            if (text.length() + width > maxWidth) { // won't fit, needs to wrap
                String words[] = text.split("\\s+");
                for (int i = 0; i < words.length; i++) {
                    String word = words[i];
                    boolean last = i == words.length - 1;
                    if (!last) // insert a space if not the last word
                        word = word + " ";
                    if (word.length() + width > maxWidth) { // wrap and reset counter
                        accum.append("\n").append(word);
                        width = word.length();
                    } else {
                        accum.append(word);
                        width += word.length();
                    }
                }
            } else { // fits as is, without need to wrap text
                accum.append(text);
                width += text.length();
            }
        }

        @Override
        public String toString() {
            return accum.toString();
        }
    }
}

3

Ceci est ma version de la traduction html en texte (la version modifiée de la réponse user121196, en fait).

Cela ne préserve pas seulement les sauts de ligne, mais aussi le formatage du texte et la suppression des sauts de ligne excessifs, des symboles d'échappement HTML, et vous obtiendrez un bien meilleur résultat de votre HTML (dans mon cas, je le reçois par courrier).

Il est à l'origine écrit en Scala, mais vous pouvez le changer facilement en Java

def html2text( rawHtml : String ) : String = {

    val htmlDoc = Jsoup.parseBodyFragment( rawHtml, "/" )
    htmlDoc.select("br").append("\\nl")
    htmlDoc.select("div").prepend("\\nl").append("\\nl")
    htmlDoc.select("p").prepend("\\nl\\nl").append("\\nl\\nl")

    org.jsoup.parser.Parser.unescapeEntities(
        Jsoup.clean(
          htmlDoc.html(),
          "",
          Whitelist.none(),
          new org.jsoup.nodes.Document.OutputSettings().prettyPrint(true)
        ),false
    ).
    replaceAll("\\\\nl", "\n").
    replaceAll("\r","").
    replaceAll("\n\\s+\n","\n").
    replaceAll("\n\n+","\n\n").     
    trim()      
}

Vous devez également ajouter une nouvelle ligne aux balises <div>. Sinon, si un div suit les balises <a> ou <span>, il ne sera pas sur une nouvelle ligne.
Andrei Volgin

2

Essaye ça:

public String noTags(String str){
    Document d = Jsoup.parse(str);
    TextNode tn = new TextNode(d.body().html(), "");
    return tn.getWholeText();
}

1
<p> <b> bonjour le monde </b> </p> <p> <br /> <b> yo </b> <a href=" google.com"> googlez </a> </ p > mais j'ai besoin de bonjour le monde yo googlez (sans balises html)
Billy

Cette réponse ne renvoie pas de texte brut; il renvoie HTML avec des nouvelles lignes insérées.
KajMagnus

1
/**
 * Recursive method to replace html br with java \n. The recursive method ensures that the linebreaker can never end up pre-existing in the text being replaced.
 * @param html
 * @param linebreakerString
 * @return the html as String with proper java newlines instead of br
 */
public static String replaceBrWithNewLine(String html, String linebreakerString){
    String result = "";
    if(html.contains(linebreakerString)){
        result = replaceBrWithNewLine(html, linebreakerString+"1");
    } else {
        result = Jsoup.parse(html.replaceAll("(?i)<br[^>]*>", linebreakerString)).text(); // replace and html line breaks with java linebreak.
        result = result.replaceAll(linebreakerString, "\n");
    }
    return result;
}

Utilisé en appelant avec le html en question, contenant le br, ainsi que la chaîne que vous souhaitez utiliser comme espace réservé de nouvelle ligne temporaire. Par exemple:

replaceBrWithNewLine(element.html(), "br2n")

La récursivité garantira que la chaîne que vous utilisez comme espace réservé de saut de ligne / de saut de ligne ne sera jamais réellement dans le code source html, car elle continuera à ajouter un "1" jusqu'à ce que la chaîne d'espace réservé pour le linkbreaker ne soit pas trouvée dans le code HTML. Il n'aura pas le problème de formatage que les méthodes Jsoup.clean semblent rencontrer avec des caractères spéciaux.


Bon, mais vous n'avez pas besoin de récursivité, ajoutez simplement cette ligne: while (dirtyHTML.contains (linebreakerString)) linebreakerString = linebreakerString + "1";
Dr NotSoKind

Ah oui. Complètement vrai. Je suppose que mon esprit s'est occupé pour une fois de pouvoir utiliser la récursivité :)
Chris6647

1

Sur la base de la réponse de user121196 et de Green Beret avec les selects et <pre>s, la seule solution qui fonctionne pour moi est:

org.jsoup.nodes.Element elementWithHtml = ....
elementWithHtml.select("br").append("<pre>\n</pre>");
elementWithHtml.select("p").prepend("<pre>\n\n</pre>");
elementWithHtml.text();
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.