Требуется XSLT-преобразование для удаления дублирующих элементов - отсортировано по атрибуту - PullRequest
6 голосов
/ 26 января 2012

У меня есть ужасный кусок XML, который мне нужно обработать через BizTalk, и мне удалось нормализовать его в этом примере ниже.Я не ниндзя XSLT, но между сетью и отладчиком VS2010 я могу обойти XSL.

Теперь мне нужен умный бит XSLT, чтобы "отсеять" дублирующиеся элементы и толькосохраняйте последние, как определено датой в атрибуте ValidFromDate .

Атрибут ValidFromDate имеет тип XSD: Date.

<SomeData>
  <A ValidFromDate="2011-12-01">A_1</A>
  <A ValidFromDate="2012-01-19">A_2</A>
  <B CalidFromDate="2011-12-03">B_1</B>
  <B ValidFromDate="2012-01-17">B_2</B>
  <B ValidFromDate="2012-01-19">B_3</B>
  <C ValidFromDate="2012-01-20">C_1</C>
  <C ValidFromDate="2011-01-20">C_2</C>
</SomeData>

После преобразования я хотел бы сохранить только следующие строки:

<SomeData>
  <A ValidFromDate="2012-01-19">A_2</A>
  <B ValidFromDate="2012-01-19">B_3</B>
  <C ValidFromDate="2012-01-20">C_1</C>
</SomeData>

Есть какие-нибудь подсказки относительно того, как я собрал этот XSL?Я опустошил Интернет, пытаясь найти решение, и я перепробовал много умных сценариев сортировки XSL, но ни один из них, по моему мнению, не направил меня в правильном направлении.

Ответы [ 6 ]

3 голосов
/ 26 января 2012

Оптимальным решением этой проблемы с Xslt 1.0 было бы использование мюнхенской группировки. (Учитывая, что элементы уже отсортированы по атрибуту ValidFromDate), следующая таблица стилей должна сделать свое дело:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes"/>

  <xsl:key name="element-key" match="/SomeData/*" use="name()" />

  <xsl:template match="/SomeData">
    <xsl:copy>
      <xsl:for-each select="*[generate-id() = generate-id(key('element-key', name()))]">
        <xsl:copy-of select="(. | following-sibling::*[name() = name(current())])[last()]" />
      </xsl:for-each>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

Вот результат, полученный при работе с вашим примером XML:

<?xml version="1.0" encoding="utf-8"?>
<SomeData>
  <A ValidFromDate="2012-01-19">A_2</A>
  <B ValidFromDate="2012-01-19">B_3</B>
  <C ValidFromDate="2011-01-20">C_2</C>
</SomeData>
2 голосов
/ 27 января 2012

Несколько проще и короче решение XSLT 1.0, чем у @ lwburk :

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

 <xsl:key name="kName" match="*/*" use="name()"/>

 <xsl:template match="/">
  <xsl:apply-templates select=
   "*/*[generate-id()
       =
        generate-id(key('kName', name())[1])
       ]
   "/>
 </xsl:template>

 <xsl:template match="*/*">
  <xsl:for-each select="key('kName', name())">
   <xsl:sort select="@ValidFromDate" order="descending"/>
   <xsl:if test="position() = 1">
    <xsl:copy-of select="."/>
   </xsl:if>
  </xsl:for-each>
 </xsl:template>
</xsl:stylesheet>

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

<SomeData>
    <A ValidFromDate="2011-12-01">A_1</A>
    <A ValidFromDate="2012-01-19">A_2</A>
    <B CalidFromDate="2011-12-03">B_1</B>
    <B ValidFromDate="2012-01-17">B_2</B>
    <B ValidFromDate="2012-01-19">B_3</B>
    <C ValidFromDate="2012-01-20">C_1</C>
    <C ValidFromDate="2011-01-20">C_2</C>
</SomeData>

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

<A ValidFromDate="2012-01-19">A_2</A>
<B ValidFromDate="2012-01-19">B_3</B>
<C ValidFromDate="2012-01-20">C_1</C>
2 голосов
/ 27 января 2012

На основе @ValidFromDate заказ:

XSLT:

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

  <xsl:key name="k" match="*" use="name()"/>

  <xsl:template match="SomeData">
    <xsl:copy>
      <xsl:apply-templates select="*[generate-id() = 
                           generate-id(key('k', name()))]"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="*">
    <xsl:apply-templates select="key('k', name())" mode="a">
      <xsl:sort select="@ValidFromDate" order="descending"/>
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="*" mode="a">
    <xsl:if test="position() = 1">
      <xsl:copy-of select="."/>
    </xsl:if>
  </xsl:template>

</xsl:stylesheet>

применяется:

<SomeData>
  <A ValidFromDate="2011-12-01">A_1</A>
  <A ValidFromDate="2012-01-19">A_2</A>
  <B CalidFromDate="2011-12-03">B_1</B>
  <B ValidFromDate="2012-01-17">B_2</B>
  <B ValidFromDate="2012-01-19">B_3</B>
  <C ValidFromDate="2012-01-20">C_1</C>
  <C ValidFromDate="2011-01-20">C_2</C>
</SomeData>

производит:

<SomeData>
  <A ValidFromDate="2012-01-19">A_2</A>
  <B ValidFromDate="2012-01-19">B_3</B>
  <C ValidFromDate="2012-01-20">C_1</C>
</SomeData>
2 голосов
/ 27 января 2012

Следующая таблица стилей дает правильный результат без зависимости от порядка ввода:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>
    <xsl:key name="byName" match="/SomeData/*" use="name()"/>
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="SomeData">
        <xsl:copy>
            <xsl:apply-templates select="@*"/>
            <xsl:for-each select="*[generate-id()=
                                    generate-id(key('byName', name())[1])]">
                <xsl:apply-templates select="key('byName', name())" mode="out">
                    <xsl:sort select="translate(@ValidFromDate, '-', '')" 
                              data-type="number" order="descending"/>
                </xsl:apply-templates>
            </xsl:for-each>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="SomeData/*" mode="out">
        <xsl:if test="position()=1">
            <xsl:apply-templates select="."/>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>

Вывод:

<SomeData>
   <A ValidFromDate="2012-01-19">A_2</A>
   <B ValidFromDate="2012-01-19">B_3</B>
   <C ValidFromDate="2012-01-20">C_1</C>
</SomeData>

Обратите внимание, что результат равен слегка отличается от того, что вы указали в качестве желаемого вывода, потому что C_1 на самом деле является самым последним элементом C (т.е. вход не уже отсортирован).Опираясь на начальный порядок сортировки (и слепо следуя указанному ожидаемому результату), существующие ответы фактически неверны.

Объяснение:

  • An xsl:keygroups all /SomeData/* по name()
  • Внешний for-each выбирает первый элемент в каждой группе
  • Затем шаблоны применяются ко всем членам этой группы, которые сортируются по @ValidFromDate
  • Один дополнительный шаблон обрабатывает выбор первого элемента из каждой отсортированной группы
  • Шаблон Identity Transform заботится обо всех остальных
1 голос
/ 27 января 2012

XLST 2.0 решение без учета порядка ввода.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:template match="/">
        <SomeData>
            <xsl:for-each-group select="/SomeData/*" group-by="name()">
                    <xsl:for-each select="current-group()">
                        <xsl:sort select="number(substring(attribute(),1,4))" order="descending" data-type="number"/> <!-- year-->
                        <xsl:sort select="number(substring(attribute(),6,2))" order="descending" data-type="number"/> <!-- month-->
                        <xsl:sort select="number(substring(attribute(),9,2))" order="descending" data-type="number"/> <!-- date-->
                        <xsl:if test="position()=1">
                                <xsl:sequence select="."/>
                        </xsl:if>
                    </xsl:for-each>
            </xsl:for-each-group>
        </SomeData>
</xsl:template>
</xsl:stylesheet>
1 голос
/ 26 января 2012

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

<xsl:template match="/SomeData">
  <xsl:copy>
    <xsl:copy-of select="*[generate-id() = generate-id(key('element-key', name())[last()])]"/>
  </xsl:copy>
</xsl:template>

Если они каждый раз дают один и тот же результат, мне это нравится, потому что он немного чище.

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