2013-05-07 32 views
5

Będąc nowicjuszem w XSLT, staram się przekształcić - przy użyciu XSLT 1.0 - XML, który opisuje następujące obiekty:Tworzenie elementów rodzic-dziecko w oparciu o atrybut wartości i tłumić zduplikowane elementy wyjścia

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

Poniższe zasady powinny obowiązywać, aby uzyskać pożądany wynik.

  1. dla każdego „Property'-elementu, dokłada nie zawierać separator”” w atrybucie "Nazwa" przekształć atrybut "Nazwa" na element potomny i wybierz wartość jego atrybutu "Wartość".
  2. Dla każdego elementu "Property", który zawiera, zawiera separator "." w „Name' atrybutu utworzyć:
    • a) za pomocą elementu nadrzędnego«podciągu, przed»separator w” Name' atrybutu i
    • b) element podrzędny przy użyciu «Fragment, po» separator w atrybucie "Nazwa" i wybierz wartość jego atrybutu "Wartość".
  3. Dodatkowe zasady do (2):
    • a) Jeżeli 'podciąg-before' w 'Name' atrybut ma zostać utworzony, istnieje w predefiniowanej tablicy and' Value' atrybut ma wartość, zamień wyjściową nazwę-elementu na wstępnie zdefiniowaną nazwę-elementu.
    • b) Dla wszystkich elementów, które mają zastosowanie (3a), zwracaj tylko pierwsze wystąpienie na wyjściu - to znaczy pomiń następujące elementy, które również mogą wystąpić w macierzy.

Pożądana moc powinna wyglądać mniej więcej tak:

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

Obecnie mam następujący kod:

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

co daje mi następujący wynik - posiadającą (niechciane) duplikat elementów "Destination":

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

Nie tego szukam ... Każda pomoc byłaby mile widziana!

+3

Doskonałe pytanie, pokazujące wejście, pożądane wyjście, wymagania, kod i rzeczywiste wyjście. – LarsH

+2

Miałem te same myśli co LarsH. – RacerNerd

Odpowiedz

4

Oto krótszy/prostsze (nie xsl:if nie xsl:key nie generate-id()) rozwiązanie:

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

Kiedy transformacja ta jest stosowana na dostarczonych dokumentów XML:

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

pożądany, prawidłowy wynik jest produkowany:

<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

Mam nadzieję, że coś w tym jest to, czego szukasz (I ponowne wykorzystanie części rozwiązania):

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

To wygeneruje żądany wyjście (jak zrozumiałem).

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

(To było trochę mocniej niż oczekiwano. Arkusz stylów może potrzebować trochę upiększanie/poprawę, ale to trochę późno.)