Как переформатировать XML с помощью группы смежных (XSLT) - PullRequest
1 голос
/ 14 января 2010

Я новичок в этой XSLT-штуке и не могу понять, как это сделать:

Это фрагмент из XML-кода, с которого я начинаю:

<Article>    
<Bullettext>10,00 </Bullettext>  
<Bullettext>8,00 </Bullettext>    
</Article>  
<Article>  
<something>some text</something>  
</Article>  
<Article>  
<Corpsdetexte>Bulgaria</Corpsdetexte>  
<Bullettext>15,0 </Bullettext>  
<Bullettext>10,0 </Bullettext>  
</Article> ` 

Вот что я хочу, чтобы вывод был:

<LIST>  
<ITEM>12,00 </ITEM>  
<ITEM>10,00 </ITEM>  
<ITEM>8,00 </ITEM>  
</LIST>  
<P>  
<something>some text</something>  
</P>  

<P>  
<Corpsdetexte>Bulgaria</Corpsdetexte>  
</P>  
<LIST>  
<ITEM>15,0 </ITEM>  
<ITEM>10,0 </ITEM>  
</LIST>  

Есть идеи ??

Ответы [ 4 ]

5 голосов
/ 14 января 2010

Из вашего комментария в ответ на ответ Рубенса Фариаса (и действительно, это то, что вы должны отредактировать, чтобы включить свой вопрос), кажется, что вы хотите универсальный способ преобразовать любую группу смежных BulletText элементов в список. Это приводит нас к двум вопросам: как мы находим такие группы, и, найдя их, как мы превращаем их в список?

Чтобы найти группу, нам нужно найти все элементы BulletText, чей непосредственно предшествующий элемент не является элементом BulletText. Каждый из них создает группу, и это те элементы, которые мы собираемся преобразовать в списки. Поэтому первое, что мы хотим сделать, это создать выражение XPath, которое найдет их:

BulletText[not(preceding-sibling::*[1][name()='BulletText'])]

Если вы посмотрите на предикаты в этом выражении XPath, он делает то, что я сказал, что нам нужно сделать: он соответствует элементу BulletText, если это не тот случай, когда его первый предшествующий брат (preceding-sibling::*[1]) имеет имя BulletText. Обратите внимание, что если элемент не имеет предшествующего брата, это выражение все равно будет ему соответствовать.

Итак, теперь мы можем создать шаблон, который соответствует этим элементам начала группы. Что мы помещаем в этот шаблон? Мы собираемся преобразовать эти элементы в LIST элементы, поэтому шаблон будет выглядеть примерно так:

<LIST>
   ...
</LIST>

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

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

Второй сложнее. Если есть следующий родственный элемент, который не является элементом BulletText, мы хотим, чтобы наш список был всеми дочерними элементами родительского элемента текущего элемента, начиная с текущего элемента и заканчивая перед элементом stop. Вот случай, когда нам нужно использовать функцию count() для вычисления начального и конечного индексов и функцию position() для определения позиции каждого элемента.

Готовый шаблон выглядит так:

<xsl:template match="BulletText[not(preceding-sibling::*[1][name()='BulletText'])]">
  <!-- find the element that we want to stop at -->
  <xsl:variable name="stop" select="./following-sibling::*[name() != 'BulletText'][1]"/>
  <LIST>
    <xsl:choose>
      <!-- first, the simple case:  there's no element we have to stop at -->
      <xsl:when test="not($stop)">
        <xsl:apply-templates select="." mode="item"/>
        <xsl:apply-templates select="./following-sibling::BulletText" mode="item"/>
      </xsl:when>
      <!-- transform all elements between the start and stop index into items -->
      <xsl:otherwise>
        <xsl:variable name="start_index" select="count(preceding-sibling::*) + 1"/>
        <xsl:variable name="stop_index" select="count($stop/preceding-sibling::*)"/>
        <xsl:apply-templates select="../*[position() &gt;= $start_index 
                                      and position() &lt;= $stop_index]"
                             mode="item"/>
      </xsl:otherwise>
    </xsl:choose>
  </LIST>
</xsl:template>

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

<xsl:template match="BulletText" mode="item">
   <ITEM>
      <xsl:value-of select="."/>
   </ITEM>
</xsl:template>

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

<xsl:template match='BulletText'/>

Из-за волшебства правил приоритета шаблонов XSLT любой элемент BulletText, которому соответствуют оба шаблона, будет преобразован первым, а этот поймает остальные.

Просто добавьте эти три шаблона в преобразование идентичности, и все готово.

1 голос
/ 06 декабря 2012

Группирование братьев и сестер с тремя шаблонами

Хотя в настоящее время существует несколько рабочих ответов на этот вопрос, на самом деле вы можете очень легко сгруппировать братьев и сестер с помощью трех шаблонов и идентификатора.

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

<xsl:template match="Bullettext" priority="1"/>

Затем определите шаблон, который соответствует любому узлу, которому не предшествует сам по себе, назначив ему более высокий приоритет. Этот шаблон внедрит группу, а затем начнет копирование узлов в другом режиме.

<xsl:template match="Bullettext[not(preceding-sibling::*[1][self::Bullettext])]" priority="2">
  <LIST>
    <xsl:apply-templates select="." mode="bullet-list"/>
  </LIST>
</xsl:template>

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

<xsl:template match="Bullettext" mode="bullet-list">
  <ITEM>
    <xsl:apply-templates select="@*|node()"/>
  </ITEM>
  <xsl:apply-templates select="following-sibling::*[1][self::Bullettext]" mode="bullet-list"/>
</xsl:template>

Вот полная таблица стилей, которая сгруппирует элементы Bullettext в примере:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes" />

  <!-- Identity -->
  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()" />
    </xsl:copy>
  </xsl:template>

  <!-- Removes the Bullettext elements in the default mode. -->
  <xsl:template match="Bullettext" priority="1" />

  <!-- Creates the LIST elements around the removed Bullettext elements. -->
  <xsl:template match="Bullettext[not(preceding-sibling::*[1][self::Bullettext])]" priority="2">
    <LIST>
      <xsl:apply-templates select="." mode="bullet-list" />
    </LIST>
  </xsl:template>

  <!-- Converts sequential Bullettext elements into ITEM elements. -->
  <xsl:template match="Bullettext" mode="bullet-list">
    <ITEM>
      <xsl:apply-templates select="@*|node()" />
    </ITEM>
    <xsl:apply-templates select="following-sibling::*[1][self::Bullettext]" mode="bullet-list" />
  </xsl:template>

</xsl:stylesheet>
1 голос
/ 14 января 2010

Я думаю, вы ищете условную глубокую копию .

Вот код в ссылке выше, переписанный для вашей ситуации:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()" />
        </xsl:copy>
    </xsl:template>

    <!-- nodes with Bullettext children -->
    <xsl:template match="*[Bullettext]">
        <!-- for every child -->
        <xsl:copy>
            <xsl:for-each select="*">
                <!-- if child is a Bullettext and it has a Bullettext before it, don't copy it (it has already been copied) -->
                <xsl:if test="not(local-name(.) = 'Bullettext' and local-name(./preceding-sibling::*[1]) = 'Bullettext')">
                    <xsl:choose>
                        <xsl:when test="local-name(.) = 'Bullettext'">
                            <!-- copy all Bullettext children adjacent to this one and each other -->
                            <LIST>
                                <xsl:call-template name="get-all-adjacent-siblings">
                                    <xsl:with-param name="sibling-before" select="." />
                                </xsl:call-template>
                            </LIST>
                        </xsl:when>
                        <xsl:otherwise>
                            <!-- copy non-Bullettext child -->
                            <xsl:apply-templates select="." />
                        </xsl:otherwise>
                    </xsl:choose>
                </xsl:if>
            </xsl:for-each>
        </xsl:copy>
    </xsl:template>

    <xsl:template name="get-all-adjacent-siblings">
        <xsl:param name="sibling-before" />
        <!-- return me -->
        <xsl:copy>
            <xsl:value-of select="$sibling-before" />
        </xsl:copy>
        <!-- return my adjacent Bullettext siblings below me -->
        <xsl:if test="local-name($sibling-before/following-sibling::*[1]) = 'Bullettext'">
            <xsl:call-template name="get-all-adjacent-siblings">
                <xsl:with-param name="sibling-before" select="$sibling-before/following-sibling::*[1]" />
            </xsl:call-template>
        </xsl:if>
    </xsl:template>

</xsl:stylesheet>

Я использовал ввод:

<?xml version="1.0" encoding="utf-8"?>
<Articles>
    <Article>
        <Bullettext>10,00 </Bullettext>
        <Bullettext>8,00 </Bullettext>
    </Article>
    <Article>
        <something>some text</something>
    </Article>
    <Article>
        <Corpsdetexte>Bulgaria</Corpsdetexte>
        <deeper>
            <before>dogs</before>
            <Bullettext>15,0 </Bullettext>
            <Bullettext>10,0 </Bullettext>
            <middle>cats</middle>
            <Bullettext>25,0 </Bullettext>
            <Bullettext>20,0 </Bullettext>
            <after>cows</after>
        </deeper>
    </Article>
</Articles>

И это дало мне:

<?xml version="1.0" encoding="UTF-8"?>
<Articles>
    <Article>
        <LIST>
            <Bullettext>10,00 </Bullettext>
            <Bullettext>8,00 </Bullettext>
        </LIST>
    </Article>
    <Article>
        <something>some text</something>
    </Article>
    <Article>
        <Corpsdetexte>Bulgaria</Corpsdetexte>
        <deeper>
            <before>dogs</before>
            <LIST>
                <Bullettext>15,0 </Bullettext>
                <Bullettext>10,0 </Bullettext>
            </LIST>
            <middle>cats</middle>
            <LIST>
                <Bullettext>25,0 </Bullettext>
                <Bullettext>20,0 </Bullettext>
            </LIST>
            <after>cows</after>
        </deeper>
    </Article>
</Articles>

Это немного беспорядочно, если вы хотите выполнить другие преобразования, такие как добавление <p></p> s в той же таблице стилей, но если вы делаете двухэтапное преобразование с двумя таблицами стилей, сначала выполняйте условное глубокое копирование выше, а затем второе Ваше основное преобразование, использующее результат первого, вам должно быть хорошо.

0 голосов
/ 14 января 2010

Попробуйте что-то вроде этого:

<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/Articles">
    <LIST>
      <xsl:for-each select="Article[1]/Bullettext">
        <ITEM>
          <xsl:value-of select="." />
        </ITEM>
      </xsl:for-each>
    </LIST>

    <p>
      <something>
        <xsl:value-of select="Article[2]/something" />
      </something>
    </p>

    <p>
      <Corpsdetexte>
        <xsl:value-of select="Article[3]/Corpsdetexte" />
      </Corpsdetexte>
    </p>

    <LIST>
      <xsl:for-each select="Article[4]/Bullettext">
        <ITEM>
          <xsl:value-of select="." />
        </ITEM>
      </xsl:for-each>
    </LIST>
  </xsl:template>
</xsl:stylesheet>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...