Удалить элементы и/или атрибуты по имени в соответствии с параметрами XSL

Далее выполняется удаление нежелательных элементов и атрибутов по имени (в данном примере «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"/>

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

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


person Witman    schedule 21.02.2012    source источник
comment
Умеете ли вы использовать XSLT 2.0?   -  person Daniel Haley    schedule 22.02.2012
comment
@DevNull - Хороший вопрос. Я просто задал его здесь.   -  person Witman    schedule 22.02.2012
comment
Благодаря всем хорошим ответам на ответы, вопрос был расширен, чтобы прояснить желаемую функцию, добавив функцию удаления атрибута в качестве отдельной функции (не объединять вместе с удалением элемента, но доступно в том же коде).   -  person Witman    schedule 02.03.2012


Ответы (4)


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

<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: то же преобразование, что и в Update1, но написанное на 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>

Обновление: в 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="'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>

ОБНОВЛЕНИЕ 2: в соответствии с запросом ОП, теперь мы реализуем возможность передачи списка имен, разделенных вертикальной чертой, для удаления элементов с этими именами и, соответственно, списка имен, разделенных вертикальной чертой, для удаления атрибуты с этими именами:

<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>
person Dimitre Novatchev    schedule 21.02.2012
comment
Как бы вы обработали несколько имен элементов, переданных в параметре? (OP подразумевает несколько имен на основе множественного использования элементов в $removeElementsNamed) - person Daniel Haley; 22.02.2012
comment
ой! мины не в патерне матча! должно работать, однако я не могу проверить дома, у меня нет банкомата программного обеспечения для разработчиков :_( - person Treemonkey; 22.02.2012
comment
@DevNull: Он хочет сказать: удалите все элементы с именем XXX. Вот почему он использует множественное число. Конечно, если ОП уточнит, что ему нужно удалить элементы с именем из списка имен, я буду рад дать ему соответствующее решение. :) - person Dimitre Novatchev; 22.02.2012
comment
@DevNull: я обновил этот ответ решением проблемы с несколькими именами. Спасибо, что спросили об этом. - person Dimitre Novatchev; 22.02.2012
comment
@DevNull: второе обновление, дающее соответствующее решение XSLT 2.0 - хотя я ни секунды не смотрел на ваше, оба кажутся почти одинаковыми ... Я торжественно заявляю, что не копировал и не вставлял ваш код :) . Недостатком XSLT 2.0 является то, что двум разработчикам гораздо проще, чем в 1.0, предложить одно и то же решение одновременно. - person Dimitre Novatchev; 22.02.2012
comment
Спасибо, Димитр! Отличный исчерпывающий ответ +1. На этот раз я не могу использовать XSLT 2.0, но планирую изменить направление в будущем. Первоначально я имел в виду удаление нескольких экземпляров одного именованного элемента, как вы поняли, но целью было гибкое преобразование, запускаемое Javascript, а возможность одновременного удаления нескольких именованных элементов даже лучше, поэтому я использовал обновление 1. Работа красиво! - person Witman; 02.03.2012
comment
@Witman: Добро пожаловать. Является ли ваше обновление новым вопросом? Я не вижу никаких изменений, которые были внесены в ваш вопрос. - person Dimitre Novatchev; 02.03.2012
comment
@DimitreNovatchev: Конечно, я отредактирую вопрос для будущих читателей. Добавление второго параметра $removeAttributesNamed и копирование второго шаблона с match=@* (вместо match=*), а также ссылка на новый параметр в этом добавленном шаблоне работают должным образом. Еще раз спасибо! - person Witman; 02.03.2012
comment
@DimitreNovatchev: Хотели бы вы обновить свой ответ с помощью функции $removeAttributesNamed? - person Witman; 02.03.2012
comment
+1 теперь должен быть немедленным, чтобы OP расширил ваш ответ на другие более сложные случаи. - person Emiliano Poggi; 02.03.2012
comment
@Witman: я обновил свой ответ решением, которое реализует новое требование - см. обновление в самом конце ответа. - person Dimitre Novatchev; 02.03.2012
comment
@Dimitre: К сожалению, я пропустил, что это не сохранило отдельный список каналов. Возможно, мой вопрос был не ясен. Если у вас есть возможность, отредактируйте свое последнее обновление, включив в него возможность удалять несколько элементов за раз и/или несколько атрибутов за раз. - person Witman; 02.03.2012
comment
@DimitreNovatchev: Красиво. Спасибо, и извините за путаницу. Надеюсь, теперь я ясно изложил вопрос! - person Witman; 02.03.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>
person Daniel Haley    schedule 21.02.2012
comment
Вы заставляете меня хотеть XSLT 2.0. Может в следующий раз... Спасибо! - person Witman; 02.03.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(),','))]"/>

You need to specify the element names to remove as a comma separated list, starting with a comma and ending with a comma, e.g. the value ",foo,bar,baz," will remove all elements named foo bar or baz. If you don't have any elements that are partial names of other elements you can simplify this to:

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

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

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

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

person Jan Thomä    schedule 21.02.2012
comment
Как заметил Димитре, это выдает ошибку. Обработка его с помощью msxml3.dll останавливается с ошибкой: В этом выражении нельзя использовать переменные. и ссылается на match=*[contains($removeElementsnamed... - person Witman; 02.03.2012
comment
Я вижу, это решение не работает с XSLT 1.0, опять же, это не было указано как требование :) - person Jan Thomä; 05.03.2012
comment
Спасибо за Ваш ответ. Мне жаль, что вопрос не был более ясен для начала; Я ничего не знал о XSLT 2.0, пока об этом не рассказал DevNull... учусь каждый день и надеюсь использовать XSLT 2.0 в будущем. - person Witman; 06.03.2012

person    schedule
comment
?? $removeMe нигде не определен. - person Daniel Haley; 22.02.2012
comment
Разве это не будет фильтровать только одно имя элемента? - person Jan Thomä; 22.02.2012
comment
@JanThomä нет, потому что это рекурсивный шаблон - person Treemonkey; 22.02.2012
comment
Ааа... изначально у тебя был removeMe по имени removeElementsNamed. - person Daniel Haley; 22.02.2012
comment
Этот код делает несколько нежелательных вещей: 1. Удаляет элемент с именем RemoveMe. 2. Использует неопределенную переменную. 3. Даже если бы переменная была определена, этот код удалял бы не только элементы, но и атрибуты. - person Dimitre Novatchev; 22.02.2012
comment
Я думаю, что @JanThomä говорит о том, что он фильтрует только одно имя элемента, переданное в качестве параметра. (OP подразумевает несколько имен на основе множественного использования элементов в $removeElementsNamed) - person Daniel Haley; 22.02.2012
comment
Действительно, я имел в виду это. Мое решение тоже не особенно чистое... - person Jan Thomä; 22.02.2012
comment
Treemonkey, вы исправили некоторые проблемы, но не все. - person Dimitre Novatchev; 22.02.2012
comment
@DimitreNovatchev, не могли бы вы объяснить мне, чем ваш ответ отличается от того, что я вижу, он такой же, но в двух шаблонах! - person Treemonkey; 22.02.2012
comment
@Treemonkey: О, но это очевидно, и я уже упоминал об этом в своем первом комментарии 18 минут назад - проблема 3. все еще остается нерешенной. Ваш код в его нынешнем виде удаляет не только элементы, но и атрибуты. - person Dimitre Novatchev; 22.02.2012
comment
@Treemonkey: настоящая версия вашего ответа не различает атрибуты и элементы, то есть ее нельзя использовать для удаления всех элементов времени без одновременного удаления всех атрибутов времени. Мой исходный код в вопросе имеет ту же проблему. Починю. - person Witman; 02.03.2012