XSLT到HTML的转换.Qt5中的generate-id()错误

XSLT to HTML transformation. generate-id() error in Qt5

本文关键字:generate-id 错误 中的 Qt5 HTML 转换 XSLT      更新时间:2023-10-16

我使用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。

<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(.), 
            '&#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()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>

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