XSLT 2.0 - Вложенные элементы на выходе из не вложенного ввода - PullRequest
1 голос
/ 16 февраля 2011

Я работаю над таблицей стилей, которая выводит иерархически из входного файла без. Каждый элемент входного файла является родственным элементом, и каждый элемент заголовка обозначает отдельный раздел.

Пример входного файла (фактические разделы под каждым заголовком примерно в 10 раз длиннее):

<Header>  
     Header1
</Header>
<Sub1>
     Sub1 first
</Sub1>
<Sub1>
     Sub1 second
</Sub1>
<Sub2>
     Sub2 first, Sub1 second
</Sub2>
<Sub1>
     Sub1 third
</Sub1>
<Sub2>
     Sub2 first, Sub1 third
</Sub2>

<Header>
     Header2
</Header>
Etc...

И вывод вышеуказанного ввода должен выглядеть следующим образом:

<Header>
     Header1
     <Step>
         Sub1 first
     </Step>
     <Step>
         Sub1 second
         <Step>
             Sub2 first, Sub1 second
         </Step>
     </Step>
     <Step>
         Sub1 third
         <Step>
             Sub2 first, Sub1 third
         </Step>
     </Step>
</Header>

<Header>
    Header2
    Etc.....
</Header>

Прямо сейчас, я могу получить вывод до «Sub2 first, sub1 second». Когда я знаю, что следующий элемент не является другим Sub2 или более глубоким подэлементом (Sub3), я отрываюсь от своего шаблона Sub2, возвращаясь обратно в шаблон Sub1. И отсюда моя переменная позиции по-прежнему имеет позицию «Sub1 секунда». У меня есть информация о позиционировании, обрабатываемая каждый раз, когда вызывается шаблон - поэтому у меня есть вся текущая позиция в порядке документов для «Sub2 first, sub1 second», но как только я отрываюсь от шаблона обратно в шаблон Sub1, я теряю эту информацию. Кажется, я не могу получить текущую позицию в порядке документа, чтобы определить, каков настоящий следующий элемент. Модифицируемый глобальный параметр или переменная были бы идеальными, но я знаю, что это невозможно в XSLT.

Я не уверен, как это сделать. Любые предложения будут великолепны!

Ответы [ 3 ]

1 голос
/ 16 февраля 2011

Это преобразование XSLT 2.0 :

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:template match="/*">
  <t>
   <xsl:apply-templates select="*[1]">
    <xsl:with-param name="pScope" select="*"/>
    <xsl:with-param name="pElemName" select="name(*[1])"/>
   </xsl:apply-templates>
  </t>
 </xsl:template>

 <xsl:template match="*">
  <xsl:param name="pScope"/>
  <xsl:param name="pElemName" select="'Step'"/>

    <xsl:for-each-group select="$pScope"
        group-starting-with="*[name()= name($pScope[1])]">
       <xsl:element name="{$pElemName}">
         <xsl:value-of select="."/>
         <xsl:apply-templates select="current-group()[2]">
          <xsl:with-param name="pScope" select=
           "current-group()[position() > 1]"/>
         </xsl:apply-templates>
       </xsl:element>
    </xsl:for-each-group>
 </xsl:template>
</xsl:stylesheet>

при применении к этому документу XML (на основе предоставленного, но обернуто в верхний элемент и добавлено однозаголовок с другими именами):

<t>
    <Header>Header1</Header>
    <Sub1>Sub1 first</Sub1>
    <Sub1>Sub1 second</Sub1>
    <Sub2>Sub2 first, Sub1 second</Sub2>
    <Sub1>Sub1 third</Sub1>
    <Sub2>Sub2 first, Sub1 third</Sub2>
    <Header>Header2</Header>
    <x>Sub1 first</x>
    <x>Sub1 second</x>
    <y>Sub2 first, Sub1 second</y>
    <x>Sub1 third</x>
    <z>Sub2 first, Sub1 third</z>
</t>

создает требуемый, правильный вывод :

<t>
   <Header>Header1<Step>Sub1 first</Step>
      <Step>Sub1 second<Step>Sub2 first, Sub1 second</Step>
      </Step>
      <Step>Sub1 third<Step>Sub2 first, Sub1 third</Step>
      </Step>
   </Header>
   <Header>Header2<Step>Sub1 first</Step>
      <Step>Sub1 second<Step>Sub2 first, Sub1 second</Step>
      </Step>
      <Step>Sub1 third<Step>Sub2 first, Sub1 third</Step>
      </Step>
   </Header>
</t>

Объяснение :

  1. Использование <xsl:for-each-group> с атрибутом group-starting-with.

  2. Мелкозернистая обработка - шаблоны применяются ко второму элементу current-group(), и область для следующей группировки передается как параметр.

0 голосов
/ 17 февраля 2011

Просто для удовольствия таблица стилей XSLT 1.0:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="*">
        <xsl:param name="pNames" select="'|'"/>
        <xsl:if test="not(contains($pNames,concat('|',name(),'|')))">
            <xsl:variable name="vNext" select="following-sibling::*[1]"/>
            <xsl:copy>
                <xsl:apply-templates select="node()[1]"/>
                <xsl:apply-templates select="$vNext">
                    <xsl:with-param name="pNames"
                                    select="concat($pNames,name(),'|')"/>
                </xsl:apply-templates>
            </xsl:copy>
            <xsl:apply-templates select="$vNext" mode="search">
                <xsl:with-param name="pNames" select="$pNames"/>
                <xsl:with-param name="pSearch" select="name()"/>
            </xsl:apply-templates>
        </xsl:if>
    </xsl:template>
    <xsl:template match="*" mode="search">
        <xsl:param name="pNames"/>
        <xsl:param name="pSearch"/>
        <xsl:if test="not(contains($pNames,concat('|',name(),'|')))">
            <xsl:choose>
                <xsl:when test="name()=$pSearch">
                    <xsl:apply-templates select=".">
                        <xsl:with-param name="pNames" select="$pNames"/>
                    </xsl:apply-templates>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:apply-templates select="following-sibling::*[1]"
                                         mode="search">
                        <xsl:with-param name="pNames" select="$pNames"/>
                        <xsl:with-param name="pSearch" select="$pSearch"/>
                    </xsl:apply-templates>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>

С подтвержденным вводом:

<root>
    <Header>Header1</Header>
    <Sub1>Sub1 first</Sub1>
    <Sub1>Sub1 second</Sub1>
    <Sub2>Sub2 first, Sub1 second</Sub2>
    <Sub1>Sub1 third</Sub1>
    <Sub2>Sub2 first, Sub1 third</Sub2>
    <Header>Header2</Header>
</root>

Вывод:

<root>
    <Header>Header1
        <Sub1>Sub1 first</Sub1>
        <Sub1>Sub1 second
            <Sub2>Sub2 first, Sub1 second</Sub2>
        </Sub1>
        <Sub1>Sub1 third
            <Sub2>Sub2 first, Sub1 third</Sub2>
        </Sub1>
    </Header>
    <Header>Header2</Header>
</root>

С заменой имени иновый реквизит одного уровня с несколькими именами, эта таблица стилей:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:r="rules"
 exclude-result-prefixes="r">
    <r:r m="|Header|">Header</r:r>
    <r:r m="|Sub1|">Step</r:r>
    <r:r m="|Sub2|">Step</r:r>
    <xsl:variable name="vRules" select="document('')/*/r:r"/>
    <xsl:template match="*">
        <xsl:param name="pNames" select="'|'"/>
        <xsl:variable name="vName" select="concat('|',name(),'|')"/>
        <xsl:if test="not(contains($pNames,$vName))">
            <xsl:variable name="vNext" select="following-sibling::*[1]"/>
            <xsl:variable name="vMatch"
                          select="$vRules[contains(@m,$vName)]"/>
            <xsl:variable name="vReplace"
                 select="concat($vMatch,name((.)[not($vMatch)]))"/>
            <xsl:variable name="vSearch"
                          select="concat($vMatch/@m,$vName)"/>
            <xsl:element name="{$vReplace}">
                <xsl:apply-templates select="node()[1]"/>
                <xsl:apply-templates select="$vNext">
                    <xsl:with-param name="pNames"
                                    select="concat($pNames,$vSearch)"/>
                </xsl:apply-templates>
            </xsl:element>
            <xsl:apply-templates select="$vNext" mode="search">
                <xsl:with-param name="pNames" select="$pNames"/>
                <xsl:with-param name="pSearch" select="$vSearch"/>
            </xsl:apply-templates>
        </xsl:if>
    </xsl:template>
    <xsl:template match="*" mode="search">
        <xsl:param name="pNames"/>
        <xsl:param name="pSearch"/>
        <xsl:variable name="vName" select="concat('|',name(),'|')"/>
        <xsl:choose>
            <xsl:when test="contains($pNames,$vName)"/>
            <xsl:when test="contains($pSearch,$vName)">
                <xsl:apply-templates select=".">
                    <xsl:with-param name="pNames" select="$pNames"/>
                </xsl:apply-templates>
            </xsl:when>
            <xsl:otherwise>
                <xsl:apply-templates select="following-sibling::*[1]"
                                         mode="search">
                    <xsl:with-param name="pNames" select="$pNames"/>
                    <xsl:with-param name="pSearch" select="$pSearch"/>
                </xsl:apply-templates>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>

Вывод:

<root>
    <Header>Header1
        <Step>Sub1 first</Step>
        <Step>Sub1 second
            <Step>Sub2 first, Sub1 second</Step>
        </Step>
        <Step>Sub1 third
            <Step>Sub2 first, Sub1 third</Step>
        </Step>
    </Header>
    <Header>Header2</Header>
</root>

Примечание : синонимы для того же уровня должны быть в r:r/@m

0 голосов
/ 16 февраля 2011

Ваш входной xml на самом деле не является xml, поэтому я добавил корневой узел. Я бы посоветовал не использовать xslt для такой обработки. Но если нужно - вот кое-что, с чего можно начать.

Btw. У ваших подразделов есть какой-либо идентификатор?

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" 
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
                xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
  <xsl:output method="xml" indent="yes"/>

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

  <xsl:template match="Header">
    <Header>
      <xsl:copy-of select="text()"/>
      <xsl:variable name="current" select="."/>
      <xsl:apply-templates select="/Root/Sub1[preceding-sibling::Header[1]/. = $current]"/>
    </Header>
  </xsl:template>

  <xsl:template match="Sub1">
    <Step>
      <xsl:copy-of select="text()"/>
      <xsl:variable name="current" select="."/>
      <xsl:apply-templates select="/Root/Sub2[preceding-sibling::Sub1[1]/. = $current]"/>
    </Step>
  </xsl:template>

  <xsl:template match="Sub2">
    <Step>
      <xsl:copy-of select="text()"/>
      <!-- todo -->
    </Step>
  </xsl:template>

</xsl:stylesheet>
...