比如一个xml的如下:
<formExport>
<values>
<column name="名字">
<value><![CDATA[abcd\&$<>阿道夫]]></value>
</column>
<column name="名字2">
<value><![CDATA[<刘&智&]]></value>
</column>
</values>
</formExport>
解析xml, 会报如下异常:
Caused by: org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 65; 在实体引用中, 实体名称必须紧跟在 ‘&’ 后面。
或
Exception in thread “main” org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 65; The entity name must immediately follow the ‘&’ in the entity reference
原因: &在xml中是非法字符.
同样的, <>也是有意义的字符, 直接解析会报错
Caused by: org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 59; 元素内容必须由格式正确的字符数据或标记组成。
或
Exception in thread “main” org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 59; The content of elements must consist of well-formed character data or markup.
解决方法
转义 (针对CDATA)中的数据
private static final Pattern CDATA_PAT_2 = Pattern.compile("<(!\\[CDATA\\[.*?]])>");
private static final String CHAR_DOLLAR = "RDS_CHAR_DOLLAR";
/**
* 替换掉非法字符
* @param xml
* @return
*/
private static String replaceInvalidChar(String xml) {
Matcher matcher = CDATA_PAT_2.matcher(xml);
StringBuffer sb = new StringBuffer();
while (matcher.find()){
String group = matcher.group(1);
String rep = group.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">")
//如果不将$符号替换, 它在replace方法中会报错 Illegal group reference
.replaceAll("\\$", CHAR_DOLLAR);;
//replacement.replaceAll("\\$", "RDS_CHAR_DOLLAR");// encode replacement;
rep = "<"+rep+">";
//appendReplacement会丢失单个斜杠,为了解决这个问题需要处理一次
rep = rep.replaceAll("\\\\+", "\\\\\\\\");
matcher.appendReplacement(sb,rep);
}
matcher.appendTail(sb);
xml = sb.toString().replaceAll(CHAR_DOLLAR,"\\$");
return xml;
}
这样得到的xml是这样的:
<formExport>
<values>
<column name="名字">
<value><![CDATA[abcd\&$<>阿道夫]]></value>
</column>
<column name="名字2">
<value><![CDATA[<刘&智&]]></value>
</column>
</values>
</formExport>
这样如果还是希望得到的是转义前的数据, 那就在xml解析成功后, 获取字段值的时候再自己手动替换一遍. 如:
private static final List<Pair<String, String>> TRANSFER_CHAR_PAIRS = new ArrayList<>();
static {
TRANSFER_CHAR_PAIRS.add(Pair.of("&","&"));
TRANSFER_CHAR_PAIRS.add(Pair.of("<","<"));
TRANSFER_CHAR_PAIRS.add(Pair.of(">",">"));
}
//将
转义过的进行恢复
for (Pair<String, String> charPair : TRANSFER_CHAR_PAIRS) {
s = s.replaceAll(charPair.getLeft(), charPair.getRight());
}
踩坑
- 替换方法中, 有两个坑, 一是美元符号在
replaceAll
和appendReplacement
中如果美元符号会特殊处理, $没有跟整数会报错, 所以要先将美元符号替换一下,后面在转回来 - 如果有反斜杠
\
, 在经过appendReplacement
处理后也没有了, 和上一个一样, 要先处理下, 再转回来