Wie entferne ich HTML-Zeichenentitäten in Java?

147

Grundsätzlich möchte ich ein bestimmtes HTML-Dokument dekodieren und alle Sonderzeichen wie " "-> " ", ">"-> ersetzen ">".

In .NET können wir verwenden HttpUtility.HtmlDecode.

Was ist die entsprechende Funktion in Java?

yinyueyouge
quelle
4
& nbsp; heißt Zeichenentität. Titel bearbeitet.
Eugene Yokota

Antworten:

182

Ich habe die Apache Commons StringEscapeUtils.unescapeHtml4 () dafür verwendet:

Entkoppelt eine Zeichenfolge, die Entity-Escapezeichen enthält, in eine Zeichenfolge, die die tatsächlichen Unicode-Zeichen enthält, die den Escapezeichen entsprechen. Unterstützt HTML 4.0-Entitäten.

Kevin Hakanson
quelle
19
Leider habe ich heute gerade festgestellt, dass es HTML-Sonderzeichen nicht sehr gut dekodiert :(
Sid
1
Ein schmutziger Trick besteht darin, den Wert zunächst in einem ausgeblendeten Feld zu speichern, um ihn zu umgehen. Dann sollte das Zielfeld den Wert aus dem ausgeblendeten Feld abrufen.
Setzamora
2
Die Klasse StringEscapeUtils ist veraltet und wird in Apache Commons-Text
Pauli
2
Ich mag die Zeichenfolge konvertieren , <p>&uuml;&egrave;</p>um <p>üé</p>mit StringEscapeUtils.unescapeHtml4()I erhalten &lt;p&gt;üè&lt;/p&gt;. Gibt es eine Möglichkeit, vorhandene HTML-Tags intakt zu halten?
Nickkk
48

Die in anderen Antworten erwähnten Bibliotheken wären gute Lösungen, aber wenn Sie in Ihrem Projekt bereits in echtem HTML-Code stöbern, ist das Jsoup Code stöbern Projekt viel mehr zu bieten als nur die Verwaltung von "kaufmännischen und Pfund-FFFF-Semikolon" -Dingen.

// textValue: <p>This is a&nbsp;sample. \"Granny\" Smith &#8211;.<\/p>\r\n
// becomes this: This is a sample. "Granny" Smith –.
// with one line of code:
// Jsoup.parse(textValue).getText(); // for older versions of Jsoup
Jsoup.parse(textValue).text();

// Another possibility may be the static unescapeEntities method:
boolean strictMode = true;
String unescapedString = org.jsoup.parser.Parser.unescapeEntities(textValue, strictMode);

Außerdem erhalten Sie die praktische API zum Extrahieren und Bearbeiten von Daten mit den besten DOM-, CSS- und jquery-ähnlichen Methoden. Es ist Open Source und MIT-Lizenz.

Tal
quelle
3
upvote +, aber ich sollte darauf hinweisen, dass neuere Versionen von Jsoup .text()anstelle von.getText()
SourceVisor
4
Vielleicht ist direkter zu verwenden org.jsoup.parser.Parser.unescapeEntities(String string, boolean inAttribute). API-Dokumente: jsoup.org/apidocs/org/jsoup/parser/…
danneu
3
Das war perfekt, da ich Jsoup bereits in meinem Projekt verwende. Auch @danneu hatte Recht - Parser.unescapeEntities funktioniert genau wie angekündigt.
MandisaW
42

Ich habe in meinem Projekt Apache Commons StringEscapeUtils.unescapeHtml3 () ausprobiert, war aber mit der Leistung nicht zufrieden. Es stellt sich heraus, dass es viele unnötige Operationen ausführt. Zum einen wird jedem Aufruf ein StringWriter zugewiesen, auch wenn in der Zeichenfolge nichts zu entfernen ist. Ich habe diesen Code anders umgeschrieben, jetzt funktioniert er viel schneller. Wer dies in Google findet, kann es gerne nutzen.

Mit dem folgenden Code werden alle HTML 3-Symbole und numerischen Escapezeichen entfernt (entspricht Apache unescapeHtml3). Sie können der Karte einfach weitere Einträge hinzufügen, wenn Sie HTML 4 benötigen.

package com.example;

import java.io.StringWriter;
import java.util.HashMap;

public class StringUtils {

    public static final String unescapeHtml3(final String input) {
        StringWriter writer = null;
        int len = input.length();
        int i = 1;
        int st = 0;
        while (true) {
            // look for '&'
            while (i < len && input.charAt(i-1) != '&')
                i++;
            if (i >= len)
                break;

            // found '&', look for ';'
            int j = i;
            while (j < len && j < i + MAX_ESCAPE + 1 && input.charAt(j) != ';')
                j++;
            if (j == len || j < i + MIN_ESCAPE || j == i + MAX_ESCAPE + 1) {
                i++;
                continue;
            }

            // found escape 
            if (input.charAt(i) == '#') {
                // numeric escape
                int k = i + 1;
                int radix = 10;

                final char firstChar = input.charAt(k);
                if (firstChar == 'x' || firstChar == 'X') {
                    k++;
                    radix = 16;
                }

                try {
                    int entityValue = Integer.parseInt(input.substring(k, j), radix);

                    if (writer == null) 
                        writer = new StringWriter(input.length());
                    writer.append(input.substring(st, i - 1));

                    if (entityValue > 0xFFFF) {
                        final char[] chrs = Character.toChars(entityValue);
                        writer.write(chrs[0]);
                        writer.write(chrs[1]);
                    } else {
                        writer.write(entityValue);
                    }

                } catch (NumberFormatException ex) { 
                    i++;
                    continue;
                }
            }
            else {
                // named escape
                CharSequence value = lookupMap.get(input.substring(i, j));
                if (value == null) {
                    i++;
                    continue;
                }

                if (writer == null) 
                    writer = new StringWriter(input.length());
                writer.append(input.substring(st, i - 1));

                writer.append(value);
            }

            // skip escape
            st = j + 1;
            i = st;
        }

        if (writer != null) {
            writer.append(input.substring(st, len));
            return writer.toString();
        }
        return input;
    }

    private static final String[][] ESCAPES = {
        {"\"",     "quot"}, // " - double-quote
        {"&",      "amp"}, // & - ampersand
        {"<",      "lt"}, // < - less-than
        {">",      "gt"}, // > - greater-than

        // Mapping to escape ISO-8859-1 characters to their named HTML 3.x equivalents.
        {"\u00A0", "nbsp"}, // non-breaking space
        {"\u00A1", "iexcl"}, // inverted exclamation mark
        {"\u00A2", "cent"}, // cent sign
        {"\u00A3", "pound"}, // pound sign
        {"\u00A4", "curren"}, // currency sign
        {"\u00A5", "yen"}, // yen sign = yuan sign
        {"\u00A6", "brvbar"}, // broken bar = broken vertical bar
        {"\u00A7", "sect"}, // section sign
        {"\u00A8", "uml"}, // diaeresis = spacing diaeresis
        {"\u00A9", "copy"}, // © - copyright sign
        {"\u00AA", "ordf"}, // feminine ordinal indicator
        {"\u00AB", "laquo"}, // left-pointing double angle quotation mark = left pointing guillemet
        {"\u00AC", "not"}, // not sign
        {"\u00AD", "shy"}, // soft hyphen = discretionary hyphen
        {"\u00AE", "reg"}, // ® - registered trademark sign
        {"\u00AF", "macr"}, // macron = spacing macron = overline = APL overbar
        {"\u00B0", "deg"}, // degree sign
        {"\u00B1", "plusmn"}, // plus-minus sign = plus-or-minus sign
        {"\u00B2", "sup2"}, // superscript two = superscript digit two = squared
        {"\u00B3", "sup3"}, // superscript three = superscript digit three = cubed
        {"\u00B4", "acute"}, // acute accent = spacing acute
        {"\u00B5", "micro"}, // micro sign
        {"\u00B6", "para"}, // pilcrow sign = paragraph sign
        {"\u00B7", "middot"}, // middle dot = Georgian comma = Greek middle dot
        {"\u00B8", "cedil"}, // cedilla = spacing cedilla
        {"\u00B9", "sup1"}, // superscript one = superscript digit one
        {"\u00BA", "ordm"}, // masculine ordinal indicator
        {"\u00BB", "raquo"}, // right-pointing double angle quotation mark = right pointing guillemet
        {"\u00BC", "frac14"}, // vulgar fraction one quarter = fraction one quarter
        {"\u00BD", "frac12"}, // vulgar fraction one half = fraction one half
        {"\u00BE", "frac34"}, // vulgar fraction three quarters = fraction three quarters
        {"\u00BF", "iquest"}, // inverted question mark = turned question mark
        {"\u00C0", "Agrave"}, // А - uppercase A, grave accent
        {"\u00C1", "Aacute"}, // Б - uppercase A, acute accent
        {"\u00C2", "Acirc"}, // В - uppercase A, circumflex accent
        {"\u00C3", "Atilde"}, // Г - uppercase A, tilde
        {"\u00C4", "Auml"}, // Д - uppercase A, umlaut
        {"\u00C5", "Aring"}, // Е - uppercase A, ring
        {"\u00C6", "AElig"}, // Ж - uppercase AE
        {"\u00C7", "Ccedil"}, // З - uppercase C, cedilla
        {"\u00C8", "Egrave"}, // И - uppercase E, grave accent
        {"\u00C9", "Eacute"}, // Й - uppercase E, acute accent
        {"\u00CA", "Ecirc"}, // К - uppercase E, circumflex accent
        {"\u00CB", "Euml"}, // Л - uppercase E, umlaut
        {"\u00CC", "Igrave"}, // М - uppercase I, grave accent
        {"\u00CD", "Iacute"}, // Н - uppercase I, acute accent
        {"\u00CE", "Icirc"}, // О - uppercase I, circumflex accent
        {"\u00CF", "Iuml"}, // П - uppercase I, umlaut
        {"\u00D0", "ETH"}, // Р - uppercase Eth, Icelandic
        {"\u00D1", "Ntilde"}, // С - uppercase N, tilde
        {"\u00D2", "Ograve"}, // Т - uppercase O, grave accent
        {"\u00D3", "Oacute"}, // У - uppercase O, acute accent
        {"\u00D4", "Ocirc"}, // Ф - uppercase O, circumflex accent
        {"\u00D5", "Otilde"}, // Х - uppercase O, tilde
        {"\u00D6", "Ouml"}, // Ц - uppercase O, umlaut
        {"\u00D7", "times"}, // multiplication sign
        {"\u00D8", "Oslash"}, // Ш - uppercase O, slash
        {"\u00D9", "Ugrave"}, // Щ - uppercase U, grave accent
        {"\u00DA", "Uacute"}, // Ъ - uppercase U, acute accent
        {"\u00DB", "Ucirc"}, // Ы - uppercase U, circumflex accent
        {"\u00DC", "Uuml"}, // Ь - uppercase U, umlaut
        {"\u00DD", "Yacute"}, // Э - uppercase Y, acute accent
        {"\u00DE", "THORN"}, // Ю - uppercase THORN, Icelandic
        {"\u00DF", "szlig"}, // Я - lowercase sharps, German
        {"\u00E0", "agrave"}, // а - lowercase a, grave accent
        {"\u00E1", "aacute"}, // б - lowercase a, acute accent
        {"\u00E2", "acirc"}, // в - lowercase a, circumflex accent
        {"\u00E3", "atilde"}, // г - lowercase a, tilde
        {"\u00E4", "auml"}, // д - lowercase a, umlaut
        {"\u00E5", "aring"}, // е - lowercase a, ring
        {"\u00E6", "aelig"}, // ж - lowercase ae
        {"\u00E7", "ccedil"}, // з - lowercase c, cedilla
        {"\u00E8", "egrave"}, // и - lowercase e, grave accent
        {"\u00E9", "eacute"}, // й - lowercase e, acute accent
        {"\u00EA", "ecirc"}, // к - lowercase e, circumflex accent
        {"\u00EB", "euml"}, // л - lowercase e, umlaut
        {"\u00EC", "igrave"}, // м - lowercase i, grave accent
        {"\u00ED", "iacute"}, // н - lowercase i, acute accent
        {"\u00EE", "icirc"}, // о - lowercase i, circumflex accent
        {"\u00EF", "iuml"}, // п - lowercase i, umlaut
        {"\u00F0", "eth"}, // р - lowercase eth, Icelandic
        {"\u00F1", "ntilde"}, // с - lowercase n, tilde
        {"\u00F2", "ograve"}, // т - lowercase o, grave accent
        {"\u00F3", "oacute"}, // у - lowercase o, acute accent
        {"\u00F4", "ocirc"}, // ф - lowercase o, circumflex accent
        {"\u00F5", "otilde"}, // х - lowercase o, tilde
        {"\u00F6", "ouml"}, // ц - lowercase o, umlaut
        {"\u00F7", "divide"}, // division sign
        {"\u00F8", "oslash"}, // ш - lowercase o, slash
        {"\u00F9", "ugrave"}, // щ - lowercase u, grave accent
        {"\u00FA", "uacute"}, // ъ - lowercase u, acute accent
        {"\u00FB", "ucirc"}, // ы - lowercase u, circumflex accent
        {"\u00FC", "uuml"}, // ь - lowercase u, umlaut
        {"\u00FD", "yacute"}, // э - lowercase y, acute accent
        {"\u00FE", "thorn"}, // ю - lowercase thorn, Icelandic
        {"\u00FF", "yuml"}, // я - lowercase y, umlaut
    };

    private static final int MIN_ESCAPE = 2;
    private static final int MAX_ESCAPE = 6;

    private static final HashMap<String, CharSequence> lookupMap;
    static {
        lookupMap = new HashMap<String, CharSequence>();
        for (final CharSequence[] seq : ESCAPES) 
            lookupMap.put(seq[1].toString(), seq[0]);
    }

}
Nick Frolov
quelle
Vor kurzem musste ich ein langsames Struts-Projekt optimieren. Es stellte sich heraus, dass Struts unter dem Deckmantel Apache für HTML-Zeichenfolgen aufruft, die standardmäßig maskiert werden ( <s:property value="..."/>). Durch Deaktivieren von Escape ( <s:property value="..." escaping="false"/>) wurden einige Seiten 5% bis 20% schneller ausgeführt.
Stephan
Später fand ich heraus, dass dieser Code in eine Schleife eintreten kann, wenn eine leere Zeichenfolge als Argument angegeben wird. In der aktuellen Ausgabe wurde dieses Problem behoben.
Nick Frolov
Entkommt das oder nicht? &Ampere; wird nicht dekodiert. Nur & wird der Karte hinzugefügt, funktioniert es also nur in eine Richtung?
mmm
3
Ein StringWriter verwendet intern einen StringBuffer, der das Sperren verwendet. Die direkte Verwendung eines StringBuilder sollte schneller sein.
Axel Dörfler
4
@ NickFrolov, deine Kommentare scheinen ein bisschen durcheinander zu sein. aumlist zum Beispiel äund nicht д.
Aioobe
12

Die folgende Bibliothek kann auch für HTML- Escapezeichen in Java verwendet werden: unbescape .

HTML kann folgendermaßen entkoppelt werden:

final String unescapedText = HtmlEscape.unescapeHtml(escapedText); 
Stephan
quelle
2
Es hat nichts getan:%3Chtml%3E%0D%0A%3Chead%3E%0D%0A%3Ctitle%3Etest%3C%2Ftitle%3E%0D%0A%3C%2Fhead%3E%0D%0A%3Cbody%3E%0D%0Atest%0D%0A%3C%2Fbody%3E%0D%0A%3C%2Fhtml%3E
Bedrohung
40
@ThreaT Ihr Text ist nicht HTML-codiert, sondern URL-codiert.
Mikhail Batcer
9

Das hat den Job für mich gemacht,

import org.apache.commons.lang.StringEscapeUtils;
...
String decodedXML= StringEscapeUtils.unescapeHtml(encodedXML);

oder

import org.apache.commons.lang3.StringEscapeUtils;
...
String decodedXML= StringEscapeUtils.unescapeHtml4(encodedXML);

Ich denke, es ist immer besser, das lang3aus offensichtlichen Gründen zu verwenden. Hoffe das hilft :)

tk_
quelle
4

Eine sehr einfache, aber ineffiziente Lösung ohne externe Bibliothek ist:

public static String unescapeHtml3( String str ) {
    try {
        HTMLDocument doc = new HTMLDocument();
        new HTMLEditorKit().read( new StringReader( "<html><body>" + str ), doc, 0 );
        return doc.getText( 1, doc.getLength() );
    } catch( Exception ex ) {
        return str;
    }
}

Dies sollte nur verwendet werden, wenn Sie nur eine geringe Anzahl von Zeichenfolgen zum Dekodieren haben.

Horkrux7
quelle
1
Sehr nah, aber nicht genau - es konvertierte "qwAS12ƷƸDžǚǪǼȌ" in "qwAS12ƷƸDžǚǪǼȌ \ n".
Greg
3

Der zuverlässigste Weg ist mit

String cleanedString = StringEscapeUtils.unescapeHtml4(originalString);

von org.apache.commons.lang3.StringEscapeUtils.

Und um den weißen Räumen zu entkommen

cleanedString = cleanedString.trim();

Dadurch wird sichergestellt, dass Leerzeichen durch Kopieren und Einfügen in Webformulare nicht in der Datenbank beibehalten werden.

mike oganyan
quelle
1

Spring Framework HtmlUtils

Wenn Sie Spring Framework bereits verwenden, verwenden Sie die folgende Methode:

import static org.springframework.web.util.HtmlUtils.htmlUnescape;

...

String result = htmlUnescape(source);
ihr Mann
quelle
0

Erwägen Sie die Verwendung der Java-Klasse HtmlManipulator . Möglicherweise müssen Sie einige Elemente hinzufügen (nicht alle Entitäten sind in der Liste enthalten).

Die von Kevin Hakanson vorgeschlagenen Apache Commons StringEscapeUtils funktionierten bei mir nicht zu 100%. Einige Entitäten wie & # 145 (linkes einfaches Anführungszeichen) wurden irgendwie in '222' übersetzt. Ich habe auch org.jsoup ausprobiert und hatte das gleiche Problem.

Joost
quelle
0

In meinem Fall verwende ich die Ersetzungsmethode, indem ich jede Entität in jeder Variablen teste. Mein Code sieht folgendermaßen aus:

text = text.replace("&Ccedil;", "Ç");
text = text.replace("&ccedil;", "ç");
text = text.replace("&Aacute;", "Á");
text = text.replace("&Acirc;", "Â");
text = text.replace("&Atilde;", "Ã");
text = text.replace("&Eacute;", "É");
text = text.replace("&Ecirc;", "Ê");
text = text.replace("&Iacute;", "Í");
text = text.replace("&Ocirc;", "Ô");
text = text.replace("&Otilde;", "Õ");
text = text.replace("&Oacute;", "Ó");
text = text.replace("&Uacute;", "Ú");
text = text.replace("&aacute;", "á");
text = text.replace("&acirc;", "â");
text = text.replace("&atilde;", "ã");
text = text.replace("&eacute;", "é");
text = text.replace("&ecirc;", "ê");
text = text.replace("&iacute;", "í");
text = text.replace("&ocirc;", "ô");
text = text.replace("&otilde;", "õ");
text = text.replace("&oacute;", "ó");
text = text.replace("&uacute;", "ú");

In meinem Fall hat das sehr gut funktioniert.

Luiz dev
quelle
2
Dies ist nicht jede besondere Entität. Sogar die beiden in der Frage genannten fehlen.
Sandy Gifford
Dies wird nicht gut skalieren
Denov
-7

Wenn Sie nachahmen möchten, welche PHP-Funktion htmlspecialchars_decode die PHP-Funktion get_html_translation_table () verwendet, um die Tabelle zu sichern und dann den Java-Code wie folgt zu verwenden:

static Map<String,String> html_specialchars_table = new Hashtable<String,String>();
static {
        html_specialchars_table.put("&lt;","<");
        html_specialchars_table.put("&gt;",">");
        html_specialchars_table.put("&amp;","&");
}
static String htmlspecialchars_decode_ENT_NOQUOTES(String s){
        Enumeration en = html_specialchars_table.keys();
        while(en.hasMoreElements()){
                String key = en.nextElement();
                String val = html_specialchars_table.get(key);
                s = s.replaceAll(key, val);
        }
        return s;
}
Bala Dutt
quelle
7
Wirf nicht so viel; Verwenden Sie Generika auf dieser HashMap! Verwenden Sie auch eine foreach, nicht eine Weile, um dies zu wiederholen, der Code wird viel lesbarer aussehen!
WhyNotHugo
3
@ Baladutt Wenn Sie Ihre Antwort verbessern, geben die Jungs Ihnen Punkte :)
Sparkyspider
3
Verbessere auch deine Funktions- und Variablennamen, @Bala.
Thomas W