Я пытаюсь написать XSLT «X_start», который создает другой XSLT «X_generated» из XML-файла, содержащего правила. Моя проблема состоит в том, чтобы создать последовательность для сравнения между тем, что я нашел в файле xml, и тем, что я сгенерировал в X_generated. Идея состоит в том, чтобы сделать это сравнение, и если оно не совпадает, я сгенерирую некоторый текст. В своих примерах я сравниваю, действительно ли я могу найти значение, потому что, если я попытаюсь увидеть, находится ли значение за пределами допустимых значений, к сожалению, я всегда преуспею.
Значение атрибута «условное» означает, что если шаблон совпадает, то используемое значение должно соответствовать некоторому набору значений.
Любая помощь/подсказка приветствуется, и дайте мне знать, если вы хотите, чтобы я что-то прояснил.
Пример по правилам:
<?xml version = "1.0" encoding = "UTF-8"?>
<rules>
<rule type = "conditional">
<xPath>//elemA/@attribute1</xPath>
<val allow = "A"/>
<val allow = "B"/>
</rule>
<rule type = "conditional">
<xPath>//elemC/@attribute1</xPath>
<val allow = "C"/>
<val allow = "D"/>
</rule>
<rule type = "conditional">
<xPath>//elemB</xPath>
<val allow = "one"/>
<val allow = "two"/>
<val allow = "three"/>
</rule>
</rules>
Мой текущий черновик XSLT:
<?xml version = "1.0" encoding = "UTF-8"?>
<xsl:stylesheet xmlns:xsl = "http://www.w3.org/1999/XSL/Transform"
xmlns:xs = "http://www.w3.org/2001/XMLSchema"
xmlns:math = "http://www.w3.org/2005/xpath-functions/math"
xmlns:axsl = "http://www.w3.org/1999/XSL/TransformAlias"
version = "3.0">
<xsl:output method = "xml" indent = "true" encoding = "UTF-8"/>
<xsl:mode on-no-match = "shallow-skip"/>
<xsl:namespace-alias stylesheet-prefix = "axsl" result-prefix = "xsl"/>
<xsl:template match = "/">
<axsl:stylesheet>
<xsl:attribute name = "expand-text" select = "'true'"/>
<xsl:attribute name = "version" select = "'3.0'"/>
<xsl:apply-templates select = "rules/*"/>
</axsl:stylesheet>
</xsl:template>
<xsl:template match = "rule">
<!-- This creates ("A", "B") -->
<xsl:variable name = "vals_from_for_each" as = "xs:string*">
<xsl:for-each select = "val/@allow">
<xsl:value-of select = "."/>
</xsl:for-each>
</xsl:variable>
<!-- This also creates ("A", "B") -->
<xsl:variable name = "vals_from_seq_text" as = "xs:string*">
<xsl:sequence select = "val/@allow"/>
</xsl:variable>
<axsl:template>
<xsl:attribute name = "match" select = "xPath"/>
<!-- Attempt to move the sequence into the generated template -->
<axsl:variable name = "vals" as = "xs:string*">
<!-- But this generates "A B", and is therefore not valid for comparison.
I cannot compare a value like ". = A B", it needs to be ". = ('A', 'B')-->
<xsl:sequence select = "$vals_from_seq_text"/>
</axsl:variable>
<axsl:if test = ". = $vals">
<axsl:message>Success</axsl:message>
</axsl:if>
<!-- trying by building a comparison tree (that doesn't work either... -->
<axsl:variable name = "value_list" as = "element()*">
<root>
<xsl:for-each select = "val/@allow">
<val><xsl:value-of select = "."/></val>
</xsl:for-each>
</root>
</axsl:variable>
<axsl:if test = ". = $value_list/root/val">
<axsl:message>Found in tree - Success</axsl:message>
</axsl:if>
</axsl:template>
</xsl:template>
Результирующий XSLT:
<?xml version = "1.0" encoding = "UTF-8"?>
<xsl:stylesheet xmlns:math = "http://www.w3.org/2005/xpath-functions/math"
xmlns:xs = "http://www.w3.org/2001/XMLSchema"
xmlns:xsl = "http://www.w3.org/1999/XSL/Transform"
expand-text = "true"
version = "3.0">
<xsl:template match = "//elemA/@attribute1">
<xsl:variable name = "vals" as = "xs:string*">A B</xsl:variable>
<xsl:if test = ". = $vals">
<xsl:message>Success</xsl:message>
</xsl:if>
<xsl:variable name = "value_list" as = "element()*">
<root>
<val>A</val>
<val>B</val>
</root>
</xsl:variable>
<xsl:if test = ". = $value_list/root/val">
<xsl:message>Found in tree - Success</xsl:message>
</xsl:if>
</xsl:template>
<xsl:template match = "//elemC/@attribute1">
<xsl:variable name = "vals" as = "xs:string*">C D</xsl:variable>
<xsl:if test = ". = $vals">
<xsl:message>Success</xsl:message>
</xsl:if>
<xsl:variable name = "value_list" as = "element()*">
<root>
<val>C</val>
<val>D</val>
</root>
</xsl:variable>
<xsl:if test = ". = $value_list/root/val">
<xsl:message>Found in tree - Success</xsl:message>
</xsl:if>
</xsl:template>
<xsl:template match = "//elemB">
<xsl:variable name = "vals" as = "xs:string*">one two three</xsl:variable>
<xsl:if test = ". = $vals">
<xsl:message>Success</xsl:message>
</xsl:if>
<xsl:variable name = "value_list" as = "element()*">
<root>
<val>one</val>
<val>two</val>
<val>three</val>
</root>
</xsl:variable>
<xsl:if test = ". = $value_list/root/val">
<xsl:message>Found in tree - Success</xsl:message>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Таким образом, ни одно из сообщений не срабатывает, мне не удается создать что-то похожее в моем полученном XSLT. Любая созданная последовательность создается как отдельные значения в строке (?), и я не могу выполнить поиск в сгенерированном сегменте дерева (что, как я догадывался, не сработает, но, как бы отчаянно это ни было, я все равно попытался). Во время написания этого я начал думать, что одним из способов может быть использование отдельного режима, в котором я мог бы создать совершенно отдельный XML-документ со значениями поиска, но это кажется немного хуже, чем создание поиска в каждом шаблоне. Итак, какие идеи о том, как я могу перенести допустимые значения из моего исходного XML в сгенерированный XSLT?
IIUC, вы НЕ хотите создавать «последовательность строк». Вы хотите создать строку, которая при оценке как выражение создаст последовательность строк.
Рассмотрим что-то вроде:
XSLT 3.0
<xsl:stylesheet version = "3.0"
xmlns:xsl = "http://www.w3.org/1999/XSL/Transform"
xmlns:axsl = "http://www.w3.org/1999/XSL/TransformAlias">
<xsl:output method = "xml" version = "1.0" encoding = "UTF-8" indent = "yes"/>
<xsl:namespace-alias stylesheet-prefix = "axsl" result-prefix = "xsl"/>
<xsl:template match = "/rules">
<axsl:stylesheet version = "3.0">
<xsl:apply-templates/>
</axsl:stylesheet>
</xsl:template>
<xsl:template match = "rule">
<axsl:template match = "{xPath}">
<axsl:if test = ". = ({string-join(val/@allow ! concat('''', ., ''''), ',')})">
<axsl:message>Success</axsl:message>
</axsl:if>
</axsl:template>
</xsl:template>
</xsl:stylesheet>
P.S. Обратите внимание, что запуск шаблона соответствия с помощью //
ничего не дает.
Один из способов, поскольку у вас есть простые строковые значения, — хранить их в массиве XDM, который вы сериализуете как JSON, и вставляете сериализованные в сгенерированный XSLT:
<?xml version = "1.0" encoding = "UTF-8"?>
<xsl:stylesheet xmlns:xsl = "http://www.w3.org/1999/XSL/Transform"
xmlns:xs = "http://www.w3.org/2001/XMLSchema"
xmlns:math = "http://www.w3.org/2005/xpath-functions/math"
xmlns:axsl = "http://www.w3.org/1999/XSL/TransformAlias"
version = "3.0">
<xsl:output method = "xml" indent = "true" encoding = "UTF-8"/>
<xsl:mode on-no-match = "shallow-skip"/>
<xsl:namespace-alias stylesheet-prefix = "axsl" result-prefix = "xsl"/>
<xsl:template match = "/">
<axsl:stylesheet>
<xsl:attribute name = "expand-text" select = "'true'"/>
<xsl:attribute name = "version" select = "'3.0'"/>
<xsl:apply-templates select = "rules/*"/>
</axsl:stylesheet>
</xsl:template>
<xsl:template match = "rule">
<!-- This also creates ("A", "B") -->
<xsl:variable name = "vals_from_seq_text" as = "array(xs:string)" select = "array {val/@allow/string() }"/>
<axsl:template>
<xsl:attribute name = "match" select = "xPath"/>
<!-- Attempt to move the sequence into the generated template -->
<axsl:variable name = "vals" as = "array(xs:string)" select = "{serialize($vals_from_seq_text, map{'method':'json'})}"/>
<axsl:if test = ". = $vals?*">
<axsl:message>Success</axsl:message>
</axsl:if>
</axsl:template>
</xsl:template>
</xsl:stylesheet>
Это работает, и я вижу, что результатом является массив. Не могли бы вы указать мне какой-нибудь простой для понимания источник в части map{'method':'json'}? Я действительно хотел бы понять эти инструменты, но, к сожалению, я не получил этого, прочитав спецификацию.
@Zug_Bug, я полагаю, вы знаете, например. <xsl:output method = "html"/>
или <xsl:output method = "text"/>
или <xsl:output method = "xml"/>
, чтобы объявить метод сериализации для таблицы стилей XSLT 2? Ну, в XSLT 3 есть два дополнительных метода, один с именем json
, другой с именем adaptive
. Кроме того, это функция XPath 3.1 с именем serialize
, которая в качестве первого аргумента принимает любую последовательность XSLT/XDM/XPath, а в качестве второго аргумента — параметры сериализации, но не в форме объявления xsl:output
, а в виде «карты» XPath 3.1.
Так что все, что я сделал, это использовал <xsl:output method = "json"/>
, но в синтаксисе XPath 3.1, а не на уровне XSLT. Синтаксис карт XSLT объясняется в спецификации XSLT 3 и спецификации XPath 3.1: w3.org/TR/xpath-31/#id-maps
Проще всего сгенерировать последовательность строк в виде XML-структуры:
<axsl:variable name = "strings-as-xml" as = "element(s)*">
<xsl:for-each select = "$vals_from_seq_text">
<s>{.}</s>
</xsl:for-each>
</axsl:variable>
<axsl:variable name = "value_list" as = "xs:string*"
select = "$strings-as-xml!string()"/>
Другое решение, которое, конечно же, работает :-) Как я уже спрашивал в другом заданном ответе, мне интересно, почему моя сконструированная переменная, содержащая элементы, не работает для сравнения? Я могу получить текстовое значение из каждого включенного элемента и запустить для него "string()", но я не могу получить их в последовательность для сравнения, которую я обычно разрешал бы, если бы узел элемента был в документе, который я запускал. XSLT включен? Смущенный...
Я думал, вы сами ответили на это в комментарии к своему коду: ваш код генерирует <xsl:variable name = "..">A B C</xsl:variable>
, который представляет собой одну строку, а не последовательность строк.
Да, это я видел, но я имел в виду переменную $value_list. @ michael.hor257k подумал, что это может сработать, если я удалю тип из переменной. Я попробую это, просто чтобы посмотреть, работает ли это, чтобы я мог понять это немного лучше.
Ахм... Взяв подсказку из ответа Майкла Кея, почему бы вам не обойти всю проблему "последовательности строк" и сделать просто:
<xsl:template match = "rule">
<axsl:template match = "{xPath}">
<axsl:variable name = "vals">
<xsl:copy-of select = "val"/>
</axsl:variable>
<axsl:if test = ". = $vals/val/@allow">
<axsl:message>Success</axsl:message>
</axsl:if>
</axsl:template>
</xsl:template>
Хорошо, значит, это тоже работает. Хотите знать, почему моя встроенная переменная (которая имела только текстовое содержимое), которая выглядит похожей, не работала для сравнения?
Какую переменную вы имеете в виду?
Я попытался создать переменную с элементами под названием «value_list». Но я предполагаю, что это как-то связано с переменными и контекстом?
Мне потребуется больше времени, чтобы исследовать это, но я считаю, что ваша попытка использовать переменную $value_list
сработает, если вы удалите атрибут as = "element()*"
.
Я попытался удалить атрибут as = "element()*"
. Это тоже работает, и при отладке в Oxygen я вижу, что он создает document-node/<#document/fragment>
, как и copy-of
.
Да. В качестве альтернативы вы можете сохранить атрибут as
, но тогда вам нужно будет изменить тест с test = ". = $value_list/root/val"
на test = ". = $value_list/val"
— см.: w3.org/TR/xslt-30/#variable-values .
Это работает, и я понимаю предлагаемое решение. Что касается // в каждом совпадении, они исходят из исходных правил, и я думаю, что они были добавлены, чтобы люди могли использовать их непосредственно в инструменте, который, возможно, анализирует весь документ и применяет выражения в некоторых функциях XPath. Я полагал, что XSLT может каким-то образом решить проблему проверки.