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


93

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

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

Answers:


40

很简单:使用XML库。这样,实际上将是正确的,而不需要XML规范的详细知识。


25
您能推荐这样的图书馆吗?(我很惊讶这不是Java版本5的标准部分……如此常见的任务)。
蒂姆·库珀

4
XML 标准Java框架的一部分-在org.w3c.sax和org.w3c.dom中查找。但是,也有一些易于使用的框架,例如JDom。请注意,可能没有“为XML输出编码字符串”方法-我更建议整个XML任务应使用库完成,而不是一次使用字符串处理来完成。
乔恩·斯基特

1
当输出XHTML时,这不是那么有用的建议-FlyingSaucer需要XML,但我绝不可能通过XML lib来模板:)。幸运的是,StringTemplate允许我快速转义所有String对象。
斯蒂芬

4
@mice:这个问题被标记为Java,并且Java有很多 XML库。确实,Java中已经嵌入了XML API,因此无需添加任何其他内容……但是即使您这样做了,如今,在移动设备之外,几百个K也不是问题。即使不是Java,我也会非常谨慎地在没有任何XML API的平台上进行开发……
Jon Skeet 2012年

2
@mice:DOM API完全能够生成XML。或者有相当小的第三方库。(例如,JDom的jar文件为114K。)仍然建议使用XML API创建XML。
乔恩·斯基特

123

正如其他人提到的那样,使用XML库是最简单的方法。如果您想逃避现实,可以StringEscapeUtilsApache Commons Lang库中进行研究。


如果您不关心绝对正确性,例如,如果您要组装一个原型,这可能就是解决方法。
Chase Seibert,

2
StringEscapeUtils.escapeXml(str)从中使用commons-lang。我在App Engine应用程序中使用了它-就像一个魅力。这是此功能的Java文档
Oleg K

StringEscapeUtils的escapeXml方法似乎有点昂贵。是否有一种更有效的方法可对StringBuffer而不是String进行操作?
CKing 2012年

此方法对XML内容和属性都有效吗?在我看来,它似乎不适用于属性。它似乎并没有逃跑\t\n\r
Lii

@Lii和\t\n还是\r需要逃脱?
Betlista

20

随便使用。

<![CDATA[ your text here ]]>

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

]]>

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

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

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


11
在大多数情况下,这不是您应该做的。太多人滥用CDATA标签。CDATA的目的是告诉处理器不要将其作为XML进行处理,而只是通过它。如果要创建XML文件,则应该创建XML,而不仅仅是通过某些包装元素传递字节。
汉兹(Mads Hansen)

2
@Mads,使用CDATA会生成有效的XML文件,因此它与“正确方法”一样好。如果您不喜欢它,则可以对其进行解析,然后对其进行身份转换并打印。
托尔比约恩Ravn的安徒生

24
如果将文本包装在CDATA元素中,则必须转义CDATA结束标记:“]]>” ...,但不能转义。因此,您必须将代码分成几部分,将一半的数据放在一个CDATA元素中,另一半放在一秒钟中:<![CDATA [此数据包含一个CDATA结束标记:“]]]] >> <! [CDATA [>“这就是为什么要拆分它的原因。]]> ...最后,转义'<','>'和'&'可能要简单得多。当然,许多应用程序会忽略数据中CDATA关闭标记的潜在问题。我猜无知是幸福。:)
Stijn de Witt 2010年

3
@StijndeWitt是绝对正确的。CDATA不是转义特殊字符的灵丹妙药。
dnault 2014年

这是一个坏主意。CDATA不允许XML编码之外的任何字符。
Florian F

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

}

1
stringBuffer.append(“&#” +(int)ch +“;”); 这不适用于多字节字符。我现在遇到一个表情符号字符,即UTF8序列F0 9F 98 8D。
凯拉尔

14

试试这个:

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

8
我至少可以看到两个错误。一个是微妙的,另一个不是。我不会有这样的错误-因为我不会一开始就重新发明轮子。
乔恩·斯基特

1
并且遍历Unicode字符串要复杂一些。看到这里:stackoverflow.com/q/1527856/402322
ceving 2012年

1
不确定它是否细微,但最好考虑情况在哪里t==null
Myobis

1
@ user1003916:XML转义旨在将任何&出现转换为&amp; 这就是它的工作方式。如果您原谅已经逃脱的字符串,那是您的错。
指针为空

3
我对最终版本感到满意。Java SE紧凑,快速且高效。在我的书中,总是做需要做的事情而不是再下载100 MB的bloatware总是更好。
罗杰F.盖伊

11

这个问题已有八年历史了,仍然不是一个完全正确的答案!不,您不必导入整个第三方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核心的一部分,也不总是最好的选择。


+1用于独立代码。只是将您的代码与guava实现进行比较,我想知道'\ t','\ n','\ r'怎么样?另请参阅番石榴文档中的
jschnasse

2
尽管它们确实使格式化变得有些难看,但它们不必有效地转义\ n,\ r和\ t。我已经修改了代码,以显示如果想要的话如何对它们进行加密。
Mike B

1
没有办法在CDATA“逃]]>”。
kmkaplan

1
然后,它应该通过抛出IllegalArgumentException拒绝内容。它在任何情况下都不应声称成功,但仍会输出无效的XML。
Mike B

您可以在此处使用我的方法stackoverflow.com/a/59475093/3882565,而不是用Unicode替换字符替换XML 1.0中的非法字符。
stonar96 '19

8

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

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

NumericEntityEscaper.below(0x20).translate(StringEscapeUtils.escapeXml(str))

7
public String escapeXml(String s) {
    return s.replaceAll("&", "&amp;").replaceAll(">", "&gt;").replaceAll("<", "&lt;").replaceAll("\"", "&quot;").replaceAll("'", "&apos;");
}

5
链接replaceAll调用效率很低,尤其是对于大字符串。每次调用都会创建一个新的String对象,该对象将一直徘徊直到垃圾被收集。同样,每个调用都需要再次遍历字符串。可以将其合并为一个手动循环,并在每次迭代中与每个目标字符进行比较。
daiscog 2015年

即使效率低下,这也应该是公认的答案。它可以单行解决问题。
斯廷普森猫

而且它有很多错误。看到上面的评论
DavidBalažic18年

要修复这些错误,您还可以在此处stackoverflow.com/a/59475093/3882565使用我的方法。请注意,这不是替代品,但可以额外使用。
stonar96 '19

6

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

考虑一下:XML 人类编写的。

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

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

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

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

6

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


1
这是Guava的XML转义符:code.google.com/p/guava-libraries/source/browse/guava/src/com/…。总的来说,我发现Guava的架构比Apache Commons更好。
jhclark 2012年



5

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

首先,像其他人一样,使用XML库。XML看起来很简单,但是编码+转义的内容是深色的伏都教(您会在遇到变音符号和日语以及其他诸如“全角数字 ”(&#FF11;为1))。使XML保持人类可读性是Sisyphus的任务。

我建议不要尝试对XML的文本编码和转义保持聪明。但是不要让那阻止你尝试;只要记住它什么时候咬你(就会)。

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

  • 如果文本中确实包含“ <”,“>”或“&”,请用 <![CDATA[ ... ]]>
  • 如果文本不包含这三个字符,请不要扭曲它。

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


5

尽管我在原则上同意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. 除非绝对必要,否则避免使用基于char的逻辑-改善unicode兼容性
  2. 假设概率是第二个“如果”条件可能是最常用的途径,则尝试尽可能地提高效率
  3. 是一个纯函数;即是线程安全的
  4. 如果实际更改了某些内容,则仅通过返回StringBuilder的内容来对垃圾回收器进行优化,否则将返回原始字符串

在某个时候,我将编写该函数的反函数toUnescaped()。我只是今天没有时间这样做。完成后,我将使用代码更新此答案。:)


对我来说看起来不错。我不希望仅使用一种方法将另一个jar添加到我的项目中。如果您可以授予权限,我可以复制代码粘贴到我的代码中吗?
2014年

1
@SatishMotwani当然,您可以接受上面的代码,并根据需要使用它。据我了解,在StackOverflow上发布的任何代码均假定为无版权的(未作为整体著作涵盖在内)。另一方面,对于某人来说,发表任何形式的版权主张并为自己期望某种结果将是极其困难的。
chaotic3quilibrium 2014年

1
感谢您的允许:-)我将使用它。
RuntimeException 2014年

您忘记了处理NUL字符。也许还有其他事情。
DavidBalažic18年



1

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

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!

如果“ if”的第一行中的“ 31”不应该是“ 32”;即小于空格字符?如果必须保留“ 31”,那么是否应该更正为“ if(c <= 31 || ...”(小于号后面的等于号)?
chaotic3quilibrium


1

只需更换

 & with &amp;

对于其他字符:

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


0

尝试使用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());

0

这是我到处搜索解决方案后发现的:

获取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()

希望这可以帮助某人

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.