<?xml version="1.0" encoding="us-ascii"?>
<!-- Converts a RFC 5388 traceroute-XML document to HTML.
Licence: as you wish.
Author: Stephane Bortzmeyer <bortz@users.sourceforge.net>
-->
<!DOCTYPE xsl:stylesheet[
  <!ENTITY newline "&#10;">
]>
<xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0"
    exclude-result-prefixes="tr"
    xmlns:tr="urn:ietf:params:xml:ns:traceroute-1.0"
    xmlns="http://www.w3.org/1999/xhtml">
  
  <!-- Displays various metadata (such as the tool name and version, the 
       maximum TTL, etc) before the table of results? -->
  <xsl:param name="display-metadata" select="'true'"/>
  <!--  Displays "user-friendly" labels (such as 
               "Timed out" or "Network unreachable) or "traditional" 
               labels (such as "*" or "!N")? -->
  <xsl:param name="user-friendly-labels" select="'true'"/>

  <xsl:param name="standalone" select="'false'"/>
  
  <xsl:template match="tr:traceRoute">  
  <div class="traceroute-root"><xsl:text>&newline;</xsl:text>
      <xsl:apply-templates select="*"/>
    </div>
  </xsl:template>

  <xsl:template match="tr:RequestMetadata">  
    <xsl:if test="$display-metadata='true'">
        <h2>Request information</h2>
        <xsl:apply-templates select="*"/>
    </xsl:if>
  </xsl:template>

  <xsl:template match="tr:Measurement">  
      <xsl:apply-templates select="*"/>
  </xsl:template>

  <xsl:template match="tr:MeasurementMetadata">  
  <h2>Traceroute: <xsl:value-of select="tr:TestName" mode="in-title"/></h2><xsl:text>&newline;</xsl:text>
      <xsl:if test="$display-metadata='true'">
        <p>
          <xsl:apply-templates select="*"/>
        </p>
      </xsl:if>
      <xsl:text>&newline;</xsl:text>
  </xsl:template>

  <xsl:template match="tr:TestName" mode="in-title">
      <xsl:apply-templates select="text()"/>
  </xsl:template>

  <xsl:template match="tr:TestName"/>

  <xsl:template match="tr:OSName">
    <xsl:text>OS name: </xsl:text><xsl:apply-templates select="text()"/><br/><xsl:text>&newline;</xsl:text>
  </xsl:template>

  <xsl:template match="tr:OSVersion">
    <xsl:text>OS version: </xsl:text><xsl:apply-templates select="text()"/><br/><xsl:text>&newline;</xsl:text>
  </xsl:template>

  <xsl:template match="tr:ToolName">
    <xsl:text>Tool name: </xsl:text><xsl:apply-templates select="text()"/><br/><xsl:text>&newline;</xsl:text>
  </xsl:template>

  <xsl:template match="tr:ToolVersion">
    <xsl:text>Tool version: </xsl:text><xsl:apply-templates select="text()"/><br/><xsl:text>&newline;</xsl:text>
  </xsl:template>

  <xsl:template match="tr:CtlSourceAddress">
          <xsl:text>Source address: </xsl:text><xsl:apply-templates select="*"/><br/><xsl:text>&newline;</xsl:text>
  </xsl:template>

  <xsl:template match="tr:CtlTargetAddress">
          <xsl:text>Target name or address: </xsl:text><xsl:apply-templates select="*"/><br/><xsl:text>&newline;</xsl:text>
  </xsl:template>

  <xsl:template match="tr:CtlInitialTtl">
    <xsl:text>Initial TTL: </xsl:text><xsl:apply-templates select="text()"/><br/><xsl:text>&newline;</xsl:text>     
  </xsl:template>

  <xsl:template match="tr:CtlMaxTtl">
    <xsl:text>Maximum TTL: </xsl:text><xsl:apply-templates select="text()"/><br/><xsl:text>&newline;</xsl:text>
  </xsl:template>

  <xsl:template match="tr:CtlType">
     <xsl:apply-templates select="*"/>
  </xsl:template>

  <!-- TODO: handle CtlType elements in the ##other namespace -->
  <xsl:template match="tr:TCP|tr:UDP|tr:ICMP">
     <xsl:value-of select="name()"/>
  </xsl:template>

  <xsl:template match="tr:probe">  
     <xsl:apply-templates select="tr:ResponseStatus"/>
  </xsl:template>

  <xsl:template match="tr:ResultsStartDateAndTime">
    <xsl:text>Starts at </xsl:text>
    <span class="traceroute-time">
      <xsl:apply-templates select="text()"/>
    </span>
    <br/>
  </xsl:template>

  <xsl:template match="tr:ResultsEndDateAndTime">
    <xsl:text>Ends at </xsl:text>
    <span class="traceroute-time">
      <xsl:apply-templates select="text()"/>
    </span>
    <br/>
  </xsl:template>

  <xsl:template match="tr:Time">
    <span class="traceroute-time">
      <xsl:apply-templates select="text()"/>
    </span>
  </xsl:template>

  <xsl:template match="tr:roundTripTimeNotAvailable">
     <!-- Ignore it if no "instatus" -->
  </xsl:template>

  <xsl:template match="tr:roundTripTimeNotAvailable" mode="instatus">
     <span class="traceroute-rtt">
        <xsl:text>*</xsl:text>
     </span>
  </xsl:template>

  <xsl:template match="tr:roundTripTime">
    <!-- Ignore it if no "instatus" -->
  </xsl:template>

  <xsl:template match="tr:roundTripTime" mode="instatus">
    <span class="traceroute-rtt">
      <xsl:apply-templates select="text()"/><xsl:text> ms</xsl:text>
    </span>
  </xsl:template>

  <xsl:template match="tr:ProbeRoundTripTime">
      <xsl:apply-templates select="*"/>
  </xsl:template>

  <xsl:template match="tr:ResponseStatus">
      <xsl:variable name="status"><xsl:value-of select="text()"/></xsl:variable>
      <td class="traceroute-cell">
        <xsl:choose>
          <xsl:when test="$status = 'responseReceived'">
            <xsl:if test="$user-friendly-labels='true'">
              <xsl:text>OK (</xsl:text>
            </xsl:if>
            <xsl:apply-templates mode="instatus" select="../tr:ProbeRoundTripTime"/>
            <xsl:if test="$user-friendly-labels='true'">
              <xsl:text>)</xsl:text>
            </xsl:if>
          </xsl:when>
          <xsl:when test="$status = 'unknown'">
            <xsl:choose>
              <xsl:when test="$user-friendly-labels='true'">
                  <xsl:text>Unknown</xsl:text>
                </xsl:when>
                <xsl:otherwise>
                  <xsl:text>?</xsl:text>
                </xsl:otherwise>
              </xsl:choose>
            </xsl:when>
          <xsl:when test="$status = 'internalError'">
            <xsl:choose>
              <xsl:when test="$user-friendly-labels='true'">
                  <xsl:text>Internal error</xsl:text>
                </xsl:when>
                <xsl:otherwise>
                  <xsl:text>???</xsl:text>
                </xsl:otherwise>
              </xsl:choose>
            </xsl:when>
          <xsl:when test="$status = 'requestTimedOut'">
            <xsl:choose>
              <xsl:when test="$user-friendly-labels='true'">
                  <xsl:text>Time out</xsl:text>
                </xsl:when>
                <xsl:otherwise>
                  <xsl:text>*</xsl:text>
                </xsl:otherwise>
              </xsl:choose>
            </xsl:when>
          <xsl:when test="$status = 'unknownDestinationAddress'">
            <xsl:choose>
              <xsl:when test="$user-friendly-labels='true'">
                  <xsl:text>Unknown destination address</xsl:text>
                </xsl:when>
                <xsl:otherwise>
                  <xsl:text>!D</xsl:text>
                </xsl:otherwise>
              </xsl:choose>
            </xsl:when>
          <xsl:when test="$status = 'noRouteToTarget'">
            <xsl:choose>
              <xsl:when test="$user-friendly-labels='true'">
                  <xsl:text>No route to target</xsl:text>
                </xsl:when>
                <xsl:otherwise>
                  <xsl:text>!N</xsl:text>
                </xsl:otherwise>
              </xsl:choose>
            </xsl:when>          
           <xsl:when test="$status = 'interfaceInactiveToTarget'">
            <xsl:choose>
              <xsl:when test="$user-friendly-labels='true'">
                  <xsl:text>Interface inactive to target</xsl:text>
                </xsl:when>
                <xsl:otherwise>
                  <xsl:text>?</xsl:text>
                </xsl:otherwise>
              </xsl:choose>
            </xsl:when>
           <xsl:when test="$status = 'arpFailure'">
            <xsl:choose>
              <xsl:when test="$user-friendly-labels='true'">
                  <xsl:text>ARP failure</xsl:text>
                </xsl:when>
                <xsl:otherwise>
                  <xsl:text>?</xsl:text>
                </xsl:otherwise>
              </xsl:choose>
            </xsl:when>
           <xsl:when test="$status = 'maxConcurrentLimitReached'">
            <xsl:choose>
              <xsl:when test="$user-friendly-labels='true'">
                  <xsl:text>Maximum concurrent limit reached</xsl:text>
                </xsl:when>
                <xsl:otherwise>
                  <xsl:text>?</xsl:text>
                </xsl:otherwise>
              </xsl:choose>
            </xsl:when>
           <xsl:when test="$status = 'unableToResolveDnsName'">
            <xsl:choose>
              <xsl:when test="$user-friendly-labels='true'">
                  <xsl:text>Unable to resolve DNS name</xsl:text>
                </xsl:when>
                <xsl:otherwise>
                  <xsl:text>?</xsl:text>
                </xsl:otherwise>
              </xsl:choose>
            </xsl:when>
           <xsl:when test="$status = 'invalidHostAddress'">
            <xsl:choose>
              <xsl:when test="$user-friendly-labels='true'">
                  <xsl:text>Invalid host address</xsl:text>
                </xsl:when>
                <xsl:otherwise>
                  <xsl:text>?</xsl:text>
                </xsl:otherwise>
              </xsl:choose>
            </xsl:when>
          <xsl:otherwise><!-- Should never happen -->
               <xsl:text>UNKNOWN STATUS</xsl:text>
          </xsl:otherwise>
        </xsl:choose>
      </td>
  </xsl:template>

  <xsl:template match="tr:HopName">
       <!-- We take only the first one. This assumes that HopName will not change from 
            probe to probe. A 
            reasonable assumption but which is not in the RFC -->
    <xsl:if test="position()=1">
    <span class="traceroute-domainname">
      <xsl:apply-templates select="text()"/>
    </span>
  </xsl:if>
  </xsl:template>

  <xsl:template match="tr:HopAddr">
    <xsl:if test="position()=1">
      <xsl:apply-templates select="*"/>
    </xsl:if>
  </xsl:template>

  <xsl:template match="tr:inetAddressDns">
    <span class="traceroute-domainname">
      <xsl:apply-templates select="text()"/>
    </span>
  </xsl:template>

  <xsl:template match="tr:inetAddressIpv4">
    <span class="traceroute-ipaddr">
      <xsl:apply-templates select="text()"/>
    </span>
  </xsl:template>

  <xsl:template match="tr:inetAddressIpv6">
    <span class="traceroute-ipaddr">
      <xsl:apply-templates select="text()"/>
    </span>
  </xsl:template>

  <xsl:template match="tr:inetAddressASNumber">
    <span class="traceroute-ipaddr">
      <xsl:apply-templates select="text()"/>
    </span>
  </xsl:template>

  <xsl:template match="tr:inetAddressUnknown">
    <span class="traceroute-ipaddr">
      <xsl:text>Address unknown</xsl:text>
    </span>
  </xsl:template>

  <xsl:template match="tr:asNumber">
    <span class="traceroute-asnum">
      <xsl:apply-templates select="text()"/>
    </span>
  </xsl:template>

  <xsl:template match="tr:ResultsIpTgtAddr">
    <xsl:text>Target IP address: </xsl:text>
    <span class="traceroute-ipaddr">
      <xsl:apply-templates select="*"/>
    </span>
    <br/>
  </xsl:template>

  <xsl:template match="tr:MeasurementResult">
      <xsl:apply-templates select="*"/>
  </xsl:template>

  <xsl:template match="tr:ProbeResults">
    <br/>
    <table class="traceroute-table"><!-- TODO: a suitable thead --><tbody>
    <!-- To have a number of <th> that matches the number of probes per hop, we do not 
         use <CtlProbesPerHop>, which is optional but we count the actual number of 
         probes in the first <hop> -->
    <xsl:variable name="probesperhop" select='count(tr:hop[position()="1"]/tr:probe)'/>
    <tr><th class="traceroute-cell">Hop</th><th class="traceroute-cell">Machine</th>   
    <xsl:call-template name="probeHeader">
      <xsl:with-param name="x" select="$probesperhop"/>
      <xsl:with-param name="max" select="$probesperhop"/>
    </xsl:call-template>
    </tr>
    <xsl:apply-templates select="*"/>
    </tbody></table>
    <br/>
  </xsl:template>

<xsl:template name="probeHeader">
  <xsl:param name="x"/>
  <xsl:param name="max"/>
  <th class="traceroute-cell">Probe #<xsl:value-of select="$max - $x + 1"/></th>
  <xsl:if test="$x &gt; 1">
   <xsl:call-template name="probeHeader">
    <xsl:with-param name="x" select="$x - 1"/>
    <xsl:with-param name="max" select="$max"/>
   </xsl:call-template>
 </xsl:if>
</xsl:template>

  <xsl:template match="tr:hop">
    <tr class="traceroute-hop">
      <td class="traceroute-cell">
        <xsl:value-of select="position()"/>
      </td>
     <td class="traceroute-cell">
       <xsl:variable name="hopname">
            <xsl:apply-templates select="tr:probe/tr:HopName"/>
       </xsl:variable>
       <xsl:variable name="hopaddr">
            <xsl:apply-templates select="tr:probe/tr:HopAddr"/>
       </xsl:variable>
          <xsl:value-of select="$hopaddr"/>
       <xsl:if test="$hopname != ''">
          <xsl:if test="$hopname != $hopaddr">
             <xsl:text> (</xsl:text>
             <xsl:value-of select="$hopname"/>
             <xsl:text>)</xsl:text>
	  </xsl:if>
       </xsl:if>
     </td>
      <xsl:apply-templates select="*"/>
    </tr>
  </xsl:template>

  <xsl:template match="/">
    <xsl:choose>
      <xsl:when test="$standalone='true'">
        <html xml:lang="en" xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/><title>Traceroute</title><link rel="stylesheet" type="text/css" href="traceroute.css" media="screen"/></head><xsl:text>&newline;</xsl:text><body><xsl:text>&newline;</xsl:text>
           <xsl:apply-templates select="*"/>
        </body></html><xsl:text>&newline;</xsl:text>
      </xsl:when>
      <xsl:otherwise>
        <xsl:apply-templates select="*"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <!-- Catch-all -->
  <xsl:template match="*">
      <unknown class="traceroute-unknown-element">
        Unknown element <xsl:value-of select="name()"/>
        <xsl:apply-templates select="*"/>
      </unknown>
    </xsl:template>

  </xsl:stylesheet>

