xsl transformar template que programacion generar ejemplos archivo xml xslt xpath biztalk

xml - transformar - xsl:template



Necesita transformada XSLT para eliminar elementos duplicados, ordenados por un atributo (6)

Basado en el orden @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>

aplicado en:

<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>

produce:

<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>

Tengo una pieza terrible de XML que necesito procesar a través de BizTalk, y he logrado normalizarlo en este ejemplo a continuación. No soy ninja XSLT, pero entre la web y el depurador VS2010, puedo encontrar mi camino alrededor de XSL.

Ahora necesito un poco de XSLT para "eliminar" los elementos duplicados y solo conservar los últimos, según lo decidido por la fecha en el atributo ValidFromDate .

El atributo ValidFromDate es del XSD: tipo de fecha.

<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>

Después de una transformación, me gustaría mantener solo estas líneas:

<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>

¿Alguna pista sobre cómo puse ese XSL juntos? He vaciado Internet tratando de buscar una solución, y he intentado muchos guiones de ordenamiento XSL inteligentes, pero ninguno me pareció llevarme en la dirección correcta.


La siguiente hoja de estilo produce el resultado correcto sin depender del orden de entrada:

<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>

Salida:

<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>

Tenga en cuenta que el resultado es ligeramente diferente de lo que usted indicó como la salida deseada, porque C_1 es realmente el último elemento C (es decir, la entrada aún no está ordenada). Al confiar en un orden de clasificación inicial (y seguir ciegamente el resultado esperado listado), las respuestas existentes son realmente incorrectas.

Explicación:

  • Una xsl:key agrupa todo /SomeData/* por name()
  • El for-each externo selecciona el primer elemento en cada grupo
  • Las plantillas se aplican a todos los miembros de ese grupo, que se ordenan por @ValidFromDate
  • Una sola plantilla adicional maneja la selección del primer elemento de cada grupo ordenado
  • Una plantilla de Transformación de identidad se ocupa del resto

La solución óptima para este problema con Xslt 1.0 sería usar la agrupación Muenchian. (Dado que los elementos ya están ordenados por el atributo ValidFromDate), la siguiente hoja de estilo debería ser la solución:

<?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>

Este es el resultado que obtuve cuando lo ejecuté contra su muestra 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>


Solución XLST 2.0 sin depender de la orden de entrada.

<?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>


Una solución XSLT 1.0 ligeramente más simple y más corta que la de @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>

cuando esta transformación se aplica en el documento XML proporcionado :

<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>

el resultado deseado y correcto se produce :

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


En base a la respuesta de Pawel , realicé la siguiente modificación, que produce el mismo resultado:

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

Si producen el mismo resultado cada vez, me gusta porque es un poco más limpio.