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

如图所示,剥离 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>

声明变量似乎有问题。