Я делаю преобразование XSLT в HTML, используя метод, рекомендованный в Qt doc:
QXmlQuery query(QXmlQuery::XSLT20);
query.setFocus(QUrl("myInput.xml"));
query.setQuery(QUrl("myStylesheet.xsl"));
query.evaluateTo(out);
Внутри XSLT я использую generate-id()
метод для генерации уникальных идентификаторов для различных блоков DIV. Он отлично работает в Qt4.8, но не в Qt5.4
¿Кто-нибудь знает причину этого и как это решить?
Изменить: я не получаю ошибки. В Qt5 я всегда получаю один и тот же идентификатор, а в Qt4 каждый раз, когда я звоню, я получаю новый уникальный идентификатор generate-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>
…Я всегда получаю один и тот же идентификатор.
<xsl:template match="/"> <xsl:value-of select="generate-id()"/> -- <xsl:value-of select="generate-id()"/> -- <xsl:value-of select="generate-id()"/>
Спасибо за фрагмент. Именно поэтому я продолжал вас обижать, приводя воспроизводимый пример.
Здесь происходит то, что вы называете generate-id()
функционировать несколько раз без изменения узла контекста. Аргументом по умолчанию для этой функции является узел контекста (здесь: /
или корневой узел).
Если вы не измените узел контекста, эта функция специально разработана для обеспечения стабильности. Это означает, что при повторном вызове с одним и тем же аргументом (также означающим: один и тот же аргумент по умолчанию, один и тот же контекст), он должен вернуть ту же строку.
Он также разработан таким образом, что он всегда возвращает уникальную строку для каждого отдельного узла. Два узла различны, если они имеют различную позицию в документе (то есть они различны, даже если они выглядят одинаково, но появляются в нескольких местах).
Итог: вы не столкнулись с ошибкой в реализации Qt XSLT 2.0, но вы обнаружили решенную проблему, которая была ошибкой и случайно использовалась в качестве функции.
Если вам требуется уникальный идентификатор в XSLT 2.0, и вы обязаны указывать тот же контекст, возможно, что-то еще изменится: например, вы можете быть в цикле, проходящем по набору чисел или строк. Вы можете использовать эту информацию для создания уникальной строки.
Еще один «хак» в 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, потому что я не мог поверить, что идентичность (которая является основной концепцией 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>
Код немного скорректирован из-за того, что 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>
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
Я использовал предварительную сборку 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()
внутри другой функцииВсе вернули одинаковый, действительный результат.
Существует относительно дорогой, но работоспособный обходной путь, который вы можете использовать, предполагая, что сгенерированный идентификатор сам по себе должен быть уникальным для каждого документа и узла, но не должен использоваться иначе, чем для уникальности (например, если используется для перекрестного ссылки, это будет работать).
<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()
возвращает сгенерированный идентификатор узла контекста и, конечно, если контекст не меняется, вы всегда получите одно и то же значение.
Я нашел решение этой проблемы. Кажется, есть некоторые различия между движком преобразования Qt4 и Qt5 XSLT в Linux.
Следующий код прекрасно работал в 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>
Кажется, есть некоторая проблема с объявлением переменной.