Java或XSLT 1.0实现XML指定字符转实体的方案咨询
解决XML特殊字符转实体:Java和XSLT 1.0实现方案
我来帮你搞定这个XML特殊字符转实体的问题!之前用正则或者单个replace方法失败,大概率是因为正则没法高效覆盖几百个字符映射,或者多次replace的顺序/覆盖范围没处理好。下面给你两种靠谱的实现方式,任你选:
Java实现方案
这个方法用Map存所有字符-实体映射,然后逐个字符遍历替换,既高效又不会遗漏任何映射,完全避开正则的坑。
import java.util.HashMap; import java.util.Map; public class XmlEntityConverter { // 初始化你的400+字符-实体映射 private static final Map<Character, String> ENTITY_MAP = new HashMap<>(); static { ENTITY_MAP.put('☆', "☆"); ENTITY_MAP.put('♥', "&heart;"); ENTITY_MAP.put('"', """); // 这里添加其他所有映射,比如: // ENTITY_MAP.put('♦', "⋄"); // ... 剩下的几百个都放这儿 } public static String convertToEntities(String input) { if (input == null || input.isEmpty()) { return input; } StringBuilder sb = new StringBuilder(input.length()); // 逐个字符检查,存在映射就替换,否则保留原字符 for (char c : input.toCharArray()) { String entity = ENTITY_MAP.get(c); sb.append(entity != null ? entity : c); } return sb.toString(); } // 测试用例 public static void main(String[] args) { String originalXmlText = "Text ☆ and \"more\" ♥?"; String convertedText = convertToEntities(originalXmlText); System.out.println(convertedText); // 输出结果:Text ☆ and "more" &heart;? } }
为什么这个方案靠谱?
- 逐个字符处理,不会出现正则的贪婪匹配、字符范围遗漏问题;
- 用Map存储映射,维护起来很方便,新增/修改映射直接改静态代码块就行;
- 性能比多次调用
replace好太多,尤其是处理长文本的时候。
XSLT 1.0实现方案
如果你的场景是纯XML处理,不想写Java代码,XSLT 1.0也能搞定。这里给你两种写法:
写法1:直接用递归模板(适合映射不多的情况,或者不想依赖扩展)
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" indent="yes" encoding="UTF-8"/> <!-- 递归替换字符的模板 --> <xsl:template name="replace-entities"> <xsl:param name="text"/> <xsl:choose> <!-- 替换☆为☆ --> <xsl:when test="contains($text, '☆')"> <xsl:value-of select="substring-before($text, '☆')"/> <xsl:text>&star;</xsl:text> <!-- 注意:XSLT里&必须转义成& --> <xsl:call-template name="replace-entities"> <xsl:with-param name="text" select="substring-after($text, '☆')"/> </xsl:call-template> </xsl:when> <!-- 替换♥为&heart; --> <xsl:when test="contains($text, '♥')"> <xsl:value-of select="substring-before($text, '♥')"/> <xsl:text>&heart;</xsl:text> <xsl:call-template name="replace-entities"> <xsl:with-param name="text" select="substring-after($text, '♥')"/> </xsl:call-template> </xsl:when> <!-- 替换"为" --> <xsl:when test="contains($text, '"')"> <xsl:value-of select="substring-before($text, '"')"/> <xsl:text>&quot;</xsl:text> <xsl:call-template name="replace-entities"> <xsl:with-param name="text" select="substring-after($text, '"')"/> </xsl:call-template> </xsl:when> <!-- 这里继续添加其他所有字符的替换规则 --> <xsl:otherwise> <xsl:value-of select="$text"/> </xsl:otherwise> </xsl:choose> </xsl:template> <!-- 匹配XML根节点,处理super-head内容 --> <xsl:template match="/root"> <root> <super-head> <xsl:call-template name="replace-entities"> <xsl:with-param name="text" select="super-head/text()"/> </xsl:call-template> </super-head> </root> </xsl:template> </xsl:stylesheet>
写法2:用EXSLT扩展(适合大量映射,更易维护)
如果有400+映射,写几百个<xsl:when>太麻烦,可以用EXSLT的node-set扩展来批量处理(主流XSLT处理器比如Xalan、Saxon都支持这个扩展):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common" extension-element-prefixes="exsl"> <xsl:output method="xml" indent="yes" encoding="UTF-8"/> <!-- 定义所有字符-实体映射 --> <xsl:variable name="entity-mappings"> <mapping char="☆" entity="&star;"/> <mapping char="♥" entity="&heart;"/> <mapping char='"' entity="&quot;"/> <!-- 这里添加剩下的所有映射 --> </xsl:variable> <!-- 递归批量替换模板 --> <xsl:template name="replace-all-entities"> <xsl:param name="text"/> <!-- 将变量中的映射转换为可遍历的节点集 --> <xsl:param name="mappings" select="exsl:node-set($entity-mappings)/mapping"/> <xsl:choose> <xsl:when test="$mappings"> <xsl:variable name="current-char" select="$mappings[1]/@char"/> <xsl:variable name="current-entity" select="$mappings[1]/@entity"/> <xsl:choose> <xsl:when test="contains($text, $current-char)"> <xsl:value-of select="substring-before($text, $current-char)"/> <xsl:value-of select="$current-entity"/> <!-- 递归处理剩余文本,继续替换当前字符的其他出现 --> <xsl:call-template name="replace-all-entities"> <xsl:with-param name="text" select="substring-after($text, $current-char)"/> <xsl:with-param name="mappings" select="$mappings"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <!-- 当前字符不存在,处理下一个映射 --> <xsl:call-template name="replace-all-entities"> <xsl:with-param name="text" select="$text"/> <xsl:with-param name="mappings" select="$mappings[position() > 1]"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:when> <xsl:otherwise> <!-- 所有映射处理完,输出剩余文本 --> <xsl:value-of select="$text"/> </xsl:otherwise> </xsl:choose> </xsl:template> <!-- 处理目标XML节点 --> <xsl:template match="/root"> <root> <super-head> <xsl:call-template name="replace-all-entities"> <xsl:with-param name="text" select="super-head/text()"/> </xsl:call-template> </super-head> </root> </xsl:template> </xsl:stylesheet>
XSLT方案注意点
- XSLT中输出实体时,必须把
&转义成&,否则会被解析成实体本身而不是实体字符串; - 写法2依赖EXSLT扩展,如果你的处理器不支持,就用写法1。
内容的提问来源于stack exchange,提问作者Kevin B.
相关产品推荐
相关产品推荐

