XSLT 到 HTML 的转换。 Qt5 中的 generate-id() 错误
XSLT to HTML transformation. generate-id() error in Qt5
我使用 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 中不运行
¿任何人都知道其中的原因,以及如何解决这个问题?
编辑:我没有收到任何错误。我在 Qt5 中得到的总是相同的 ID,而在 Qt4 中我每次调用 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。
调用generate-id()
returns生成的上下文节点id,当然,如果上下文没有改变,你将始终得到相同的值。
<xsl:template match="/">
<xsl:value-of select="generate-id()"/> --
<xsl:value-of select="generate-id()"/> --
<xsl:value-of select="generate-id()"/>
感谢您的片段。这确实是为什么我一直烦扰你提供一个可重现的例子。
这里发生的是你多次调用generate-id()
函数而不改变上下文节点。此函数的默认参数是上下文节点(此处:/
,或根节点)。
除非你改变上下文节点,否则这个函数被特意设计成稳定的。这意味着,如果使用相同的参数重复调用(也意味着:相同的默认参数,相同的上下文),它 必须 return 相同的字符串。
它的设计还使得它始终 return 为每个不同的节点提供一个唯一的字符串。如果两个节点在文档中的位置不同,则它们是不同的(即,即使它们看起来相同,但出现在多个地方,它们也是不同的)。
底线:您没有遇到 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 的核心概念)不能用于它。因此,我创建了一个包含所有六种类型的相似节点的文档(我忽略了 namespace 节点,因为对它的支持是可选的)。
输入测试文档
<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
如图所示,剥离 space 不适用于 Qt,因为它认为它们是文本节点。但是正如您还看到的那样,generate-id
函数适用于每个节点,无论它们是处理指令、文本节点、看起来相同还是空元素等。是否:
- 使用
generate-id()
与 generate-id(.)
- 放入
xsl:for-each
或正常模板处理
- 在使用前使用变量存储结果
- 将
generate-id()
隐藏在另一个函数中
所有 return 都得到相同的有效结果。
更新:解决方法
假设生成的 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>
这显然会影响性能,但是如果您的文档不是那么大 and/or 您不要经常调用此函数,应该没问题。如果定义了需要密钥的子集,您可以考虑创建一个密钥。
我找到了解决这个问题的方法。 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>
声明变量似乎有问题。
我使用 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 中不运行
¿任何人都知道其中的原因,以及如何解决这个问题?
编辑:我没有收到任何错误。我在 Qt5 中得到的总是相同的 ID,而在 Qt4 中我每次调用 generate-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。
调用generate-id()
returns生成的上下文节点id,当然,如果上下文没有改变,你将始终得到相同的值。
<xsl:template match="/"> <xsl:value-of select="generate-id()"/> -- <xsl:value-of select="generate-id()"/> -- <xsl:value-of select="generate-id()"/>
感谢您的片段。这确实是为什么我一直烦扰你提供一个可重现的例子。
这里发生的是你多次调用generate-id()
函数而不改变上下文节点。此函数的默认参数是上下文节点(此处:/
,或根节点)。
除非你改变上下文节点,否则这个函数被特意设计成稳定的。这意味着,如果使用相同的参数重复调用(也意味着:相同的默认参数,相同的上下文),它 必须 return 相同的字符串。
它的设计还使得它始终 return 为每个不同的节点提供一个唯一的字符串。如果两个节点在文档中的位置不同,则它们是不同的(即,即使它们看起来相同,但出现在多个地方,它们也是不同的)。
底线:您没有遇到 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 的核心概念)不能用于它。因此,我创建了一个包含所有六种类型的相似节点的文档(我忽略了 namespace 节点,因为对它的支持是可选的)。
输入测试文档
<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
如图所示,剥离 space 不适用于 Qt,因为它认为它们是文本节点。但是正如您还看到的那样,generate-id
函数适用于每个节点,无论它们是处理指令、文本节点、看起来相同还是空元素等。是否:
- 使用
generate-id()
与generate-id(.)
- 放入
xsl:for-each
或正常模板处理 - 在使用前使用变量存储结果
- 将
generate-id()
隐藏在另一个函数中
所有 return 都得到相同的有效结果。
更新:解决方法
假设生成的 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>
这显然会影响性能,但是如果您的文档不是那么大 and/or 您不要经常调用此函数,应该没问题。如果定义了需要密钥的子集,您可以考虑创建一个密钥。
我找到了解决这个问题的方法。 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>
声明变量似乎有问题。