(XSLT, оптимизация кода) Как вывести узлы, ссылающиеся на значение узлов-братьев ..? - PullRequest
4 голосов
/ 18 февраля 2010

Я преобразовываю XML в XML с использованием XSLT. Цель - прочитать значение тега <node1>, если оно равно нулю, ему должно быть присвоено значение <node2>, если incase <node2>, также null, тогда текст по умолчанию "Default" должен быть назначен .. обоим тегам ..
РЕДАКТИРОВАТЬ: Если <node2> имеет значение NULL, а <node1> нет .., тогда код не должен обновлять <node2> текстом 'Default', но его необходимо преобразовать в том виде, как он ..

Это тестовый XML, который я пытаюсь использовать:

<root>
    <node1></node1>
    <node2></node2>
  <parent>
    <node1>data1</node1>
    <node2></node2>
  </parent>
  <parent>
    <node1></node1>
    <node2>data2</node2>
  </parent>
  <parent>
    <node1>data1</node1>
    <node2>data2</node2>
  </parent>
</root>

И этот код XSLT, который я разработал:

   <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>
  <xsl:template name="template1" match="node2[(following-sibling::node1[.='']|preceding-sibling::node1[.=''])]">
    <xsl:choose>
      <xsl:when test=".=''">
        <node1><xsl:text>Default</xsl:text></node1>
        <node2><xsl:text>Default</xsl:text></node2>
      </xsl:when>
      <xsl:otherwise>
        <node1>
          <xsl:value-of select="text()"/>
        </node1>
        <xsl:copy>
          <xsl:apply-templates select="node()"/>
        </xsl:copy>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <xsl:template name="template2" match="node1[.='']"/>

Хотя мой код работает, меня не устраивает его громоздкость кода ... Есть ли способ избавиться от избыточных (если таковые имеются) строк .... И есть ли альтернатива использованию 2 шаблонов для достижения этой цели ( а именно template1 и template2), возможно ли уменьшить количество шаблонов?

Ответы [ 4 ]

3 голосов
/ 19 февраля 2010

I. Решение XSLT 1.0:

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

    <xsl:variable name="vReplacement">Default</xsl:variable>

       <xsl:variable name="vRep" select=
        "document('')/*/xsl:variable[@name='vReplacement']/text()"/>

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

     <xsl:template match="node1[not(node())] | node2[../node1[not(node())]]">
      <xsl:copy>
          <xsl:copy-of select="../node2/text() | $vRep[not(current()/../node2/text())]"/>
      </xsl:copy>
     </xsl:template>
</xsl:stylesheet>

Это короче и проще , чем текущие решения - на 7 строк меньше и, что более важно, , на один шаблон меньше , чем текущее выбранное решение.

Что еще более важно , это решение полностью декларативно и работает в стиле push - без вызова именованных шаблонов, и только <xsl:apply-templates> содержится в правиле идентификации.

II. Решение 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="node()|@*">
        <xsl:copy>
          <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
 </xsl:template>

 <xsl:template match="node1[not(node())] | node2[../node1[not(node())]]">
  <xsl:copy>
      <xsl:sequence select="(../node2/text(), 'Default')[1]"/>
  </xsl:copy>
 </xsl:template>
</xsl:stylesheet>

Используя возможности последовательностей XPath 2.0, это решение значительно короче, чем решение XSLT 1.0 .

Нечто подобное невозможно в XSLT 1.0 (например, выбор первого из объединения двух узлов без указания предикатов, чтобы сделать эти два узла взаимоисключающими), поскольку узел с текстом по умолчанию и Узлы узел1 / узел2 принадлежат разным документам, и, как мы хорошо знаем, упорядочение узлов между узлами разных документов зависит от реализации и не гарантируется / не предписывается.

Это решение полностью декларативное (нет if / then / else) и полностью push-стиль: единственный <xsl:apply-templates> находится в правиле идентификации.

2 голосов
/ 18 февраля 2010
<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="node1[.=''] | node2[.='']">
    <xsl:copy>
      <xsl:call-template name="GetOwnValue" />
    </xsl:copy>
  </xsl:template>

  <xsl:template name="GetOwnValue">
    <xsl:variable name="node2" select="following-sibling::node2[1]" />
    <xsl:choose>
      <xsl:when test="$node2 != ''">
        <xsl:value-of select="$node2" />
      </xsl:when>
      <xsl:otherwise>
        <xsl:text>Default</xsl:text>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

</xsl:stylesheet>
2 голосов
/ 18 февраля 2010

Я изменил ответ Томалак и выполнил требование ..
Как я уже упоминал в своем вопросе, этот код передает node2 как нулевое значение (если оно равно null), если одноуровневый узел1 не равен нулю (а также если нет одноуровневого узла 1) ..

Этот код, наконец, стал альтернативой тому, который я опубликовал в своей Q. (я не говорю, что он достаточно совершенен ... но я рад, что смог попробовать ..: -)
И этот код более эффективен, чем мой, примерно на 10-20 мсек ..: -)

Вот и все ..

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

  <xsl:template match="node1[.=''] | node2[.='']">
    <xsl:copy>
      <xsl:call-template name="GetOwnValue">
        <xsl:with-param name="node">
          <xsl:value-of select="name()"/>
        </xsl:with-param>
      </xsl:call-template>
    </xsl:copy>
  </xsl:template>

  <xsl:template name="GetOwnValue">
    <xsl:param name="node"/>
    <xsl:variable name="node2" select="following-sibling::node2[1]|preceding-sibling::node2[1]" />
    <xsl:variable name="node1" select="following-sibling::node1[1]|preceding-sibling::node1[1]" />
     <xsl:choose>
      <xsl:when test="$node2 != ''">
          <xsl:value-of select="$node2" />
      </xsl:when>
       <xsl:when test="$node!='node1' and ($node1!='' or not(following-sibling::node1[1]|preceding-sibling::node1[1]))"/>
      <xsl:otherwise>
          <xsl:text>Default</xsl:text>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
0 голосов
/ 18 февраля 2010

Используя XSLT 2.0, я бы сделал это, но в любом случае ваш легче читать.

<xsl:template match="node1[.='']">
    <xsl:copy>
        <xsl:value-of select="if (following-sibling::node2[.!='']) then following-sibling::node2[.!=''] else if (preceding-sibling::node2[.!='']) then preceding-sibling::node2[.!=''] else 'Default'"/>
    </xsl:copy>
</xsl:template>
<xsl:template match="node2[.='']">
    <xsl:copy>
        <xsl:value-of select="if (following-sibling::node1[.!='']) then '' else if (preceding-sibling::node1[.!='']) then '' else 'Default'"/>
    </xsl:copy>
</xsl:template>
...