【问题标题】:removing invalid XML characters from a string in java从java中的字符串中删除无效的XML字符
【发布时间】: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
  • 整洁的解决方案stackoverflow.com/a/9635310/489364

标签: java xml regex invalid-characters


【解决方案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 中编码。在您尝试对字符数据进行编码之前,必须处理这些问题。
      猜你喜欢
      • 2018-06-06
      • 1970-01-01
      • 2016-02-16
      • 2017-02-02
      • 1970-01-01
      • 1970-01-01
      • 2013-12-22
      • 1970-01-01
      • 2011-08-07
      相关资源
      最近更新 更多