jsoup을 사용하여 html을 일반 텍스트로 변환 할 때 줄 바꿈을 어떻게 유지합니까?


101

다음 코드가 있습니다.

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

그리고 결과가 있습니다.

hello world yo googlez

그러나 나는 줄을 끊고 싶다.

hello world
yo googlez

나는 살펴 보았다 jsoup의 TextNode # getWholeText () 하지만 난 그것을 사용하는 방법을 알아낼 수 없습니다.

<br>구문 분석 한 마크 업에 a 가있는 경우 결과 출력에서 ​​줄 바꿈을 어떻게 얻을 수 있습니까?


텍스트 수정-질문에 줄 바꿈이 표시되지 않습니다. 일반적으로 질문을 게시하기 전에 미리보기를 읽고 모든 것이 올바르게 표시되는지 확인하십시오.
Robin Green

동일한 질문을했지만 (jsoup 요구 사항없이) 여전히 좋은 해결책이 없습니다. stackoverflow.com/questions/2513707/…
Eduardo

@zeenosaur의 답변을 참조하십시오.
Jang-Ho Bae

답변:


102

줄 바꿈을 유지하는 실제 솔루션은 다음과 같아야합니다.

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

다음 요구 사항을 충족합니다.

  1. 원본 HTML에 newline (\ n)이 포함되어 있으면 보존됩니다.
  2. 원본 html에 br 또는 p 태그가 포함되어 있으면 newline (\ n)으로 변환됩니다.

5
이 답변이 선택되어야합니다
duy dec.

2
br2nl하지 가장 유용한 또는 정확한 방법 이름
DD.

2
이것이 최고의 답변입니다. 하지만 for (Element e : document.select("br")) e.after(new TextNode("\n", ""));시퀀스가 아닌 실제 개행을 추가 하는 것은 어떻습니까? \ n? 차이점 은 Node :: after ()Elements :: append () 를 참조하십시오. 은 replaceAll()이 경우 필요하지 않습니다. p 및 기타 블록 요소와 유사합니다.
user2043553

1
@ user121196의 답변이 선택된 답변이어야합니다. 입력 HTML을 정리 한 후에도 여전히 HTML 엔티티가있는 경우 Jsoup 정리의 출력에 StringEscapeUtils.unescapeHtml (...) Apache commons를 적용하십시오.
karth500

6
이 문제에 대한 포괄적 인 답변 은 github.com/jhy/jsoup/blob/master/src/main/java/org/jsoup/… 을 참조하십시오 .
Malcolm Smith

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

여기에서이 방법을 사용합니다.

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

이를 전달함으로써 Whitelist.none()모든 HTML이 제거되었는지 확인합니다.

패스 new OutputSettings().prettyPrint(false)를 통해 출력이 다시 형식화되지 않고 줄 바꿈이 유지되는지 확인합니다.


이것은 유일한 정답이어야합니다. 다른 모든 것은 br태그 만 새 줄을 생성 한다고 가정합니다 . 무엇 HTML의 다른 블록 요소에 대한 같은 div, p, ul등? 그들 모두는 새로운 라인을 소개합니다.
adarshr

7
이 솔루션으로 html "<html> <body> <div> line 1 </ div> <div> line 2 </ div> <div> line 3 </ div> </ body> </ html>"이 생성됩니다. 출력 : "line 1line 2line 3", 새 줄 없음.
JohnC

2
이것은 나를 위해 작동하지 않습니다. <br>은 (는) 줄 바꿈을 생성하지 않습니다.
JoshuaD

43

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

당신은 출력이 있습니다

"A B" 

그리고 아닙니다

A

B

이를 위해 다음을 사용하고 있습니다.

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

2
실제로 이것은 쉽게 완화 할 수 있지만 IMHO는 Jsoup 라이브러리 자체에서 완전히 처리해야합니다 (현재 이와 같은 몇 가지 방해 행위가 있습니다. 그렇지 않으면 훌륭한 라이브러리입니다!).
SRG

5
JSoup이 DOM을 제공하지 않습니까? 모든 <br>요소를 새 줄이 포함 된 텍스트 노드로 교체 한 다음 .text()정규식 변환 대신 호출하여 다음 과 같은 일부 문자열에 대해 잘못된 출력을 발생시키는 것은 어떻습니까<div title=<br>'not an attribute'></div>
Mike Samuel

5
좋지만 그 "descrizione"은 어디에서 왔습니까?
Steve Waters

"descrizione는"일반 텍스트에 할당됩니다 변수를 나타냅니다
enigma969

23

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

좋은 그것은 작은 변화로 날을 작동 new Document.OutputSettings().prettyPrint(true)
아슈

이 솔루션은 "& nbsp;"를 남깁니다. 공백으로 파싱하는 대신 텍스트로.
Andrei Volgin 19

13

Jsoup v1.11.2에서 이제 Element.wholeText().

예제 코드 :

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

user121196's 대답은 여전히 작동합니다. 그러나 wholeText()텍스트의 정렬을 유지합니다.


매우 멋진 기능!
Denis Kulagin

8

더 복잡한 HTML의 경우 위의 솔루션 중 어느 것도 제대로 작동하지 않았습니다. 줄 바꿈을 유지하면서 변환을 성공적으로 수행 할 수있었습니다.

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

(버전 1.10.3)


1
모든 답변 중 최고! 감사합니다 Andy Res!
Bharath Nadukatla

6

주어진 요소를 순회 할 수 있습니다.

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

그리고 당신의 코드

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

나는 경우 테스트해야합니다 생각 isBlocktail(node, depth)대신하고, APPEND \n를 입력 할 때보다는 블록을 떠날 때? 나는 그것을 (즉 사용 tail)하고 있고 그것은 잘 작동합니다. 그러나 내가 head당신처럼 사용한다면 이것은 <p>line one<p>line two한 줄로 끝납니다.
KajMagnus

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

HTML 자체에 "br2n"이 포함되지 않은 경우 작동합니다.

그래서,

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

더 안정적이고 쉽게 작동합니다.


4

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

textNodes()텍스트 노드 목록을 가져 오는 데 사용 합니다. 그런 다음 \n구분 기호로 연결하십시오 . 여기에 제가 사용하는 스칼라 코드가 있습니다. 자바 포트는 쉬울 것입니다.

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

3

이 질문에 대한 다른 답변과 의견을 바탕으로 여기에 오는 대부분의 사람들은 HTML 문서의 멋진 형식의 일반 텍스트 표현을 제공하는 일반적인 솔루션을 실제로 찾고있는 것 같습니다. 나는 알고있다.

다행히도 JSoup은이를 달성하는 방법에 대한 매우 포괄적 인 예제를 이미 제공합니다. HtmlToPlainText.java

이 예제 FormattingVisitor는 원하는대로 쉽게 조정할 수 있으며 대부분의 블록 요소와 줄 바꿈을 다룹니다.

링크 부패를 방지하기 위해 다음은 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

이것은 html을 텍스트로 번역하는 내 버전입니다 (실제로 user121196 답변의 수정 된 버전).

이것은 줄 바꿈을 유지하는 것뿐만 아니라 텍스트 서식을 지정하고 과도한 줄 바꿈, HTML 이스케이프 기호를 제거하면 HTML에서 훨씬 더 나은 결과를 얻을 수 있습니다 (제 경우에는 메일에서 수신합니다).

원래 Scala로 작성되었지만 쉽게 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()      
}

<div> 태그에도 새 줄을 추가해야합니다. 그렇지 않으면 div가 <a> 또는 <span> 태그 뒤에 오는 경우 새 줄에 있지 않습니다.
Andrei Volgin 19

2

이 시도:

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

1
<p> <b> 안녕하세요 </ b> </ p> <p> <br /> <b> 요 </ b> <a href=" google.com"> googlez </a> </ p > 하지만 난 (HTML 태그없이) 안녕하세요 세계 요의 googlez 필요
빌리

이 답변은 일반 텍스트를 반환하지 않습니다. 개행 문자가 삽입 된 HTML을 반환합니다.
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;
}

임시 줄 바꿈 자리 표시 자로 사용할 문자열과 함께 br을 포함하는 문제의 html로 호출하여 사용됩니다. 예를 들면 :

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

재귀를 사용하면 줄 바꿈 / 줄 바꿈 자리 표시 자로 사용하는 문자열이 실제로 소스 html에 있지 않을 것입니다. 링크 브레이커 자리 표시 자 문자열이 html에서 발견되지 않을 때까지 "1"을 계속 추가하기 때문입니다. Jsoup.clean 메서드에서 특수 문자가 발생하는 것처럼 보이는 형식 지정 문제가 없습니다.


좋지만 재귀가 필요하지 않습니다. 다음 줄을 추가하면됩니다. while (dirtyHTML.contains (linebreakerString)) linebreakerString = linebreakerString + "1";
Dr NotSoKind

아 예. 완전히 사실입니다. 내 마음이 실제로 재귀를 사용할 수 있다는 사실에 사로 잡힌 것
같아요.

1

user121196과 Green Beret의 selects 및 <pre>s에 대한 답변을 기반으로 , 나를 위해 작동하는 유일한 솔루션은 다음과 같습니다.

org.jsoup.nodes.Element elementWithHtml = ....
elementWithHtml.select("br").append("<pre>\n</pre>");
elementWithHtml.select("p").prepend("<pre>\n\n</pre>");
elementWithHtml.text();
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.