Jak zachować podziały wierszy podczas używania jsoup do konwersji HTML na zwykły tekst?


101

Mam następujący kod:

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

I mam wynik:

hello world yo googlez

Ale chcę przełamać linię:

hello world
yo googlez

Mam spojrzał na jsoup za TextNode # getWholeText () , ale nie mogę dowiedzieć się, jak go używać.

Jeśli <br>w znaczniku, który analizuję, znajduje się znak, w jaki sposób mogę uzyskać podział wiersza w wynikowym wyniku?


edytuj swój tekst - w pytaniu nie ma podziału wiersza. Ogólnie przeczytaj podgląd swojego pytania przed wysłaniem go, aby sprawdzić, czy wszystko jest w porządku.
Robin Green,

Zadałem to samo pytanie (bez wymagania jsoup), ale nadal nie mam dobrego rozwiązania: stackoverflow.com/questions/2513707/ ...
Eduardo

zobacz odpowiedź @zeenosaura.
Jang-Ho Bae

Odpowiedzi:


102

Prawdziwe rozwiązanie, które zachowuje podziały wierszy, powinno wyglądać następująco:

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

Spełnia następujące wymagania:

  1. jeśli oryginalny kod HTML zawiera znak nowej linii (\ n), zostaje zachowany
  2. jeśli oryginalny kod HTML zawiera znaczniki br lub p, zostaną one przetłumaczone na znak nowej linii (\ n).

5
To powinna być wybrana odpowiedź
tak,

2
br2nl nie jest najbardziej przydatną ani dokładną nazwą metody
DD.

2
To najlepsza odpowiedź. Ale co powiesz na for (Element e : document.select("br")) e.after(new TextNode("\n", ""));dodanie prawdziwego znaku nowej linii, a nie sekwencji \ n? Zobacz Node :: after () i Elements :: append (), aby zobaczyć różnicę. W replaceAll()tym przypadku nie jest potrzebny. Podobnie jest z p i innymi elementami blokowymi.
user2043553

1
Odpowiedź @ user121196 powinna być wybraną odpowiedzią. Jeśli nadal masz encje HTML po wyczyszczeniu wejściowego HTML, zastosuj StringEscapeUtils.unescapeHtml (...) Apache commons do danych wyjściowych z Jsoup clean.
karth500

6
Zobacz github.com/jhy/jsoup/blob/master/src/main/java/org/jsoup/…, aby uzyskać kompleksową odpowiedź na ten problem.
Malcolm Smith

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

Używamy tutaj tej metody:

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

Przekazując go Whitelist.none()upewniamy się, że cały HTML został usunięty.

Przekazując upewniamy new OutputSettings().prettyPrint(false)się, że wynik nie jest ponownie formatowany i zachowywane są podziały wierszy.


To powinna być jedyna poprawna odpowiedź. Wszyscy inni zakładają, że tylko brtagi tworzą nowe linie. Co o każdym innym elemencie bloku w HTML, takich jak div, p, ulitp? Wszyscy oni również wprowadzają nowe linie.
adarshr

7
Dzięki temu rozwiązaniu html „<html> <body> <div> wiersz 1 </div> <div> wiersz 2 </div> <div> wiersz 3 </div> </body> </html>” utworzył wynik: "linia 1 linia 2 linia 3" bez nowych linii.
John C

2
To nie działa dla mnie; <br> nie tworzy podziałów wierszy.
JoshuaD

43

Z

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

masz wyjście

"A B" 

i nie

A

B

Do tego używam:

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

2
Rzeczywiście, jest to łatwy środek uśmierzający, ale IMHO powinno to być w pełni obsługiwane przez samą bibliotekę Jsoup (która ma w tej chwili kilka niepokojących zachowań, takich jak to - w przeciwnym razie jest to świetna biblioteka!).
SRG

5
Czy JSoup nie daje Ci DOM? Dlaczego po prostu nie zastąpić wszystkich <br>elementów węzłami tekstowymi zawierającymi nowe linie, a następnie wywołać .text()zamiast przekształcenia wyrażenia regularnego, co spowoduje nieprawidłowe wyjście dla niektórych ciągów, takich jak<div title=<br>'not an attribute'></div>
Mike Samuel

5
Fajnie, ale skąd się wzięło to „descrizione”?
Steve Waters

„descrizione” reprezentuje zmienną, do której przypisywany jest zwykły tekst
enigma969

23

Spróbuj tego, używając 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));
}

fajnie działa mi z małą zmianą new Document.OutputSettings().prettyPrint(true)
Ashu

To rozwiązanie pozostawia „& nbsp;” jako tekst zamiast analizować je w spację.
Andrei Volgin

13

W Jsoup v1.11.2 możemy teraz używać Element.wholeText().

Przykładowy kod:

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

user121196's odpowiedź nadal działa. Ale wholeText()zachowuje wyrównanie tekstów.


Super fajna funkcja!
Denis Kulagin

8

W przypadku bardziej złożonego HTML żadne z powyższych rozwiązań nie działało poprawnie; Udało mi się pomyślnie przeprowadzić konwersję, zachowując podziały wierszy za pomocą:

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

(wersja 1.10.3)


1
Najlepsza ze wszystkich odpowiedzi! Dzięki Andy Res!
Bharath Nadukatla

6

Możesz przejść przez dany element

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

I dla twojego kodu

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

Myślę, że należy sprawdzić, czy isBlockw tail(node, depth)zamian i dołączyć \npo opuszczeniu bloku zamiast podczas wprowadzania go? Robię to (tj. Używam tail) i to działa dobrze. Jednak jeśli używam headtak jak ty, to <p>line one<p>line twokończy się to jako pojedyncza linia.
KajMagnus

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

działa, jeśli sam html nie zawiera „br2n”

Więc,

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

działa bardziej niezawodnie i łatwiej.


4

Spróbuj tego, używając 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

Użyj, textNodes()aby uzyskać listę węzłów tekstowych. Następnie połącz je \njako separator. Oto kod scala, którego używam do tego, port java powinien być łatwy:

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

3

Na podstawie innych odpowiedzi i komentarzy do tego pytania wydaje się, że większość ludzi przyjeżdżających tutaj naprawdę szuka ogólnego rozwiązania, które zapewni ładnie sformatowaną reprezentację tekstową dokumentu HTML. Wiem, że byłem.

Na szczęście JSoup dostarcza już całkiem obszerny przykład, jak to osiągnąć: HtmlToPlainText.java

Przykład FormattingVisitormożna łatwo dostosować do własnych preferencji i dotyczy on większości elementów blokowych i zawijania linii.

Aby uniknąć gnicia linków, oto pełne rozwiązanie Jonathana Hedleya :

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

To jest moja wersja tłumaczenia html na tekst (właściwie zmodyfikowana wersja odpowiedzi user121196).

To nie tylko zachowuje podziały wierszy, ale także formatuje tekst i usuwa nadmierne podziały wierszy, symbole ucieczki HTML, a uzyskasz znacznie lepszy wynik z HTML (w moim przypadku otrzymuję go z poczty).

Pierwotnie został napisany w Scali, ale możesz go łatwo zmienić na Javę

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

Musisz również dołączyć nowy wiersz do znaczników <div>. W przeciwnym razie, jeśli element div występuje po tagach <a> lub <span>, nie będzie w nowym wierszu.
Andrei Volgin

2

Spróbuj tego:

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

1
<p> <b> witaj świecie </b> </p> <p> <br /> <b> yo </b> <a href=" google.com"> googlez </a> </ p > ale ja potrzebuję hello world yo googlez (bez tagów html)
Billy

Ta odpowiedź nie zwraca zwykłego tekstu; zwraca HTML z wstawionymi znakami nowej linii.
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;
}

Używane przez wywołanie z odpowiednim kodem HTML, który zawiera br, wraz z dowolnym ciągiem, którego chcesz użyć jako tymczasowego symbolu zastępczego nowej linii. Na przykład:

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

Rekurencja zapewni, że ciąg używany jako symbol zastępczy nowej linii / łamacza linii nigdy nie będzie w rzeczywistości znajdował się w źródłowym html, ponieważ będzie dodawać „1” do momentu, gdy w html nie zostanie znaleziony znak zastępczy łamacza linku. Nie będzie miał problemu z formatowaniem, który wydają się napotykać metody Jsoup.clean ze znakami specjalnymi.


Dobra, ale nie potrzebujesz rekursji, po prostu dodaj tę linię: while (dirtyHTML.contains (linebreakerString)) linebreakerString = linebreakerString + "1";
Dr NotSoKind

O tak. Całkowita prawda. Chyba mój umysł został złapany,
chociaż

1

Opierając się na odpowiedzi użytkownika121196 i Green Bereta z selects i <pre>s, jedynym rozwiązaniem, które działa dla mnie, jest:

org.jsoup.nodes.Element elementWithHtml = ....
elementWithHtml.select("br").append("<pre>\n</pre>");
elementWithHtml.select("p").prepend("<pre>\n\n</pre>");
elementWithHtml.text();
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.