从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 中有 &#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("<"); break;
case '>': sb.append(">"); break;
case '\"': sb.append("""); break;
case '&': sb.append("&"); break;
case '\'': sb.append("'"); break;
default:
if(c>0x7e)
sb.append("&#"+((int)c)+";");
else
sb.append(c);
return sb.toString();
【讨论】:
没有。一个状态如何逐个枚举字符作为我不明白的最佳方式。 除了一一检查之外别无选择。如果您使用其他方法,那么方法必须这样做 - 必须有人这样做。如果另一种方法效率较低,您将面临额外开销的风险。在您的应用程序中编写更少的行与拥有最高效运行的代码不同..【参考方案5】:Jun 的解决方案,简化。使用StringBuffer#appendCodePoint(int)
,我不需要char current
或String#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字符的主要内容,如果未能解决你的问题,请参考以下文章
无效的 XML 字符错误 - 如何从 VARCHAR2 数据库列中查找无效字符?