【问题标题】:How to unescape HTML character entities in Java?如何在 Java 中取消转义 HTML 字符实体?
【发布时间】:2010-11-02 22:04:56
【问题描述】:

基本上我想解码给定的 Html 文档,并替换所有特殊字符,例如 " " -> " "">" -> ">"

在 .NET 中我们可以使用HttpUtility.HtmlDecode

Java 中的等价函数是什么?

【问题讨论】:

  •  称为字符实体。修改了标题。

标签: java html string eclipse decode


【解决方案1】:

我为此使用了 Apache Commons StringEscapeUtils.unescapeHtml4()

取消转义包含实体的字符串 转义到包含 实际的 Unicode 字符 对应于逃逸。支持 HTML 4.0 实体。

【讨论】:

  • 遗憾的是我今天才意识到它不能很好地解码 HTML 特殊字符:(
  • 一个肮脏的技巧是最初将值存储在隐藏字段中以对其进行转义,然后目标字段应该从隐藏字段中获取值。
  • Class StringEscapeUtils 已弃用并移至Apache commons-text
  • 我想将字符串<p>üè</p> 转换为<p>üé</p>,用StringEscapeUtils.unescapeHtml4() 我得到<p>üè</p>。有没有办法保持现有的 html 标签完好无损?
  • 如果我有类似“ 的东西,在 Windows-1252 中转义为引号,但在 Unicode 中有一些控制字符,是否可以更改转义编码?
【解决方案2】:

其他答案中提到的库将是很好的解决方案,但如果您已经在项目中挖掘真实世界的 html,那么 Jsoup 项目提供的不仅仅是管理 "&磅 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);

您还可以获得用于提取和操作数据的便捷 API,使用最好的 DOM、CSS 和类似 jquery 的方法。它是开源和 MIT 许可证。

【讨论】:

  • upvote+,但我应该指出,较新版本的 Jsoup 使用 .text() 而不是 .getText()
  • 或许更直接的是使用org.jsoup.parser.Parser.unescapeEntities(String string, boolean inAttribute)。 API 文档:jsoup.org/apidocs/org/jsoup/parser/…
  • 这很完美,因为我已经在我的项目中使用了 Jsoup。此外,@danneu 是对的 - Parser.unescapeEntities 的工作原理与宣传的完全一样。
【解决方案3】:

我在我的项目中尝试了 Apache Commons StringEscapeUtils.unescapeHtml3(),但对其性能并不满意。事实证明,它做了很多不必要的操作。一方面,它为每个调用分配一个 StringWriter,即使字符串中没有任何东西可以取消转义。我已经以不同的方式重写了该代码,现在它的运行速度要快得多。欢迎在 google 中找到这个的人使用它。

以下代码取消转义所有 HTML 3 符号和数字转义(相当于 Apache unescapeHtml3)。如果您需要 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]);
    }

}

【讨论】:

  • 最近,我不得不优化一个慢速的 Struts 项目。事实证明,在默认情况下,Struts 会调用 Apache 进行 html 字符串转义 (&lt;s:property value="..."/&gt;)。关闭转义 (&lt;s:property value="..." escaping="false"/&gt;) 可使某些页面的运行速度提高 5% 到 20%。
  • 后来我发现当给定空字符串作为参数时,这段代码可以进入循环。当前版本已修复该问题。
  • 这是转义还是取消空格? &未解码。只有 & 被添加到地图中,所以它只能以一种方式工作?
  • 一个 StringWriter 在内部使用一个使用锁定的 StringBuffer。直接使用 StringBuilder 应该更快。
  • @NickFrolov,您的 cmets 似乎有点搞砸了。 auml 是例如 ä 而不是 д
【解决方案4】:

以下库也可用于 Java 中的 HTML 转义:unbescape

HTML 可以这样转义:

final String unescapedText = HtmlEscape.unescapeHtml(escapedText); 

【讨论】:

  • 它没有做任何事情:%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 您的文本不是 html 编码的,而是 url 编码的。
【解决方案5】:

这为我完成了工作,

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

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

出于显而易见的原因,我想使用lang3 总是更好。 希望这会有所帮助:)

【讨论】:

    【解决方案6】:

    Spring 框架 HtmlUtils

    如果您已经在使用 Spring 框架,请使用以下方法:

    import static org.springframework.web.util.HtmlUtils.htmlUnescape;
    
    ...
    
    String result = htmlUnescape(source);
    
    

    【讨论】:

      【解决方案7】:

      没有任何外部库的一个非常简单但低效的解决方案是:

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

      仅当您只有少量字符串要解码时才应使用此选项。

      【讨论】:

      • 非常接近,但不准确 - 它将“qwAS12ƷƸDžǚǪǼȌ”转换为“qwAS12ƷƸDžǚǪǼȌ\n”。
      【解决方案8】:

      最靠谱的办法是用

      String cleanedString = StringEscapeUtils.unescapeHtml4(originalString);
      

      来自org.apache.commons.lang3.StringEscapeUtils

      并转义空格

      cleanedString = cleanedString.trim();
      

      这将确保由于在 Web 表单中复制和粘贴而产生的空格不会保留在 DB 中。

      【讨论】:

        【解决方案9】:

        考虑使用HtmlManipulator Java 类。您可能需要添加一些项目(并非所有实体都在列表中)。

        Kevin Hakanson 建议的 Apache Commons StringEscapeUtils 对我来说并不是 100% 有效;像 ‘ (左单引号)这样的几个实体以某种方式被翻译成'222'。我也试过org.jsoup,也遇到了同样的问题。

        【讨论】:

          【解决方案10】:

          在我的例子中,我通过测试每个变量中的每个实体来使用替换方法,我的代码如下所示:

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

          就我而言,这非常有效。

          【讨论】:

          • 这不是每个特殊的实体。甚至问题中提到的两个都不见了。
          • 这不会很好地扩展
          【解决方案11】:

          StringEscapeUtils (Apache Commons Lang)
          为 Java、JavaScript、HTML 和 XML 转义和取消转义字符串。

          import org.apache.commons.lang.StringEscapeUtils;
          ....
          StringEscapeUtils.unescapeHtml(comment);
          

          参考:https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/StringEscapeUtils.html

          【讨论】:

            【解决方案12】:

            如果您想模仿 php 函数 htmlspecialchars_decode 确实使用 php 函数 get_html_translation_table() 转储表,然后使用 java 代码,例如,

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

            【讨论】:

            • 别投那么多;在那个 HashMap 上使用泛型!此外,使用 foreach,而不是一段时间来迭代它,代码看起来会更具可读性!
            • @BalaDutt 如果你改进了你的答案,他们会给你加分:)
            • 改进你的函数和变量名,@Bala。
            猜你喜欢
            • 1970-01-01
            • 2018-08-16
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-09-12
            相关资源
            最近更新 更多