У меня часто возникает проблема с выбором предыдущего «двоюродного брата» узла.
Например:
<root>
<level1>
<level2 id = "1"/>
<level2 id = "2"/>
<level2 id = "3"/>
<level2 id = "4"/>
</level1>
<level1>
<level2 id = "5">
<level2 id = "9"/>
</level2>
<level2 id = "6"/>
<level2 id = "7"/>
<level2 id = "8"/>
</level1>
</root>
Если узел контекста
/root/level1/level2[@id='6']
не хочу "id=9"; Я хочу "id=5".
Аналогично, если узел контекста
/root/level1/level2[@id='5']
Я хочу "id=4".
Итак....preceding-sibling работает в первом случае, но не во втором. preceding выбирает в последнем случае, но не в первом.
(И, вероятно, существует множество других случаев.)
Итак, мое текущее решение таково (используя id=6 в качестве узла контекста... я мог бы поместить его в функцию, но это должно быть достаточно ясно)
(/root/level1/level2[@id='6']/preceding::level2 intersect /root/level1/level2)[last()]
это работает, но кажется... запутанным и, возможно, неэффективным... мне не хватает более простого решения?
(Сейчас я использую XPath 3.1 в XSLT 3, но думаю, что это общий вопрос.)
(Я ограничил это XPath 2.0+, но, вероятно, открою вопрос для XPath 1.0, поскольку это кажется еще хуже, потому что intersect не существует... но я сделаю это в другом посте.)
Для XSLT 1.0 это работает:
/root/level1/level2[@id='6']/preceding::level2[parent::level1[parent::root]][1]
и, несомненно, является лучшим решением.
То, что я хотел бы сделать, это
/root/level1/level2[@id='6']/preceding::(root/level/level2)[1]
и интерпретировать это как совпадение на пути root/level/level2,
но я не думаю, что это действительно так?
здесь есть действительно хороший ответ, я хочу полностью прояснить, в чем был вопрос, извините.
Я ищу всех «n-кузенов», где 0-кузены являются братьями и сестрами.
раздражает, что элегантные решения просто получают 0 и 1, а более сложные решения являются «правильными», но довольно неуклюжими (без обид).
На данный момент самое элегантное решение, которое у меня есть, — это (как программист) выбрать абсолютно простой путь к вашему текущему узлу, в данном случае
let $this = ....
(root/level1/level2)[. << $this][last()]
умные решения делают это программно (одно использует глубину, другое — путь), и меня это немного ошеломляет.
элегантные решения используют оператор «<<», но сопоставляют только 0-го и 1-го братьев и сестер (что было разумной интерпретацией).
решение XSLT 1.0 аналогичным образом обрабатывает только 0-й и 1-й, и я думаю, что на самом деле я бы использовал решение parent:: выше и сопоставил полный путь к корню, чтобы гарантировать совпадение всех двоюродных братьев.
(конечно, предполагается, что вы знаете полный путь, хотя решение Мартина Хонненса в целом работает).
это то, что я делаю в интересах "пересечь /root/level1/level2"
Да, я это вижу, мне просто интересно, существовал ли <root><foo><level2/></foo><level1><level2/></level1></root>, считали ли вы или не считали level2 внутри элемента foo двоюродным братом level2 внутри элемента level1?
(в этом контексте) двоюродные братья - это их положение в иерархии, а не их имя, возможно, пример не очень удачный… то, что я сделал, работает… но кажется довольно запутанным, чтобы сделать что-то, казалось бы, простое. Другая проблема заключается в том, что пересечение не работает в XPath 1.0... возможно, это другой вопрос.
Ваши новые требования (я ищу всех «двоюродных братьев и сестер», где 0-двоюродные братья и сестры) неясны и противоречат вашим первоначальным требованиям (выберите предыдущего «двоюродного брата» для узла). Хотя ваше первое заявление о ваших требованиях было подкреплено вашим примером, похоже, что ваш пример устарел или по другим причинам неадекватен, чтобы охватить то, что следует, а что не следует выбирать в целом.
хм.... я не уверен.... пример на самом деле показывает, что я хочу братьев и сестер и двоюродных братьев и сестер, я не упоминаю n-ных двоюродных братьев и сестер, это было расплывчато, но я не вижу противоречия.
Я открыл для себя этот вопрос только сейчас, и меня смущает слово «кузен». Два узла, выбранные соответственно: /root/level1/level2[@id='6'] и /root/level1/level2[@id='5'], на самом деле являются братьями и сестрами (братом или сестрой), а не «двоюродными братьями». Не могли бы вы отредактировать вопрос, чтобы устранить эту путаницу?
в названии написано «двоюродные братья (включая братьев и сестер)».





Я не могу придумать простое выражение, мне интересно, реализует ли следующий код, использующий path и xsl:evaluate, это требование:
<?xml version = "1.0" encoding = "utf-8"?>
<xsl:stylesheet xmlns:xsl = "http://www.w3.org/1999/XSL/Transform"
version = "3.0"
xmlns:xs = "http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes = "#all"
xmlns:mf = "http://example.com/mf"
expand-text = "yes">
<xsl:function name = "mf:cousins" as = "node()*">
<xsl:param name = "node" as = "node()"/>
<xsl:variable name = "path" select = "path($node)"/>
<xsl:variable name = "path-without-predicates" select = "replace($path, '\[[0-9]+\]', '')"/>
<xsl:variable name = "cousins" as = "node()*">
<xsl:evaluate context-item = "$node" xpath = "$path-without-predicates"/>
</xsl:variable>
<xsl:sequence select = "$cousins except $node"/>
</xsl:function>
<xsl:function name = "mf:preceding-cousins" as = "node()*">
<xsl:param name = "node" as = "node()"/>
<xsl:sequence select = "mf:cousins($node)[. << $node]"/>
</xsl:function>
<xsl:template match = "level2[@id = 6] | level2[@id = 5]">
<xsl:comment>preceding cousin: {mf:preceding-cousins(.)[last()]=>serialize(map{ 'method' : 'xml' })}</xsl:comment>
<xsl:next-match/>
</xsl:template>
<xsl:output method = "xml" indent = "no"/>
<xsl:mode on-no-match = "shallow-copy"/>
</xsl:stylesheet>
Это немного похоже на «взлом» использования функции path, затем замены строки и xsl:evaluate, но я не вижу простого прямого XPath.
фу... противно... без обид... умно... но это из тех, что кладешь в библиотеку и надеешься, что это правильно.
я отмечу это как ответ, если мы не сможем найти что-то получше... опция <xsl:иначе>
Подождите, пока другие, такие как Майкл Кей, выскажут свое мнение/предложения о том, как к этому подойти.
вы выиграли по умолчанию!... это правильная интерпретация проблемы и общее решение, ответы Майкла Кея были заманчиво элегантными, но не n-ми родственниками, а решение, основанное на глубине, хотя и правильное, не дает детализации сопоставления пути .
Если я правильно понимаю ваши требования, XPath 1.0 — это все, что вам нужно.
Учитывая ваш входной XML, это XSLT 2.0 (для тестовой программы ключ XPath равен 1.0),
<?xml version = "1.0" encoding = "utf-8"?>
<xsl:stylesheet xmlns:xsl = "http://www.w3.org/1999/XSL/Transform"
version = "2.0">
<xsl:output indent = "yes"/>
<xsl:template match = "/">
<results>
<xsl:for-each select = "//level2">
<case id = "{@id}">
<xsl:variable name = "depth" select = "count(ancestor::*)"/>
<xsl:sequence select = "preceding::level2[count(ancestor::*) = $depth][1]"/>
</case>
</xsl:for-each>
</results>
</xsl:template>
</xsl:stylesheet>
выдает эти результаты испытаний,
<?xml version = "1.0" encoding = "UTF-8"?>
<results>
<case num = "1"/>
<case num = "2">
<level2 id = "1"/>
</case>
<case num = "3">
<level2 id = "2"/>
</case>
<case num = "4">
<level2 id = "3"/>
</case>
<case num = "5">
<level2 id = "4"/>
</case>
<case num = "9"/>
<case num = "6">
<level2 id = "5">
<level2 id = "9"/>
</level2>
</case>
<case num = "7">
<level2 id = "6"/>
</case>
<case num = "8">
<level2 id = "7"/>
</case>
</results>
который охватывает два проблемных случая и дает приемлемые результаты для всех стартовых элементов level2.
здорово, это технически правильно, хотя использование глубины вместо пути немного пугает.
Я считаю, что явное использование глубины проясняет ваши конкретные требования к двоюродному брату, но посмотрите умный способ Майкла Кея представить отношения без явной глубины. Мне очень нравится его решение своей элегантностью.
на самом деле я рассматриваю это решение
Я передумал, думаю, это ДЕЙСТВИТЕЛЬНО соответствует духу вопроса, если мне нужно применить дополнительные предикаты, я думаю, это отдельный вопрос.
Я думаю, я бы, наверное, сделал
let $this := .
let $precedingCousins := (../../*/*)[. << $this]
return $precedingCousins[last()]
или его эквивалент в XSLT.
эээ... что значит "<<"? (я посмотрю)
Я предполагаю, что это означает все, что предшествует порядку документов.
@MrDatKookerellaLtd, да, см. w3.org/TR/xpath-31/#id-node-comparisons «Сравнение с оператором << возвращает true, если левый узел операнда предшествует правому узлу операнда в порядке документа; в противном случае он возвращает ложь.". Я также использовал это в своем коде (в XSLT вы используете <<).
моя единственная проблема в том, что он не работает в xslt 1.0, но, учитывая, что я явно исключил это, было бы немного подло не отметить это как ответ... ключ - '<<', о котором я никогда не знал.. .(для меня это композиция функций ;-))
@MartinHonnen, честно говоря, твое решение так разболело мне голову, что я не понял его нюансов
ах, извините... это тоже "неправильно" в том смысле, что я имел в виду двоюродных братьев и сестер... извините, если вопрос не ясен
хотя я думаю, что использование оператора << означает, что на практике я могу это сделать
Решение XPath 1.0 будет
(preceding-sibling::* | ../preceding-sibling::*/*)[last()]
я бы отметил это как ответ, но «кузены» были всего лишь примером использования, и я нахожу это в целом ошеломляющим, как и мой вариант «parent::foo[parent::bar]»... на самом деле я думаю я нахожу это более ошеломляющим.
ах, хотя моя родительская вещь на самом деле не занимается двоюродными братьями, она выполняет сопоставление сегментов путей
Я думаю, это должно быть [1], а не [last()]?... но я ломаю голову
на самом деле я думаю, что это неправильно (хотя мой вопрос не ясен)... это касается братьев и сестер и двоюродных братьев и сестер... Я имел в виду n-ных двоюродных братьев и сестер, где 0-ю двоюродные братья и сестры являются братьями и сестрами
Должно быть [last()], а не [1], потому что результат выражения объединения находится в порядке документа.
ах ок, конечно
Чтобы сделать N-ых двоюродных братьев, где N — параметр, предоставляемый динамически, я бы написал рекурсивную функцию, которая обязательно делает ее XPath 2.0+.
В нотации XQuery
declare function my:cousins($node as node(),
$degree as xs:integer) as node()* {
if ($degree eq 0)
then $node/../*
else my:cousins($node/.., $degree - 1)/*
}
Честно говоря, ответ Майкла Кея в основном там, за исключением того, что он проверяет уровень напрямую, мне бы хотелось, чтобы это был вариант, который игнорировал уровень и просто возвращал всех кузенов.
<xsl:function name = "kooks:cousins" as = "node()*">
<xsl:param name = "node" as = "node()"/>
<xsl:sequence select = "$node/../*"/>
<xsl:if test = "$node/..">
<xsl:sequence select = "kooks:cousins($node/..)/*"/>
</xsl:if>
</xsl:function>
обратите внимание, что, как и в другом ответе, предиката соответствия нет, он просто соответствует всем двоюродным братьям элементов, а также не обрабатывает предыдущий/следующий, но это можно сделать с помощью '<<'
Если вы знаете полный путь, то
(/foo/bar/wibble)[. << $this]
это просто и понятно
где $this является членом "/foo/bar/wibble"
это МОЖЕТ работать в xslt 1.0, я его не использовал, и порядок меня беспокоит из-за использования набора узлов (для следования вам может потребоваться изменить порядок операндов объединения), и согласно ответу М. Кея заказ – это заказ документа.
<xsl:template mode = "preceding-cousin" match = "/"/>
<xsl:template mode = "preceding-cousin" match = "*">
<xsl:variable name = "precedingUnclesRTF">
<xsl:apply-templates mode = "preceding-cousin" select = ".."/>
</xsl:variable>
<xsl:variable name = "precedingUncles" select = "msxsl:node-set($precedingUnclesRTF)/*"/>
<xsl:copy-of select = "$precedingUncles/* | preceding-sibling::*"/>
</xsl:template>
и ответ на глубину также работает в XSLT 1.0, он проще, чем приведенный выше, и не будет страдать от экспоненциального копирования узлов, как описано выше, но обрабатывает все предыдущие узлы в документе, он работает во всех версиях XSLT и не является рекурсивным. (так что не уничтожим стек), хотя я думаю, что ответ рекурсивной функции можно переписать как свертку или итерацию xsl.
<xsl:template mode = "preceding-cousin2" match = "*">
<xsl:variable name = "depth" select = "count(ancestor::*)"/>
<xsl:copy-of select = "preceding::*[count(ancestor::*) = $depth]"/>
</xsl:template>
Я не уверен, что существует простое решение. И ищете ли вы каких-либо «двоюродных братьев»
level2или только тех, кто имеет ту же структуру предков (как в вашем примере, который вы проверяете наroot/level1/level2)?