Выбор предыдущих двоюродных братьев и сестер (включая братьев и сестер)

У меня часто возникает проблема с выбором предыдущего «двоюродного брата» узла.

Например:

<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:: выше и сопоставил полный путь к корню, чтобы гарантировать совпадение всех двоюродных братьев.

(конечно, предполагается, что вы знаете полный путь, хотя решение Мартина Хонненса в целом работает).

Я не уверен, что существует простое решение. И ищете ли вы каких-либо «двоюродных братьев» level2 или только тех, кто имеет ту же структуру предков (как в вашем примере, который вы проверяете на root/level1/level2)?

Martin Honnen 20.02.2024 13:32

это то, что я делаю в интересах "пересечь /root/level1/level2"

MrD at KookerellaLtd 20.02.2024 13:34

Да, я это вижу, мне просто интересно, существовал ли <root><foo><level2/></foo><level1><level2/></level1></root>, считали ли вы или не считали level2 внутри элемента foo двоюродным братом level2 внутри элемента level1?

Martin Honnen 20.02.2024 13:37

(в этом контексте) двоюродные братья - это их положение в иерархии, а не их имя, возможно, пример не очень удачный… то, что я сделал, работает… но кажется довольно запутанным, чтобы сделать что-то, казалось бы, простое. Другая проблема заключается в том, что пересечение не работает в XPath 1.0... возможно, это другой вопрос.

MrD at KookerellaLtd 20.02.2024 14:19

Ваши новые требования (я ищу всех «двоюродных братьев и сестер», где 0-двоюродные братья и сестры) неясны и противоречат вашим первоначальным требованиям (выберите предыдущего «двоюродного брата» для узла). Хотя ваше первое заявление о ваших требованиях было подкреплено вашим примером, похоже, что ваш пример устарел или по другим причинам неадекватен, чтобы охватить то, что следует, а что не следует выбирать в целом.

kjhughes 20.02.2024 18:06

хм.... я не уверен.... пример на самом деле показывает, что я хочу братьев и сестер и двоюродных братьев и сестер, я не упоминаю n-ных двоюродных братьев и сестер, это было расплывчато, но я не вижу противоречия.

MrD at KookerellaLtd 21.02.2024 16:11

Я открыл для себя этот вопрос только сейчас, и меня смущает слово «кузен». Два узла, выбранные соответственно: /root/level1/level2[@id='6'] и /root/level1/level2[@id='5'], на самом деле являются братьями и сестрами (братом или сестрой), а не «двоюродными братьями». Не могли бы вы отредактировать вопрос, чтобы устранить эту путаницу?

Dimitre Novatchev 14.04.2024 00:15

в названии написано «двоюродные братья (включая братьев и сестер)».

MrD at KookerellaLtd 15.04.2024 10:38
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
8
127
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Я не могу придумать простое выражение, мне интересно, реализует ли следующий код, использующий 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)[. &lt;&lt; $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.

фу... противно... без обид... умно... но это из тех, что кладешь в библиотеку и надеешься, что это правильно.

MrD at KookerellaLtd 20.02.2024 15:36

я отмечу это как ответ, если мы не сможем найти что-то получше... опция <xsl:иначе>

MrD at KookerellaLtd 20.02.2024 15:38

Подождите, пока другие, такие как Майкл Кей, выскажут свое мнение/предложения о том, как к этому подойти.

Martin Honnen 20.02.2024 15:39

вы выиграли по умолчанию!... это правильная интерпретация проблемы и общее решение, ответы Майкла Кея были заманчиво элегантными, но не n-ми родственниками, а решение, основанное на глубине, хотя и правильное, не дает детализации сопоставления пути .

MrD at KookerellaLtd 20.02.2024 17:54
Ответ принят как подходящий

Если я правильно понимаю ваши требования, 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.

здорово, это технически правильно, хотя использование глубины вместо пути немного пугает.

MrD at KookerellaLtd 20.02.2024 16:19

Я считаю, что явное использование глубины проясняет ваши конкретные требования к двоюродному брату, но посмотрите умный способ Майкла Кея представить отношения без явной глубины. Мне очень нравится его решение своей элегантностью.

kjhughes 20.02.2024 17:04

на самом деле я рассматриваю это решение

MrD at KookerellaLtd 21.02.2024 16:16

Я передумал, думаю, это ДЕЙСТВИТЕЛЬНО соответствует духу вопроса, если мне нужно применить дополнительные предикаты, я думаю, это отдельный вопрос.

MrD at KookerellaLtd 21.02.2024 16:25

Я думаю, я бы, наверное, сделал

let $this := .
let $precedingCousins := (../../*/*)[. << $this]
return $precedingCousins[last()]

или его эквивалент в XSLT.

эээ... что значит "<<"? (я посмотрю)

MrD at KookerellaLtd 20.02.2024 16:29

Я предполагаю, что это означает все, что предшествует порядку документов.

MrD at KookerellaLtd 20.02.2024 16:31

@MrDatKookerellaLtd, да, см. w3.org/TR/xpath-31/#id-node-comparisons «Сравнение с оператором << возвращает true, если левый узел операнда предшествует правому узлу операнда в порядке документа; в противном случае он возвращает ложь.". Я также использовал это в своем коде (в XSLT вы используете &lt;&lt;).

Martin Honnen 20.02.2024 16:32

моя единственная проблема в том, что он не работает в xslt 1.0, но, учитывая, что я явно исключил это, было бы немного подло не отметить это как ответ... ключ - '<<', о котором я никогда не знал.. .(для меня это композиция функций ;-))

MrD at KookerellaLtd 20.02.2024 16:44

@MartinHonnen, честно говоря, твое решение так разболело мне голову, что я не понял его нюансов

MrD at KookerellaLtd 20.02.2024 16:45

ах, извините... это тоже "неправильно" в том смысле, что я имел в виду двоюродных братьев и сестер... извините, если вопрос не ясен

MrD at KookerellaLtd 20.02.2024 17:43

хотя я думаю, что использование оператора << означает, что на практике я могу это сделать

MrD at KookerellaLtd 20.02.2024 17:44

Решение XPath 1.0 будет

(preceding-sibling::* | ../preceding-sibling::*/*)[last()]

я бы отметил это как ответ, но «кузены» были всего лишь примером использования, и я нахожу это в целом ошеломляющим, как и мой вариант «parent::foo[parent::bar]»... на самом деле я думаю я нахожу это более ошеломляющим.

MrD at KookerellaLtd 20.02.2024 17:33

ах, хотя моя родительская вещь на самом деле не занимается двоюродными братьями, она выполняет сопоставление сегментов путей

MrD at KookerellaLtd 20.02.2024 17:36

Я думаю, это должно быть [1], а не [last()]?... но я ломаю голову

MrD at KookerellaLtd 20.02.2024 17:39

на самом деле я думаю, что это неправильно (хотя мой вопрос не ясен)... это касается братьев и сестер и двоюродных братьев и сестер... Я имел в виду n-ных двоюродных братьев и сестер, где 0-ю двоюродные братья и сестры являются братьями и сестрами

MrD at KookerellaLtd 20.02.2024 17:42

Должно быть [last()], а не [1], потому что результат выражения объединения находится в порядке документа.

Michael Kay 20.02.2024 18:16

ах ок, конечно

MrD at KookerellaLtd 21.02.2024 10:02

Чтобы сделать 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>

Другие вопросы по теме