我使用Qt文档中推荐的方法进行XSLT到HTML的转换:
QXmlQuery query(QXmlQuery::XSLT20);
query.setFocus(QUrl("myInput.xml"));
query.setQuery(QUrl("myStylesheet.xsl"));
query.evaluateTo(out);
在XSLT中,我使用generate-id()
方法为不同的DIV块生成唯一的id。它在Qt4.8中工作完美,但在Qt5.4中不工作有人知道其中的原因和解决方法吗?
generate-id()
时,我得到一个不同的唯一ID。
我这样生成ID:
<xsl:variable name="tc_id" select="generate-id()"/>
我这样使用它:
<xsl:value-of select="$tc_id"/>
这是执行转换的cpp代码:
// generate output string
QXmlQuery query(QXmlQuery::XSLT20);
QString output;
query.setFocus(QUrl(_final_output_filepath.c_str()));
query.setQuery(xslt_code.c_str());
query.evaluateTo(&output);
编辑2:
当我使用这个代码时…
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
xmlns:xdt="http://www.w3.org/2005/xpath-datatypes">
<xsl:template match="/">
<xsl:for-each select="trial/testsuite">
<xsl:for-each select="testcase">
<xsl:variable name="tc_index" select="position()"/>
<xsl:variable name="tc_id" select="generate-id(.)"/>
<xsl:value-of select="$tc_id"/>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
…我总是得到相同的ID。
<xsl:template match="/"> <xsl:value-of select="generate-id()"/> -- <xsl:value-of select="generate-id()"/> -- <xsl:value-of select="generate-id()"/>
谢谢您的代码片段。这就是为什么我一直缠着你要给一个可重复的例子。
这里发生的情况是,您多次调用generate-id()
函数而不更改上下文节点。这个函数的默认参数是上下文节点(这里是/
,或根节点)。
除非更改上下文节点,否则该函数被故意设计为稳定的。这意味着,如果使用相同的参数(也意味着:相同的默认参数,相同的上下文)重复调用,它必须返回相同的字符串。
它也被设计成总是为每个不同的节点返回一个唯一的字符串。如果两个节点在文档中有不同的位置,则它们是不同的(即,即使它们看起来相同,但出现在多个位置,它们也是不同的)。
底线:您没有在XSLT 2.0的Qt实现中遇到错误,但是您遇到了一个已解决的问题,该问题是一个错误,并且偶然被用作功能。
如果在XSLT 2.0中需要唯一的ID,并且必须提供相同的上下文,则可能会有其他变化:例如,您可能处于遍历一组数字或字符串的循环中。你可以使用这个信息来创建一个唯一的字符串。
XSLT 2.0中的另一个"hack"是在规范中使用不保证确定性的单个点:在创建新节点时:
<xsl:function name="my:gen-id" as="xs:string">
<xsl:sequence select="generate-id(my:gen-id-getnode())" />
</xsl:function>
<xsl:function name="my:gen-id-getnode" as="element()">
<node />
</xsl:function>
这个小函数涉及到一些高级概念,最近,在XSL工作组中进行讨论的人们一致认为,如果不需要节点标识,则允许优化掉新节点的创建。目前还不清楚处理器是否正确地检测到此
XSLT 3.0在xsl:function
: @new-each-time
上引入了一个新属性,它通知处理器函数每次都应该求值,而不是内联。
更新:Qt 5.5或5.4的测试
我已经用Qt测试了你的代码的一个变体,因为我无法相信身份(这是XSLT的核心概念)不能与它一起工作。因此,我创建了一个具有所有六种类型的类似节点的文档(我忽略了名称空间节点,因为对它的支持是可选的)。
输入测试文档
<root test="bla">
<?pi do-something ?>
<row></row>
<!-- comment here -->
<row>content</row>
<row>content</row>
<row id="bla" xml:id="bla">content</row>
</root>
XSLT 2.0代码
由于Qt不正确支持@separator
,代码略有调整。
<xsl:stylesheet
xmlns:my="my-functions"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:strip-space elements="*"/>
<xsl:template match="node()|@*">
<xsl:value-of select="string-join(
('gen-id(',
my:decorate(.), '[', my:depth(.), ']', ') = ', generate-id(.),
'
'), '')" />
<xsl:apply-templates select="@*|node()" />
</xsl:template>
<!-- remove prev. and use this if you think for-each is different -->
<!--xsl:template match="/">
<xsl:for-each select="//node() | //@*">
<xsl:value-of select="string-join(
('gen-id(',
my:decorate(.), '[', my:depth(.), ']', ') = ', generate-id(.),
'
'), '')" />
</xsl:for-each>
</xsl:template-->
<xsl:function name="my:depth" as="xs:string">
<xsl:param name="node" />
<xsl:sequence select="
string(count($node/(/)//node()[$node >> .]) + 1)" />
</xsl:function>
<xsl:function name="my:decorate">
<xsl:param name="node" />
<xsl:sequence select="
($node/self::text(), 'text')[2],
($node/self::element(), concat('Q{}', name($node)))[2],
($node/self::document-node(), 'document')[2],
($node/self::comment(), 'comment')[2],
($node/self::attribute(), concat('@', name($node)))[2],
($node/self::processing-instruction(), 'processing-instruction')[2]
" />
</xsl:function>
</xsl:stylesheet>
使用Exselt输出
gen-id(Q{}root[1]) = x1e2
gen-id(@test[2]) = x1e2a0
gen-id(processing-instruction[2]) = x1p3
gen-id(Q{}row[3]) = x1e4
gen-id(comment[4]) = x1c5
gen-id(Q{}row[5]) = x1e6
gen-id(text[6]) = x1t7
gen-id(Q{}row[7]) = x1e8
gen-id(text[8]) = x1t9
gen-id(Q{}row[9]) = x1e10
gen-id(@id[10]) = x1e10a0
gen-id(@xml:id[10]) = x1e10a1
gen-id(text[10]) = x1t11
使用Qt 5.5或5.4输出
我使用预构建xmlpatterns.exe
并将其称为xmlpatterns test.xsl input.xml
,但其代码使用与您使用的库相同:
gen-id(Q{}root[1]) = T756525610
gen-id(@test[2]) = T756525620
gen-id(text[2]) = T756525630
gen-id(processing-instruction[3]) = T756525640
gen-id(text[4]) = T756525650
gen-id(Q{}row[5]) = T756525660
gen-id(text[6]) = T756525670
gen-id(comment[7]) = T756525680
gen-id(text[8]) = T756525690
gen-id(Q{}row[9]) = T7565256100
gen-id(text[10]) = T7565256110
gen-id(text[11]) = T7565256120
gen-id(Q{}row[12]) = T7565256130
gen-id(text[13]) = T7565256140
gen-id(text[14]) = T7565256150
gen-id(Q{}row[15]) = T7565256160
gen-id(@id[16]) = T7565256170
gen-id(@xml:id[16]) = T7565256180
gen-id(text[16]) = T7565256190
gen-id(text[17]) = T7565256200
如上图所示,剥离空间对Qt不起作用,因为它认为它们是文本节点。但是,正如您所看到的,generate-id
函数适用于每个节点,无论它们是处理指令,文本节点,外观相同,还是空元素等。不管是否:
- 使用
generate-id()
和generate-id(.)
- 将其放入
xsl:for-each
或正常模板处理 - 在使用 之前使用一个变量来存储结果
- 将
generate-id()
隐藏在另一个函数中
都返回相同的有效结果。
<
更新:工作区/h2>
有一个相对昂贵但可行的解决方法,您可以使用,假设生成的ID本身在每个文档和节点中必须是唯一的,但不能以另一种方式用于惟一性(例如,如果用于交叉引用,这将有效)。
<xsl:variable name="doc" select=".//node()" />
<xsl:function name="my:gen-id" as="xs:integer">
<xsl:param name="elem" as="node()" />
<xsl:sequence select="
for $i in 1 to count($doc)
return if($doc[$i] is $elem then $i else ())" />
</xsl:function>
这显然会对性能造成影响,但是如果文档不是那么大,并且/或者您不经常调用这个函数,应该没有问题。如果定义了需要键的子集,则可以考虑创建键。
对generate-id()
的调用返回上下文节点生成的id,当然,如果上下文没有改变,您将始终获得相同的值。
我找到了一个解决这个问题的方法。Linux中的Qt4和Qt5 XSLT转换引擎似乎有些不同。
下面的代码在Qt4中可以正常工作,但在Qt5中不行。tc_id
始终具有相同的值:
<xsl:for-each select="testcase">
<xsl:choose>
<xsl:when test="@result != 'pass'">
<xsl:variable name="tc_id" select="generate-id(.)"/>
<xsl:attribute name="onClick">
ExpandCollapse('<xsl:value-of select="$tc_id"/>');
</xsl:attribute>
<div style="display:none">
<xsl:attribute name="id"><xsl:value-of select="$tc_id"/></xsl:attribute>
</div>
</xsl:when>
<xsl:otherwise>
...
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
下面的代码在Qt4和Qt5中都可以正常工作:
<xsl:for-each select="testcase">
<xsl:choose>
<xsl:when test="@result != 'pass'">
<xsl:attribute name="onClick">
ExpandCollapse('<xsl:value-of select="generate-id(.)"/>');
</xsl:attribute>
<div style="display:none">
<xsl:attribute name="id"><xsl:value-of select="generate-id(.)"/></xsl:attribute>
</div>
</xsl:when>
<xsl:otherwise>
...
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
声明变量似乎有一些问题。