Search code examples
xmlxsltmuenchian-grouping

muenchian grouping to create node from xsl key


Using xsl key to parse some unique entries and creating new nodes with the key values in the key.

The following xml:

<Services>
        <Service Name="Publish" TypeName="FzUDP.Publish" ProviderName="FzUDP" Position="69,102.533530201483" InitPriority="1">
          <Parameter Name="MultiCastIp" Value="224.0.0.0" />
          <Parameter Name="MultiCastPort" Value="61499" />
          <Parameter Name="Encryption" Value="0" />
          <Parameter Name="PacketFormat" Value="0" />
        </Service>
        <Service Name="Subscribe" TypeName="FzUDP.Subscribe" ProviderName="FzUDP_1" Position="547,107.533530201483" InitPriority="2">
          <Parameter Name="MultiCastIp" Value="224.0.0.0" />
          <Parameter Name="MultiCastPort" Value="61499" />
          <Parameter Name="Decryption" Value="0" />
          <Parameter Name="PacketFormat" Value="0" />
        </Service>
<Service Name="Subscribe2" TypeName="FzUDP.Subscribe" ProviderName="FzUDP_1" Position="547,107.533530201483" InitPriority="2">
          <Parameter Name="MultiCastIp" Value="224.0.0.0" />
          <Parameter Name="MultiCastPort" Value="61499" />
          <Parameter Name="Decryption" Value="0" />
          <Parameter Name="PacketFormat" Value="0" />
        </Service>
</Services>

needs to be transformed into:

<ServiceProviders>
<ServiceProvider Name="FzUDP" TypeName="FzUDP">
    <Services>
        <Service Name="Publish" TypeName="Publish" InitPriority="1">
          <Parameter Name="MultiCastIp" Value="224.0.0.0" />
          <Parameter Name="MultiCastPort" Value="61499" />
          <Parameter Name="Encryption" Value="0" />
          <Parameter Name="PacketFormat" Value="0" />
        </Service>
        <Service Name="Subscribe" TypeName="Subscribe" InitPriority="2">
          <Parameter Name="MultiCastIp" Value="224.0.0.0" />
          <Parameter Name="MultiCastPort" Value="61499" />
          <Parameter Name="Decryption" Value="0" />
          <Parameter Name="PacketFormat" Value="0" />
        </Service>
    </Services>
</ServiceProvider>  
<ServiceProvider Name="FzUDP_1" TypeName="FzUDP">
    <Services>
        <Service Name="Subscribe2" TypeName="Subscribe" InitPriority="2">
          <Parameter Name="MultiCastIp" Value="224.0.0.0" />
          <Parameter Name="MultiCastPort" Value="61499" />
          <Parameter Name="Decryption" Value="0" />
          <Parameter Name="PacketFormat" Value="0" />
        </Service>
    </Services>
</ServiceProvider>

The xsl code I have come up with so far is:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output method="xml" indent="yes"/>

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

<xsl:key name="uniqueProviders" match="Service[@ProviderName]" use="Service[@Name]" />


<xsl:template match="Services">
<ServiceProviders>
  <xsl:for-each select=?>


  </xsl:for-each>


</ServiceProviders>
</xsl:template>
</xsl:stylesheet>

I am trying to use the ProviderName inside the Service element and put them as the key and the Service Name as values. But I am not sure how to use them. Also notice that:

a. Attribute Position is not required in the transformed XML.
b. The TypeName attribute of the ServiceProvider element is the first '.' split of the TypeName attribute of the Service element.

So I think Service element needs to be created after ServiceProviders, but Parameter element can be probably copied. Where do I go from here?

edit 1: I got the following using XSLT 2.0

<?xml version="1.0" encoding="utf-8"?>

<xsl:stylesheet version="2.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:saxon="http://saxon.sf.net/"
                extension-element-prefixes="saxon"
  xmlns:bootFile="http://tempuri.org/BootfileDefinition"
                xmlns:apps="http://tempuri.org/FZDevice">

  <xsl:output method="xml" indent="yes"/>


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



  <xsl:template match ="//apps:Services">
    <xsl:element name="ServiceProviders">
      <xsl:for-each-group select="//apps:Service" group-by="//apps:Service/@ProviderName">
        <xsl:element name="ServiceProvider">
          <xsl:attribute name="Name">
            <xsl:value-of select="current-grouping-key()"/>
          </xsl:attribute>

          <xsl:attribute name="TypeName">
            <xsl:value-of select="tokenize(current-group()[1]/@TypeName, '\.')[1]"/>
          </xsl:attribute>

          <xsl:for-each select="current-group()">
            <xsl:element name="Service">

              <xsl:attribute name="Name">
                <xsl:value-of select="@Name"/>
              </xsl:attribute>

              <xsl:attribute name="TypeName">
                <xsl:value-of select="tokenize(@TypeName, '\.')[2]"/>
              </xsl:attribute>

              <xsl:attribute name="InitPriority">
                <xsl:value-of select="@InitPriority"/>
              </xsl:attribute>

              <xsl:copy-of select="//apps:Parameter"/>

            </xsl:element>
          </xsl:for-each> 

        </xsl:element>
      </xsl:for-each-group>
     </xsl:element>
  </xsl:template>


</xsl:stylesheet>

Note that there are namespaces prefixed to the element names. I would get rid of them in the output XML and would appreciate if someone can help me with doing the same thing in XSLT 1.0.


Solution

  • Consider this adjustment of Muenchian Grouping as you do not need an Identity Transform but a rewrite of Service template tailored to grouped key. Also, to split @TypeName content by period, use the substring-before and substring-after functions. And it seems you want the <xsl:key> to be a concatenation of @ProviderName and @TypeName (before period part).

    <?xml version="1.0" encoding="utf-8"?>
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                                  xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
        <xsl:output method="xml" indent="yes"/>
        <xsl:strip-space elements="*"/>
    
        <xsl:key name="uniqueProviders" match="Service" use="concat(@ProviderName, substring-before(@TypeName, '.'))" />
    
        <xsl:template match="/Services">
            <ServiceProviders>
                <xsl:apply-templates select="Service"/>
            </ServiceProviders>
        </xsl:template>  
    
        <xsl:template match="Service[generate-id() = generate-id(key('uniqueProviders', concat(@ProviderName, substring-before(@TypeName, '.')))[1])]">
            <ServiceProvider Name="{@ProviderName}" TypeName="{substring-before(@TypeName, '.')}">
                <xsl:for-each select="key('uniqueProviders', concat(@ProviderName, substring-before(@TypeName, '.')))">
                    <Service Name="{@Name}" TypeName="{substring-after(@TypeName, '.')}" InitPriority="{@InitPriority}">
                        <xsl:copy-of select="*"/>
                    </Service>       
                </xsl:for-each>
            </ServiceProvider>
        </xsl:template>    
    
    </xsl:stylesheet>
    

    Output

    <?xml version="1.0" encoding="utf-8"?>
    <ServiceProviders>
      <ServiceProvider Name="FzUDP" TypeName="FzUDP">
        <Service Name="Publish" TypeName="Publish" InitPriority="1">
          <Parameter Name="MultiCastIp" Value="224.0.0.0" />
          <Parameter Name="MultiCastPort" Value="61499" />
          <Parameter Name="Encryption" Value="0" />
          <Parameter Name="PacketFormat" Value="0" />
        </Service>
      </ServiceProvider>
      <ServiceProvider Name="FzUDP_1" TypeName="FzUDP">
        <Service Name="Subscribe" TypeName="Subscribe" InitPriority="2">
          <Parameter Name="MultiCastIp" Value="224.0.0.0" />
          <Parameter Name="MultiCastPort" Value="61499" />
          <Parameter Name="Decryption" Value="0" />
          <Parameter Name="PacketFormat" Value="0" />
        </Service>
        <Service Name="Subscribe2" TypeName="Subscribe" InitPriority="2">
          <Parameter Name="MultiCastIp" Value="224.0.0.0" />
          <Parameter Name="MultiCastPort" Value="61499" />
          <Parameter Name="Decryption" Value="0" />
          <Parameter Name="PacketFormat" Value="0" />
        </Service>
      </ServiceProvider>
    </ServiceProviders>