Bagaimana cara mempertahankan jeda baris saat menggunakan jsoup untuk mengubah html menjadi teks biasa?


101

Saya memiliki kode berikut:

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

Dan saya mendapatkan hasilnya:

hello world yo googlez

Tapi saya ingin memutus batasan:

hello world
yo googlez

Saya telah melihat TextNode # getWholeText () jsoup tetapi saya tidak tahu cara menggunakannya.

Jika ada <br>di markup yang saya parse, bagaimana saya bisa mendapatkan jeda baris pada keluaran yang saya hasilkan?


edit teks Anda - tidak ada baris baru yang muncul di pertanyaan Anda. Secara umum, harap baca pratinjau pertanyaan Anda sebelum mempostingnya, untuk memeriksa semuanya muncul dengan benar.
Robin Green

Saya mengajukan pertanyaan yang sama (tanpa persyaratan jsoup) tetapi saya masih belum memiliki solusi yang baik: stackoverflow.com/questions/2513707/…
Eduardo

lihat jawaban @zeenosaurus.
Jang-Ho Bae

Jawaban:


102

Solusi nyata yang mempertahankan penggalan baris harus seperti ini:

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

Itu memenuhi persyaratan berikut:

  1. jika html asli berisi baris baru (\ n), itu dipertahankan
  2. jika html asli berisi tag br atau p, mereka akan diterjemahkan ke baris baru (\ n).

5
Ini harus menjadi jawaban yang dipilih
duy

2
br2nl bukanlah nama metode yang paling berguna atau akurat
DD.

2
Ini jawaban terbaik. Tapi bagaimana kalau for (Element e : document.select("br")) e.after(new TextNode("\n", ""));menambahkan baris baru nyata dan bukan urutannya \ n? Lihat Node :: after () dan Elements :: append () untuk mengetahui perbedaannya. Tidak replaceAll()diperlukan dalam kasus ini. Mirip untuk p dan elemen blok lainnya.
pengguna2043553

1
Jawaban @ user121196 haruslah jawaban yang dipilih. Jika Anda masih memiliki entitas HTML setelah Anda membersihkan input HTML, terapkan StringEscapeUtils.unescapeHtml (...) Apache commons ke output dari pembersihan Jsoup.
karth500

6
Lihat github.com/jhy/jsoup/blob/master/src/main/java/org/jsoup/… untuk jawaban komprehensif atas masalah ini.
Malcolm Smith

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

Kami menggunakan metode ini di sini:

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

Dengan meneruskannya Whitelist.none()kami memastikan bahwa semua HTML dihapus.

Dengan melewatkan new OutputSettings().prettyPrint(false)kami memastikan bahwa output tidak diformat ulang dan jeda baris dipertahankan.


Ini harus menjadi satu-satunya jawaban yang benar. Semua yang lain berasumsi bahwa hanya brtag yang menghasilkan baris baru. Bagaimana setiap elemen blok lainnya di HTML seperti div, p, uldll? Semuanya juga memperkenalkan baris baru.
adarshr

7
Dengan solusi ini, html "<html> <body> <div> baris 1 </div> <div> baris 2 </div> <div> baris 3 </div> </body> </html>" dihasilkan keluaran: "baris 1 baris 2 baris 3" tanpa baris baru.
JohnC

2
Ini tidak berhasil untuk saya; <br> tidak membuat jeda baris.
JoshuaD

43

Dengan

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

Anda memiliki keluaran

"A B" 

dan tidak

A

B

Untuk ini saya menggunakan:

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

2
Memang ini adalah paliatif yang mudah, tetapi IMHO ini harus sepenuhnya ditangani oleh perpustakaan Jsoup itu sendiri (yang saat ini memiliki beberapa perilaku mengganggu seperti ini - selain itu perpustakaan yang hebat!).
SRG

5
Bukankah JSoup memberi Anda DOM? Mengapa tidak mengganti semua <br>elemen dengan node teks yang berisi baris baru dan kemudian memanggil .text()alih-alih melakukan transformasi regex yang akan menyebabkan keluaran yang salah untuk beberapa string seperti<div title=<br>'not an attribute'></div>
Mike Samuel

5
Bagus, tapi dari mana "descrizione" itu berasal?
Steve Waters

"descrizione" mewakili variabel yang diberikan teks biasa
enigma969

23

Coba ini dengan menggunakan 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));
}

bagus itu berhasil saya dengan perubahan kecil new Document.OutputSettings().prettyPrint(true)
Ashu

Solusi ini meninggalkan "& nbsp;" sebagai teks alih-alih menguraikannya menjadi spasi.
Andrei Volgin

13

Di Jsoup v1.11.2, sekarang kita dapat menggunakan Element.wholeText().

Kode contoh:

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

user121196's jawaban masih berfungsi. Tapi wholeText()mempertahankan keselarasan teks.


Fitur super bagus!
Denis Kulagin

8

Untuk HTML yang lebih kompleks, tidak ada solusi di atas yang bekerja dengan baik; Saya berhasil melakukan konversi sambil mempertahankan jeda baris dengan:

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

(versi 1.10.3)


1
Terbaik dari semua jawaban! Terima kasih Andy Res!
Bharath Nadukatla

6

Anda dapat melintasi elemen tertentu

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

Dan untuk kode Anda

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

Saya pikir Anda harus menguji apakah isBlockmasuk tail(node, depth), dan menambahkan \nsaat meninggalkan blok daripada saat memasukkannya? Saya melakukan itu (yaitu menggunakan tail) dan itu berfungsi dengan baik. Namun jika saya menggunakan headseperti yang Anda lakukan, maka ini: <p>line one<p>line twoberakhir sebagai satu baris.
KajMagnus

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

berfungsi jika html itu sendiri tidak berisi "br2n"

Begitu,

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

bekerja lebih andal dan lebih mudah.


4

Coba ini dengan menggunakan 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

Gunakan textNodes()untuk mendapatkan daftar node teks. Kemudian gabungkan dengan \nsebagai pemisah. Berikut beberapa kode scala yang saya gunakan untuk ini, port java seharusnya mudah:

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

3

Berdasarkan jawaban lain dan komentar pada pertanyaan ini, tampaknya kebanyakan orang yang datang ke sini benar-benar mencari solusi umum yang akan memberikan representasi teks biasa yang diformat dengan baik dari dokumen HTML. Saya tahu saya.

Untungnya JSoup sudah memberikan contoh yang cukup komprehensif tentang bagaimana mencapai ini: HtmlToPlainText.java

Contoh ini FormattingVisitordapat dengan mudah disesuaikan dengan preferensi Anda dan menangani sebagian besar elemen blok dan pembungkusan garis.

Untuk menghindari pembusukan tautan, berikut adalah solusi lengkap dari 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

Ini adalah versi saya untuk menerjemahkan html ke teks (sebenarnya versi modifikasi jawaban user121196).

Ini tidak hanya mempertahankan jeda baris, tetapi juga memformat teks dan menghapus jeda baris yang berlebihan, simbol pelolosan HTML, dan Anda akan mendapatkan hasil yang jauh lebih baik dari HTML Anda (dalam kasus saya, saya menerimanya dari surat).

Ini aslinya ditulis dalam Scala, tetapi Anda dapat mengubahnya ke Java dengan mudah

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

Anda juga perlu menambahkan baris baru ke tag <div>. Sebaliknya, jika div mengikuti tag <a> atau <span>, itu tidak akan berada di baris baru.
Andrei Volgin

2

Coba ini:

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

1
<p> <b> halo dunia </b> </p> <p> <br /> <b> yo </b> <a href=" google.com"> googlez </a> </ p > tapi saya butuh halo dunia yo googlez (tanpa tag html)
Billy

Jawaban ini tidak mengembalikan teks biasa; itu mengembalikan HTML dengan baris baru dimasukkan.
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;
}

Digunakan dengan memanggil dengan html yang dipermasalahkan, yang berisi br, bersama dengan string apa pun yang ingin Anda gunakan sebagai placeholder baris baru sementara. Sebagai contoh:

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

Rekursi akan memastikan bahwa string yang Anda gunakan sebagai placeholder baris baru / pemecah baris tidak akan pernah benar-benar ada di html sumber, karena akan terus menambahkan "1" sampai string placeholder linkbreaker tidak ditemukan di html. Itu tidak akan memiliki masalah pemformatan yang tampaknya dihadapi metode Jsoup.clean dengan karakter khusus.


Bagus, tetapi Anda tidak perlu rekursi, cukup tambahkan baris ini: while (dirtyHTML.contains (linebreakerString)) linebreakerString = linebreakerString + "1";
Dr NotSoKind

Ah iya. Benar sekali. Kurasa pikiran saya terjebak sekali benar-benar dapat menggunakan rekursi :)
Chris6647

1

Berdasarkan jawaban pengguna121196 dan Green Baret dengan selects dan <pre>s, satu-satunya solusi yang berhasil untuk saya adalah:

org.jsoup.nodes.Element elementWithHtml = ....
elementWithHtml.select("br").append("<pre>\n</pre>");
elementWithHtml.select("p").prepend("<pre>\n\n</pre>");
elementWithHtml.text();
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.