Làm cách nào để giữ ngắt dòng khi sử dụng jsoup để chuyển đổi html thành văn bản thuần túy?


101

Tôi có mã sau:

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

Và tôi có kết quả:

hello world yo googlez

Nhưng tôi muốn ngắt dòng:

hello world
yo googlez

Tôi đã xem TextNode # getWholeText () của jsoup nhưng tôi không thể tìm ra cách sử dụng nó.

Nếu có <br>đánh dấu mà tôi phân tích cú pháp, làm cách nào để tôi có thể ngắt dòng trong kết quả đầu ra của mình?


chỉnh sửa văn bản của bạn - không có ngắt dòng nào hiển thị trong câu hỏi của bạn. Nói chung, vui lòng đọc bản xem trước câu hỏi của bạn trước khi đăng nó, để kiểm tra mọi thứ đang hiển thị đúng.
Robin Green

Tôi hỏi những câu hỏi tương tự (không có yêu cầu jsoup) nhưng tôi vẫn không có một giải pháp tốt: stackoverflow.com/questions/2513707/...
Eduardo

xem câu trả lời của @zeenosaur.
Jang-Ho Bae

Câu trả lời:


102

Giải pháp thực sự để duy trì dấu ngắt dòng phải như thế này:

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

Nó đáp ứng các yêu cầu sau:

  1. nếu html gốc chứa dòng mới (\ n), nó sẽ được giữ nguyên
  2. nếu html gốc chứa thẻ br hoặc p, chúng sẽ được dịch sang dòng mới (\ n).

5
Đây nên là câu trả lời được chọn
duy

2
br2nl không phải là tên phương pháp hữu ích hoặc chính xác nhất
DD.

2
Đây là câu trả lời tốt nhất. Nhưng làm thế nào về việc for (Element e : document.select("br")) e.after(new TextNode("\n", ""));thêm dòng mới thực chứ không phải chuỗi \ n? Xem Node :: after ()Elements :: append () để biết sự khác biệt. Các replaceAll()không cần thiết trong trường hợp này. Tương tự đối với p và các phần tử khối khác.
user2043553

1
Câu trả lời của @ user121196 phải là câu trả lời được chọn. Nếu bạn vẫn còn các thực thể HTML sau khi làm sạch HTML đầu vào, hãy áp dụng các dấu phẩy của StringEscapeUtils.unescapeHtml (...) Apache cho đầu ra từ Jsoup clean.
karth500

6
Xem github.com/jhy/jsoup/blob/master/src/main/java/org/jsoup/… để có câu trả lời toàn diện cho vấn đề này.
Malcolm Smith

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

Chúng tôi đang sử dụng phương pháp này ở đây:

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

Bằng cách chuyển nó, Whitelist.none()chúng tôi đảm bảo rằng tất cả HTML sẽ bị xóa.

Bằng cách dán, new OutputSettings().prettyPrint(false)chúng tôi đảm bảo rằng đầu ra không bị định dạng lại và các ngắt dòng được giữ nguyên.


Đây sẽ là câu trả lời đúng duy nhất. Tất cả những người khác cho rằng chỉ có brthẻ tạo ra các dòng mới. Những gì về bất kỳ yếu tố khối khác trong HTML như div, p, ulvv? Tất cả họ đều giới thiệu các dòng mới.
adarshr

7
Với giải pháp này, html "<html> <body> <div> dòng 1 </div> <div> dòng 2 </div> <div> dòng 3 </div> </body> </html>" được tạo ra đầu ra: "dòng 1 dòng 2 dòng 3" không có dòng mới.
JohnC

2
Điều này không hiệu quả với tôi; <br> không tạo ngắt dòng.
JoshuaD

43

Với

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

bạn có đầu ra

"A B" 

và không

A

B

Đối với điều này, tôi đang sử dụng:

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

2
Quả thực đây là một cách giảm nhẹ dễ dàng, nhưng IMHO điều này sẽ được xử lý hoàn toàn bởi chính thư viện Jsoup (tại thời điểm này có một số hành vi đáng lo ngại như hành vi này - nếu không thì đó là một thư viện tuyệt vời!).
SRG

5
JSoup không cung cấp cho bạn DOM? Tại sao không chỉ thay thế tất cả <br>các yếu tố với các nút văn bản có chứa dòng mới và sau đó gọi .text()thay vì làm một regex chuyển đổi sẽ gây ra không chính xác đối với một số chuỗi như<div title=<br>'not an attribute'></div>
Mike Samuel

5
Đẹp, nhưng "descrizione" đó đến từ đâu?
Steve Waters

"Miêu tả" đại diện cho biến các văn bản đơn giản được giao nhiệm vụ
enigma969

23

Hãy thử điều này bằng cách sử dụng 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));
}

đẹp nó hoạt động cho tôi với một sự thay đổi nhỏ new Document.OutputSettings().prettyPrint(true)
Ashu

Giải pháp này để lại "& nbsp;" dưới dạng văn bản thay vì phân tích cú pháp chúng thành một khoảng trắng.
Andrei Volgin

13

Trên Jsoup v1.11.2, bây giờ chúng ta có thể sử dụng Element.wholeText().

Mã ví dụ:

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

user121196's câu trả lời vẫn hoạt động. Nhưng wholeText()duy trì sự liên kết của các văn bản.


Tính năng siêu hay!
Denis Kulagin

8

Đối với HTML phức tạp hơn, không có giải pháp nào ở trên hoạt động tốt; Tôi đã có thể thực hiện chuyển đổi thành công trong khi vẫn bảo toàn ngắt dòng với:

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

(phiên bản 1.10.3)


1
Tốt nhất của tất cả các câu trả lời! Cảm ơn Andy Res!
Bharath Nadukatla

6

Bạn có thể duyệt qua một phần tử nhất định

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

Và cho mã của bạn

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

Tôi nghĩ bạn nên kiểm tra xem isBlocktrong tail(node, depth)thay vào đó, và append \nkhi rời khỏi khối hơn là khi vào nó? Tôi đang làm điều đó (tức là sử dụng tail) và điều đó hoạt động tốt. Tuy nhiên, nếu tôi sử dụng headnhư bạn làm, thì điều này: <p>line one<p>line twokết thúc như một dòng duy nhất.
KajMagnus

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

hoạt động nếu bản thân html không chứa "br2n"

Vì thế,

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

hoạt động đáng tin cậy hơn và dễ dàng hơn.


4

Hãy thử điều này bằng cách sử dụng 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

Sử dụng textNodes()để lấy danh sách các nút văn bản. Sau đó nối chúng với \nlàm dấu phân cách. Đây là một số mã scala tôi sử dụng cho việc này, cổng java sẽ dễ dàng:

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

3

Dựa trên các câu trả lời khác và các nhận xét về câu hỏi này, có vẻ như hầu hết mọi người đến đây đang thực sự tìm kiếm một giải pháp chung sẽ cung cấp một biểu diễn văn bản thuần túy được định dạng độc đáo của một tài liệu HTML. Tôi biết tôi đã.

May mắn thay, JSoup đã cung cấp một ví dụ khá toàn diện về cách đạt được điều này: HtmlToPlainText.java

Ví dụ FormattingVisitorcó thể dễ dàng được điều chỉnh theo sở thích của bạn và xử lý hầu hết các phần tử khối và gói dòng.

Để tránh bị thối liên kết, đây là giải pháp đầy đủ của 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

Đây là phiên bản dịch html sang văn bản của tôi (thực tế là phiên bản sửa đổi của câu trả lời user121196).

Điều này không chỉ duy trì ngắt dòng mà còn định dạng văn bản và loại bỏ ngắt dòng quá mức, ký hiệu thoát HTML và bạn sẽ nhận được kết quả tốt hơn nhiều từ HTML của mình (trong trường hợp của tôi là tôi nhận được từ thư).

Ban đầu nó được viết bằng Scala, nhưng bạn có thể thay đổi nó sang Java một cách dễ dàng

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

Bạn cũng cần thêm một dòng mới vào các thẻ <div>. Ngược lại, nếu một div theo sau các thẻ <a> hoặc <span>, nó sẽ không nằm trên một dòng mới.
Andrei Volgin

2

Thử cái này:

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

1
<p> <b> xin chào thế giới </b> </p> <p> <br /> <b> yo </b> <a href=" google.com"> googlez </a> </ p > nhưng tôi cần xin chào thế giới yo googlez (không có thẻ html)
Billy

Câu trả lời này không trả về văn bản thuần túy; nó trả về HTML có chèn dòng mới.
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;
}

Được sử dụng bằng cách gọi với html được đề cập, chứa br, cùng với bất kỳ chuỗi nào bạn muốn sử dụng làm trình giữ chỗ dòng mới tạm thời. Ví dụ:

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

Đệ quy sẽ đảm bảo rằng chuỗi bạn sử dụng làm trình giữ chỗ dòng mới / ngắt dòng thực sự sẽ không bao giờ có trong html nguồn, vì nó sẽ tiếp tục thêm "1" cho đến khi không tìm thấy chuỗi trình giữ chỗ của trình ngắt dòng liên kết trong html. Nó sẽ không có vấn đề định dạng mà các phương thức Jsoup.clean dường như gặp phải với các ký tự đặc biệt.


Tốt, nhưng bạn không cần đệ quy, chỉ cần thêm dòng này: while (dirtyHTML.contains (linebreakerString)) linebreakerString = linebreakerString + "1";
Dr NotSoKind

À, vâng. Hoàn toàn đúng. Đoán tâm trí của tôi đã bị cuốn vào một lần thực sự có khả năng sử dụng đệ quy :)
Chris6647

1

Dựa trên câu trả lời của user121196 và Green Beret với các selects và <pre>s, giải pháp duy nhất phù hợp với tôi là:

org.jsoup.nodes.Element elementWithHtml = ....
elementWithHtml.select("br").append("<pre>\n</pre>");
elementWithHtml.select("p").prepend("<pre>\n\n</pre>");
elementWithHtml.text();
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.