Удалить элементы и / или атрибуты по имени для параметров XSL - PullRequest
13 голосов
/ 22 февраля 2012

Следующее выполняет работу по удалению нежелательных элементов и атрибутов по имени (в данном примере «removeMe») из файла XML:

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

 <xsl:output omit-xml-declaration="yes" indent="yes"/>

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

 <xsl:template match="removeMe"/>
</xsl:stylesheet>

Проблемы заключаются в том, что он не различает элементы и атрибуты,имя жестко закодировано, и оно может принимать только одно имя. Как это можно переписать, чтобы использовать пару входных параметров, как показано ниже, для удаления одного или нескольких конкретных элементов и / или атрибутов?

<xsl:param name="removeElementsNamed"/>
<xsl:param name="removeAttributesNamed"/>

Желаемым результатом является возможностьудалить один или несколько элементов и / или один или несколько атрибутов , при этом различая элементы и атрибуты (другими словами, должна быть возможность удалить все элементы "time" без , а также удалить все атрибуты "time" ).

Пока мне требуется XSLT1.0 в этом раунде, решения XSLT 2.0 в принятых и другие ответы могут быть полезны для других.

Ответы [ 4 ]

22 голосов
/ 22 февраля 2012

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

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

 <xsl:param name="removeElementsNamed" select="'x'"/>

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

 <xsl:template match="*">
  <xsl:if test="not(name() = $removeElementsNamed)">
   <xsl:call-template name="identity"/>
  </xsl:if>
 </xsl:template>
</xsl:stylesheet>

при применении к любому документу XML, скажите, что :

<t>
    <a>
        <b/>
        <x/>
    </a>
    <c/>
    <x/>
    <d/>
</t>

производиттребуемый правильный результат - копия исходного XML-документа, в котором удаляется любой вхождение элемента, имя которого является значением параметра $removeElementsNamed, :

<t>
   <a>
      <b/>
   </a>
   <c/>
   <d/>
</t>

Обратите внимание : В XSLT 1.0 синтаксически недопустимо иметь ссылку на переменную или параметр внутри шаблона совпадения шаблона .Вот почему решения @Jan Thomä и @treeMonkey выдают ошибку с любым 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:strip-space elements="*"/>

 <xsl:param name="removeElementsNamed" select="'|x|c|'"/>

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

 <xsl:template match="*">
  <xsl:if test=
   "not(contains($removeElementsNamed,
                 concat('|',name(),'|' )
                 )
        )
   ">
   <xsl:call-template name="identity"/>
  </xsl:if>
 </xsl:template>
</xsl:stylesheet>

При применении к тому же XML-документу (см. выше) преобразование снова производит требуемый, правильный вывод -- исходный XML-документ со всеми элементами, чье имя указано в параметре $removeElementsNamed - удалено :

<t>
   <a>
      <b/>
   </a>
   <d/>
</t>

Update2 : то же преобразование, что и в Обновление 1 , но написано в 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:strip-space elements="*"/>

 <xsl:param name="removeElementsNamed" select="'|x|c|'"/>

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

 <xsl:template match=
 "*[name() = tokenize($removeElementsNamed, '\|')]"/>
</xsl:stylesheet>

Обновление : ОП добавил требование также иметь возможность удалять все атрибуты, имеющие определенное имя.

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

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

     <xsl:param name="removeElementsNamed" select="'x'"/>
     <xsl:param name="removeAttributesNamed" select="'n'"/>

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

     <xsl:template match="*">
      <xsl:if test="not(name() = $removeElementsNamed)">
       <xsl:call-template name="identity"/>
      </xsl:if>
     </xsl:template>

     <xsl:template match="@*">
      <xsl:if test="not(name() = $removeAttributesNamed)">
       <xsl:call-template name="identity"/>
      </xsl:if>
     </xsl:template>
</xsl:stylesheet>

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

<t>
    <a>
        <b m="1" n="2"/>
        <x/>
    </a>
    <c/>
    <x/>
    <d n="3"/>
</t>

желаемый, правильный результат получается (все элементы с именем x и все атрибуты с именем n удаляются):

<t>
   <a>
      <b m="1"/>
   </a>
   <c/>
   <d/>
</t>

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

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

     <xsl:param name="removeElementsNamed" select="'|c|x|'"/>
     <xsl:param name="removeAttributesNamed" select="'|n|p|'"/>

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

     <xsl:template match="*">
      <xsl:if test=
      "not(contains($removeElementsNamed,
                    concat('|', name(), '|')
                    )
           )
      ">
       <xsl:call-template name="identity"/>
      </xsl:if>
     </xsl:template>

     <xsl:template match="@*">
      <xsl:if test=
      "not(contains($removeAttributesNamed,
                    concat('|', name(), '|')
                    )
           )
       ">
       <xsl:call-template name="identity"/>
      </xsl:if>
     </xsl:template>
</xsl:stylesheet>

Когда это преобразование применяется к следующему документу XML :

<t>
    <a p="0">
        <b m="1" n="2"/>
        <x/>
    </a>
    <c/>
    <x/>
    <d n="3"/>
</t>

желаемый, правильный результат выдается (элементы с именами c и x и атрибуты с именами n и p удаляются):

<t>
   <a>
      <b m="1"/>
   </a>
   <d/>
</t>
3 голосов
/ 22 февраля 2012

Вот вариант XSLT 2.0, если вы можете использовать 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:strip-space elements="*"/>

  <xsl:param name="removeElementsNamed" select="'bar,baz'"/>  

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

  <xsl:template match="*[name()=tokenize($removeElementsNamed,'[\|, \t]')]"/>  

</xsl:stylesheet>
0 голосов
/ 22 февраля 2012

Это что-то хакерское, но оно может дать вам общее представление:

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

<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="removeElementsNamed"/>

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

<xsl:template match="*[contains($removeElementsNamed, concat(',',name(),','))]"/>

Вам необходимо указать имена элементов для удаления в виде списка через запятую, начиная сзапятая и заканчивающаяся запятой, например значение ", foo, bar, baz," удалит все элементы с именем foo bar или baz.Если у вас нет элементов, которые являются частичными именами других элементов, вы можете упростить это до:

<xsl:template match="*[contains($removeElementsNamed,name())]"/>

Однако, если у вас есть XML, такой как

<foo>
  <bar>..<bar>
  <barbara>..</barbara>
<foo>

и используйте "bar"msgstr "в качестве параметра удаляются теги bar и barbara, поэтому первый подход безопаснее.

0 голосов
/ 22 февраля 2012
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

   <xsl:output omit-xml-declaration="yes" indent="yes"/>
   <xsl:param name="removeMe"/>

   <xsl:template match="node() | @*">
      <xsl:if test="not(name(.)=$removeMe)">
        <xsl:copy>
           <xsl:apply-templates select="node() | @*"/>
        </xsl:copy>
      </xsl:if>
   </xsl:template>   


</xsl:stylesheet>
...