XSLT Вставить элемент, только если он не существует - PullRequest
3 голосов
/ 05 августа 2009

У меня есть исходный документ:

<?xml version="1.0"?>
<source>
  <ItemNotSubstituted/>
  <ItemToBeSubstituted Id='MatchId' />
</source>

И таблица стилей, содержащая контент, который я хочу заменить на источник:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes" method="xml" omit-xml-declaration="no" version="1.0"/>
  <xsl:preserve-space elements="//*"/>

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

  <xsl:template match="ItemToBeSubstituted[@Id = 'MatchId']">
    <xsl:copy>
      <xsl:copy-of select="@*|*"/>
      <Element1/>
      <Element2 Value="foo"/>
      <Element3 Value="bar"/>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

Эта таблица стилей успешно копирует <Element1/><Element2 Value="foo"/><Element3 Value="bar"/> в ItemToBeSubstituted. Но когда я использую другой исходный документ, в котором ItemToBeSubstituted уже имеет содержимое:

<?xml version="1.0"?>
<source>
  <ItemNotSubstituted/>
  <ItemToBeSubstituted Id='MatchId'>
    <Element3 Value="baz"/>
  </ItemToBeSubstituted>
</source>

Я получаю этот вывод:

<?xml version="1.0"?>
<source>
  <ItemNotSubstituted/>
  <ItemToBeSubstituted Id="MatchId">
    <Element3 Value="baz"/>
    <Element1/>
    <Element2 Value="foo"/>
    <Element3 Value="bar"/>
  </ItemToBeSubstituted>
</source>

Я бы хотел заменять только элементы из таблицы стилей, которые еще не существуют в исходном документе. Это вывод, который я ищу после применения таблицы стилей ко второму документу, используя только элемент <Element3> из исходного документа:

<?xml version="1.0"?>
<source>
  <ItemNotSubstituted/>
  <ItemToBeSubstituted Id="MatchId">
    <Element3 Value="baz"/>
    <Element1/>
    <Element2 Value="foo"/>
  </ItemToBeSubstituted>
</source>

Каков наилучший подход для этого с XSL? Таблица стилей может содержать множество элементов для замены. Поэтому я не хочу использовать подход, который требует <xsl:if> вокруг каждого элемента. Есть ли лучший способ, чем использовать одну таблицу стилей для вставки содержимого, а затем иметь вторую таблицу стилей, которая удаляет повторяющиеся элементы?

Ответы [ 2 ]

2 голосов
/ 05 августа 2009

Это решение XSLT 1.0 делает то, что вы намерены:

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

  <!-- expand this section to contain all your default elements/values -->
  <subst:defaults>
    <subst:element name="ItemToBeSubstituted" id="MatchId">
      <subst:Element1/>
      <subst:Element2 Value="foo"/>
      <subst:Element3 Value="bar"/>
    </subst:element>
  </subst:defaults>

  <!-- this makes the above available as a variable -->
  <xsl:variable name="defaults" select="document('')/*/subst:defaults" />

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

  <!-- expand the match expression to contain all elements 
       names that need default values -->
  <xsl:template match="ItemToBeSubstituted">
    <xsl:copy>
      <xsl:copy-of select="@*|*"/>
      <xsl:call-template name="create-defaults" />
    </xsl:copy>
  </xsl:template>

  <!-- this does all the heavy lifting -->
  <xsl:template name="create-defaults">
    <xsl:variable name="this" select="." />

    <xsl:for-each select="
      $defaults/subst:element[@name = name($this) and @id = $this/@Id]/*
    ">
      <xsl:if test="not($this/*[name() = local-name(current())])">
        <xsl:apply-templates select="." />
      </xsl:if>
    </xsl:for-each>
  </xsl:template>

  <!-- create the default nodes without namespaces -->
  <xsl:template match="subst:*">
    <xsl:element name="{local-name()}">
      <xsl:apply-templates select="subst:*|@*" />
    </xsl:element>
  </xsl:template>

</xsl:stylesheet>

Использование отдельного пространства имен («subst») позволяет вам сохранить значения по умолчанию в таблице стилей. Хорошо это или нет, зависит, по крайней мере, от двух файлов.

Если вы предпочитаете, чтобы таблица стилей была отделена от значений по умолчанию, поместите их в дополнительный файл и используйте вместо этого эту строку.

<xsl:variable name="defaults" select="document('defaults.xml')/subst:defaults" />

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

2 голосов
/ 05 августа 2009

Я бы использовал что-то вроде этого:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes" method="xml" omit-xml-declaration="no" version="1.0"/>
  <xsl:preserve-space elements="//*"/>

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

  <xsl:template match="ItemToBeSubstituted[@Id = 'MatchId']">
    <xsl:variable name="node" select="." />
    <xsl:copy>
      <xsl:copy-of select="@*|*"/>

      <xsl:for-each select="document('elements.xml')/elements/*">
        <xsl:if test="not($node/*[name() = name(current())])">
          <xsl:copy-of select="." />
        </xsl:if>
      </xsl:for-each>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

Где elements.xml - это файл, в котором вы храните элементы для добавления по умолчанию

<?xml version="1.0" encoding="utf-8" ?>
<elements>
  <Element1/>
  <Element2 Value="foo"/>
  <Element3 Value="bar"/>
</elements>

Используя <for-each>, мы перебираем элементы по умолчанию, проверяем, есть ли элемент с таким именем как дочерний элемент для текущего узла, затем добавляем его, если его нет.

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