XSLT 2.0: Преобразование элемента, включая всех предков, без использования foreach

В настоящее время я пытаюсь преобразовать XML-файл в несколько выходных XML-файлов, взяв определенный его элемент и пройдя предков выбранных элементов.

Например:

Исходный XML:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <level_a> 
    <level_b> 
      <level_c> 
        <level_d> 
          <level_e/> 
        </level_d> 
      </level_c> 
    </level_b> 
    <level_b> 
      <level_c> 
        <level_d> 
          <level_e/> 
        </level_d> 
      </level_c> 
    </level_b> 
    <level_b> 
      <level_c> 
        <level_d> 
          <level_e/> 
          <level_e/> 
          <level_e/> 
        </level_d> 
      </level_c> 
    </level_b> 
    <level_b> 
      <level_c> 
        <level_d> 
          <level_d> 
            <level_e/> 
          </level_d> 
        </level_d> 
      </level_c> 
    </level_b>
  </level_a>
</root>

Выходные XML-файлы:

1:

 <output>
        <output level="b"> 
          <output level="c"> 
            <output level="d"> 
              <output level="e"/> 
            </output> 
          </output> 
        </output> 
 </output>

2:

 <output>
        <output level="b"> 
          <output level="c"> 
            <output level="d"> 
              <output level="e"/> 
            </output> 
          </output> 
        </output> 
 </output>

3:

 <output>
        <output level="b"> 
          <output level="c"> 
            <output level="d"> 
              <output level="e"/>
              <output level="e"/> 
              <output level="e"/>  
            </output> 
          </output> 
        </output> 
 </output>

4:

 <output>
      <output level="b"> 
        <output level="c"> 
          <output level="d"> 
            <output level="d"> 
              <output level="e"/> 
            </output> 
          </output> 
        </output> 
      </output>
 </output>

Я знаю, что могу что-то сделать в строке создания последовательности со следующим XPath:

(//level_d[not(level_d)])[last()]/ancestor::*[self::level_c|self::level_b]

а затем повторите его с помощью foreach, и я получу свои результаты в порядке документа.

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

Любая подсказка приветствуется

С наилучшими пожеланиями
Влакс


person Vlax    schedule 04.06.2013    source источник
comment
Не могли бы вы просто сопоставить /*/* и создать оттуда результирующий документ?   -  person Daniel Haley    schedule 05.06.2013
comment
Возможно, вам придется уточнить вопрос (или задать другой); из этого вопроса совсем не ясно, почему подход Дэниела Хейли не решает вашу проблему. Вы получаете баллы за упрощение, избавление от лишних деталей в постановке вопроса, но помните совет Эйнштейна: как можно проще, но не проще!   -  person C. M. Sperberg-McQueen    schedule 05.06.2013
comment
Теперь я изменил пример, который был действительно слишком упрощен, фактическая версия должна отражать мой реальный источник xml. На самом деле xml не обязательно должен иметь 2 элемента после корневого элемента, он может иметь любое число или только один. Единственная уверенность, которая у меня есть, заключается в том, что есть элементы level_d, и они должны быть извлечены для вывода файлов xml со всем деревом предков до определенного элемента (например, element_a в моем примере xml).   -  person Vlax    schedule 05.06.2013
comment
@Vlax - Как определить, что такое element_a? Это первый элемент, который содержит более одного дочернего элемента?   -  person Daniel Haley    schedule 05.06.2013
comment
@DanielHaley на самом деле element_a - это известный элемент, имя которого, например, element_a   -  person Vlax    schedule 05.06.2013
comment
@Vlax - Если element_a известен, вы можете изменить match="/*/*" на match="level_a/*" в моем ответе, и он создаст 4 желаемых выходных файла с обновленным вводом XML, который вы предоставили.   -  person Daniel Haley    schedule 05.06.2013


Ответы (2)


В качестве переменного решения вы можете настроить значения здесь, если level_a или level_d когда-либо изменятся:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" indent="yes"/>
    <xsl:template match="//level_d[not(ancestor::level_d)]">
            <xsl:result-document href="{concat('file_', count(preceding::*[self::level_d])+1,'.xml')}">
                <output>
                    <xsl:apply-templates select="ancestor-or-self::node()[parent::level_a]" mode="output"></xsl:apply-templates>
                </output>
            </xsl:result-document>
    </xsl:template>

    <xsl:template match="*" mode="output">
        <output level="{substring-after(local-name(),'level_')}">
            <xsl:apply-templates mode="output"/>
        </output>
    </xsl:template>
</xsl:stylesheet>

Он имеет те же результаты, что и ожидалось.

person AlwaysWrong    schedule 04.06.2013
comment
Спасибо! Это именно то, что я искал. Не всегда ошибаюсь :) - person Vlax; 07.06.2013
comment
На самом деле вы можете написать level_d[not(ancestor::level_d)] вместо //level_d[not(ancestor::level_d)] - person Vlax; 07.06.2013

Не уверен, почему вы идете снизу вверх, а не сверху вниз. Я бы сопоставил /*/* и создал там результирующий документ.

Пример...

Ввод XML

<root>
    <level_a> 
        <level_b> 
            <level_c> 
                <level_d> 
                    <level_e/> 
                </level_d> 
            </level_c> 
        </level_b> 
    </level_a>
    <level_b> 
        <level_c> 
            <level_d> 
                <level_d> 
                    <level_e/> 
                </level_d> 
            </level_d> 
        </level_c> 
    </level_b>
</root>

XSLT 2.0

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes" encoding="UTF-8"/>
    <xsl:strip-space elements="*"/>

    <xsl:template name="output">
        <output level="{substring-after(name(),'level_')}">
            <xsl:apply-templates/>
        </output>       
    </xsl:template>

    <xsl:template match="/*/*" priority="1">
        <xsl:result-document href="{generate-id()}.xml">
            <output>
                <xsl:call-template name="output"/>              
            </output>
        </xsl:result-document>
    </xsl:template>

    <xsl:template match="*[starts-with(name(),'level_')]">
        <xsl:call-template name="output"/>
    </xsl:template>

</xsl:stylesheet>

Файл 1 (имя будет другим из-за generate-id())

<output>
   <output level="a">
      <output level="b">
         <output level="c">
            <output level="d">
               <output level="e"/>
            </output>
         </output>
      </output>
   </output>
</output>

Файл 2 (имя будет другим из-за generate-id())

<output>
   <output level="b">
      <output level="c">
         <output level="d">
            <output level="d">
               <output level="e"/>
            </output>
         </output>
      </output>
   </output>
</output>
person Daniel Haley    schedule 04.06.2013
comment
Спасибо за ваш ответ, но я, возможно, упростил свой пример. На самом деле мне нужно начать с level_d, потому что мой реальный xml намного сложнее. Если хотите, я могу попытаться воспроизвести более реальный пример из жизни. Но в основном мне нужно использовать либо ancestor::*, либо parent::* рекурсивно... - person Vlax; 05.06.2013
comment
@Vlax - Не могли бы вы проверить, есть ли потомок level_d (match="/*/*[descendant::level_d]")? Вы можете остановить обработку на уровне level_d, добавив соответствующий шаблон. Обновленный пример может помочь мне понять, почему вы хотите вернуться вверх по дереву. - person Daniel Haley; 05.06.2013
comment
Извините за мою первую версию xml, теперь она должна лучше отражать фактический xml. Если вы посмотрите на последнюю версию, вы увидите, что первый элемент level_b имеет много элементов level_d в своем дереве. Итак, моя идея состояла в том, чтобы найти все элементы level_d, извлечь предков и получить потомков. Каждый level_d в отдельном файле. Из вложенного level_d мне нужно заботиться только о последнем в дереве, остальные вложенные level_d вверх по дереву не нуждаются в отдельном файле и рассматриваются только как предки. - person Vlax; 05.06.2013
comment
@Vlax - Если element_a известен, вы можете изменить match="/*/*" на match="level_a/*" в моем ответе, и он создаст 4 желаемых выходных файла с обновленным вводом XML, который вы предоставили. - person Daniel Haley; 05.06.2013