XSLT到HTML转换。 在Qt5生成id()错误

我使用Qt doc中推荐的方法对HTML转换做XSLT:

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。

 <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中的另一个“黑客”是在确定性不能保证的规范中使用单点:创建新节点:

 <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(.), '&#xA;'), '')" /> <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(.), '&#xA;'), '')" /> </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() vs generate-id(.)
  • 把它放在xsl:for-each或者正常的模板处理中
  • 使用前使用变量来存储结果
  • 在另一个函数中隐藏generate-id()

所有返回相同的,有效的结果。


更新:解决方法

假设生成的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> 

这似乎有一些与声明变量的问题。