应使用 doc() 函数跨文档读取,xsl:document 仅用于输出;doc() 加载外部 xml 后需用 /root/* 跳过根节点避免多根错误,并注意路径解析上下文、编码声明及命名空间处理。

用 xsl:document 还是 doc()?别搞混了
XML 合并不是靠“拼字符串”,XSLT 里没有“追加节点”这种操作;真正能跨文档读取的是 doc() 函数,而 xsl:document 是输出用的,跟合并无关。很多人一看到“多文档”就去查 xsl:document,结果卡死在语法报错里。
实际做法是:主样式表用 doc("file2.xml") 加载另一个 XML,再用 XPath 定位它的根或子节点,最后用 xsl:copy-of 或 xsl:apply-templates 把内容拉进来。
-
doc()只接受单个 URI 字符串,不支持通配符或目录遍历 - 路径是相对主 XML 文件(不是 XSLT 文件)解析的,容易因工作目录不同而
FOOD0001: Failed to load document - 如果被加载的 XML 编码不是 UTF-8,且没声明
<?xml version="1.0" encoding="GBK"?>,doc()会直接失败,不报编码错误,只报“无法解析”
合并多个 XML 时怎么避免重复根节点
直接 xsl:copy-of select="doc('a.xml')/root"/> + xsl:copy-of select="doc('b.xml')/root"/> 会导致两个 <root></root> 并列,违反 XML 单根约束。必须人为构造一个新容器,比如 <merged></merged>,再把各文档内容塞进去。
常见写法是:
<merged> <xsl:copy-of select="doc('a.xml')/root/*"/> <xsl:copy-of select="doc('b.xml')/root/*"/> </merged>
- 用
/root/*而不是/root,跳过源文件的根标签本身,只取子元素——这是避免嵌套根的关键 - 如果各 XML 的根名不同(比如
<user></user>和<order></order>),不能硬写/root,得用/*[1]或具体路径,否则doc()返回空 - XSLT 1.0 不支持动态构造元素名,所以没法用变量控制容器名;XSLT 2.0+ 可用
xsl:element name="{$container-name}"
用 Collection() 批量加载多个 XML 文件靠谱吗
靠谱,但依赖处理器支持,且配置麻烦。Saxon 支持 collection("file:///path/?select=*.xml"),但默认不开启 URI 解析器,要加 jvm 参数或 API 设置;Xalan、libxslt 基本不支持。
更稳的替代方案是:写个脚本(Python/Shell)先生成一个索引 XML,里面列好所有待合并文件路径,再让 XSLT 读这个索引,循环调用 doc()。
-
collection()返回的是节点集,不是文档集,不能直接对每个文档做独立处理,得配合for-each和位置判断 - 路径中的通配符行为各处理器不一致,Saxon 支持
?select=*.xml&recurse=yes,但 libxslt 会直接忽略整个参数 - 大文件多(>50 个)时,
collection()内存占用陡增,不如分批doc()控制节奏
合并后命名空间混乱怎么办
两个 XML 如果声明了不同前缀但相同 URI(比如 xmlns:a="http://ex.com" 和 xmlns:b="http://ex.com"),合并后节点会带上原始前缀,导致同一命名空间出现多个别名,后续 XPath 查询可能失效。
解决办法不是删前缀,而是统一用 xsl:copy-of 时加 copy-Namespaces="no"(XSLT 2.0+),或手动用 xsl:element 重建节点并指定前缀。
- XSLT 1.0 没有
copy-namespaces,只能用模板匹配所有元素,用xsl:element name="{local-name()}" namespace="http://ex.com"强制重写 - 如果源 XML 有默认命名空间(
xmlns="http://ex.com"),XPath 必须显式绑定前缀,否则//item查不到任何东西 - 合并后用
xmlstar或xmllint验证是否只剩一个命名空间声明,避免隐性污染
XSLT 合并 XML 看似简单,真正卡住人的往往不是语法,而是路径解析上下文、命名空间继承规则、以及不同处理器对 doc() 的实现差异。动手前先确认你用的是哪个引擎,再决定走标准路径还是绕道脚本预处理。