5

XSLT の初心者である私は、XSLT 1.0 を使用して、オブジェクトを記述する次の 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>

目的の出力を得るには、次のルールを適用する必要があります。

  1. 区切り文字「.」を含まない「Property」要素ごとに。「名前」属性で、「名前」属性を子要素に変換し、その「値」属性の値を選択します。
  2. 区切り文字「.」を含む「 Property」要素ごとに。「名前」属性で、次を作成します。
    • a) 「Name」属性の区切り文字の「substring-before」を使用する親要素、および
    • b) 'Name' 属性のセパレーターの 'substring-after' を使用し、その 'Value' 属性の値を選択する子要素。
  3. (2) への追加ルール:
    • a) 作成する「Name」属性の「substring-before」が定義済みの配列に存在し、 Value」属性に値がある場合、出力要素名を定義済みの要素名に置き換えます。
    • b) (3a) が適用されるすべての要素について、出力で最初に出現するもののみを返します。つまり、配列でも出現する可能性のある次の要素をスキップします。

したがって、目的の出力は次のようになります。

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

現在、次のコードがあります。

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

これにより、次の出力が得られます-(不要な)重複した「宛先」要素があります:

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

私が探しているものではありません...どんな助けでも大歓迎です!

4

2 に答える 2

4

うまくいけば、このようなものがあなたが探しているものです(私はあなたのソリューションの一部を再利用します):

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

これにより、要求された出力が生成されます(私が理解しているように)。

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

(これは予想より少し難しかったです。スタイルシートは少し美化/改善が必要かもしれませんが、今は少し遅れています。)

于 2013-05-07T19:53:49.160 に答える
4

これは、より短く/より単純な (no xsl:if、 no xsl:key、 no generate-id()) ソリューションです。

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

この変換が提供された 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>

必要な正しい結果が生成されます。

<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>
于 2013-05-08T03:47:30.907 に答える