Wie behalte ich Zeilenumbrüche bei, wenn ich mit jsoup HTML in einfachen Text konvertiere?


101

Ich habe folgenden Code:

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

Und ich habe das Ergebnis:

hello world yo googlez

Aber ich möchte die Grenze brechen:

hello world
yo googlez

Ich habe mir jsoups TextNode # getWholeText () angesehen , kann aber nicht herausfinden, wie ich ihn verwenden soll.

<br>Wie kann ich einen Zeilenumbruch in meiner resultierenden Ausgabe erzielen, wenn das von mir analysierte Markup ein Zeilenumbruch enthält?


Bearbeiten Sie Ihren Text - in Ihrer Frage wird kein Zeilenumbruch angezeigt. Im Allgemeinen lesen Sie bitte die Vorschau Ihrer Frage, bevor Sie sie veröffentlichen, um zu überprüfen, ob alles richtig angezeigt wird.
Robin Green

Ich habe die gleiche Frage gestellt (ohne die jsoup-Anforderung), aber ich habe immer noch keine gute Lösung: stackoverflow.com/questions/2513707/…
Eduardo

Siehe die Antwort von @zeenosaur.
Jang-Ho Bae

Antworten:


102

Die wirkliche Lösung, die Zeilenumbrüche bewahrt, sollte folgendermaßen aussehen:

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

Es erfüllt folgende Anforderungen:

  1. Wenn das ursprüngliche HTML Newline (\ n) enthält, bleibt es erhalten
  2. Wenn der ursprüngliche HTML-Code br- oder p-Tags enthält, werden diese in Zeilenumbruch (\ n) übersetzt.

5
Dies sollte die ausgewählte Antwort sein
duy

2
br2nl ist nicht der hilfreichste oder genaueste Methodenname
DD.

2
Dies ist die beste Antwort. Aber wie wäre es mit dem for (Element e : document.select("br")) e.after(new TextNode("\n", ""));Anhängen einer echten Newline und nicht der Sequenz \ n? Siehe Node :: after () und Elements :: append () für den Unterschied. Das replaceAll()wird in diesem Fall nicht benötigt. Ähnliches gilt für p und andere Blockelemente.
user2043553

1
Die Antwort von @ user121196 sollte die gewählte Antwort sein. Wenn Sie nach dem Bereinigen des Eingabe-HTML-Codes noch HTML-Entitäten haben, wenden Sie StringEscapeUtils.unescapeHtml (...) Apache Commons auf die Ausgabe von Jsoup Clean an.
karth500

6
Eine umfassende Antwort auf dieses Problem finden Sie unter github.com/jhy/jsoup/blob/master/src/main/java/org/jsoup/… .
Malcolm Smith

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

Wir verwenden diese Methode hier:

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

Indem Whitelist.none()wir es übergeben, stellen wir sicher, dass alles HTML entfernt wird.

Durch Passing stellen new OutputSettings().prettyPrint(false)wir sicher, dass die Ausgabe nicht neu formatiert wird und Zeilenumbrüche erhalten bleiben.


Dies sollte die einzig richtige Antwort sein. Alle anderen gehen davon aus, dass nur brTags neue Zeilen erzeugen. Was über alle anderen Blockelemente in HTML wie div, p, uletc? Alle von ihnen führen auch neue Linien ein.
Adarshr

7
Mit dieser Lösung wird das HTML "<html> <body> <div> Zeile 1 </ div> <div> Zeile 2 </ div> <div> Zeile 3 </ div> </ body> </ html>" erzeugt die Ausgabe: "Zeile 1 Zeile 2 Zeile 3" ohne neue Zeilen.
JohnC

2
Das funktioniert bei mir nicht; <br> erstellt keine Zeilenumbrüche.
JoshuaD

43

Mit

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

Sie haben ausgegeben

"A B" 

und nicht

A

B

Dafür benutze ich:

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

2
In der Tat ist dies ein einfaches Palliativ, aber meiner Meinung nach sollte dies vollständig von der Jsoup-Bibliothek selbst erledigt werden (die zu diesem Zeitpunkt einige störende Verhaltensweisen wie dieses aufweist - ansonsten ist es eine großartige Bibliothek!).
SRG

5
Gibt Ihnen JSoup kein DOM? Warum nicht einfach alle <br>Elemente durch Textknoten ersetzen , die neue Zeilen enthalten, und dann aufrufen, .text()anstatt eine Regex-Transformation durchzuführen, die bei einigen Zeichenfolgen wie<div title=<br>'not an attribute'></div>
Mike Samuel

5
Schön, aber woher kommt diese "Beschreibung"?
Steve Waters

"Descrizione" repräsentiert die Variable, der der Klartext zugewiesen wird
enigma969

23

Versuchen Sie dies mit 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));
}

schön, es funktioniert mir mit einer kleinen Änderung new Document.OutputSettings().prettyPrint(true)
Ashu

Diese Lösung hinterlässt "& nbsp;" als Text, anstatt sie in ein Leerzeichen zu analysieren.
Andrei Volgin

13

Auf Jsoup v1.11.2 können wir jetzt verwenden Element.wholeText().

Beispielcode:

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

user121196's Antwort funktioniert immer noch. Aber wholeText()bewahrt die Ausrichtung von Texten.


Super schönes Feature!
Denis Kulagin

8

Für komplexeres HTML funktionierte keine der oben genannten Lösungen ganz richtig. Ich konnte die Konvertierung erfolgreich durchführen und dabei Zeilenumbrüche beibehalten mit:

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

(Version 1.10.3)


1
Beste aller Antworten! Danke Andy Res!
Bharath Nadukatla

6

Sie können ein bestimmtes Element durchlaufen

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

Und für deinen Code

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

Ich denke, Sie sollten stattdessen testen, ob Sie isBlockin sind tail(node, depth), und \nbeim Verlassen des Blocks anhängen , anstatt ihn zu betreten? Ich mache das (dh benutze tail) und das funktioniert gut. Wenn ich jedoch headwie Sie verwende, wird <p>line one<p>line twoFolgendes als einzelne Zeile angezeigt.
KajMagnus

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

funktioniert, wenn das HTML selbst nicht "br2n" enthält

So,

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

arbeitet zuverlässiger und einfacher.


4

Versuchen Sie dies mit 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

Verwenden Sie textNodes()diese Option , um eine Liste der Textknoten abzurufen. Dann verketten Sie sie mit \nals Trennzeichen. Hier ist ein Scala-Code, den ich dafür verwende. Der Java-Port sollte einfach sein:

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

3

Basierend auf den anderen Antworten und den Kommentaren zu dieser Frage scheinen die meisten Leute, die hierher kommen, wirklich nach einer allgemeinen Lösung zu suchen, die eine schön formatierte Klartextdarstellung eines HTML-Dokuments bietet. Ich weiß, dass ich es war.

Glücklicherweise bietet JSoup bereits ein ziemlich umfassendes Beispiel dafür: HtmlToPlainText.java

Das Beispiel FormattingVisitorkann leicht nach Ihren Wünschen angepasst werden und behandelt die meisten Blockelemente und Zeilenumbrüche.

Um Link Rot zu vermeiden, ist hier die vollständige Lösung von Jonathan Hedley :

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

Dies ist meine Version der Übersetzung von HTML in Text (die geänderte Version der Antwort von user121196).

Dies bewahrt nicht nur Zeilenumbrüche, sondern formatiert auch Text und entfernt übermäßige Zeilenumbrüche, HTML-Escape-Symbole, und Sie erhalten ein viel besseres Ergebnis aus Ihrem HTML-Code (in meinem Fall erhalte ich ihn per E-Mail).

Es ist ursprünglich in Scala geschrieben, aber Sie können es leicht in Java ändern

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()      
}

Sie müssen <div> -Tags ebenfalls eine neue Zeile voranstellen. Andernfalls steht ein div, wenn es auf <a> oder <span> -Tags folgt, nicht in einer neuen Zeile.
Andrei Volgin

2

Versuche dies:

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

1
<p> <b> Hallo Welt </ b> </ p> <p> <br /> <b> yo </ b> <a href=" google.com"> googlez </a> </ p > aber ich brauche hallo Welt yo googlez (ohne HTML-Tags)
Billy

Diese Antwort gibt keinen einfachen Text zurück. Es gibt HTML mit eingefügten Zeilenumbrüchen zurück.
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;
}

Wird verwendet, indem mit dem betreffenden HTML-Code aufgerufen wird, der das br zusammen mit der Zeichenfolge enthält, die Sie als temporären Platzhalter für Zeilenumbrüche verwenden möchten. Beispielsweise:

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

Durch die Rekursion wird sichergestellt, dass die Zeichenfolge, die Sie als Platzhalter für Zeilenumbrüche / Zeilenumbrüche verwenden, niemals im Quell-HTML enthalten ist, da weiterhin eine "1" hinzugefügt wird, bis die Linkbreaker-Platzhalterzeichenfolge nicht im HTML-Code gefunden wird. Es wird kein Formatierungsproblem geben, auf das die Jsoup.clean-Methoden mit Sonderzeichen zu stoßen scheinen.


Gut, aber Sie brauchen keine Rekursion, fügen Sie einfach diese Zeile hinzu: while (dirtyHTML.contains (linebreakerString)) linebreakerString = linebreakerString + "1";
Dr. NotSoKind

Ah ja. Komplett wahr. Ich
schätze,

1

Basierend auf der Antwort von user121196 und Green Beret mit selects und <pre>s ist die einzige Lösung, die für mich funktioniert ,:

org.jsoup.nodes.Element elementWithHtml = ....
elementWithHtml.select("br").append("<pre>\n</pre>");
elementWithHtml.select("p").prepend("<pre>\n\n</pre>");
elementWithHtml.text();
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.