Jak usunąć znaki HTML w języku Java?

147

Zasadniczo chciałbym zdekodować dany dokument HTML i zamienić wszystkie znaki specjalne, takie jak " "-> " ", ">"-> ">".

W .NET możemy skorzystać z HttpUtility.HtmlDecode.

Jaka jest równoważna funkcja w Javie?

yinyueyouge
źródło
4
& nbsp; nazywana jest jednostką znaku. Edytował tytuł.
Eugene Yokota

Odpowiedzi:

182

W tym celu użyłem Apache Commons StringEscapeUtils.unescapeHtml4 () :

Cofa ciąg znaków zawierający jednostkę ucieczki do ciągu zawierającego rzeczywiste znaki Unicode odpowiadające klawiszom ucieczki. Obsługuje encje HTML 4.0.

Kevin Hakanson
źródło
19
Niestety, właśnie dzisiaj zdałem sobie sprawę, że nie dekoduje znaków specjalnych HTML zbyt dobrze :(
Sid,
1
brudną sztuczką jest przechowywanie wartości początkowo w ukrytym polu, aby przed nią uciec, a następnie pole docelowe powinno pobrać wartość z ukrytego pola.
setzamora
2
Klasa StringEscapeUtils jest przestarzała i przeniesiona do Apache commons-text
Pauli
2
Chcę przekonwertować ciąg <p>&uuml;&egrave;</p>na <p>üé</p>, StringEscapeUtils.unescapeHtml4()otrzymam &lt;p&gt;üè&lt;/p&gt;. Czy istnieje sposób na utrzymanie istniejących tagów HTML w stanie nienaruszonym?
Nickkk
48

Biblioteki wymienione w innych odpowiedziach byłyby dobrym rozwiązaniem, ale jeśli zdarzyło Ci się już przekopywać w swoim projekcie rzeczywisty kod HTML, Jsoup projekt ma znacznie więcej do zaoferowania niż tylko zarządzanie „ampersand pound FFFF średnik” .

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

Otrzymujesz także wygodny interfejs API do wyodrębniania i manipulowania danymi przy użyciu najlepszych metod DOM, CSS i jquery. Jest to open source i licencja MIT.

Dołek
źródło
3
upvote +, ale powinienem zaznaczyć, że nowsze wersje Jsoup używają .text()zamiast.getText()
SourceVisor
4
Być może bardziej bezpośrednie jest użycie org.jsoup.parser.Parser.unescapeEntities(String string, boolean inAttribute). Dokumentacja
danneu
3
To było idealne, ponieważ już używam Jsoup w moim projekcie. Również @danneu miał rację - Parser.unescapeEntities działa dokładnie tak, jak w reklamie.
MandisaW,
42

Wypróbowałem Apache Commons StringEscapeUtils.unescapeHtml3 () w moim projekcie, ale nie byłem zadowolony z jego wydajności. Okazuje się, że wykonuje wiele niepotrzebnych operacji. Po pierwsze, przydziela StringWriter dla każdego wywołania, nawet jeśli w ciągu nie ma nic do cofnięcia. Przepisałem ten kod inaczej, teraz działa znacznie szybciej. Każdy, kto znajdzie to w Google, może z niego skorzystać.

Poniższy kod powoduje usunięcie wszystkich symboli HTML 3 i liczbowych znaków ucieczki (odpowiednik Apache unescapeHtml3). Możesz po prostu dodać więcej wpisów do mapy, jeśli potrzebujesz HTML 4.

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
źródło
Ostatnio musiałem zoptymalizować powolny projekt Struts. Okazało się, że pod osłoną Struts wywołuje Apache domyślnie dla znaków html ze znakami ucieczki ( <s:property value="..."/>). Wyłączenie ucieczki ( <s:property value="..." escaping="false"/>) spowodowało, że niektóre strony działały o 5% do 20% szybciej.
Stephan,
Później dowiedziałem się, że ten kod może wejść w pętlę, gdy podany zostanie pusty łańcuch jako argument. Bieżąca edycja rozwiązała ten problem.
Nick Frolov
Czy to ucieka, czy przestaje? & amp; nie jest dekodowany. Tylko & jest dodawane do mapy, więc działa tylko w jedną stronę?
mmm
3
StringWriter używa wewnętrznie StringBuffer, który używa blokowania. Bezpośrednie użycie StringBuilder powinno być szybsze.
Axel Dörfler
4
@NickFrolov, twoje komentarze wydają się trochę pomieszane. aumljest na przykład, äa nie д.
aioobe
12

Następująca biblioteka może być również używana do ucieczki HTML w Javie: unbescape .

Kod HTML można usunąć w ten sposób:

final String unescapedText = HtmlEscape.unescapeHtml(escapedText); 
Stephan
źródło
2
To nic nie dało:%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
Groźba
40
@ThreaT Twój tekst nie jest zakodowany w formacie HTML, jest zakodowany w postaci adresu URL.
Michaił Batcer
9

To wykonało pracę za mnie,

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

lub

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

Myślę, że zawsze lepiej jest korzystać z lang3oczywistych powodów. Mam nadzieję że to pomoże :)

tk_
źródło
4

Bardzo prostym, ale nieefektywnym rozwiązaniem bez żadnej zewnętrznej biblioteki jest:

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

Powinno to być używane tylko wtedy, gdy masz tylko małą liczbę ciągów do zdekodowania.

Horkruks7
źródło
1
Bardzo blisko, ale nie dokładnie - przekonwertował „qwAS12ƷƸDžǚǪǼȌ” na „qwAS12ƷƸDžǚǪǼȌ \ n”.
Greg,
3

Najbardziej niezawodny sposób to

String cleanedString = StringEscapeUtils.unescapeHtml4(originalString);

od org.apache.commons.lang3.StringEscapeUtils.

I uciec od białych znaków

cleanedString = cleanedString.trim();

Zapewni to, że spacje spowodowane kopiowaniem i wklejaniem w formularzach internetowych nie zostaną utrwalone w bazie danych.

mike oganyan
źródło
1

Spring Framework HtmlUtils

Jeśli korzystasz już ze środowiska Spring, użyj następującej metody:

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

...

String result = htmlUnescape(source);
Hermana
źródło
0

Rozważ użycie klasy Java HtmlManipulator . Może być konieczne dodanie niektórych elementów (nie wszystkie elementy znajdują się na liście).

Apache Commons StringEscapeUtils, jak zasugerował Kevin Hakanson, nie działało dla mnie w 100%; kilka jednostek, takich jak & # 145 (pojedynczy cudzysłów po lewej), zostało w jakiś sposób przetłumaczonych na '222'. Próbowałem też org.jsoup i miałem ten sam problem.

Joost
źródło
0

W moim przypadku używam metody replace, testując każdą jednostkę w każdej zmiennej, mój kod wygląda następująco:

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;", "ú");

W moim przypadku to zadziałało bardzo dobrze.

Luiz dev
źródło
2
To nie jest każda szczególna jednostka. Brakuje nawet dwóch wymienionych w pytaniu.
Sandy Gifford
to nie będzie dobrze skalowane
denov
-7

Przypuśćmy, że chcesz naśladować funkcję php, która htmlspecialchars_decode używa funkcji php get_html_translation_table (), aby zrzucić tabelę, a następnie użyć kodu java, takiego jak:

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
źródło
7
Nie rzucaj tak dużo; użyj typów generycznych na tej HashMap! Ponadto, użyj foreach, nie czekaj długo, aby to sprawdzić, kod będzie wyglądał o wiele bardziej czytelnie!
WhyNotHugo
3
@BalaDutt jeśli poprawisz swoją odpowiedź, chłopaki dadzą Ci punkty :)
sparkyspider
3
Popraw również nazwy funkcji i zmiennych, @Bala.
Thomas W