Comment échapper aux entités de caractères HTML en Java?

147

Fondamentalement, je voudrais décoder un document Html donné et remplacer tous les caractères spéciaux, tels que " "-> " ", ">"-> ">".

Dans .NET, nous pouvons utiliser HttpUtility.HtmlDecode.

Quelle est la fonction équivalente en Java?

yinyueyouge
la source
4
& nbsp; est appelée entité de caractère. Modification du titre.
Eugene Yokota

Réponses:

182

J'ai utilisé Apache Commons StringEscapeUtils.unescapeHtml4 () pour cela:

Annule l'échappement d'une chaîne contenant des échappements d'entité vers une chaîne contenant les caractères Unicode réels correspondant aux échappements. Prend en charge les entités HTML 4.0.

Kevin Hakanson
la source
19
Malheureusement, je viens de réaliser aujourd'hui qu'il ne décode pas très bien les caractères HTMLspecial :(
Sid
1
un truc sale est de stocker la valeur initialement dans un champ caché pour l'échapper, puis le champ cible devrait obtenir la valeur du champ caché.
setzamora
2
La classe StringEscapeUtils est obsolète et déplacée vers Apache commons-text
Pauli
2
Je veux convertir la chaîne <p>&uuml;&egrave;</p>en <p>üé</p>, avec StringEscapeUtils.unescapeHtml4()je reçois &lt;p&gt;üè&lt;/p&gt;. Existe-t-il un moyen de conserver intactes les balises HTML existantes?
Nickkk le
48

Les bibliothèques mentionnées dans d'autres réponses seraient de bonnes solutions, mais si vous êtes déjà en train de fouiller dans le html du monde réel dans votre projet, le Jsoupprojet a beaucoup plus à offrir que de simplement gérer des choses «esperluette FFFF point-virgule» .

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

Et vous obtenez également l'API pratique pour extraire et manipuler des données, en utilisant le meilleur des méthodes DOM, CSS et jquery. C'est une licence open source et MIT.

Vallée
la source
3
upvote +, mais je dois souligner que les nouvelles versions de Jsoup utilisent à la .text()place de.getText()
SourceVisor
4
Peut-être plus direct est à utiliser org.jsoup.parser.Parser.unescapeEntities(String string, boolean inAttribute). Documents de l'
danneu
3
C'était parfait, puisque j'utilise déjà Jsoup dans mon projet. De plus, @danneu avait raison - Parser.unescapeEntities fonctionne exactement comme annoncé.
MandisaW
42

J'ai essayé Apache Commons StringEscapeUtils.unescapeHtml3 () dans mon projet, mais je n'étais pas satisfait de ses performances. Il s'avère que cela fait beaucoup d'opérations inutiles. D'une part, il alloue un StringWriter pour chaque appel, même s'il n'y a rien à échapper dans la chaîne. J'ai réécrit ce code différemment, maintenant cela fonctionne beaucoup plus rapidement. Quiconque trouve cela dans Google est invité à l'utiliser.

Le code suivant déséchappe tous les symboles HTML 3 et les échappements numériques (équivalent à Apache unescapeHtml3). Vous pouvez simplement ajouter d'autres entrées à la carte si vous avez besoin de 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
la source
Récemment, j'ai dû optimiser un projet Struts lent. Il s'est avéré que sous la couverture Struts appelle Apache pour une chaîne html s'échappant par défaut ( <s:property value="..."/>). La désactivation de l'échappement ( <s:property value="..." escaping="false"/>) a permis à certaines pages de s'exécuter 5% à 20% plus rapidement.
Stephan
Plus tard, j'ai découvert que ce code peut entrer en boucle lorsqu'il est donné une chaîne vide comme argument. L'édition actuelle a ce problème résolu.
Nick Frolov
Est-ce que cela s'échappe ou manque d'espace? & amp; n'est pas décodé. Seul & est ajouté à la carte, donc cela ne fonctionne que dans un seul sens?
mmm
3
Un StringWriter utilise un StringBuffer en interne qui utilise le verrouillage. L'utilisation directe d'un StringBuilder devrait être plus rapide.
Axel Dörfler
4
@NickFrolov, vos commentaires semblent un peu foirés. aumlest par exemple äet non д.
aioobe
12

La bibliothèque suivante peut également être utilisée pour l'échappement HTML en Java: unbescape .

Le HTML peut être sans échappement de cette façon:

final String unescapedText = HtmlEscape.unescapeHtml(escapedText); 
Stéphan
la source
2
Cela n'a rien fait:%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
ThreaT
40
@ThreaT Votre texte n'est pas codé html, il est codé url.
Mikhail Batcer
9

Cela a fait le travail pour moi,

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

ou

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

Je suppose qu'il est toujours préférable d'utiliser le lang3pour des raisons évidentes. J'espère que cela t'aides :)

tk_
la source
4

Une solution très simple mais inefficace sans bibliothèque externe est:

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

Cela ne doit être utilisé que si vous n'avez qu'un petit nombre de chaînes à décoder.

Horcruxe7
la source
1
Très proche, mais pas exact - il a converti "qwAS12ƷƸDžǚǪǼȌ" en "qwAS12ƷƸDžǚǪǼȌ \ n".
Greg
3

Le moyen le plus fiable est avec

String cleanedString = StringEscapeUtils.unescapeHtml4(originalString);

de org.apache.commons.lang3.StringEscapeUtils.

Et pour échapper aux espaces blancs

cleanedString = cleanedString.trim();

Cela garantira que les espaces en raison du copier-coller dans les formulaires Web ne seront pas persistants dans DB.

Mike oganyan
la source
1

Spring Framework HtmlUtils

Si vous utilisez déjà le framework Spring, utilisez la méthode suivante:

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

...

String result = htmlUnescape(source);
Herman
la source
0

Pensez à utiliser la classe Java HtmlManipulator . Vous devrez peut-être ajouter certains éléments (toutes les entités ne sont pas dans la liste).

Apache Commons StringEscapeUtils comme suggéré par Kevin Hakanson n'a pas fonctionné à 100% pour moi; plusieurs entités comme & # 145 (guillemet simple à gauche) ont été traduites d'une manière ou d'une autre en «222». J'ai également essayé org.jsoup et j'ai eu le même problème.

Joost
la source
0

Dans mon cas, j'utilise la méthode replace en testant chaque entité dans chaque variable, mon code ressemble à ceci:

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

Dans mon cas, cela a très bien fonctionné.

Luiz dev
la source
2
Ce ne sont pas toutes les entités spéciales. Même les deux mentionnés dans la question manquent.
Sandy Gifford
cela ne va pas bien à l'échelle
denov
-7

Dans le cas où vous voudriez imiter quelle fonction php htmlspecialchars_decode utilise la fonction php get_html_translation_table () pour vider la table, puis utilisez le code java comme,

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
la source
7
Ne lancez pas tellement; utilisez des génériques sur ce HashMap! Aussi, utilisez un foreach, pas un moment pour répéter cela, le code aura l'air beaucoup plus lisible!
WhyNotHugo
3
@BalaDutt si vous améliorez votre réponse, les gars vous donneront des points :)
sparkyspider
3
Améliorez également vos noms de fonctions et de variables, @Bala.
Thomas W le