2013-05-07 10 views
5

Essendo un novizio a XSLT, sto cercando di trasformare - utilizzando XSLT 1.0 - il seguente codice XML che descrive gli oggetti:Creare elementi padre-figlio basate su attributi-valori e sopprimere gli elementi duplicati in uscita

<Data> 
    <Object> 
     <Property Name="Id" Value="001"/> 
     <Property Name="P.Id" Value="Id P"/> 
     <Property Name="P.Description" Value="Descr P"/> 
     <Property Name="A.Id" Value="Id A" /> 
     <Property Name="A.Description" Value="Descr A"/> 
     <Property Name="B.Id" Value="Id B"/> 
     <Property Name="B.Description" Value="Descr B"/> 
     <Property Name="C.Id" Value="" /> 
     <Property Name="C.Description" Value=""/> 
    </Object> 
    <Object> 
     <Property Name="Id" Value="002"/> 
     <Property Name="P.Id" Value="Id P"/> 
     <Property Name="P.Description" Value="Descr P"/> 
     <Property Name="A.Id" Value="" /> 
     <Property Name="A.Description" Value=""/> 
     <Property Name="B.Id" Value="Id B"/> 
     <Property Name="B.Description" Value="Descr B"/> 
     <Property Name="C.Id" Value="Id C" /> 
     <Property Name="C.Description" Value="Descr C"/> 
    </Object> 
</Data> 

le seguenti regole dovrebbero applicarsi a ottenere il risultato desiderato:

  1. per ogni 'Property' elemento che fa non contengono separatore' '. nell'attributo "Nome", trasforma l'attributo "Nome" in un elemento figlio e seleziona il valore dell'attributo "Valore".
  2. Per ogni elemento "Proprietà" che fa contiene il separatore "." nel 'Name'-attributo, creare:
    • a) un elemento padre usando 'substring-before' il separatore nella' Name'-attributo e
    • b) un elemento figlio usando 'substring-after' il separatore nell'attributo "Nome" e seleziona il valore dell'attributo "Valore".
  3. regole addizionali a (2):
    • a) Se 'substring-before' nella 'Name'-attributo da creare, esiste in una matrice predefinita e' Value' attributo ha un valore, sostituire l'output nome-elemento con un nome-elemento predefinito.
    • b) Per tutti gli elementi che (3a) si applicano, restituire solo la prima occorrenza nell'output, ad esempio saltare gli elementi che potrebbero verificarsi anche nell'array.

L'output desiderato dovrebbe quindi essere simile a questa:

<?xml version="1.0" encoding="UTF-8"?> 
<Root> 
    <ObjectData> 
     <Id>001</Id> 
     <P> 
      <Id>Id P</Id> 
      <Description>Descr P</Description> 
     </P> 
     <Destination> 
      <Type>A</Type> 
      <Id>Id A</Id> 
      <Description>Descr A</Description> 
     </Destination> 
    </ObjectData> 
    <ObjectData> 
     <Id>002</Id> 
     <P> 
      <Id>Id P</Id> 
      <Description>Descr P</Description> 
     </P> 
     <Destination> 
      <Type>B</Type> 
      <Id>Id B</Id> 
      <Description>Descr B</Description> 
     </Destination> 
    </ObjectData> 
</Root> 

Attualmente ho il seguente codice:

<?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" encoding="UTF-8" indent="yes" omit-xml-declaration="no"/> 
    <xsl:strip-space elements="*"/> 

    <!-- Define keys --> 
    <xsl:key name="kPropertyByName" match="Property[contains(@Name, '.')]" use="concat(generate-id(..), '|', substring-before(@Name,'.'))"/> 

    <!-- Define variables --> 
    <xsl:variable name="vDestinationArray" select="'A,B,C'" /> 

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

    <!-- Match Data --> 
    <xsl:template match="Data" name="Data"> 
     <xsl:element name="Root"> 
      <xsl:for-each select="Object"> 
       <xsl:element name="ObjectData"> 
        <xsl:call-template name="Object" /> 
       </xsl:element> 
      </xsl:for-each> 
     </xsl:element> 
    </xsl:template> 

    <!-- Match Object --> 
    <xsl:template match="Object" name="Object"> 
     <!-- For each 'Property'-element that does *not* contain separator '.' in 'Name'-attribute, just select value as-is--> 
     <xsl:for-each select="Property[not(contains(@Name, '.'))]"> 
      <xsl:element name="{@Name}"> 
       <xsl:value-of select="@Value"/> 
      </xsl:element> 
     </xsl:for-each> 
     <!-- For each 'Property'-element that *does* contain separator '.' in 'Name'-attribute, create a parent element using substring-before separator--> 
     <xsl:for-each select="Property[generate-id(.) = generate-id(key('kPropertyByName',concat(generate-id(..), '|', substring-before(@Name,'.')))[1])]"> 
      <!-- Determine whether parent exists in 'array'-variable --> 
      <xsl:choose> 
       <!-- Parent *does* exists in 'array'-variable --> 
       <xsl:when test="contains(concat(',',$vDestinationArray,','),concat(',',substring-before(@Name,'.'),','))"> 
        <xsl:choose> 
         <!-- If value is not empty, create 'Destination'-element --> 
         <xsl:when test="@Value!=''"> 
           <xsl:element name="Destination"> 
           <xsl:element name="Type"> 
            <xsl:value-of select="substring-before(@Name,'.')" /> 
           </xsl:element> 
           <xsl:for-each select="key('kPropertyByName', concat(generate-id(..), '|', substring-before(@Name,'.')))"> 
            <xsl:element name="{substring-after(@Name,'.')}"> 
             <xsl:value-of select="@Value"/> 
            </xsl:element> 
           </xsl:for-each>        
          </xsl:element> 
         </xsl:when> 
        </xsl:choose> 
       </xsl:when> 
       <!-- Parent does *not* exists in 'array'-variable -->       
       <xsl:otherwise> 
        <!-- Create child element using substring-after separator --> 
        <xsl:element name="{substring-before(@Name,'.')}"> 
         <xsl:for-each select="key('kPropertyByName', concat(generate-id(..), '|', substring-before(@Name,'.')))"> 
          <xsl:element name="{substring-after(@Name,'.')}"> 
           <xsl:value-of select="@Value"/> 
          </xsl:element> 
         </xsl:for-each> 
        </xsl:element>       
       </xsl:otherwise> 
      </xsl:choose> 
     </xsl:for-each> 
    </xsl:template> 
</xsl:stylesheet> 

che mi dà il seguente risultato - vista (indesiderato) duplicare gli elementi "Destinazione":

<?xml version="1.0" encoding="UTF-8"?> 
<Root> 
    <ObjectData> 
     <Id>001</Id> 
     <P> 
      <Id>Id P</Id> 
      <Description>Descr P</Description> 
     </P> 
     <Destination> 
      <Type>A</Type> 
      <Id>Id A</Id> 
      <Description>Descr A</Description> 
     </Destination> 
     <Destination> 
      <Type>B</Type> 
      <Id>Id B</Id> 
      <Description>Descr B</Description> 
     </Destination> 
    </ObjectData> 
    <ObjectData> 
     <Id>002</Id> 
     <P> 
      <Id>Id P</Id> 
      <Description>Descr P</Description> 
     </P> 
     <Destination> 
      <Type>B</Type> 
      <Id>Id B</Id> 
      <Description>Descr B</Description> 
     </Destination> 
     <Destination> 
      <Type>C</Type> 
      <Id>Id C</Id> 
      <Description>Descr C</Description> 
     </Destination> 
    </ObjectData> 
</Root> 

Non è quello che sto cercando ... Qualsiasi aiuto sarebbe molto apprezzato!

+3

Ottima domanda, mostrando input, output desiderato, requisiti, codice e output effettivo. – LarsH

+2

Ho avuto gli stessi pensieri di LarsH. – RacerNerd

risposta

4

Ecco un semplice (no xsl:if, no xsl:key, senza generate-id()) soluzione più breve/:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
xmlns:my="my:my" extension-element-prefixes="my"> 
<xsl:output omit-xml-declaration="yes" indent="yes"/> 
<xsl:strip-space elements="*"/> 
<my:names> 
    <n>A</n> 
    <n>B</n> 
    <n>C</n> 
</my:names> 

<xsl:template match="*"> 
    <Root><xsl:apply-templates/></Root> 
</xsl:template> 

<xsl:template match="/*/*"> 
    <ObjectData><xsl:apply-templates/></ObjectData> 
</xsl:template> 

<xsl:template match="Property[not(contains(@Name, '.'))]"> 
    <xsl:element name="{@Name}"> 
    <xsl:value-of select="@Value"/> 
    </xsl:element> 
</xsl:template> 

<xsl:template match="Property"> 
    <xsl:element name="{substring-before(@Name, '.')}"> 
    <xsl:element name="{substring-after(@Name, '.')}"> 
     <xsl:value-of select="@Value"/> 
    </xsl:element> 
    <xsl:apply-templates mode="descr" select= 
    "../*[@Name = concat(substring-before(current()/@Name, '.'),'.','Description')]"/> 
    </xsl:element> 
</xsl:template> 

<xsl:template match= 
    "Property[string(@Value) and contains(@Name, '.') 
    and substring-before(@Name, '.') = document('')/*/my:names/*] 
    [1] 
    "> 
    <Destination> 
     <Type><xsl:value-of select="substring-before(@Name, '.')"/></Type> 
     <xsl:element name="{substring-after(@Name, '.')}"> 
      <xsl:value-of select="@Value"/> 
     </xsl:element> 
    <xsl:apply-templates mode="descr" select= 
    "../*[@Name = concat(substring-before(current()/@Name, '.'),'.','Description')]"/> 

    </Destination> 
</xsl:template> 

    <xsl:template match= 
    "Property[contains(@Name, '.') 
      and substring-before(@Name, '.') = document('')/*/my:names/* 
      and not(string(@Value)) 
      ]"/> 
    <xsl:template match= 
    "Property[contains(@Name, '.') 
      and substring-before(@Name, '.') = document('')/*/my:names/* 
      and string(@Value) 
      ][not(position() = 1)]"/> 
<xsl:template match="*[substring-after(@Name,'.') = 'Description']"/> 

<xsl:template match="*" mode="descr"> 
    <Description><xsl:apply-templates select="@Value"/></Description> 
</xsl:template> 
</xsl:stylesheet> 

Quando questa trasformazione viene applicata sul documento XML fornito:

<Data> 
    <Object> 
     <Property Name="Id" Value="001"/> 
     <Property Name="P.Id" Value="Id P"/> 
     <Property Name="P.Description" Value="Descr P"/> 
     <Property Name="A.Id" Value="Id A" /> 
     <Property Name="A.Description" Value="Descr A"/> 
     <Property Name="B.Id" Value="Id B"/> 
     <Property Name="B.Description" Value="Descr B"/> 
     <Property Name="C.Id" Value="" /> 
     <Property Name="C.Description" Value=""/> 
    </Object> 
    <Object> 
     <Property Name="Id" Value="002"/> 
     <Property Name="P.Id" Value="Id P"/> 
     <Property Name="P.Description" Value="Descr P"/> 
     <Property Name="A.Id" Value="" /> 
     <Property Name="A.Description" Value=""/> 
     <Property Name="B.Id" Value="Id B"/> 
     <Property Name="B.Description" Value="Descr B"/> 
     <Property Name="C.Id" Value="Id C" /> 
     <Property Name="C.Description" Value="Descr C"/> 
    </Object> 
</Data> 

il ricercato, il risultato corretto è prodotto:

<Root> 
    <ObjectData> 
     <Id>001</Id> 
     <P> 
     <Id>Id P</Id> 
     <Description>Descr P</Description> 
     </P> 
     <Destination> 
     <Type>A</Type> 
     <Id>Id A</Id> 
     <Description>Descr A</Description> 
     </Destination> 
    </ObjectData> 
    <ObjectData> 
     <Id>002</Id> 
     <P> 
     <Id>Id P</Id> 
     <Description>Descr P</Description> 
     </P> 
     <Destination> 
     <Type>B</Type> 
     <Id>Id B</Id> 
     <Description>Descr B</Description> 
     </Destination> 
    </ObjectData> 
</Root> 
4

Speriamo che qualcosa di simile è quello che state cercando (I riutilizzare le parti della soluzione):

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

    <!-- Define keys --> 
    <xsl:key name="kPropertyByName" match="Property[contains(@Name, '.')]" use="concat(generate-id(..), '|', substring-before(@Name,'.'))"/> 
    <!-- Define variables --> 
    <xsl:variable name="vDestinationArray" select="'A,B,C'" /> 

    <xsl:template match="Data" > 
     <Root> 
      <xsl:apply-templates /> 
     </Root> 
    </xsl:template> 
    <xsl:template match="Object" > 
     <ObjectData> 
      <!-- (rule 1.)--> 
      <xsl:apply-templates select="Property[not(contains(@Name, '.'))]"/> 

      <!-- For each 'Property'-element that *does* contain separator '.' in 'Name'-attribute, 
      and *does* NOT exists in 'array'-variable 
      (rule 2.) 
      --> 

      <xsl:for-each 
       select="Property[generate-id(.) = 
         generate-id(key('kPropertyByName', 
        concat(generate-id(..), '|', substring-before(@Name,'.')))[1]) 
        and not ( 
        contains(
          concat(',',$vDestinationArray,','),concat(',',substring-before(@Name,'.'),',')) 
        ) 
        ] "> 
        <xsl:apply-templates select="." mode ="parent" /> 
      </xsl:for-each> 

      <!-- For each 'Property'-element that *does* contain separator '.' in 'Name'-attribute, 
      and *does* exists in 'array'-variable 
      and Value attribute is not '' 
      (rule 3) 
      --> 
      <xsl:for-each 
       select="Property[generate-id(.) = 
       generate-id(key('kPropertyByName', 
       concat(generate-id(..), '|', substring-before(@Name,'.')))[1]) 
       and 
       contains(
         concat(',',$vDestinationArray,','),concat(',',substring-before(@Name,'.'),',')) 
       and @Value != '' 
       ] "> 
       <!-- only for firs one (rule 3-b.)--> 
       <xsl:if test="position() = 1" > 
        <Destination> 
         <xsl:element name="Type"> 
          <xsl:value-of select="substring-before(@Name,'.')" /> 
         </xsl:element> 
         <xsl:apply-templates 
          mode="replace" 
          select="../Property[ 
          substring-before(current()/@Name,'.') = 
          substring-before(./@Name,'.') 
          and @Value != '' ]"/> 
        </Destination> 
       </xsl:if> 
      </xsl:for-each> 
     </ObjectData> 
    </xsl:template> 

    <xsl:template match="Property[not(contains(@Name, '.'))]" > 
     <xsl:element name="{@Name}"> 
      <xsl:value-of select="@Value"/> 
     </xsl:element> 

    </xsl:template> 

    <xsl:template match="Property[@Value != '']" mode ="replace"> 
      <xsl:element name="{substring-after(@Name,'.')}"> 
       <xsl:value-of select="@Value"/> 
      </xsl:element> 
    </xsl:template> 

    <xsl:template match="Property[(contains(@Name, '.'))]" mode ="child"> 
     <xsl:element name="{substring-after(@Name,'.')}"> 
      <xsl:value-of select="@Value"/> 
     </xsl:element> 
    </xsl:template> 

    <xsl:template match="Property[(contains(@Name, '.'))]" mode ="parent"> 
     <xsl:element name="{substring-before(@Name,'.')}"> 
      <xsl:apply-templates 
       mode="child" 
       select="../Property[ 
        substring-before(current()/@Name,'.') = 
        substring-before(./@Name,'.')]"/> 
     </xsl:element> 
    </xsl:template> 

</xsl:stylesheet> 

Questo genererà l'output richiesto (come ho capito).

<?xml version="1.0"?> 
<Root> 
    <ObjectData> 
     <Id>001</Id> 
     <P> 
      <Id>Id P</Id> 
      <Description>Descr P</Description> 
     </P> 
     <Destination> 
      <Type>A</Type> 
      <Id>Id A</Id> 
      <Description>Descr A</Description> 
     </Destination> 
    </ObjectData> 
    <ObjectData> 
     <Id>002</Id> 
     <P> 
      <Id>Id P</Id> 
      <Description>Descr P</Description> 
     </P> 
     <Destination> 
      <Type>B</Type> 
      <Id>Id B</Id> 
      <Description>Descr B</Description> 
     </Destination> 
    </ObjectData> 
</Root> 

(Questo è stato un po 'più difficile del previsto. Il foglio di stile potrebbe avere bisogno di un po' di abbellimento/miglioramento, ma è un po 'tardi ormai.)

Problemi correlati