views:

205

answers:

2

Given the following XML:

<interface name="Serial1/0"/>
<interface name="Serial2/0.0"/>
<interface name="Serial2/0.1"/>
<interface name="Serial3/0:0"/>
<interface name="Serial3/0:1"/>

I am trying to produce the following output:

<interface name="Serial1/0">
  <unit name="Serial1/0"/>
</interface>
<interface name="Serial2/0">
  <unit name="Serial2/0.0"/>
  <unit name="Serial2/0.1"/>
</interface>
<interface name="Serial3/0">
  <unit name="Serial3/0:0"/>
  <unit name="Serial3/0:1"/>
</interface>

I have created the following function for splitting the string:

<xsl:template name="getPhysicalInterfaceName">
  <xsl:param name="interfaceName"/>
  <xsl:choose>
    <xsl:when test="contains($interfaceName, ':')">
      <xsl:value-of select="substring-before($interfaceName, ':')"/>
    </xsl:when>
    <xsl:when test="contains($interfaceName, '.')">
      <xsl:value-of select="substring-before($interfaceName, '.')"/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="$interfaceName"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

I found references to using the xsl:key element, but I didn't see an obvious way to use it in the context. Any idea? (I am using xsltproc (XSLT1.0) to do the transformation.)

+1  A: 

You should to take a look into Muenchian method to group XML items.

Rubens Farias
Thanks, this looks useful. The main problem I have with the method is how to use my getPhysicalInterfaceName() method to split the elements that are added into the xsl:key. This does not work: 'use="getPhysicalInterfaceName(@name)"'.
knipknap
try to create a variable with those names and, them, to apply that code
Rubens Farias
+1  A: 

XSLT 1.0:

<xsl:stylesheet 
  version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
  <xsl:key 
    name  = "kInterfaceByName" 
    match = "interface"
    use   = "
      substring-before(concat(
        substring-before(concat(@name, ':'), ':'),
      '.'), '.')
    "
  />

  <xsl:template match="i">
    <xsl:copy>
      <xsl:apply-templates mode="group" select="interface" />
    </xsl:copy>
  </xsl:template>

  <xsl:template match="interface" mode="group">
    <xsl:variable name="name" select="
      substring-before(concat(
        substring-before(concat(@name, ':'), ':'),
      '.'), '.')
    " />
    <xsl:variable name="interfaces" select="key('kInterfaceByName', $name)" />

    <!-- Muenchian step -->
    <xsl:if test="generate-id()=generate-id($interfaces[1])">
      <interface name="{$name}">
        <xsl:apply-templates mode="unit" select="$interfaces" />
      </interface>
    </xsl:if>
  </xsl:template>

  <xsl:template match="interface" mode="unit">
    <unit name="{@name}" />
  </xsl:template>


</xsl:stylesheet>

when applied to

<i>
  <interface name="Serial1/0"/>
  <interface name="Serial2/0.0"/>
  <interface name="Serial2/0.1"/>
  <interface name="Serial3/0:0"/>
  <interface name="Serial3/0:1"/>
</i>

results in

<i>
  <interface name="Serial1/0">
    <unit name="Serial1/0" />
  </interface>
  <interface name="Serial2/0">
    <unit name="Serial2/0.0" />
    <unit name="Serial2/0.1" />
  </interface>
  <interface name="Serial3/0">
    <unit name="Serial3/0:0" />
    <unit name="Serial3/0:1" />
  </interface>
</i>

The XPath expression replaces your getPhysicalInterfaceName template.

It does, on the examples of 'Serial2/0.0' and 'Serial3/0:1':

  1. append a ':' (=> 'Serial2/0.0:'; 'Serial3/0:1:')
  2. take everything before the first ':' (=> 'Serial2/0.0'; 'Serial3/0')
  3. append a '.' (=> 'Serial2/0.0.'; 'Serial3/0.')
  4. take everything before the first '.' (=> 'Serial2/0'; 'Serial3/0')

EDIT: Simplified XPath expression. My first try worked but was more complex:

concat(
  substring-before(@name, '/'), 
  '/',
  substring-before(
    concat(
      translate(substring-after(@name, '/'), '.', ':'), ':'
    ),
    ':'
  )
)

On the plus side, the above expression correctly handles colons and dots in the first part of the name, e.g. 'Some.Serial3/0:1'. The shorter one does not. If you expect dots in the name, use the longer expression. An explanation of it is in the revision history of this post.

Tomalak
Thanks a lot for the long response, this was very helpful!
knipknap
You're welcome. ;)
Tomalak