xslt: выбрать уникальный узел через промежуточный ссылочный узел?

XSLT 2.

Привет, у меня есть xml, который имеет 3 узла, названных с точки зрения «детей»: «Дети», «Отцы» и «Отцы-матери». Начиная с узла «Отцы», мне нужно найти дочерний узел MothersFather на основе идентификаторов в дочерних узлах (дочерний узел является промежуточной ссылкой, соединяющей два других).

Итак, для каждого Отца получите отдельных Матери Отцов своих детей - это не люди, у отца могут быть сотни детей, но только двадцать или около того связанных Матери Отцов :)

Упрощенная версия XML (в реальной жизни имеется около 80 узлов-отцов, 3000 узлов-потомков и 400 узлов-отцов-матерей):

<t>
<Children>
    <Child>
        <ID>1</ID>
        <FathersID>100</FathersID>
        <MothersFatherID>200</MothersFatherID>    
    </Child>
    <Child>
        <ID>2</ID>
        <FathersID>100</FathersID>
        <MothersFatherID>201</MothersFatherID>    
    </Child>
    <Child>
        <ID>3</ID>
        <FathersID>100</FathersID>
        <MothersFatherID>202</MothersFatherID>    
    </Child>
    <Child>
        <ID>4</ID>
        <FathersID>100</FathersID>
        <MothersFatherID>201</MothersFatherID>    
    </Child>
    <Child>
        <ID>5</ID>
        <FathersID>101</FathersID>
        <MothersFatherID>201</MothersFatherID>    
    </Child>
</Children>
<Fathers>
    <Father>
        <ID>100</ID>
    </Father>
    <Father>
        <ID>101</ID>
    </Father>
</Fathers>
<MothersFathers>
    <MothersFather>
        <ID>200</ID>
    </MothersFather>
    <MothersFather>
        <ID>201</ID>
    </MothersFather>
    <MothersFather>
        <ID>202</ID>
    </MothersFather>
</MothersFathers>        
</t>

Мой xslt выглядит так:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:key name="kFathersChildren" match="Child" use="FathersID"/>

    <xsl:template match="/">
        <xsl:apply-templates select="//Fathers"></xsl:apply-templates>
    </xsl:template>

    <xsl:template match="Fathers">
        <xsl:apply-templates select="Father"></xsl:apply-templates>
    </xsl:template>

    <xsl:template match="Father">
        <xsl:text>&#10;FATHER: ID=</xsl:text><xsl:value-of select="ID"/>
        <!-- Now show all this fathers childrens maternal grandfathers based on the ID in the Child node -->

        <!--TRY 1: this works, as in gets the right nodes, but doesn't do distinct values....--> 
        <xsl:for-each select="key('kFathersChildren', ID)">  <!-- get the fathers children --> 
            <xsl:text>&#10; found child: current MFid=</xsl:text><xsl:value-of select="current()/MothersFatherID"/>
            <xsl:text> ID=</xsl:text><xsl:value-of select="ID"/>
            <xsl:apply-templates select="//MothersFathers/MothersFather[ID=current()/MothersFatherID]"></xsl:apply-templates>
        </xsl:for-each>

        <!-- *** THIS IS WHERE I GET LOST??? - Do the same thing but only get distinct MothersFatherID's... -->

        <!--TRY 2: note- won't compile in current state... -->
        <xsl:for-each select="distinct-values(key('kFathersChildren', ID)[MothersFatherID])">  
            <xsl:text>&#10;  Distinct MothersFatherID ???? - don't know what to select </xsl:text><xsl:value-of select="."/>
            <xsl:apply-templates select="//MothersFathers/MothersFather[ID=??????????"></xsl:apply-templates>
        </xsl:for-each>
    </xsl:template>

    <xsl:template match="//MothersFathers/MothersFather">
        <xsl:text>&#10;      IN MothersFather template... ID=</xsl:text><xsl:value-of select="ID"/>
    </xsl:template>
</xsl:stylesheet>

В Try 1 я могу получить все узлы и MothersFatherID. Результат Try1:

FATHER: ID=100
 found child: current MFid=200 ID=1
      IN MothersFather template... ID=200
 found child: current MFid=201 ID=2
      IN MothersFather template... ID=201
 found child: current MFid=202 ID=3
      IN MothersFather template... ID=202
 found child: current MFid=201 ID=4
      IN MothersFather template... ID=201
FATHER: ID=101
 found child: current MFid=201 ID=5
      IN MothersFather template... ID=201

В Try2, где я выбираю «отдельное значение», я хотел бы получить следующий результат:

FATHER: ID=100
      IN MothersFather template... ID=201
      IN MothersFather template... ID=200
      IN MothersFather template... ID=202
FATHER: ID=101
      IN MothersFather template... ID=201

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

НО я не могу понять, что я должен использовать для ссылки на уникальный MothersFatherID, который нужно передать вызову «apply-templates».

Независимо от того, что я пробовал, я получаю варианты ошибок, например: Required item type of first operand of '/' is node(); supplied value has item type xs:anyAtomicType или Axis step child::element('':MothersFatherID) cannot be used here: the context item is an atomic value. Я думаю, они имеют в виду, что я пытаюсь выбрать узлы, в которых используется строковое значение, или наоборот .... может быть, я неправильно использую функцию отличного значения ()?

Кто-нибудь может пролить свет на то, как это сделать, пожалуйста? (Я продолжаю надеяться, что у этого xslt будет какой-то дзен-момент просветления, когда я не буду зацикливаться на подобных вещах).

Вдобавок, как только я это сделаю, я захочу, чтобы MothersFather в отсортированном порядке для каждого отца - в реальном xml есть `` Имя '', связанное с каждым `` идентификатором '' - надеюсь, выражение для каждого `` сортировки '' будет аналогичная ссылка на то, что устраняет вышеуказанную проблему?

Спасибо за ваше время. Брайс.

РЕДАКТИРОВАТЬ:

Ух ты!! Спасибо за ответ Димитр. Я просмотрел это и надеялся, что вы сможете немного разбить его для меня, поскольку я не полностью его грок? Ответ был такой:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text"/>

 <xsl:key name="kMFByFId" match="MothersFatherID"
          use="../FathersID"/>

 <xsl:key name="kMFById" match="MothersFather" use="ID"/>

 <xsl:key name="ChildByFIdAndMFId" match="Child"
  use="concat(FathersID, '+', MothersFatherID)"/>

 <xsl:template match="Children|MothersFathers|text()"/>

 <xsl:template match="Father">
   Father ID=<xsl:value-of select="ID"/>
  <xsl:apply-templates select=
   "key('kMFById',
         key('kMFByFId', ID)
          [generate-id(..)
          =
           generate-id(key('ChildByFIdAndMFId',
                            concat(../FathersID,'+',.)
                          )[1]
                       )
          ]
        )">
     <xsl:sort select="ID" data-type="number"/>
   </xsl:apply-templates>
 </xsl:template>

 <xsl:template match="MothersFather">
      MothersFather ID=<xsl:value-of select="ID"/>
 </xsl:template>
</xsl:stylesheet>

Я использую задействованные ключи.

Строка <xsl:template match="Children|MothersFathers|text()"/> - как эта линия делает свое дело? Если я пропущу его через отладчик, он просто перескочит прямо за эту строку. Если я прокомментирую это, будет много лишнего вывода, источник которого я не вижу.

И строка apply-templates, которая дает узел MothersFather <xsl:apply-templates select= "key('kMFById', key('kMFByFId', ID)[generate-id(..) =
generate-id(key('ChildByFIdAndMFId', concat(../FathersID,'+',.))[1] ) ] )">
- я пытался разобрать это на бумаге, чтобы увидеть магию, но не совсем понял. Это что-то вроде key('kMFById', key('kMFByFId', ID) означает получение соответствующих узлов MothersFather по текущему идентификатору отца, где [generate-id(..) сгенерированный идентификатор '(точка точка)' - что-то связанное с родительским узлом? который из? равен сгенерированному идентификатору на основе ключа ChildByFIdAndMFId [1] - получает ли этот 1 только первое вхождение совпадающего сгенерированного идентификатора, тем самым давая мое отдельное значение?

(Этот ответ Димитра также очень похож на ответ Дж. Л. Риши. Кажется, его вид работает, я что-то упускаю, Димитр?)

С уважением, Брайс.


person user1840734    schedule 25.01.2013    source источник
comment
user1840734, Решение JLRishe правильно сортирует? Да, сортировка выполняется правильно сейчас. Но два дня назад, когда я опубликовал свой ответ, сортировка выполнялась неправильно. С тех пор он дважды редактировал свой ответ и исправил ошибку сортировки. Что касается этого ответа Димитра, он также очень похож на ответ JLRishie, я бы сказал, что они / были существенно разными в том, что мое решение работало правильно с самого начала, в то время как сортировка JLRishe была неправильной. В отличие от других, я всегда проверяю свои решения. Другое отличие заключается в краткости и удобочитаемости - в обоих случаях мое решение явно лучше.   -  person Dimitre Novatchev    schedule 28.01.2013
comment
@DimitreNovatchev Я не понимаю, почему вы все время говорите, что ваш ответ короче. У меня 23 строки (изначально 20), а у вас 34. Возможно, у вас на 10 или 15 меньше отображаемых символов, и вы переместили какой-то текст влево, но вряд ли этим стоит хвастаться. Вы подразумеваете, что я не проверял свой ответ, но я проверил - вывод в моем исходном сообщении был фактическим выводом и соответствовал запрошенному выводу вопроса. Это правда, что я недостаточно продумал возможность сортировки по отдельному полю, но я не буду повторять эту ошибку.   -  person JLRishe    schedule 28.01.2013
comment
@JLRishe, да разве это не очевидно? Ваше преобразование занимает очень длинные строки, что делает его нечитаемым - я уверен, что вы могли бы поместить все преобразование в одну строку ... Когда я форматирую ваше преобразование так же, как мое форматируется - чтобы сделать его читабельным, он не занимает 23 строки, но 37 строк. И, как мы знаем, 37 ›34. Если бы я использовал ваш стиль форматирования, чего я никогда не буду, моим ответом было бы всего 19 строк кода. Если вы не перестанете давать нечитаемые ответы, они будут бесполезны для читателей, несмотря на реальную ценность, которую, я считаю, имеют эти ответы - просто из-за форматирования.   -  person Dimitre Novatchev    schedule 28.01.2013
comment
@DimitreNovatchev Думаю, у нас с вами разные представления о том, что считать нечитаемым и бесполезным. В обычном редакторе кода строки в моем ответе полностью отображаются и находятся в пределах читабельности (ИМХО). Тем не менее, я приложу постоянные усилия, чтобы в будущем мои реплики не были слишком длинными.   -  person JLRishe    schedule 28.01.2013
comment
@JLRishe, Неверно, что в обычном редакторе кода строки в моем ответе полностью отображаются и находятся в пределах читабельности. Я использую XSLT IDE (его обычная редакционная часть) и даже выделяю ему весь экран, значительная часть кода требует прокрутки вправо для отображения. Однако прокрутка вправо делает невидимой крайнюю левую часть кода. Ваш код доходит до 147 позиций вправо - это ни в коем случае не является полностью отображаемым и находится в пределах читабельности. Я приветствую ваше решение улучшить это.   -  person Dimitre Novatchev    schedule 28.01.2013
comment
@DimitreNovatchev Теперь все исправлено. Думаю, ваш экран немного меньше того, который я использую. На моем 19-м экране с разрешением 1920x1200 даже эта 147-символьная строка занимала только 2/3 ширины экрана. Мне регулярно приходится проверять XSLT шириной до 300 символов, так что 147 для меня не очень много.   -  person JLRishe    schedule 28.01.2013
comment
@JLRishe, теперь я могу видеть весь код без прокрутки - хороший первый шаг. Однако код по-прежнему не совсем читается - вы можете использовать пустые строки и вертикальное выравнивание, чтобы улучшить читаемость. Это особенно полезно для новичков в программировании XSLT, но даже опытные программисты хмурятся, когда видят 4 скобки и три квадратные скобки на одной строке.   -  person Dimitre Novatchev    schedule 28.01.2013


Ответы (2)


Это преобразование - короче, хорошо форматируется и читается без горизонтальной / вертикальной прокрутки. Кроме того, он правильно применяет сортировку, в отличие от других ответов:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text"/>

 <xsl:key name="kMFByFId" match="MothersFatherID"
          use="../FathersID"/>

 <xsl:key name="kMFById" match="MothersFather" use="ID"/>

 <xsl:key name="ChildByFIdAndMFId" match="Child"
  use="concat(FathersID, '+', MothersFatherID)"/>

 <xsl:template match="Children|MothersFathers|text()"/>

 <xsl:template match="Father">
   Father ID=<xsl:value-of select="ID"/>
  <xsl:apply-templates select=
   "key('kMFById',
         key('kMFByFId', ID)
          [generate-id(..)
          =
           generate-id(key('ChildByFIdAndMFId',
                            concat(../FathersID,'+',.)
                          )[1]
                       )
          ]
        )">
     <xsl:sort select="ID" data-type="number"/>
   </xsl:apply-templates>
 </xsl:template>

 <xsl:template match="MothersFather">
      MothersFather ID=<xsl:value-of select="ID"/>
 </xsl:template>
</xsl:stylesheet>

при применении к этому XML-документу (предоставлен, но немного перетасован для проверки правильности сортировки):

<t>
<Children>
    <Child>
        <ID>2</ID>
        <FathersID>100</FathersID>
        <MothersFatherID>201</MothersFatherID>
    </Child>
    <Child>
        <ID>1</ID>
        <FathersID>100</FathersID>
        <MothersFatherID>200</MothersFatherID>
    </Child>
    <Child>
        <ID>3</ID>
        <FathersID>100</FathersID>
        <MothersFatherID>202</MothersFatherID>
    </Child>
    <Child>
        <ID>4</ID>
        <FathersID>100</FathersID>
        <MothersFatherID>201</MothersFatherID>
    </Child>
    <Child>
        <ID>5</ID>
        <FathersID>101</FathersID>
        <MothersFatherID>201</MothersFatherID>
    </Child>
</Children>
<Fathers>
    <Father>
        <ID>100</ID>
    </Father>
    <Father>
        <ID>101</ID>
    </Father>
</Fathers>
<MothersFathers>
    <MothersFather>
        <ID>200</ID>
    </MothersFather>
    <MothersFather>
        <ID>201</ID>
    </MothersFather>
    <MothersFather>
        <ID>202</ID>
    </MothersFather>
</MothersFathers>
</t>

дает желаемый правильный результат:

   Father ID=100
      MothersFather ID=200
      MothersFather ID=201
      MothersFather ID=202
   Father ID=101
      MothersFather ID=201

Обратите внимание:

Преобразование выполняется правильно как с процессором XSLT 1.0, так и с процессором XSLT 2.0.


Обновление:

OP отредактировал вопрос, задав несколько вопросов об этом решении:

Я использую задействованные ключи.

Строка <xsl:template match="Children|MothersFathers|text()"/> - как эта строка делает свое дело? Если я пропущу его через отладчик, он просто перескочит прямо за эту строку. Если я прокомментирую это, будет много лишнего вывода, источник которого я не вижу.

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

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

Чтобы избежать этого, я предоставил шаблон, соответствующий элементам thode. Это отменяет (подавляет) встроенный шаблон. Поскольку этот шаблон не имеет тела, вывод не производится.

И строка apply-templates, которая дает MothersFather узел <xsl:apply-templates select= "key('kMFById', key('kMFByFId', ID)[generate-id(..) = generate-id(key('ChildByFIdAndMFId', concat(../FathersID,'+',.))[1] ) ] )"> - я пытался разобрать это на бумаге, чтобы увидеть волшебство, но не совсем понял. Это что-то вроде key('kMFById', key('kMFByFId', ID) означает получение совпадающих MothersFather узлов по текущему Father ID, где [generate-id(..) the generated id of '(dot dot)' - что-то делать с родительским узлом? который из? равно сгенерированному идентификатору на основе ChildByFIdAndMFId key [1] - получает ли этот 1 только первое вхождение совпадающего сгенерированного идентификатора, тем самым давая мое отдельное значение?

Ваш вопрос касается этого фрагмента кода:

  <xsl:apply-templates select=
   "key('kMFById',
         key('kMFByFId', ID)
          [generate-id(..)
          =
           generate-id(key('ChildByFIdAndMFId',
                            concat(../FathersID,'+',.)
                          )[1]
                       )
          ]
        )">
     <xsl:sort select="ID" data-type="number"/>
   </xsl:apply-templates>

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

По сути, приведенный выше фрагмент кода говорит:

обрабатывать все MothersFather элементы, которые являются первым таким элементом, который является родственником FathersID, имеющего то же значение, что и ID текущего (Father) узла.

person Dimitre Novatchev    schedule 25.01.2013
comment
Спасибо, Димитр, я отредактировал вопрос выше, пытаясь лучше понять твой ответ. Извините за плохое форматирование, я редактировал на маленьком экране и просто предполагал, что панель будет увеличиваться на большом экране - я вижу, что это не так. Постараюсь посмотреть его в будущем, чтобы код оставался видимым без прокрутки .... - person user1840734; 28.01.2013
comment
Я надеялся, что вы прокомментируете приведенное выше редактирование исходного вопроса, ищущего понимания. Еще одна вещь, которую я тоже не понимаю в вашем ответе: ключ <xsl:key name="kMFByFId" match="MothersFatherID" use="../FathersID"/>. Что такое ../FathersID. Я думал, что .. относится к родительскому элементу, но я не могу понять, как оказаться на более низком узле, где находится FathersID, поскольку «Дочерний» узел, где обнаружен FathersID, уже является самым нижним узлом? - person user1840734; 28.01.2013
comment
@ user1840734, ../FathersID при оценке с помощью узла контекста MothersFatherID выбирает всех FathersID братьев и сестер (мы знаем, что это будет только один элемент) узла контекста. Чтобы подробно описать это: 1) .. изменяет контекстный узел на родительский. 2) ../FathersID означает выбрать всех FathersID дочерних элементов моего родителя - и в результате будут выбраны все FathersID братьев и сестер. Я мог бы просто написать: preceding-sibling::FathersID, но я не был уверен, что FathersID всегда будет предшествовать MothersFatherID. - person Dimitre Novatchev; 28.01.2013
comment
@ user1840734, я обновил ответ и ответил на ваши новые вопросы. - person Dimitre Novatchev; 28.01.2013

Я считаю, что это должно делать то, что вы хотите сделать:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text" />
  <xsl:key name="kFathersChildren" match="Child" 
           use="concat(FathersID, ' - ', MothersFatherID)"/>
  <xsl:key name="kChildByFatherId" match="Child" use="FathersID"/>
  <xsl:key name="kMothersFatherById" match="MothersFather" use="ID" />

  <xsl:template match="text() | Children | MothersFathers" />

  <xsl:template match="Father">
    <xsl:value-of select="concat('&#10;FATHER: ID=', ID)" />

    <xsl:apply-templates 
      select="key('kMothersFatherById', 
                 key('kChildByFatherId', ID)
                   [generate-id() = 
                     generate-id(
                       key('kFathersChildren', 
                          concat(FathersID, ' - ', MothersFatherID)
                           )[1])
                   ]/MothersFatherID)">
      <xsl:sort select="ID" data-type="number" />
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="MothersFather">
    <xsl:value-of select="concat('&#10;      IN MothersFather template... ID=', ID)"/>
  </xsl:template>
</xsl:stylesheet>

При запуске с образцом ввода это дает:

FATHER: ID=100
      IN MothersFather template... ID=200
      IN MothersFather template... ID=201
      IN MothersFather template... ID=202
FATHER: ID=101
      IN MothersFather template... ID=201
person JLRishe    schedule 25.01.2013
comment
Спасибо, JLRishie, я пошел с ответом Димитра, но они оба очень похожи. Ваше здоровье. - person user1840734; 28.01.2013