代码之家  ›  专栏  ›  技术社区  ›  Epaga Alex Reynolds

在Java中为XML编码文本数据的最佳方法?

  •  82
  • Epaga Alex Reynolds  · 技术社区  · 16 年前

    非常相似 this question ,除了Java。

    在Java中为XML输出编码字符串的推荐方法是什么?字符串可能包含“&”、“<”等字符。

    20 回复  |  直到 6 年前
        1
  •  39
  •   Jon Skeet    16 年前

    非常简单:使用XML库。那样的话 正确的 而不需要详细了解XML规范的各个部分。

        2
  •  113
  •   Stevoisiak    7 年前

    正如其他人提到的,使用XML库是最简单的方法。如果你真的想逃离自己,你可以看看 StringEscapeUtils Apache Commons Lang 图书馆。

        3
  •  18
  •   ng.    16 年前

    就用吧。

    <![CDATA[ your text here ]]>
    

    这将允许除结尾以外的任何字符

    ]]>
    

    因此,您可以包含非法字符,如&和>。例如。

    <element><![CDATA[ characters such as & and > are allowed ]]></element>
    

    但是,属性将需要转义,因为CDATA块不能用于它们。

        4
  •  14
  •   Pointer Null sdabet    12 年前

    试试这个:

    String xmlEscapeText(String t) {
       StringBuilder sb = new StringBuilder();
       for(int i = 0; i < t.length(); i++){
          char c = t.charAt(i);
          switch(c){
          case '<': sb.append("&lt;"); break;
          case '>': sb.append("&gt;"); break;
          case '\"': sb.append("&quot;"); break;
          case '&': sb.append("&amp;"); break;
          case '\'': sb.append("&apos;"); break;
          default:
             if(c>0x7e) {
                sb.append("&#"+((int)c)+";");
             }else
                sb.append(c);
          }
       }
       return sb.toString();
    }
    
        5
  •  13
  •   Redwood    14 年前

    这对于我提供文本字符串的转义版本很有效:

    public class XMLHelper {
    
    /**
     * Returns the string where all non-ascii and <, &, > are encoded as numeric entities. I.e. "&lt;A &amp; B &gt;"
     * .... (insert result here). The result is safe to include anywhere in a text field in an XML-string. If there was
     * no characters to protect, the original string is returned.
     * 
     * @param originalUnprotectedString
     *            original string which may contain characters either reserved in XML or with different representation
     *            in different encodings (like 8859-1 and UFT-8)
     * @return
     */
    public static String protectSpecialCharacters(String originalUnprotectedString) {
        if (originalUnprotectedString == null) {
            return null;
        }
        boolean anyCharactersProtected = false;
    
        StringBuffer stringBuffer = new StringBuffer();
        for (int i = 0; i < originalUnprotectedString.length(); i++) {
            char ch = originalUnprotectedString.charAt(i);
    
            boolean controlCharacter = ch < 32;
            boolean unicodeButNotAscii = ch > 126;
            boolean characterWithSpecialMeaningInXML = ch == '<' || ch == '&' || ch == '>';
    
            if (characterWithSpecialMeaningInXML || unicodeButNotAscii || controlCharacter) {
                stringBuffer.append("&#" + (int) ch + ";");
                anyCharactersProtected = true;
            } else {
                stringBuffer.append(ch);
            }
        }
        if (anyCharactersProtected == false) {
            return originalUnprotectedString;
        }
    
        return stringBuffer.toString();
    }
    
    }
    
        6
  •  8
  •   Steve Mitchell    12 年前

    StringEscapeUtils.escapeXml() 不转义控制字符(<0x20)。XML 1.1允许控制字符;XML 1.0不允许。例如, XStream.toXML() 将愉快地将Java对象的控制字符序列化为XML,XML 1解析器将拒绝它。

    要使用apache commons lang转义控制字符,请使用

    NumericEntityEscaper.below(0x20).translate(StringEscapeUtils.escapeXml(str))
    
        7
  •  6
  •   Amr Mostafa    15 年前

    理想主义认为使用一个XML库,而imho如果你对XML有一个基本的概念,那么常识和性能就一直在说模板。可以说,它的可读性也更高。尽管使用库的转义例程可能是个好主意。

    考虑这个:XML 是人类写的。

    将XML作为“对象”时,使用库生成XML更好地模拟问题。例如,如果可插入模块参与构建这个XML的过程。

    编辑:关于如何在模板中实际转义XML,使用CDATA或 escapeXml(string) JSTL提供了两个很好的解决方案, escapeXML(字符串) 可以这样使用:

    <%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
    
    <item>${fn:escapeXml(value)}</item>
    
        8
  •  6
  •   Jasper Krijgsman    13 年前

    StringEscapeUtils.escapeXML()的行为已从Commons Lang 2.5更改为3.0。 它现在不再转义大于0x7f的Unicode字符。

    这是一件好事,旧方法有点急于转义那些可以插入到UTF8文档中的实体。

    GoogleGuava11.0中包含的新逃犯似乎也很有希望: http://code.google.com/p/guava-libraries/issues/detail?id=799

        9
  •  6
  •   iCrazybest    10 年前
    public String escapeXml(String s) {
        return s.replaceAll("&", "&amp;").replaceAll(">", "&gt;").replaceAll("<", "&lt;").replaceAll("\"", "&quot;").replaceAll("'", "&apos;");
    }
    
        10
  •  6
  •   Mike B    6 年前

    这个问题已经8岁了,仍然不是一个完全正确的答案!不,您不必导入整个第三方API来完成这个简单的任务。糟糕的建议。

    以下方法将:

    • 正确处理基本多语言平面外的字符
    • XML中需要转义字符
    • 转义任何非ASCII字符,这是可选的,但很常见
    • 代替 非法的 XML 1.0中带有Unicode替换字符的字符。这里没有最佳选择-删除它们同样有效。

    我尝试过针对最常见的情况进行优化,同时仍然确保您可以通过管道/dev/random来完成这一过程,并获得XML中的有效字符串。

    public static String encodeXML(CharSequence s) {
        StringBuilder sb = new StringBuilder();
        int len = s.length();
        for (int i=0;i<len;i++) {
            int c = s.charAt(i);
            if (c >= 0xd800 && c <= 0xdbff && i + 1 < len) {
                c = ((c-0xd7c0)<<10) | (s.charAt(++i)&0x3ff);    // UTF16 decode
            }
            if (c < 0x80) {      // ASCII range: test most common case first
                if (c < 0x20 && (c != '\t' && c != '\r' && c != '\n')) {
                    // Illegal XML character, even encoded. Skip or substitute
                    sb.append("&#xfffd;");   // Unicode replacement character
                } else {
                    switch(c) {
                      case '&':  sb.append("&amp;"); break;
                      case '>':  sb.append("&gt;"); break;
                      case '<':  sb.append("&lt;"); break;
                      // Uncomment next two if encoding for an XML attribute
    //                  case '\''  sb.append("&apos;"); break;
    //                  case '\"'  sb.append("&quot;"); break;
                      // Uncomment next three if you prefer, but not required
    //                  case '\n'  sb.append("&#10;"); break;
    //                  case '\r'  sb.append("&#13;"); break;
    //                  case '\t'  sb.append("&#9;"); break;
    
                      default:   sb.append((char)c);
                    }
                }
            } else if ((c >= 0xd800 && c <= 0xdfff) || c == 0xfffe || c == 0xffff) {
                // Illegal XML character, even encoded. Skip or substitute
                sb.append("&#xfffd;");   // Unicode replacement character
            } else {
                sb.append("&#x");
                sb.append(Integer.toHexString(c));
                sb.append(';');
            }
        }
        return sb.toString();
    }
    

    编辑:对于那些仍然坚持愚蠢的编写自己的代码,当有完全好的Java API来处理XML时,你可能想知道,包含在Oracle Java 8中的STAXAPI(我还没有测试过其他人)没有正确地编码CDATA内容:它不逃逸]内容中的序列。第三方库,甚至是Java核心的一部分,并不总是最好的选择。

        11
  •  5
  •   Aaron Digulla    16 年前

    注意:你的问题是关于 逃逸 不是 编码 . 转义使用<等来允许解析器区分“这是一个XML命令”和“这是一些文本”。编码是您在XML头(utf-8、iso-8859-1等)中指定的内容。

    首先,正如其他人所说,使用XML库。XML看起来很简单,但是编码+转义的东西是暗巫毒(一旦遇到umlauts、日语和其他奇怪的东西,比如” full width digits “(&ff11;为1))。保持XML的可读性是西西弗斯的任务。

    我建议不要尝试巧妙地使用XML进行文本编码和转义。但不要让它阻止你尝试;只要记住它什么时候咬了你(它会的)。

    也就是说,如果您只使用UTF-8,为了使内容更具可读性,可以考虑使用以下策略:

    • 如果文本不包含“<”、“>”或“&”,请将其换行 <![CDATA[ ... ]]>
    • 如果文本不包含这三个字符,请不要扭曲它。

    我在一个SQL编辑器中使用它,它允许开发人员将SQL从第三方SQL工具剪切并粘贴到XML中,而不用担心转义。这是因为在我们的例子中,SQL不能包含umlauts,所以我是安全的。

        12
  •  4
  •   chaotic3quilibrium    11 年前

    虽然原则上我同意jon skeet的观点,但有时我没有选择使用外部XML库。在Java中包含的标准XML库中不存在简单的值(属性或标记,而不是完整文档)。

    因此,基于我在这里和其他地方看到的不同答案,这里是我最终创建的解决方案(没有简单的复制/粘贴工作):

      public final static String ESCAPE_CHARS = "<>&\"\'";
      public final static List<String> ESCAPE_STRINGS = Collections.unmodifiableList(Arrays.asList(new String[] {
          "&lt;"
        , "&gt;"
        , "&amp;"
        , "&quot;"
        , "&apos;"
      }));
    
      private static String UNICODE_LOW =  "" + ((char)0x20); //space
      private static String UNICODE_HIGH = "" + ((char)0x7f);
    
      //should only use for the content of an attribute or tag      
      public static String toEscaped(String content) {
        String result = content;
    
        if ((content != null) && (content.length() > 0)) {
          boolean modified = false;
          StringBuilder stringBuilder = new StringBuilder(content.length());
          for (int i = 0, count = content.length(); i < count; ++i) {
            String character = content.substring(i, i + 1);
            int pos = ESCAPE_CHARS.indexOf(character);
            if (pos > -1) {
              stringBuilder.append(ESCAPE_STRINGS.get(pos));
              modified = true;
            }
            else {
              if (    (character.compareTo(UNICODE_LOW) > -1)
                   && (character.compareTo(UNICODE_HIGH) < 1)
                 ) {
                stringBuilder.append(character);
              }
              else {
                stringBuilder.append("&#" + ((int)character.charAt(0)) + ";");
                modified = true;
              }
            }
          }
          if (modified) {
            result = stringBuilder.toString();
          }
        }
    
        return result;
      }
    

    上面包含了几个不同的内容:

    1. 避免使用基于字符的逻辑,直到它必须提高Unicode兼容性
    2. 考虑到第二个“如果”条件可能是最常用的途径,尽量提高效率。
    3. 是纯函数;即线程安全
    4. 通过只返回StringBuilder的内容(如果某些内容实际发生了更改),可以很好地使用垃圾收集器进行优化-否则,将返回原始字符串

    在某个时刻,我将编写这个函数的反转,tounescaped()。我只是今天没时间这么做。当我这样做时,我会来用代码更新这个答案。:)

        13
  •  4
  •   Dariusz    8 年前

    对于那些寻找最快编写解决方案的人:使用 apache commons-lang :

    记住包括依赖关系:

    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.5</version> <!--check current version! -->
    </dependency>
    
        14
  •  3
  •   Greg Burdett    13 年前

    要转义XML字符,最简单的方法是使用ApacheCommonsLang项目,JAR可从以下位置下载: http://commons.apache.org/lang/

    类是:org.apache.commons.lang3.StringEscapeUtils;

    它有一个名为“escapeXML”的方法,它将返回一个适当的转义字符串。

        15
  •  1
  •   Mike    12 年前

    这里有一个简单的解决方案,它也很适合编码重音字符!

    String in = "Hi Lârry & Môe!";
    
    StringBuilder out = new StringBuilder();
    for(int i = 0; i < in.length(); i++) {
        char c = in.charAt(i);
        if(c < 31 || c > 126 || "<>\"'\\&".indexOf(c) >= 0) {
            out.append("&#" + (int) c + ";");
        } else {
            out.append(c);
        }
    }
    
    System.out.printf("%s%n", out);
    

    输出

    Hi L&#226;rry &#38; M&#244;e!
    
        16
  •  0
  •   Fernando Miguélez    16 年前

    使用 JAXP 忘记文本处理,它将自动为您完成。

        17
  •  0
  •   Carbine    11 年前

    尝试使用ApacheXML序列化程序对XML进行编码

    //Serialize DOM
    OutputFormat format    = new OutputFormat (doc); 
    // as a String
    StringWriter stringOut = new StringWriter ();    
    XMLSerializer serial   = new XMLSerializer (stringOut, 
                                              format);
    serial.serialize(doc);
    // Display the XML
    System.out.println(stringOut.toString());
    
        18
  •  0
  •   Software Craftsman snymty    7 年前

    你可以用 Enterprise Security API (ESAPI) library ,它提供了如下方法 encodeForXML encodeForXMLAttribute . 查看 Encoder 接口;它还包含如何创建 DefaultEncoder .

        19
  •  0
  •   jschnasse    6 年前

    如果您正在寻找一个库来完成任务,请尝试:

    1. Guava 26.0 文件化的 here

      return XmlEscapers.xmlContentEscaper().escape(text);

      注:还有一个 xmlAttributeEscaper()

    2. Apache Commons Text 1.4 文件化的 here

      StringEscapeUtils.escapeXml11(text)

      注:还有一个 escapeXml10() 方法

        20
  •  -1
  •   raman rayat    6 年前

    只是替换

     & with &amp;
    

    对于其他角色:

    > with &gt;
    < with &lt;
    \" with &quot;
    ' with &apos;