Учитывая следующий образец xml:
<?xml version = "1.0" encoding = "UTF-8"?>
<root>
<data>
<?format-number id?>
<id>123</id>
<?xml-multiple comment?>
<?format-string comment?>
<comment>hello</comment>
<?format-string comment?>
<comment>goodbye</comment>
<text>bar</text>
</data>
</root>
Мне нужно написать XSL, который копирует заданный элемент, а также все предыдущие инструкции по обработке до предыдущего элемента (родственного или родственного). Копия инструкций по обработке должна быть общей (неважно, что это за инструкции, если они логически «принадлежат» к элементу (т.е. воспринимайте их как аннотации к последующему элементу, и их может быть несколько) Также не имеет значения, какой у предыдущего элемента (опять же, это может быть то же имя элемента, другое имя элемента или родительский элемент).
XSL может выглядеть примерно так:
<?xml version = "1.0" encoding = "UTF-8" standalone = "no"?>
<xsl:stylesheet xmlns:fn = "http://www.w3.org/2005/xpath-functions"
xmlns:xs = "http://www.w3.org/2001/XMLSchema" xmlns:xsl = "http://www.w3.org/1999/XSL/Transform"
version = "2.0">
<xsl:output byte-order-mark = "no" encoding = "UTF-8" indent = "yes" method = "xml"
omit-xml-declaration = "yes" />
<xsl:template match = "/">
<root>
<xsl:for-each select = "/root/data">
<data>
<xsl:for-each select = "comment">
<!-- some select usage of preceding-sibling and processing-instruction() ? -->
<xsl:copy-of select = "???" />
<xsl:copy-of select = "." />
<foo>bar</foo>
</xsl:for-each>
<xsl:for-each select = "text">
<xsl:copy-of select = "." />
</xsl:for-each>
</data>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
Исходя из вышеизложенного, результат должен быть:
<?xml version = "1.0" encoding = "UTF-8"?>
<root>
<data>
<?xml-multiple comment?>
<?format-string comment?>
<comment>hello</comment>
<?format-string comment?>
<comment>goodbye</comment>
<text>bar</text>
</data>
</root>
Обновлять
Дополнительный контекст. У меня есть несколько существующих xsl, которые были написаны/протестированы таким образом, чтобы нацеливаться на определенные элементы по мере их преобразования (т. е. интенсивное использование for-each select = "element"
). Этот xsl был сгенерирован из MapForce (графический картографический инструмент), но не копирует инструкции по обработке xml!. В идеале я мог бы расширить этот существующий xsl таким образом, чтобы он был относительно ненавязчивым (например, добавляя какой-нибудь xsl прямо перед каждым copy-of
внутри for-each
, который копирует инструкции по обработке этих конкретных элементов.
По сути, трюк состоит в том, чтобы включить фрагмент, который говорит: «внутри for-each
, который выбирает элемент, сначала скопируйте все инструкции по обработке текущего элемента (те, которые идут до текущего элемента, но после предыдущего элемента), а затем выполните любую логику. уже есть, т.е. больше copy-of
и т. д.
Обновление 2
После дальнейшего тестирования приведенного ниже ответа на основе key
появился еще один пограничный случай, в котором иногда встречаются «осиротевшие» инструкции по обработке, то есть те, которые не аннотируют элемент following-sibling
, но идут в конце, т.е. <?xml-multiple colors?>
ниже.
<?xml version = "1.0" encoding = "UTF-8"?>
<root>
<data>
<?format-number id?>
<id>123</id>
<?xml-multiple comment?>
<?format-string comment?>
<comment>hello</comment>
<?format-string comment?>
<comment>goodbye</comment>
<?format-string text?>
<text>bar</text>
<?xml-multiple colors?>
</data>
</root>
В этом случае результат должен быть
<?xml version = "1.0" encoding = "UTF-8"?>
<root>
<data>
<?xml-multiple comment?>
<?format-string comment?>
<comment>hello</comment>
<?format-string comment?>
<comment>goodbye</comment>
<text>bar</text>
<?xml-multiple colors?>
</data>
</root>
Это сложно, потому что у него нет following-sibling
, скорее, у него нет «целевого» элемента. В связи с этим я также понял, что инструкции по обработке «данных» (т. Е. Если format-number
— это имя, id
— это данные) — это действительно то, что связывает их с целью. В этом отношении я действительно хочу сделать копию инструкций по обработке на основе элемента, на который они нацелены. Другими словами, я думаю, что хочу создать ключ на основе узла, указанного в инструкции обработки, и следующего брата.
<xsl:copy-of select = "key('pi', generate-id('foo'))" />
вычеркнет инструкции по обработке, которые были проиндексированы, потому что они были найдены как:
<?format-number foo?>
<id>123</id>
Я не уверен, что полностью следую вашему описанию. Возможно, что-то вроде этого может сработать для вас:
XSLT 2.0
<xsl:stylesheet version = "2.0"
xmlns:xsl = "http://www.w3.org/1999/XSL/Transform">
<xsl:output method = "xml" version = "1.0" encoding = "UTF-8" indent = "yes"/>
<xsl:template match = "/root">
<root>
<xsl:for-each select = "data">
<data>
<xsl:for-each-group select = "* | processing-instruction()" group-ending-with = "*">
<xsl:if test = "current-group()[last()][self::comment]">
<xsl:copy-of select = "current-group()" />
</xsl:if>
</xsl:for-each-group>
<xsl:copy-of select = "text" />
</data>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
Кажется, это работает! Итак, если бы я хотел сделать это несколько в общем, похоже, я мог бы использовать один и тот же стиль for-each-group
для каждого узла, который я выбираю. Другими словами, вот так (замена copy-of
для элемента text
на аналогичный for-each-group
, но с другим именем self::tag-name). Кстати говоря, не то чтобы это критично, но могу ли я переместить условие if
для имени узла в for-each-group
select?
Кроме того, есть ли версия, в которой for-each-group
копирует инструкции по обработке, которые предшествуют элементу, но не сам элемент?
Вы можете изменить последнее условие [self::comment]
, чтобы включить больше элементов. Чтобы исключить копирование самого элемента, вы можете сделать <xsl:copy-of select = "current-group()[not(self::*)]"/>
.
Проблема, с которой я сталкиваюсь (см. Обновление в моем вопросе), заключается в том, что это связано с обновлением существующего xsl, и я хотел бы попытаться избежать его слишком большой перестройки.
Боюсь, я не вижу ваш существующий XSLT, поэтому я не знаю, что означает «слишком много переделывать». Вы можете использовать xsl:apply-templates
вместо xsl:copy-of
и добавить шаблон для каждого элемента. Или, возможно, используйте другой метод, который я только что опубликовал.
Вот другой метод для достижения того же результата, который вы можете найти более удобным для адаптации к вашей ситуации:
XSLT 1.0
<xsl:stylesheet version = "1.0"
xmlns:xsl = "http://www.w3.org/1999/XSL/Transform">
<xsl:output method = "xml" version = "1.0" encoding = "UTF-8" indent = "yes"/>
<xsl:key name = "pi" match = "processing-instruction()" use = "generate-id(following-sibling::*[1])" />
<xsl:template match = "/root">
<root>
<xsl:for-each select = "data">
<data>
<xsl:for-each select = "comment">
<xsl:copy-of select = "key('pi', generate-id())" />
<xsl:copy-of select = "." />
</xsl:for-each>
</data>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
да! это выглядит очень чистым и, судя по ранним испытаниям дыма, выглядит великолепно. прежде чем копаться в том, как работают ключи xsl, я предполагаю, что это что-то вроде «назначить ключ каждой инструкции обработки (из-за атрибута match
) со значением вызова функции generate-id
на following-sibling::*[1]
. Если это так, это кажется как говорится, возьмите первый экземпляр, т. е. [1], всех родственных узлов.Если это так, то как они связываются с элементом в области видимости, т. е. что повторяется для каждого?
Это работает следующим образом: каждая инструкция обработки индексируется идентификатором первого следующего за ней элемента. Это позволяет каждому элементу выбирать «свои» инструкции по обработке, вызывая ключ со значением его идентификатора. Это прямой поиск, а не итерация.
но если бы было несколько инструкций обработки, инструкция обработки получила бы идентификатор инструкции обработки 2 (т.е. это первый следующий за ним элемент)? Разве не нужно получать идентификатор первого элемента, не относящегося к инструкции по обработке, следующего за родственным элементом?
Инструкция обработки не является элементом (и наоборот): w3.org/TR/xpath-datamodel/#Node
еще одна вещь, которую я заметил, это то, что могут быть моменты, когда мне нужно передать ссылку на generate-id
. В моем случае я знаю, на какой элемент нацелен, поэтому, если нужно, я могу просто сказать generate-id(<expression-targetting-element>)
, что отлично работает.
см. мое последнее обновление. Я думаю, мне нужно настроить «индекс», который использует мой ключ, чтобы он основывался на целевом узле инструкций по обработке (раздел данных инструкции по обработке). должно быть прямо?
в соответствии с этими строками инструкция обработки, которая была проиндексирована ключом, будет находиться в контексте своего родительского элемента с возможностью также нацеливаться на (несуществующий) элемент. т. е. data/<?format-string comment?> будет нацелен на данные/комментарий (и будет доступен для инъекций в то время как for-each над данными/комментариями. однако data/<?xml-multiple color?> будет нацелен на данные/цвет, но данные/ цвет может не обязательно быть там. Однако я все равно хотел бы ввести эту инструкцию обработки в результат, чтобы отразить «пустой список» (в конечном итоге используемый ниже по течению для преобразования формата данных)
Вы можете выбрать «оставшиеся» PI, используя key('pi', '')
. --- Тот факт, что PI содержат имя элемента, которому они "принадлежат", кажется неуместным, поскольку это имя не уникально. Если только между PI и его «истинным» элементом не может быть другого элемента с другим именем.
Если подумать, вы могли бы использовать этот факт для получения основной части вашего результата, используя просто: <xsl:copy-of select = "processing-instruction()[.='comment'] | comment" />
. Но тогда выбор «осиротевших» ИП будет не таким тривиальным — возможно, что-то вроде: <xsl:copy-of select = "processing-instruction()[not(.=../*/name())]"/>
.
что делает key('pi', '')? Мне интересно, как он знает, что нужно получать инструкции по обработке только внутри контекста для каждого по сравнению со всеми из них.
Я попробовал подход processing-instruction()[.='comment']
, но мне нужно быть внутри for-each (исходя из того, как изначально был сгенерирован xsl), так что на самом деле это не работает так же. Если я помещу его за пределами for-each, он не совсем будет ассоциироваться с каждым узлом в for-each (посмотрите, как в первом комментарии есть два pi, а во втором ни одного... потому что я сделал это до for- см. здесь xsltransform.net/gVhDDzC
другими словами, xml <xsl:for-each select = "comment"> <!-- what would I do here?--> <xsl:copy-of select = "processing-instruction()[.='comment']" /> <xsl:copy-of select = "." /> </xsl:for-each>
единственное, что касается ключевого подхода, это то, что я действительно не понимаю, как работает ключ ('pi', ''). В противном случае, для контекста, то, что я изначально пытался сделать, было смесью «внутри for-each, ищите preceding-sibling
назад, пока не нажмете следующий узел, а затем захватите их, пока не нажмете текущий узел». Итак, «дайте мне все инструкции по обработке между текущим узлом и предыдущим узлом»
Боюсь, это выходит из-под контроля. Мы обсуждаем разные возможные методы одновременно. Я предлагаю вам выбрать один метод, а затем, если необходимо, опубликовать новый вопрос.
Давайте продолжим обсуждение в чате.
резюме в stackoverflow.com/questions/75401863/…
Если вы используете XSLT 2.0, прикрепите этот тег к своему вопросу; гораздо легче ответить на этот вопрос, если вы хорошо знаете версию XSLT, которую используете.