XSL - Как сопоставить последовательные теги через запятую - PullRequest
4 голосов
/ 24 июня 2011

Я пытаюсь сопоставить серию тегов xml, разделенных запятыми, а затем применить преобразование xslt ко всей группе узлов плюс текст. Например, учитывая следующий частичный XML:

<p>Some text here
    <xref id="1">1</xref>,
    <xref id="2">2</xref>,
    <xref id="3">3</xref>.
</p>

Я бы хотел закончить с:

<p>Some text here <sup>1,2,3</sup>.</p>

В этот момент также будет приемлемым гораздо более беспорядочный вариант:

<p>Some text here <sup>1</sup><sup>,</sup><sup>2</sup><sup>,</sup><sup>3</sup>.</p>

У меня есть преобразование, чтобы перейти от одной внешней ссылки к sup:

<xsl:template match="xref"">
    <sup><xsl:apply-templates/></sup>
</xsl:template>

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

Спасибо.

Ответы [ 5 ]

3 голосов
/ 25 июня 2011

Обновление : Благодаря @ Flynn1179, который предупредил меня, что решение не дает желаемого результата, я немного изменил его. Теперь желаемый «хороший» формат создается .

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

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

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

     <xsl:template match=
     "xref[not(preceding-sibling::node()[1]
                  [self::text() and starts-with(.,',')]
               )
          ]">

      <xsl:variable name="vBreakText" select=
      "following-sibling::text()[not(starts-with(.,','))][1]"/>

      <xsl:variable name="vPrecedingTheBreak" select=
       "$vBreakText/preceding-sibling::node()"/>

      <xsl:variable name="vFollowing" select=
      ".|following-sibling::node()"/>

      <xsl:variable name="vGroup" select=
      "$vFollowing[count(.|$vPrecedingTheBreak)
                  =
                   count($vPrecedingTheBreak)
                  ]
      "/>

      <sup>
       <xsl:apply-templates select="$vGroup" mode="group"/>
      </sup>
      <xsl:apply-templates select="$vBreakText"/>
     </xsl:template>

     <xsl:template match="text()" mode="group">
       <xsl:value-of select="normalize-space()"/>
     </xsl:template>
</xsl:stylesheet>

при применении к следующемуXML-документ (на основе предоставленного, но сделан более сложным и интересным):

<p>Some text here    
    <xref id="1">1</xref>,    
    <xref id="2">2</xref>,    
    <xref id="3">3</xref>.
    <ttt/>
    <xref id="4">4</xref>,
    <xref id="5">5</xref>,
    <xref id="6">6</xref>.
    <zzz/>
</p>

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

<p>Some text here        
    <sup>1,2,3</sup>.    
    <ttt/>
    <sup>4,5,6</sup>.    
    <zzz/>
</p>

Объяснение :

  1. Мы используем «детализированное» правило идентификации, которое обрабатывает документ узел за узлом в порядке документа и копирует соответствующий узел »"как есть"

  2. Мы переопределяем правило идентификации с помощью шаблона, который соответствует любому элементу xref, который является первым в группе из xref элементов, каждый из которых (кромепоследний) сопровождается непосредственным братом text-node-sibling, который начинается с символа ','.Здесь мы находим первого родственного элемента text-node, который нарушает правило (его начальный символ не ','.

  3. Затем мы находим все узлы в группе, используя Kayessian(после @Michael Kay) формула для пересечения двух узлов. Эта формула: $ns1[count(.|$ns2) = count($ns2)]

  4. Затем мы обрабатываем все узлы в группе в режиме с именем «группа».

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

2 голосов
/ 27 июня 2011

Существует почти тривиальное решение для вашей «грязной альтернативы»:

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

<xsl:template match="xref">
  <sup>
    <xsl:apply-templates />
  </sup>
</xsl:template>

<xsl:template match="text()[normalize-space(.)=',']">
  <sup>,</sup>
</xsl:template>

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

</xsl:stylesheet>

РЕДАКТИРОВАТЬ: я только что заметил, что это почти клон решения Мартина, за исключением без дополнительной проверки предыдущего элемента xref на запятых. Его, вероятно, безопаснее:)

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

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

<xsl:template match="xref[not(preceding-sibling::text()[normalize-space(.)=','])]">
  <sup>
    <xsl:value-of select="." />
    <xsl:for-each select="following-sibling::text() | following-sibling::xref">
      <xsl:if test="following-sibling::text()[substring(.,1,1)='.']">
        <xsl:value-of select="normalize-space(.)" />
      </xsl:if>
    </xsl:for-each>
  </sup>
</xsl:template>

<xsl:template match="xref | text()[normalize-space(.)=',']" />

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

</xsl:stylesheet>
2 голосов
/ 24 июня 2011

Интересный вопрос. + 1.

Вот решение XSLT 2.0:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
   xmlns:xs="http://www.w3.org/2001/XMLSchema"
   exclude-result-prefixes="xs"
   version="2.0">
   <xsl:variable name="comma-regex">^\s*,\s*$</xsl:variable>

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

   <!-- Don't directly process xrefs that are second or later in a comma-separated series.
      Note that this template has a higher default priority than the following one,
      because of the predicate. -->
   <xsl:template match="xref[preceding-sibling::node()[1]/
      self::text()[matches(., $comma-regex)]/
      preceding-sibling::*[1]/self::xref]" />

   <!-- Don't directly process comma text nodes that are in the middle of a series. -->
   <xsl:template match="text()[matches(., $comma-regex) and
      preceding-sibling::*[1]/self::xref and following-sibling::*[1]/self::xref]" />

   <!-- for xrefs that first (or solitary) in a comma-separated series: -->
   <xsl:template match="xref">
      <sup>
         <xsl:call-template name="process-xref-series">
            <xsl:with-param name="next" select="." />
         </xsl:call-template>
      </sup>
   </xsl:template>

   <xsl:template name="process-xref-series">
      <xsl:param name="next"/>
      <xsl:if test="$next">
         <xsl:value-of select="$next"/>
         <xsl:variable name="followingXref"
            select="$next/following-sibling::node()[1]/
                     self::text()[matches(., $comma-regex)]/
                     following-sibling::*[1]/self::xref"/>
         <xsl:if test="$followingXref">
            <xsl:text>,</xsl:text>
            <xsl:call-template name="process-xref-series">
               <xsl:with-param name="next" select="$followingXref"/>
            </xsl:call-template>
         </xsl:if>         
      </xsl:if>

   </xsl:template>
</xsl:stylesheet>

(Это можно было бы упростить, если бы мы могли сделать некоторые предположения относительно ввода.)

Выполнить на вводе образца, который вы дали, результат:

<p>Some text here
   <sup>1,2,3</sup>.
</p>
2 голосов
/ 24 июня 2011

Вторая альтернатива может быть достигнута с помощью

<xsl:stylesheet version="1.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>

<xsl:template match="p/text()[normalize-space() = ',' and preceding-sibling::node()[1][self::xref]]">
  <sup>,</sup>
</xsl:template>

<xsl:template match="xref">
  <sup>
    <xsl:apply-templates/>
  </sup>
</xsl:template>

</xsl:stylesheet>
1 голос
/ 24 июня 2011

В случае, если вы можете использовать XSLT 2.0 (например, с Saxon 9 или AltovaXML или XQSharp), вот решение XSLT 2.0, которое должно выдавать первый запрашиваемый вывод:

<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>

<xsl:template match="p">
  <xsl:for-each-group select="node()" group-adjacent="self::xref or self::text()[normalize-space() = ',']">
    <xsl:choose>
      <xsl:when test="current-grouping-key()">
        <sup>
          <xsl:value-of select="current-group()/normalize-space()" separator=""/>
        </sup>
      </xsl:when>
      <xsl:otherwise>
        <xsl:apply-templates select="current-group()"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:for-each-group>
</xsl:template>

</xsl:stylesheet>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...