从java中的字符串中删除无效的XML字符

Posted

技术标签:

【中文标题】从java中的字符串中删除无效的XML字符【英文标题】:removing invalid XML characters from a string in java 【发布时间】:2011-05-13 08:44:55 【问题描述】:

嗨 我想从字符串中删除所有无效的 XML 字符。 我想在 string.replace 方法中使用正则表达式。

喜欢

line.replace(regExp,"");

什么是正确的正则表达式?

无效的 XML 字符是不是这个的一切:

[#x1-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]

谢谢。

【问题讨论】:

这取决于您要替换的内容。什么是“无效的 XML 字符”? 你说得对,我已经添加了信息 为什么您认为该范围内的字符对 XML 无效?您可以使用 [^\u0001-\uD7FF\uE000-\uFFFD] 匹配超出范围的 2 字节 unicode 字符(需要检查,我不确定语法)。对 24 位字符一无所知,抱歉。 在这里找到了有效的 XML 字符:w3.org/TR/2006/REC-xml11-20060816/#NT-RestrictedChar 整洁的解决方案***.com/a/9635310/489364 【参考方案1】:
String xmlData = xmlData.codePoints().filter(c -> isValidXMLChar(c)).collect(StringBuilder::new,
                StringBuilder::appendCodePoint, StringBuilder::append).toString();

private boolean isValidXMLChar(int c) 
    if((c == 0x9) ||
       (c == 0xA) ||
       (c == 0xD) ||
       ((c >= 0x20) && (c <= 0xD7FF)) ||
       ((c >= 0xE000) && (c <= 0xFFFD)) ||
       ((c >= 0x10000) && (c <= 0x10FFFF)))
    
        return true;
    
    return false;

【讨论】:

【参考方案2】:

到目前为止,所有这些答案都只替换了字符本身。但有时 XML 文档会包含无效的 XML 实体序列,从而导致错误。例如,如果您的 xml 中有 &amp;#2;,则 java xml 解析器将抛出 Illegal character entity: expansion character (code 0x2 at ...

这是一个简单的java程序,可以替换那些无效的实体序列。

  public final Pattern XML_ENTITY_PATTERN = Pattern.compile("\\&\\#(?:x([0-9a-fA-F]+)|([0-9]+))\\;");

  /**
   * Remove problematic xml entities from the xml string so that you can parse it with java DOM / SAX libraries.
   */
  String getCleanedXml(String xmlString) 
    Matcher m = XML_ENTITY_PATTERN.matcher(xmlString);
    Set<String> replaceSet = new HashSet<>();
    while (m.find()) 
      String group = m.group(1);
      int val;
      if (group != null) 
        val = Integer.parseInt(group, 16);
        if (isInvalidXmlChar(val)) 
          replaceSet.add("&#x" + group + ";");
        
       else if ((group = m.group(2)) != null) 
        val = Integer.parseInt(group);
        if (isInvalidXmlChar(val)) 
          replaceSet.add("&#" + group + ";");
        
      
    
    String cleanedXmlString = xmlString;
    for (String replacer : replaceSet) 
      cleanedXmlString = cleanedXmlString.replaceAll(replacer, "");
    
    return cleanedXmlString;
  

  private boolean isInvalidXmlChar(int val) 
    if (val == 0x9 || val == 0xA || val == 0xD ||
            val >= 0x20 && val <= 0xD7FF ||
            val >= 0x10000 && val <= 0x10FFFF) 
      return false;
    
    return true;
  

【讨论】:

这对我来说确实是正确的答案。我正在将 JSONObject 转换为 XML,它将控制字符从“\u0001”转义为“”。这段代码完美地删除了它。【参考方案3】:

如果您想以类似 XML 的形式存储带有禁止字符的文本元素,您可以使用 XPL 代替。开发工具包提供并发 XPL 到 XML 和 XML 处理 - 这意味着从 XPL 到 XML 的转换没有时间成本。或者,如果您不需要 XML(命名空间)的全部功能,您可以使用 XPL。

Web Page: HLL XPL

【讨论】:

【参考方案4】:

来自Best way to encode text data for XML in Java?

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】:

Jun 的解决方案,简化。使用StringBuffer#appendCodePoint(int),我不需要char currentString#charAt(int)。我可以通过检查 codePoint 是否大于 0xFFFF 来判断代理对。

(没有必要执行 i++,因为低代理不会通过过滤器。但是如果将代码重新用于不同的代码点,它会失败。我更喜欢编程而不是黑客攻击。)

StringBuilder sb = new StringBuilder();
for (int i = 0; i < text.length(); i++) 
    int codePoint = text.codePointAt(i);
    if (codePoint > 0xFFFF) 
        i++;
    
    if ((codePoint == 0x9) || (codePoint == 0xA) || (codePoint == 0xD)
            || ((codePoint >= 0x20) && (codePoint <= 0xD7FF))
            || ((codePoint >= 0xE000) && (codePoint <= 0xFFFD))
            || ((codePoint >= 0x10000) && (codePoint <= 0x10FFFF))) 
        sb.appendCodePoint(codePoint);
    

【讨论】:

我显然被否决了。我想知道为什么。可能只是有人在骗我,但如果算法有问题,我想知道。 你知道如何构造一个包含超过最大值的无效 Unicode 字符的字符串吗? 0x10FFFF 代码点? 0x10FFFF 应该对应于 Java 字符串“\udbff\udfff”。我试图构造无效字符 0x110000,它应该是 Java 字符串“\udbff\ue000”。但是 Java 将其解析为 2 个代码点。因此最后一次检查(codePoint codePointAt() 返回它。【参考方案6】:

Java's regex supports supplementary characters,因此您可以使用两个 UTF-16 编码字符指定这些高范围。

这是删除XML 1.0中非法字符的模式:

// XML 1.0
// #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
String xml10pattern = "[^"
                    + "\u0009\r\n"
                    + "\u0020-\uD7FF"
                    + "\uE000-\uFFFD"
                    + "\ud800\udc00-\udbff\udfff"
                    + "]";

大多数人会想要 XML 1.0 版本。

这是删除XML 1.1中非法字符的模式:

// XML 1.1
// [#x1-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
String xml11pattern = "[^"
                    + "\u0001-\uD7FF"
                    + "\uE000-\uFFFD"
                    + "\ud800\udc00-\udbff\udfff"
                    + "]+";

您需要使用String.replaceAll(...) 而不是String.replace(...)

String illegal = "Hello, World!\0";
String legal = illegal.replaceAll(pattern, "");

【讨论】:

链接坏了,右边的好像是:oracle.com/technetwork/articles/javase/… 可能我错了,但这个范围不会删除像 \b (\u0008) 等字符。但是这个字符也会破坏 xml 编组。您能否在 Mark McLaren 的博客中提示您对答案的评论?谢谢! @evgenyl U+0008 在 "\u0001-\uD7FF" 范围内,不会被替换 - 它在 XML 中的使用是合法的。如果要删除 restricted or discouraged ranges 中的文本,则必须修改正则表达式。 Renaud 的答案的问题在于它检查 char 值而不是 Unicode 代码点。 Jun的回答显示了UTF-16代码单元到代码点的转换 \ud800\udc00-\udbff\udfff 语法起初对我来说非常具有误导性,只是 Java 正则表达式引擎将该对解释为单个字符,对吗? @ŁukaszL。正确的。 UTF-16 序列D800 DC00 是代码点 U+10000,DBFF DFFF 是 U+10FFFF,Java 的正则表达式引擎尊重代理项对。【参考方案7】:

我们应该考虑代理字符吗?否则 '(current >= 0x10000) && (current

还测试了正则表达式的方式似乎比以下循环慢。

if (null == text || text.isEmpty()) 
    return text;

final int len = text.length();
char current = 0;
int codePoint = 0;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < len; i++) 
    current = text.charAt(i);
    boolean surrogate = false;
    if (Character.isHighSurrogate(current)
            && i + 1 < len && Character.isLowSurrogate(text.charAt(i + 1))) 
        surrogate = true;
        codePoint = text.codePointAt(i++);
     else 
        codePoint = current;
    
    if ((codePoint == 0x9) || (codePoint == 0xA) || (codePoint == 0xD)
            || ((codePoint >= 0x20) && (codePoint <= 0xD7FF))
            || ((codePoint >= 0xE000) && (codePoint <= 0xFFFD))
            || ((codePoint >= 0x10000) && (codePoint <= 0x10FFFF))) 
        sb.append(current);
        if (surrogate) 
            sb.append(text.charAt(i));
        
    

【讨论】:

那么这段代码在做什么 - 删除非法字符?用不同的字符替换它们的函数怎么样? :)【参考方案8】:

来自Mark McLaren's Weblog

  /**
   * This method ensures that the output String has only
   * valid XML unicode characters as specified by the
   * XML 1.0 standard. For reference, please see
   * <a href="http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char">the
   * standard</a>. This method will return an empty
   * String if the input is null or empty.
   *
   * @param in The String whose non-valid characters we want to remove.
   * @return The in String, stripped of non-valid characters.
   */
  public static String stripNonValidXMLCharacters(String in) 
      StringBuffer out = new StringBuffer(); // Used to hold the output.
      char current; // Used to reference the current character.

      if (in == null || ("".equals(in))) return ""; // vacancy test.
      for (int i = 0; i < in.length(); i++) 
          current = in.charAt(i); // NOTE: No IndexOutOfBoundsException caught here; it should not happen.
          if ((current == 0x9) ||
              (current == 0xA) ||
              (current == 0xD) ||
              ((current >= 0x20) && (current <= 0xD7FF)) ||
              ((current >= 0xE000) && (current <= 0xFFFD)) ||
              ((current >= 0x10000) && (current <= 0x10FFFF)))
              out.append(current);
      
      return out.toString();
     

【讨论】:

@McDowell 您能否详细说明未涵盖的内容以及原因?这与君的答案基本相同,您并没有否决。 @ŁukaszL。此代码测试 UTF-16 代码单元。 Jun 的代码转换并测试 32 位代码点。例如,代码点 U+1D50A 在支持的范围 0x10000-0x10FFFF 内。它必须在 UTF-16 中表示为代理对 - 例如文字"\uD835\uDD0A"。上述算法将错误地删除代理对表示的任何内容。请参阅Character 类型的代码点方法。 @McDowell 我正在使用上面的代码,所以请告诉我是否理解正确,我应该从该代码中删除范围 0x10000-0x10FFFF。相反,我应该检查 Character.isHighSurrogate(current)。如果是这样,我应该检查下一个字符是否是 Character.isLowSurrogate() 然后才添加两者。 “\uD801\uDC00”是正确的Unicode字符,而“\uDC00\uD801”不是? @ŁukaszL。那可行。另见here。另外,正确的,\uDC00\uD801 不是有意义的数据,因为这对是向后的 - 损坏的数据。 @McDowell 谢谢。我已经更新了我的代码并进行了 JUnit 测试。但是,由于问题实际上是关于正则表达式的,所以在这里发帖是不合适的,并且已经与君的答案相似。【参考方案9】:

相信下面的文章可以帮到你。

http://commons.apache.org/lang/api-2.1/org/apache/commons/lang/StringEscapeUtils.html http://www.javapractices.com/topic/TopicAction.do?Id=96

很快,尝试使用 Jakarta 项目中的 StringEscapeUtils。

【讨论】:

我看不出这对原始海报有何帮助 - 问题是有一系列字符无法在 XML 中编码。在您尝试对字符数据进行编码之前,必须处理这些问题。

以上是关于从java中的字符串中删除无效的XML字符的主要内容,如果未能解决你的问题,请参考以下文章

C#从xml中删除无效字符[重复]

具有无效字符的java xml

以 XML 格式格式化字符串并删除无效的属性字符

无效的 XML 字符错误 - 如何从 VARCHAR2 数据库列中查找无效字符?

Flex TextArea - 从 Word 复制/粘贴 - xml 解析中的 unicode 字符无效

XML 中的无效字符使 Datastage 作业失败