2010-04-24 80 views
8

Tengo una hoja xslt bastante complicada que transforma un formato xml a otro usando plantillas. Sin embargo, en el xml resultante, necesito tener todos los elementos vacíos excluidos. ¿Cómo se hace eso?XSLT: ¿Cómo excluir elementos vacíos de mi resultado?

Esta es la forma en la base de XSLT se parece a:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:far="http://www.itella.com/fargo/fargogate/" xmlns:a="http://tempuri.org/XMLSchema.xsd" xmlns:p="http://tempuri.org/XMLSchema.xsd"> 
    <xsl:import href="TransportCDMtoFDM_V0.6.xsl"/> 
    <xsl:import href="ConsignmentCDMtoFDM_V0.6.xsl"/> 
    <xsl:template match="/"> 
     <InboundFargoMessage> 
      <EdiSender> 
       <xsl:value-of select="TransportInformationMessage/SenderId"/> 
      </EdiSender> 
      <EdiReceiver> 
       <xsl:value-of select="TransportInformationMessage/RecipientId"/> 
      </EdiReceiver> 
      <EdiSource> 
       <xsl:value-of select="TransportInformationMessage/Waybill/Parties/Consignor/Id"/> 
      </EdiSource> 
      <EdiDestination>FARGO</EdiDestination> 
      <Transportations> 
       <xsl:for-each select="TransportInformationMessage/TransportUnits/TransportUnit"> 
        <xsl:call-template name="transport"/> 
       </xsl:for-each> 
       <xsl:for-each select="TransportInformationMessage/Waybill/TransportUnits/TransportUnit"> 
        <xsl:call-template name="transport"/> 
       </xsl:for-each> 
       <xsl:for-each select="TransportInformationMessage/Waybill"> 
        <EdiImportTransportationDTO> 
         <Consignments> 
          <xsl:for-each select="Shipments/Shipment"> 
           <xsl:call-template name="consignment"/> 
          </xsl:for-each> 
         </Consignments> 
         <EdiTerminalDepartureTime> 
          <xsl:value-of select="DatesAndTimes/EstimatedDepartureDateTime"/> 
          <xsl:value-of select="DatesAndTimes/DepartureDateTime"/> 
         </EdiTerminalDepartureTime> 
         <EdiAgentTerminalArrivalDate> 
          <xsl:value-of select="DatesAndTimes/EstimatedArrivalDateTime"/> 
          <xsl:value-of select="DatesAndTimes/ArrivalDateTime"/> 
         </EdiAgentTerminalArrivalDate> 
         <EdiActivevehicle> 
          <xsl:value-of select="Vehicle/TransportShiftNumber"/> 
         </EdiActivevehicle> 
         <EdiConveyerZipCodeTown><xsl:text> </xsl:text></EdiConveyerZipCodeTown> 
        </EdiImportTransportationDTO> 
       </xsl:for-each> 
      </Transportations> 
     </InboundFargoMessage> 
    </xsl:template> 
</xsl:stylesheet> 

Lo que hay que añadir, de manera que los elementos vacíos se quedan fuera?

Por ejemplo, un fragmento del XML resultante:

<?xml version="1.0" encoding="UTF-8"?> 
<InboundFargoMessage xmlns:p="http://tempuri.org/XMLSchema.xsd" 
     xmlns:far="http://www.itella.com/fargo/fargogate/" 
     xmlns:a="http://tempuri.org/XMLSchema.xsd"> 
    <EdiSender>XXXX</EdiSender> 
    <EdiReceiver>YYYY</EdiReceiver> 
    <EdiSource>TR/BAL/IST</EdiSource> 
    <EdiDestination>FARGO</EdiDestination> 
    <Transportations> 
     <EdiImportTransportationDTO> 
      <Consignments> 
       <EdiImportConsignmentDTO> 
        <ConsignmentLines> 
         <EdiImportConsignmentLineDTO> 
          <DangerousGoodsItems> 
           <EdiImportDangerGoodsItemDTO> 
            <EdiKolliTypeOuter/> 
            <EdiKolliTypeInner/> 
            <EdiTechnicalDescription/> 
            <EdiUNno/> 
            <EdiClass/> 
            <EdiDangerFactor/> 
            <EdiEmergencyTemperature/> 
           </EdiImportDangerGoodsItemDTO> 
          </DangerousGoodsItems> 
          <BarCodes> 
           <EdiImportConsignmentLineBarcodeDTO/> 
          </BarCodes> 
          <EdiNumberOfPieces>00000002</EdiNumberOfPieces> 
          <EdiGrossWeight>0.000</EdiGrossWeight> 
          <EdiHeight/> 
          <EdiWidth/> 
          <EdiLength/> 
          <EdiGoodsDescription/> 
          <EdiMarkingAndNumber/> 
          <EdiKolliType>road</EdiKolliType> 
          <EdiCbm/> 
          <EdiLdm/> 
         </EdiImportConsignmentLineDTO> 

que realmente tiene que ser:

<?xml version="1.0" encoding="UTF-8"?> 
<InboundFargoMessage xmlns:p="http://tempuri.org/XMLSchema.xsd" 
     xmlns:far="http://www.itella.com/fargo/fargogate/" 
     xmlns:a="http://tempuri.org/XMLSchema.xsd"> 
    <EdiSender>XXXX</EdiSender> 
    <EdiReceiver>YYYY</EdiReceiver> 
    <EdiSource>TR/BAL/IST</EdiSource> 
    <EdiDestination>FARGO</EdiDestination> 
    <Transportations> 
     <EdiImportTransportationDTO> 
      <Consignments> 
       <EdiImportConsignmentDTO> 
        <ConsignmentLines> 
         <EdiImportConsignmentLineDTO> 
          <DangerousGoodsItems/> 
          <BarCodes/> 
          <EdiNumberOfPieces>00000002</EdiNumberOfPieces> 
          <EdiGrossWeight>0.000</EdiGrossWeight> 
          <EdiKolliType>road</EdiKolliType> 
         </EdiImportConsignmentLineDTO> 

En otras palabras: Los elementos vacíos debe dejarse de lado.

+0

Sé más específico. ¿Desea omitir nodos vacíos en los bucles for-each? ¿Desea omitir elementos donde el valor de está en blanco? – harpo

+0

Quiero omitir elementos donde el valor está en blanco. Editaré la pregunta para agregar un ejemplo ... –

+0

Buena pregunta (+1). Vea mi respuesta para una solución que es probablemente la solución XSLT más simple y fundamental. :) –

Respuesta

11

El código XSLT proporcionado (parcial) ilustra bien un antipatrón XSLT. Intente casi siempre evitar el uso de <xsl:for-each>.

A continuación hay un documento XML de muestra y una transformación que copia todos los nodos a excepción de los elementos "vacíos". Aquí por "vacío" queremos decir sin hijos, o con un nodo hijo de solo espacios en blanco para niños.

documento XML:

<a> 
<b> 
    <c> </c> 
    <d/> 
    <e>1</e> 
</b> 
</a> 

Transformación:

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

<xsl:template match= 
    "*[not(node())] 
    | 
    *[not(node()[2]) 
    and 
    node()/self::text() 
    and 
    not(normalize-space()) 
    ] 
    "/> 
</xsl:stylesheet> 

Resultado:

<a> 
    <b> 
     <e>1</e> 
    </b> 
</a> 

hacer la nota:

  1. El uso de la regla de identidad.

  2. Cómo invalidamos la Regla de identidad con una plantilla que solo coincide con los elementos "vacíos". Como esta plantilla no hace nada (no tiene ningún cuerpo), esto no copia ("elimina") los elementos "vacíos".

Uso y anulando la regla de identidad es el patrón de diseño XSLT más importante.

+2

Creo que preferiría' * [not (*) and not (normalize-space())] '. Sí, los elementos solo contienen comentarios y/o las instrucciones de procesamiento serán filtradas por esto, pero supongo que probablemente no sea indeseable. Dicho esto, tu comentario sobre el original que es un antipatrón XSLT es perfecto. –

+0

Un colega me pasó una solución similar que ahora parece funcionar, por lo que marcaré esta respuesta como * la * solución. –

+0

@ Robert-Rossney: Preferiría no trabajar excesivamente en el modo de adivinar - esto es para lo que SO es: los usuarios pueden hacer preguntas nuevas y más específicas, sin ajustar su pregunta original en docenas de tiempo. Gracias por tu apreciación. –

1

Esta es probablemente la forma más sencilla:

<xsl:for-each select="Nodes/Node[text() != '']"> 

</xsl:for-each> 

Si usted tiene el control de la generación de XML a continuación, no agrega el nodo raíz si no hay niños. Independientemente de qué camino elijas XSL es bastante detallado.

+0

¿Pero no tendría que agregar esto a cada uno de los nodos? Eso no sería realmente simple, ¡porque hay unos cientos de nodos en total! –

+0

@Fedor - XSL no fue diseñado teniendo en cuenta la concisión. :( – ChaosPandion

+0

@Fedor - En cuanto a su actualización, esto puede satisfacer sus necesidades. – ChaosPandion

0

Existen algunos casos complicados en los que la respuesta de Dimitre (que sin duda es el enfoque correcto) podría comportarse inesperadamente.Por ejemplo, si has refactorizado su XSLT para utilizar el patrón de identidad (que debería), y que ha creado una plantilla como esta:

<xsl:template match="Vehicle/TransportShiftNumber[. != '123']"> 
    <EdiActivevehicle> 
     <xsl:value-of select="."/> 
    </EdiActivevehicle> 
</xsl:template> 

la transformada todavía puede crear vacíos EdiActivevehicle elementos TransportShiftNumber si está vacía.

Normalmente, si varias plantillas coinciden con un nodo, se seleccionará el que sea más específico. "Más específico" generalmente significa que los patrones que tienen un predicado vencerán patrones que no lo hacen. (Las reglas reales de resolución de conflictos están más involucradas, consulte la sección 5.5 de la recomendación XSLT). En este caso, tanto la plantilla anterior como la plantilla de elementos vacíos usan predicados, y por lo tanto ambos tienen la misma prioridad.

Así que el procesador XSLT hará una de dos cosas: se informará de un error (que está permitido, aunque nunca he visto un procesador XSLT que antipáticos), o de lo contrario seleccionar la plantilla que aparece última en el hoja de estilo

Hay dos formas de arreglar esto. Cualquiera de poner la plantilla vacía-elemento de filtrado en la parte inferior de la hoja de estilo, o explícitamente asignar una prioridad que es más alto que 0,5 (que es el valor por defecto para la mayoría de los patrones que tienen predicados):

I' Probablemente haga lo último, porque generalmente estructura las hojas de estilo con la expectativa de que el orden de las plantillas no es significativo y no quiero sorpresas desagradables si empiezo a mover las cosas. Pero estoy seguro de que pondré un comentario allí y me explico: nunca he visto a nadie usar una prioridad explícita en una plantilla.

0

empecé con la solución de Dimitre anterior (gracias!) Pero todavía tenía los elementos de salida o nulos con niños nulos, así:

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

Esto parece funcionar ... sigue probando.

<?xml version="1.0" encoding="UTF-8"?> 
<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:date="http://exslt.org/dates-and-times" 
    xmlns:exsl="http://exslt.org/common" 
    xmlns:func="http://exslt.org/common" 
    xmlns:random="http://exslt.org/random" 
    xmlns:regexp="http://exslt.org/regular-expressions" 
    xmlns:set="http://exslt.org/sets" 
    xmlns:str="http://exslt.org/strings" 
    version="1.0" 
    extension-element-prefixes="date exsl func random regexp set str"> 

    <xsl:output 
    method="xml" 
    encoding="utf-8" 
    omit-xml-declaration="no" 
    indent="yes"/> 

    <xsl:strip-space elements="*"/> 

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

    <xsl:template match= 
    "*[not(node())] 
    | 
    *[not(string())] 
    "/> 
</xsl:stylesheet> 
Cuestiones relacionadas