XSLT1.0中xsl:element无法使用属性值作为元素名的问题
问题描述
尝试将元素属性值转换为标签名,例如把<test name="testname"/>转换为<testname/>,但操作失败,关键错误如下:
xsl:element: The value '$test' of the attribute 'name' is not a valid QName.
最小复现代码
XSLT 1.0代码
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:output method="xml" version="1.0" indent="yes" encoding="utf-8"/> <xsl:template match="/"> <xsl:variable name="test"> <xsl:value-of select="/test/@name"/> </xsl:variable> <xsl:element name="$test"/> </xsl:template> </xsl:stylesheet>
测试XML代码
<test name="testname"/>
疑问点
- 为何会出现这个错误?硬编码字符串作为
$test值时可正常运行。 - 什么是QName?
- 如何在XSLT 1.0(使用xsltproc工具)中从属性值生成有效的QName?
问题解答
1. 错误原因
你写的<xsl:element name="$test"/>里,name属性的值是字面量字符串$test,并非引用变量。XSLT 1.0中,xsl:element的name属性要动态引用变量,必须使用属性值模板——把变量用{}包裹,写成name="{$test}"。当前写法相当于让处理器创建一个名为$test的元素,而$test不符合XML标签名规则,因此报错。
2. 什么是QName?
QName(Qualified Name,限定名称)是XML中的合法命名,结构为[前缀:]本地名称(比如x:foo),需符合以下规则:
- 本地名称不能以数字或标点符号开头
- 不能包含空格、冒号(前缀与本地名之间的冒号除外)等特殊字符
- 不能是XML保留字(比如以
xml开头的名称,预定义命名空间前缀除外)
简单来说,QName就是符合规范的XML标签名,无论是否带命名空间前缀。
3. XSLT 1.0(xsltproc)中生成有效QName的方法
基础修正:正确引用变量
如果属性值本身是合法QName(比如测试用例里的testname),只需修正变量引用方式即可解决问题。修改后的XSLT代码如下:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:output method="xml" version="1.0" indent="yes" encoding="utf-8"/> <xsl:template match="/"> <xsl:variable name="test" select="/test/@name"/> <!-- 使用属性值模板引用变量 --> <xsl:element name="{$test}"/> </xsl:template> </xsl:stylesheet>
用xsltproc处理测试XML,就能输出<testname/>。
进阶处理:清理非法属性值
如果属性值可能存在不符合QName规则的情况(比如开头是数字、含特殊字符),可以借助xsltproc支持的EXSLT函数做清理。例如处理开头为数字的情况、替换非法字符为下划线:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:str="http://exslt.org/strings" version="1.0"> <xsl:output method="xml" version="1.0" indent="yes" encoding="utf-8"/> <xsl:template match="/"> <xsl:variable name="raw-name" select="/test/@name"/> <!-- 处理开头为数字的情况,前缀一个合法字母 --> <xsl:variable name="valid-name"> <xsl:choose> <xsl:when test="starts-with($raw-name, '0') or starts-with($raw-name, '1') or starts-with($raw-name, '2') or starts-with($raw-name, '3') or starts-with($raw-name, '4') or starts-with($raw-name, '5') or starts-with($raw-name, '6') or starts-with($raw-name, '7') or starts-with($raw-name, '8') or starts-with($raw-name, '9')"> <xsl:text>a</xsl:text> <xsl:value-of select="$raw-name"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$raw-name"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <!-- 替换所有非法字符为下划线 --> <xsl:variable name="clean-name" select="str:replace($valid-name, '[^a-zA-Z0-9_.-]', '_')"/> <xsl:element name="{$clean-name}"/> </xsl:template> </xsl:stylesheet>
内容的提问来源于stack exchange,提问作者peterh
相关产品推荐
相关产品推荐

