¿Cómo deshacerse de las entidades de caracteres HTML en Java?

147

Básicamente, me gustaría decodificar un documento Html dado y reemplazar todos los caracteres especiales, como " "-> " ", ">"-> ">".

En .NET podemos hacer uso de HttpUtility.HtmlDecode.

¿Cuál es la función equivalente en Java?

yinyueyouge
fuente
44
& nbsp; se llama entidad de caracteres. Editado el título.
Eugene Yokota

Respuestas:

182

He usado Apache Commons StringEscapeUtils.unescapeHtml4 () para esto:

Desensambla una cadena que contiene una entidad que se escapa a una cadena que contiene los caracteres Unicode reales correspondientes a los escapes. Admite entidades HTML 4.0.

Kevin Hakanson
fuente
19
Lamentablemente, hoy me di cuenta de que no decodifica caracteres HTML muy bien :(
Sid
1
un truco sucio es almacenar el valor inicialmente en un campo oculto para escapar de él, luego el campo objetivo debe obtener el valor del campo oculto.
setzamora
2
La clase StringEscapeUtils está en desuso y se trasladó a Apache commons-text
Pauli
2
Quiero convertir la cadena <p>&uuml;&egrave;</p>a <p>üé</p>, con StringEscapeUtils.unescapeHtml4()lo consigo &lt;p&gt;üè&lt;/p&gt;. ¿Hay alguna manera de mantener intactas las etiquetas html existentes?
Nickkk
48

Las bibliotecas mencionadas en otras respuestas serían buenas soluciones, pero si ya estás buscando en el mundo real html en tu proyecto, el Jsoupproyecto tiene mucho más que ofrecer que solo administrar "ampersand libra FFFF punto y coma" .

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

Y también obtienes la API conveniente para extraer y manipular datos, utilizando los mejores métodos DOM, CSS y jquery. Es de código abierto y licencia MIT.

Valle
fuente
3
upvote +, pero debo señalar que las versiones más nuevas de Jsoup usan en .text()lugar de.getText()
SourceVisor
44
Quizás más directo es el uso org.jsoup.parser.Parser.unescapeEntities(String string, boolean inAttribute). Documentos de la
danneu
3
Esto fue perfecto, ya que ya estoy usando Jsoup en mi proyecto. Además, @danneu tenía razón: Parser.unescapeEntities funciona exactamente como se anuncia.
MandisaW
42

Intenté Apache Commons StringEscapeUtils.unescapeHtml3 () en mi proyecto, pero no estaba satisfecho con su rendimiento. Resulta que hace muchas operaciones innecesarias. Por un lado, asigna un StringWriter para cada llamada, incluso si no hay nada que escapar en la cadena. He reescrito ese código de manera diferente, ahora funciona mucho más rápido. Quien encuentre esto en Google puede usarlo.

El siguiente código desempaqueta todos los símbolos HTML 3 y los escapes numéricos (equivalente a Apache unescapeHtml3). Simplemente puede agregar más entradas al mapa si necesita 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
fuente
Recientemente, tuve que optimizar un proyecto lento de Struts. Resultó que debajo de la cubierta Struts llama a Apache para que la cadena html se escape por defecto ( <s:property value="..."/>). Al desactivar el escape ( <s:property value="..." escaping="false"/>), algunas páginas se ejecutan entre un 5% y un 20% más rápido.
Stephan
Más tarde descubrí que este código puede ingresar al bucle cuando se le da una cadena vacía como argumento. La edición actual tiene ese problema solucionado.
Nick Frolov
¿Esto escapa o deja de funcionar? &erio; No está decodificado. ¿Solo & se agrega al mapa, por lo que solo funciona de una manera?
mmm
3
Un StringWriter usa un StringBuffer internamente que usa bloqueo. Usar un StringBuilder directamente debería ser más rápido.
Axel Dörfler
44
@NickFrolov, tus comentarios parecen un poco desordenados. aumles por ejemplo äy no д.
aioobe
12

La siguiente biblioteca también se puede utilizar para el escape de HTML en Java: unbescape .

HTML puede ser escapado de esta manera:

final String unescapedText = HtmlEscape.unescapeHtml(escapedText); 
Stephan
fuente
2
No hizo nada para esto:%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 Su texto no está codificado en html, está codificado en url.
Mikhail Batcer
9

Esto hizo el trabajo por mí

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

o

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

Supongo que siempre es mejor usarlo lang3por razones obvias. Espero que esto ayude :)

tk_
fuente
4

Una solución muy simple pero ineficiente sin ninguna biblioteca externa es:

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

Esto debe usarse solo si tiene un pequeño recuento de cadenas para decodificar.

Horrocrux7
fuente
1
Muy cerca, pero no exacto: convirtió "qwAS12ƷƸDžǚǪǼȌ" en "qwAS12ƷƸDžǚǪǼȌ \ n".
Greg
3

La forma más confiable es con

String cleanedString = StringEscapeUtils.unescapeHtml4(originalString);

de org.apache.commons.lang3.StringEscapeUtils.

Y para escapar de los espacios en blanco

cleanedString = cleanedString.trim();

Esto asegurará que los espacios en blanco debido a copiar y pegar en formularios web no se conserven en DB.

Mike Oganyan
fuente
1

Spring Framework HtmlUtils

Si ya está utilizando Spring Framework, use el siguiente método:

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

...

String result = htmlUnescape(source);
Germán
fuente
0

Considere usar la clase Java HtmlManipulator . Es posible que deba agregar algunos elementos (no todas las entidades están en la lista).

Apache Commons StringEscapeUtils como lo sugirió Kevin Hakanson no funcionó al 100% para mí; varias entidades como & # 145 (comilla simple izquierda) se tradujeron de alguna manera a '222'. También probé org.jsoup y tuve el mismo problema.

Joost
fuente
0

En mi caso, uso el método de reemplazo probando cada entidad en cada variable, mi código se ve así:

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

En mi caso esto funcionó muy bien.

Luiz dev
fuente
2
Esta no es toda entidad especial. Incluso faltan los dos mencionados en la pregunta.
Sandy Gifford
esto no escalará bien
denov
-7

En caso de que desee imitar qué función php htmlspecialchars_decode usa la función php get_html_translation_table () para volcar la tabla y luego usar el código java como,

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
fuente
77
No eches tanto; ¡use genéricos en ese HashMap! Además, use un foreach, no un momento para repetirlo, ¡el código se verá mucho más legible!
WhyNotHugo
3
@BalaDutt si mejoras tu respuesta, los chicos te darán puntos :)
sparkyspider
3
Mejore su función y nombres de variables también, @Bala.
Thomas W