Como remover as entidades de caracteres HTML em Java?

147

Basicamente, eu gostaria de decodificar um determinado documento HTML e substituir todos os caracteres especiais, como " "-> " ", ">"-> ">".

No .NET, podemos usar HttpUtility.HtmlDecode.

Qual é a função equivalente em Java?

yinyueyouge
fonte
4
& nbsp; é chamado de entidade de caractere. Editou o título.
Eugene Yokota

Respostas:

182

Eu usei o Apache Commons StringEscapeUtils.unescapeHtml4 () para isso:

Retira o escape de uma string que contém uma entidade que escapa para uma string que contém os caracteres Unicode reais correspondentes às fugas. Suporta entidades HTML 4.0.

Kevin Hakanson
fonte
19
Infelizmente eu só percebi hoje que não decodifica HTMLspecial personagens muito bem :(
Sid
1
um truque sujo é armazenar o valor inicialmente em um campo oculto para escapar dele, e o campo de destino deve obter o valor do campo oculto.
setzamora
2
Classe StringEscapeUtils é obsoleto e mudou-se para Apache commons-texto
Pauli
2
Eu quero converter a string <p>&uuml;&egrave;</p>para <p>üé</p>, com StringEscapeUtils.unescapeHtml4()eu recebo &lt;p&gt;üè&lt;/p&gt;. Existe uma maneira de manter intactas as tags html existentes?
Nickkk 13/01
48

As bibliotecas mencionadas em outras respostas seriam ótimas soluções, mas se você já está pesquisando html do mundo real em seu projeto, o Jsoupprojeto tem muito mais a oferecer do que apenas gerenciar coisas de "e comercial e ponto-e-vírgula FFFF" .

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

E você também obtém a API conveniente para extrair e manipular dados, usando o melhor dos métodos DOM, CSS e do tipo jquery. É de código aberto e licença MIT.

Dale
fonte
3
upvote +, mas devo apontar que versões mais recentes do Jsoup usam em .text()vez de.getText()
SourceVisor
4
Talvez mais direto seja o uso org.jsoup.parser.Parser.unescapeEntities(String string, boolean inAttribute). Documentos da API: jsoup.org/apidocs/org/jsoup/parser/…
danneu
3
Isso foi perfeito, já que eu já estou usando o Jsoup no meu projeto. Além disso, @danneu estava certa - Parser.unescapeEntities funciona exatamente como anunciado.
precisa saber é o seguinte
42

Tentei o Apache Commons StringEscapeUtils.unescapeHtml3 () no meu projeto, mas não estava satisfeito com seu desempenho. Acontece que ele faz muitas operações desnecessárias. Por um lado, ele aloca um StringWriter para todas as chamadas, mesmo que não haja nada a ser retirado da string. Eu reescrevi esse código de maneira diferente, agora ele funciona muito mais rápido. Quem encontrar isso no google, poderá usá-lo.

O código a seguir remove todos os símbolos HTML 3 e escapes numéricos (equivalente ao Apache unescapeHtml3). Você pode adicionar mais entradas ao mapa se precisar do 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
fonte
Recentemente, tive que otimizar um projeto lento do Struts. Acontece que, sob a capa, o Struts chama o Apache por string html escapando por padrão ( <s:property value="..."/>). Ao desativar o escape ( <s:property value="..." escaping="false"/>), algumas páginas são executadas 5% a 20% mais rapidamente.
Stephan
Mais tarde, descobri que esse código pode entrar em loop quando recebe uma string vazia como argumento. A edição atual tem esse problema corrigido.
Nick Frolov
Isso escapa ou não é espaço? & amp; não é decodificado. Somente & é adicionado ao mapa, portanto, funciona apenas de uma maneira?
mmm
3
Um StringWriter usa um StringBuffer internamente que usa bloqueio. Usar um StringBuilder diretamente deve ser mais rápido.
Axel Dörfler 22/02
4
@NickFrolov, seus comentários parecem um pouco confusos. aumlé por exemplo äe não д.
aioobe
12

A seguinte biblioteca também pode ser usada para escape de HTML em Java: unbescape .

O HTML pode ser sem escape da seguinte maneira:

final String unescapedText = HtmlEscape.unescapeHtml(escapedText); 
Stephan
fonte
2
Isso não fez nada:%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 27/08/2015
40
@ ThreaT Seu texto não é codificado em html, é url.
Mikhail Batcer
9

Isso fez o trabalho para mim,

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

ou

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

Eu acho que é sempre melhor usar o lang3por razões óbvias. Espero que isto ajude :)

tk_
fonte
4

Uma solução muito simples, mas ineficiente, sem qualquer biblioteca externa, é:

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

Isso deve ser usado apenas se você tiver apenas uma pequena contagem de cadeias para decodificar.

Horcrux7
fonte
1
Muito próximo, mas não exato - ele converteu "qwAS12ƷƸDžǚǪǼȌ" em "qwAS12ƷƸDžǚǪǼȌ \ n".
Greg
3

A maneira mais confiável é com

String cleanedString = StringEscapeUtils.unescapeHtml4(originalString);

de org.apache.commons.lang3.StringEscapeUtils.

E para escapar dos espaços em branco

cleanedString = cleanedString.trim();

Isso garantirá que os espaços em branco devidos para copiar e colar nos formulários da web não sejam persistidos no banco de dados.

mike oganyan
fonte
1

Spring Framework HtmlUtils

Se você já estiver usando o framework Spring, use o seguinte método:

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

...

String result = htmlUnescape(source);
herman
fonte
0

Considere usar a classe Java HtmlManipulator . Pode ser necessário adicionar alguns itens (nem todas as entidades estão na lista).

O Apache Commons StringEscapeUtils, como sugerido por Kevin Hakanson, não funcionou 100% para mim; várias entidades como & # 145 (aspas simples à esquerda) foram traduzidas para '222' de alguma forma. Eu também tentei o org.jsoup e tive o mesmo problema.

Joost
fonte
0

No meu caso, eu uso o método de substituição testando todas as entidades em todas as variáveis, meu código fica assim:

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

No meu caso, isso funcionou muito bem.

Luiz dev
fonte
2
Esta não é toda entidade especial. Até os dois mencionados na pergunta estão ausentes.
Sandy Gifford
isso não será bem dimensionado
denov 03/01/19
-7

Caso queira imitar qual função php htmlspecialchars_decode usa a função php get_html_translation_table () para despejar a tabela e usar o 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
fonte
7
Não lance muito; use genéricos nesse HashMap! Além disso, use um foreach, não um tempo para iterar, o código ficará muito mais legível!
WhyNotHugo
3
@BalaDutt se você melhorar a sua resposta, os caras vão dar-lhe pontos :)
sparkyspider
3
Melhore também sua função e nomes de variáveis, @Bala.
Thomas W