繁体   English   中英

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

[英]Best way to encode text data for XML in Java?

这个问题非常相似,除了Java。

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

正如其他人提到的,使用 XML 库是最简单的方法。 如果您确实想逃避自己,可以查看Apache Commons Lang库中的StringEscapeUtils

很简单:使用 XML 库。 这样它实际上是正确的,而不需要详细了解 XML 规范的位。

就用吧。

<![CDATA[ your text here ]]>

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

]]>

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

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

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

尝试这个:

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

这对我来说很有效,可以提供文本字符串的转义版本:

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

}

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

以下方法将:

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

我已经尝试针对最常见的情况进行优化,同时仍然确保您可以通过这个管道 /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 中包含的 StAX API(我还没有测试其他) 无法正确编码 CDATA 内容:它不会转义内容中的 ]]> 序列。 第三方库,即使是 Java 核心的一部分,也并不总是最好的选择。

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

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

NumericEntityEscaper.below(0x20).translate(StringEscapeUtils.escapeXml(str))
public String escapeXml(String s) {
    return s.replaceAll("&", "&amp;").replaceAll(">", "&gt;").replaceAll("<", "&lt;").replaceAll("\"", "&quot;").replaceAll("'", "&apos;");
}

虽然理想主义说使用 XML 库,但恕我直言,如果您对 XML 有基本的了解,那么常识和性能就可以说是模板化。 它也可以说更具可读性。 尽管使用库的转义例程可能是一个好主意。

考虑一下:XML本来是由人类编写的。

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

编辑:至于如何在模板中实际转义 XML,使用来自 JSTL 的 CDATA 或escapeXml(string)是两个很好的解决方案, escapeXml(string)可以这样使用:

<%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>

<item>${fn:escapeXml(value)}</item>

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

请记住包含依赖项:

<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-lang3</artifactId>
  <version>3.5</version> <!--check current version! -->
</dependency>

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

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

包含在 Google Guava 11.0 中的新转义符似乎也很有希望: http : //code.google.com/p/guava-libraries/issues/detail? id=799

虽然我原则上同意 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_NULL = "" + ((char)0x00); //null
  private static String UNICODE_LOW =  "" + ((char)0x20); //space
  private static String UNICODE_HIGH = "" + ((char)0x7f);

  //should only be used 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 {
            //Per URL reference below, Unicode null character is always restricted from XML
            //URL: https://en.wikipedia.org/wiki/Valid_characters_in_XML
            if (character.compareTo(UNICODE_NULL) != 0) {
              stringBuilder.append("&#" + ((int)character.charAt(0)) + ";");
            }
            modified = true;
          }
        }
      }
      if (modified) {
        result = stringBuilder.toString();
      }
    }
    
    return result;
  }

上面包含了几种不同的东西:

  1. 避免使用基于字符的逻辑,直到它绝对必须 - 提高 unicode 兼容性
  2. 考虑到概率是第二个“if”条件可能是最常用的途径,因此尝试尽可能高效
  3. 是纯函数; 即是线程安全的
  4. 通过仅在实际更改时返回 StringBuilder 的内容来很好地优化垃圾收集器 - 否则,将返回原始字符串

在某个时候,我会写出这个函数的反转,toUnescaped()。 我只是今天没有时间这样做。 当我这样做时,我会用代码来更新这个答案。 :)

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

首先,就像其他人所说的那样,使用 XML 库。 XML 看起来很简单,但编码+转义的东西是黑暗的巫术(一旦你遇到变音符号和日语以及其他奇怪的东西,比如“全角数字”(&#FF11; 是 1),你就会注意到这一点)。 保持 XML 可读性是 Sisyphus 的任务。

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

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

  • 如果文本确实包含 '<'、'>' 或 '&',请将其包裹在<![CDATA[ ... ]]>
  • 如果文本不包含这三个字符,请不要扭曲它。

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

如果您正在寻找一个图书馆来完成工作,请尝试:

  1. 此处记录的番石榴 26.0

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

    注意:还有一个xmlAttributeEscaper()

  2. 此处记录的Apache Commons Text 1.4

    StringEscapeUtils.escapeXml11(text)

    注意:还有一个escapeXml10()方法

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

这个类是这样的:org.apache.commons.lang3.StringEscapeUtils;

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

这是一个简单的解决方案,它也非常适合对重音字符进行编码!

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!

您可以使用Enterprise Security API (ESAPI) 库,它提供了encodeForXMLencodeForXMLAttribute等方法。 看一下Encoder接口的文档; 它还包含如何创建DefaultEncoder实例的示例。

只需更换

 & with &amp;

对于其他角色:

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

尝试使用 Apache XML 序列化程序对 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());

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

这是我在到处寻找解决方案后发现的:

获取 Jsoup 库:

<!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.12.1</version>
</dependency>

然后:

import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.nodes.Entities
import org.jsoup.parser.Parser

String xml = '''<?xml version = "1.0"?>
<SOAP-ENV:Envelope
   xmlns:SOAP-ENV = "http://www.w3.org/2001/12/soap-envelope"
   SOAP-ENV:encodingStyle = "http://www.w3.org/2001/12/soap-encoding">

   <SOAP-ENV:Body xmlns:m = "http://www.example.org/quotations">
      <m:GetQuotation>
         <m:QuotationsName> MiscroSoft@G>>gle.com </m:QuotationsName>
      </m:GetQuotation>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>'''



Document doc = Jsoup.parse(new ByteArrayInputStream(xml.getBytes("UTF-8")), "UTF-8", "", Parser.xmlParser())
doc.outputSettings().charset("UTF-8")
doc.outputSettings().escapeMode(Entities.EscapeMode.base)

println doc.toString()

希望这有助于某人

我在这里创建了我的包装器,希望它会有所帮助,单击此处您可以根据您的要求进行修改

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM