XSL Удалить все предыдущие одноуровневые элементы на основе значений элемента

Привет, мне нужна помощь в разборе следующего XML.

<xmeml>
<Doc>
    <Test>
        <Unit>abc</Unit>
        <Unit2>1234</Unit2>
    </Test>
    <Test>
        <Unit>bcd</Unit>
        <Unit2>2345</Unit2>
    </Test>
</Doc>
<Doc>
    <Test>
        <Unit>abc</Unit>
        <Unit2>3456</Unit2>
    </Test>
    <Test>
        <Unit>cde</Unit>
        <Unit2>3456</Unit2>
    </Test> 
</Doc>
<Doc>
    <Test>
        <Unit>abc</Unit>
        <Unit2>1234</Unit2>
    </Test>
    <Test>
        <Unit>def</Unit>
        <Unit2>4567</Unit2>
    </Test> 
</Doc>
<Doc>
    <Test>
        <Unit>abc</Unit>
        <Unit2>1234</Unit2>
    </Test>
    <Test>
        <Unit>efg</Unit>
        <Unit2>2345</Unit2>
    </Test> 
</Doc>
</xmeml>

заканчивая следующим

<xmeml>
<Doc>
    <Test>
        <Unit>bcd</Unit>
        <Unit2>2345</Unit2>
    </Test>
</Doc>
<Doc>
    <Test>
        <Unit>abc</Unit>
        <Unit2>3456</Unit2>
    </Test>
    <Test>
        <Unit>cde</Unit>
        <Unit2>3456</Unit2>
    </Test> 
</Doc>
<Doc>
    <Test>
        <Unit>def</Unit>
        <Unit2>4567</Unit2>
    </Test> 
</Doc>
<Doc>
    <Test>
        <Unit>abc</Unit>
        <Unit2>1234</Unit2>
    </Test>
    <Test>
        <Unit>efg</Unit>
        <Unit2>2345</Unit2>
    </Test> 
</Doc>
</xmeml>

Я пытаюсь создать XSLT-документ для этого, но пока не нашел работающего. Я должен отметить, что обязательными параметрами сопоставления в «Документе» являются , в данном случае «abc» и «1234». В реальном мире это переменные, и они никогда не будут статической доступной для поиска сущностью.

Итак, на английском языке мой XSL будет таким: для любого родителя, содержащего совпадающие значения «Unit» и «unit2», удалите все предыдущие родительские «Test», содержащие повторяющиеся значения «Unit» и «Unit2», кроме последнего.

Вся помощь наиболее ценится Спасибо


person user1540142    schedule 21.07.2012    source источник


Ответы (3)


Вот относительно простой способ сделать это, хотя я вполне уверен, что есть более эффективный способ, использующий метод Meunchian. Однако, если производительность не является проблемой, это, вероятно, легче выполнить:

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

  <xsl:template match="Test">
    <xsl:variable name="vUnit" select="Unit" />
    <xsl:variable name="vUnit2" select="Unit2" />
    <xsl:if test="not(following::Test[Unit = $vUnit and Unit2 = $vUnit2])">
      <xsl:call-template name="identity" />
    </xsl:if>
  </xsl:template>

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

Шаблон Test просто проверяет, есть ли более поздний элемент Test с теми же значениями в Unit и Unit2, и если нет, выводит его как обычно.

person Flynn1179    schedule 21.07.2012
comment
Спасибо, Флинн. Идеальный результат для вас. - person user1540142; 21.07.2012
comment
Привет, Флинн. Как бы я адаптировал приведенное выше, если бы я хотел добавить еще одну фразу соответствия. IE: ищите дубликаты Unit и Unit2, а также ищите совпадения, где, скажем, Unit3 является известным значением 4567. Нам нужно представить, что Unit3 также присутствует во всех вышеперечисленных тестовых узлах. - person user1540142; 22.07.2012
comment
Это довольно просто, просто добавьте and Unit3 = 4567 в предикат Test в атрибуте test элемента xsl:if. - person Flynn1179; 22.07.2012
comment
Еще раз спасибо, Флинн, а как насчет того, чтобы Unit3 начинался с 456? Что-то вроде и Unit3[starts-with(.,'456')] ? - person user1540142; 22.07.2012
comment
Примерно так, хотя лично я бы сделал starts-with(Unit3, '456'), но по сути то же самое. - person Flynn1179; 22.07.2012
comment
Флинн, Должен ли я также определять Unit 3 как переменную? т. е. ‹xsl:variable name=vUnit3 select=Unit3 /› и затем добавить start-with($vUnit3, '456) . - person user1540142; 22.07.2012
comment
Привет, Флинн. Извините, не волнуйтесь, я задал более конкретный вопрос. Спасибо, хотя я тоже использую ваш первый результат. - person user1540142; 22.07.2012

Многие проблемы, связанные с устранением дубликатов, можно решить в XSLT 2.0 с помощью конструкции for-each-group. В этом случае решение с использованием для каждой группы неочевидно, потому что на самом деле это не проблема группировки (с проблемами группировки мы обычно создаем один элемент на выходе, который соответствует группе элементов на входе, и здесь это не так.) Я бы решил это так же, как Дмитрий: используйте for-each-group для определения групп и, следовательно, элементов Test, которые необходимо сохранить, по сравнению с теми, которые необходимо удалить. На самом деле я начал решать это и придумал решение, очень похожее на решение Дмитрия, за исключением того, что я думаю, что последнее правило шаблона можно упростить до

<xsl:template match="Test[not(. intersect $vLastInGroup)]"/>

Это пример шаблона кодирования, который я иногда использую, когда вы настраиваете глобальные переменные с набором узлов, содержащие все элементы с определенной характеристикой, а затем используете шаблонные правила, которые проверяют принадлежность к глобальному набору узлов (используя предикат [. intersect $node-set] ). Следуя этому шаблону и используя новый синтаксис, доступный в XSLT 3.0, я бы написал код следующим образом:

<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xs="http://www.w3.org/2001/XMLSchema">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>
 <xsl:mode on-no-match="shallow-copy"/>

 <xsl:variable name="deletedElements" as="element()*">
  <xsl:for-each-group select="/*/Doc/Test"
                      group-by="Unit, Unit2" composite="yes">
   <xsl:sequence select="current-group()[position() ne last()]"/>
  </xsl:for-each-group>
 </xsl:variable>

 <xsl:template match="$deletedElements"/>
</xsl:stylesheet>
person Michael Kay    schedule 22.07.2012

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

Вот простое (без переменных, без xsl:if, без осей, без xsl:call-template) применение самого эффективного из известных методов группировки 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:key name="kTestByData" match="Test"
  use="concat(Unit, '|', Unit2)"/>

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

 <xsl:template match=
  "Test[not(generate-id()
           = generate-id(key('kTestByData',concat(Unit, '|', Unit2))[last()])
            )]"/>
</xsl:stylesheet>

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

<xmeml>
    <Doc>
        <Test>
            <Unit>abc</Unit>
            <Unit2>1234</Unit2>
        </Test>
        <Test>
            <Unit>bcd</Unit>
            <Unit2>2345</Unit2>
        </Test>
    </Doc>
    <Doc>
        <Test>
            <Unit>abc</Unit>
            <Unit2>3456</Unit2>
        </Test>
        <Test>
            <Unit>cde</Unit>
            <Unit2>3456</Unit2>
        </Test>
    </Doc>
    <Doc>
        <Test>
            <Unit>abc</Unit>
            <Unit2>1234</Unit2>
        </Test>
        <Test>
            <Unit>def</Unit>
            <Unit2>4567</Unit2>
        </Test>
    </Doc>
    <Doc>
        <Test>
            <Unit>abc</Unit>
            <Unit2>1234</Unit2>
        </Test>
        <Test>
            <Unit>efg</Unit>
            <Unit2>2345</Unit2>
        </Test>
    </Doc>
</xmeml>

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

<xmeml>
   <Doc>
      <Test>
         <Unit>bcd</Unit>
         <Unit2>2345</Unit2>
      </Test>
   </Doc>
   <Doc>
      <Test>
         <Unit>abc</Unit>
         <Unit2>3456</Unit2>
      </Test>
      <Test>
         <Unit>cde</Unit>
         <Unit2>3456</Unit2>
      </Test>
   </Doc>
   <Doc>
      <Test>
         <Unit>def</Unit>
         <Unit2>4567</Unit2>
      </Test>
   </Doc>
   <Doc>
      <Test>
         <Unit>abc</Unit>
         <Unit2>1234</Unit2>
      </Test>
      <Test>
         <Unit>efg</Unit>
         <Unit2>2345</Unit2>
      </Test>
   </Doc>
</xmeml>

Обратите внимание: для наборов узлов с большим количеством узлов, подлежащих дедупликации, метод мюнхианской группировки во много раз быстрее, чем квадратичное (O(N^2)) группирование сравнения братьев и сестер.


II. Решения XSLT 2.0:

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

 <xsl:template match=
  "Test[concat(Unit, '+', Unit2) = following::Test/concat(Unit, '+', Unit2)]"/>
</xsl:stylesheet>

II.2 Эффективное решение с использованием xsl:for-each-group:

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

 <xsl:variable name="vLastInGroup" as="element()*">
  <xsl:for-each-group select="/*/Doc/Test"
       group-by="concat(Unit, '+', Unit2)">
   <xsl:sequence select="current-group()[last()]"/>
  </xsl:for-each-group>
 </xsl:variable>

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

 <xsl:template match=
 "Test[for $t in .
        return
         not($vLastInGroup[. is $t])
      ]"/>
</xsl:stylesheet>
person Dimitre Novatchev    schedule 21.07.2012
comment
«Простота» — относительное понятие. Для опытного пользователя, такого как вы, я бы согласился, что это более простое решение, но для новичка/новичка в XSLT группировка по Мюнху на самом деле довольно сложна для понимания. Хотя это не самое эффективное (как я уже сказал), проверку на отсутствие следующих братьев и сестер гораздо проще понять, учитывая, что это концептуально похоже на описание необходимого решения. Однако, если вам нужна скорость и вы можете ее понять, я согласен, что это решение лучше. - person Flynn1179; 21.07.2012
comment
@Flynn1179: Что касается понятности - я с вами согласен. Однако ваше преобразование излишне сложно, и это на самом деле затрудняет его понимание. Этим комментарием я действительно помогу сделать ваше решение более простым и элегантным. - person Dimitre Novatchev; 21.07.2012
comment
@Flynn1179: Есть некоторая объективная мера понятности - проще говоря, количество движущихся частей. Если в одном решении есть одна ось xsl:if, две оси xsl:variable, одна ось following, одна xsl:call-template, а в другом нет ничего из этого, то какое решение проще? - person Dimitre Novatchev; 21.07.2012
comment
@MichaelKay, меня удивляет, что решить эту проблему с помощью xsl:for-each-group очень сложно и сложно по сравнению с первым, мюнхенским решением. Можете ли вы предложить лучшее решение? - person Dimitre Novatchev; 21.07.2012
comment
Еще раз спасибо, Димитр, за решение xsl v1.0, извините за это, но .... что бы я сделал, если бы я также хотел добавить третий элемент в ключ, который был чем-то вроде запуска Unit3 с «345». Приходится делать вид, что Unit3 присутствует и в каждом тестовом узле. - person user1540142; 22.07.2012
comment
на самом деле, Дмитрий, это снова другой вопрос. так что не обращайте внимания на предыдущий комментарий. Спасибо, в любом случае :) - person user1540142; 22.07.2012
comment
@ user1540142: ваши комментарии непонятны - пожалуйста, начните новый вопрос и подробно объясните. - person Dimitre Novatchev; 22.07.2012