<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <!--
    TAT-1641 — GNSS measurement report (CSV / SurPad-style columns)
    FieldBook PointRecords may omit WGS84/ECEF; use Reductions/Point WGS84 by Point ID when present.
    ECEF X,Y,Z computed from WGS84 (WGS84 ellipsoid) when not stored on the point.

    Wave#4 changes (per Michael Thies, comment 2026-05-05):
      - "Geoid file:" now emits the geoid file with its full path AND extension
        (default: /storage/emulated/0/Trimble Data/System Files/<name>.ggf)
      - "Grid file:"  now emits the grid file with its full path AND extension
        (default: /storage/emulated/0/Trimble Data/System Files/<name>.cgf)
      - Path prefix and extensions are exposed as XSL variables and are only
        applied when the JOB stored a bare name (no separator, no extension).
  -->

  <xsl:output method="text" omit-xml-declaration="yes" encoding="UTF-8"/>

  <xsl:decimal-format name="Standard"
      decimal-separator="." grouping-separator=","
      infinity="Infinity" minus-sign="-" NaN="" />

  <xsl:variable name="fileExt" select="'csv'"/>

  <xsl:variable name="DecPt" select="'.'"/>
  <xsl:variable name="DecPl2" select="concat('#0', $DecPt, '00')"/>
  <xsl:variable name="DecPl3" select="concat('#0', $DecPt, '000')"/>
  <xsl:variable name="DecPl8" select="concat('#0', $DecPt, '00000000')"/>

  <!-- WGS84 -->
  <xsl:variable name="WGS84_a" select="6378137.0"/>
  <xsl:variable name="WGS84_e2" select="0.0066943799901413165"/>

  <xsl:variable name="userField1" select="'reportSubtitle|Subtitle / site line (optional)|String'"/>
  <xsl:variable name="reportSubtitle" select="''"/>

  <!-- Defaults applied only when the JOB stored a bare geoid/grid name
       (no path separator, no extension). Adjust here if the customer ships
       these files in a different location or in a different format. -->
  <xsl:variable name="geoidDefaultPath" select="'/storage/emulated/0/Trimble Data/System Files/'"/>
  <xsl:variable name="gridDefaultPath"  select="'/storage/emulated/0/Trimble Data/System Files/'"/>
  <xsl:variable name="geoidDefaultExt"  select="'.ggf'"/>
  <xsl:variable name="gridDefaultExt"   select="'.cgf'"/>

  <xsl:key name="pointByName" match="PointRecord" use="normalize-space(Name)"/>
  <xsl:key name="antennaById" match="AntennaRecord" use="string(@ID)"/>
  <xsl:key name="reductionPointById" match="/JOBFile/Reductions/Point" use="string(ID)"/>

  <xsl:variable name="jobTs" select="/JOBFile/@TimeStamp"/>
  <xsl:variable name="projectName" select="/JOBFile/@jobName"/>
  <xsl:variable name="hoursToUtc" select="number(/JOBFile/FieldBook/TimeZoneRecord[1]/HoursToUTC)"/>
  <xsl:variable name="coordZone"
      select="/JOBFile/FieldBook/CoordinateSystemRecord[last()]/ZoneName"/>
  <xsl:variable name="geoidName"
      select="/JOBFile/FieldBook/VerticalAdjustmentRecord[Type='GeoidModel'][1]/GeoidName"/>
  <xsl:variable name="shiftGrid"
      select="/JOBFile/FieldBook/ProjectionRecord[Projection/ShiftGridFileName!=''][last()]/Projection/ShiftGridFileName"/>
  <xsl:variable name="gpsRx"
      select="/JOBFile/FieldBook/GPSEquipmentRecord[ReceiverType!=''][1]"/>

  <!-- **************************************************************** -->
  <xsl:template match="/">
    <xsl:text>GNSS MEASUREMENT REPORT</xsl:text>
    <xsl:call-template name="NewLine"/>
    <xsl:if test="string-length($reportSubtitle) &gt; 0">
      <xsl:value-of select="$reportSubtitle"/>
      <xsl:call-template name="NewLine"/>
    </xsl:if>
    <xsl:text>Project Name:</xsl:text>
    <xsl:value-of select="$projectName"/>
    <xsl:call-template name="NewLine"/>
    <xsl:call-template name="NewLine"/>
    <xsl:text>Date:</xsl:text>
    <xsl:call-template name="FormatDateDMY">
      <xsl:with-param name="ts" select="$jobTs"/>
    </xsl:call-template>
    <xsl:call-template name="NewLine"/>
    <xsl:call-template name="NewLine"/>
    <xsl:text>Coordinate system:</xsl:text>
    <xsl:value-of select="$coordZone"/>
    <xsl:call-template name="NewLine"/>
    <xsl:if test="string-length($geoidName) &gt; 0">
      <xsl:text>Geoid file:</xsl:text>
      <xsl:call-template name="EmitFileWithPath">
        <xsl:with-param name="name"        select="$geoidName"/>
        <xsl:with-param name="defaultPath" select="$geoidDefaultPath"/>
        <xsl:with-param name="defaultExt"  select="$geoidDefaultExt"/>
      </xsl:call-template>
      <xsl:call-template name="NewLine"/>
    </xsl:if>
    <xsl:if test="string-length($shiftGrid) &gt; 0">
      <xsl:text>Grid file:</xsl:text>
      <xsl:call-template name="EmitFileWithPath">
        <xsl:with-param name="name"        select="$shiftGrid"/>
        <xsl:with-param name="defaultPath" select="$gridDefaultPath"/>
        <xsl:with-param name="defaultExt"  select="$gridDefaultExt"/>
      </xsl:call-template>
      <xsl:call-template name="NewLine"/>
    </xsl:if>
    <xsl:call-template name="NewLine"/>

    <xsl:text>GPS receiver</xsl:text>
    <xsl:call-template name="NewLine"/>
    <xsl:if test="$gpsRx">
      <xsl:text>Receiver Type:</xsl:text>
      <xsl:value-of select="$gpsRx/ReceiverType"/>
      <xsl:call-template name="NewLine"/>
      <xsl:text>Receiver serial number:</xsl:text>
      <xsl:value-of select="$gpsRx/ReceiverSerialNumber"/>
      <xsl:call-template name="NewLine"/>
      <xsl:text>Antenna type:</xsl:text>
      <xsl:value-of select="$gpsRx/AntennaType"/>
      <xsl:call-template name="NewLine"/>
      <xsl:if test="string-length($gpsRx/AntennaVerticalOffset) &gt; 0">
        <xsl:text>L1 offset:</xsl:text>
        <xsl:value-of select="format-number(number($gpsRx/AntennaVerticalOffset), $DecPl3, 'Standard')"/>
        <xsl:call-template name="NewLine"/>
      </xsl:if>
      <xsl:text>Firmware Name:</xsl:text>
      <xsl:value-of select="$gpsRx/ReceiverFirmwareVersion"/>
      <xsl:call-template name="NewLine"/>
    </xsl:if>
    <xsl:call-template name="NewLine"/>

    <xsl:text>GNSS base station</xsl:text>
    <xsl:call-template name="NewLine"/>
    <xsl:text>Nr,Date,Time,B,L,H,X,Y,Z,N,E,Z</xsl:text>
    <xsl:call-template name="NewLine"/>
    <xsl:for-each select="/JOBFile/FieldBook/PointRecord[Deleted='false' and Method='FromBase']">
      <xsl:call-template name="BaseSummaryRow"/>
    </xsl:for-each>
    <xsl:call-template name="NewLine"/>

    <xsl:text>GNSS measurements</xsl:text>
    <xsl:call-template name="NewLine"/>
    <xsl:text>Nr,Date,Time,GMT,Mode,B,L,H,X,Y,Z,N,E,Z,E,Sat,PDOP,mp,mh,H.ant</xsl:text>
    <xsl:call-template name="NewLine"/>

    <xsl:for-each select="/JOBFile/FieldBook/PointRecord
        [Deleted='false'
         and Method != 'FromBase'
         and ComputedGrid/North
         and QualityControl1
         and (RTK_Base or SurveyMethod='RTK' or contains(Method,'Gps'))]">
      <xsl:sort select="@TimeStamp" data-type="text" order="ascending"/>
      <xsl:call-template name="MeasurementRow">
        <xsl:with-param name="seq" select="position()"/>
      </xsl:call-template>
    </xsl:for-each>
  </xsl:template>

  <!-- **************************************************************** -->
  <xsl:template name="BaseSummaryRow">
    <xsl:variable name="latStr">
      <xsl:call-template name="ResolveWgs84Latitude">
        <xsl:with-param name="pr" select="."/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="lonStr">
      <xsl:call-template name="ResolveWgs84Longitude">
        <xsl:with-param name="pr" select="."/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="hStr">
      <xsl:call-template name="ResolveWgs84Height">
        <xsl:with-param name="pr" select="."/>
      </xsl:call-template>
    </xsl:variable>

    <xsl:value-of select="Name"/>
    <xsl:text>,</xsl:text>
    <xsl:call-template name="FormatDateDMY">
      <xsl:with-param name="ts" select="@TimeStamp"/>
    </xsl:call-template>
    <xsl:text>,</xsl:text>
    <xsl:call-template name="FormatTimeHMS">
      <xsl:with-param name="ts" select="@TimeStamp"/>
    </xsl:call-template>
    <xsl:text>,</xsl:text>
    <xsl:if test="string-length(normalize-space($latStr)) &gt; 0">
      <xsl:value-of select="format-number(number($latStr), $DecPl8, 'Standard')"/>
    </xsl:if>
    <xsl:text>,</xsl:text>
    <xsl:if test="string-length(normalize-space($lonStr)) &gt; 0">
      <xsl:value-of select="format-number(number($lonStr), $DecPl8, 'Standard')"/>
    </xsl:if>
    <xsl:text>,</xsl:text>
    <xsl:if test="string-length(normalize-space($hStr)) &gt; 0">
      <xsl:value-of select="format-number(number($hStr), $DecPl3, 'Standard')"/>
    </xsl:if>
    <xsl:text>,</xsl:text>

    <xsl:call-template name="EmitEcefTriple">
      <xsl:with-param name="pr" select="."/>
      <xsl:with-param name="latStr" select="$latStr"/>
      <xsl:with-param name="lonStr" select="$lonStr"/>
      <xsl:with-param name="hStr" select="$hStr"/>
    </xsl:call-template>

    <xsl:text>,</xsl:text>
    <xsl:value-of select="format-number(number(ComputedGrid/North), $DecPl3, 'Standard')"/>
    <xsl:text>,</xsl:text>
    <xsl:value-of select="format-number(number(ComputedGrid/East), $DecPl3, 'Standard')"/>
    <xsl:text>,</xsl:text>
    <xsl:value-of select="format-number(number(ComputedGrid/Elevation), $DecPl3, 'Standard')"/>
    <xsl:call-template name="NewLine"/>
  </xsl:template>

  <!-- **************************************************************** -->
  <xsl:template name="MeasurementRow">
    <xsl:param name="seq"/>
    <xsl:variable name="ts" select="@TimeStamp"/>
    <xsl:variable name="elMask" select="preceding-sibling::RoverSurveyOptionsRecord[1]/ElevationMask"/>

    <xsl:variable name="latStr">
      <xsl:call-template name="ResolveWgs84Latitude">
        <xsl:with-param name="pr" select="."/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="lonStr">
      <xsl:call-template name="ResolveWgs84Longitude">
        <xsl:with-param name="pr" select="."/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="hStr">
      <xsl:call-template name="ResolveWgs84Height">
        <xsl:with-param name="pr" select="."/>
      </xsl:call-template>
    </xsl:variable>

    <xsl:variable name="antHt">
      <xsl:choose>
        <xsl:when test="AntennaID and key('antennaById', string(AntennaID))/ReducedHeight">
          <xsl:value-of select="format-number(number(key('antennaById', string(AntennaID))/ReducedHeight), $DecPl3, 'Standard')"/>
        </xsl:when>
        <xsl:otherwise></xsl:otherwise>
      </xsl:choose>
    </xsl:variable>

    <xsl:text>S</xsl:text>
    <xsl:value-of select="$seq"/>
    <xsl:text>,</xsl:text>
    <xsl:call-template name="FormatDateDMY">
      <xsl:with-param name="ts" select="$ts"/>
    </xsl:call-template>
    <xsl:text>,</xsl:text>
    <xsl:call-template name="FormatTimeHMS">
      <xsl:with-param name="ts" select="$ts"/>
    </xsl:call-template>
    <xsl:text>,</xsl:text>
    <xsl:call-template name="FormatUtcTimeHMS">
      <xsl:with-param name="ts" select="$ts"/>
    </xsl:call-template>
    <xsl:text>,</xsl:text>
    <xsl:text>FIXED</xsl:text>
    <xsl:text>,</xsl:text>

    <xsl:if test="string-length(normalize-space($latStr)) &gt; 0">
      <xsl:value-of select="format-number(number($latStr), $DecPl8, 'Standard')"/>
    </xsl:if>
    <xsl:text>,</xsl:text>
    <xsl:if test="string-length(normalize-space($lonStr)) &gt; 0">
      <xsl:value-of select="format-number(number($lonStr), $DecPl8, 'Standard')"/>
    </xsl:if>
    <xsl:text>,</xsl:text>
    <xsl:if test="string-length(normalize-space($hStr)) &gt; 0">
      <xsl:value-of select="format-number(number($hStr), $DecPl3, 'Standard')"/>
    </xsl:if>
    <xsl:text>,</xsl:text>

    <xsl:call-template name="EmitEcefTriple">
      <xsl:with-param name="pr" select="."/>
      <xsl:with-param name="latStr" select="$latStr"/>
      <xsl:with-param name="lonStr" select="$lonStr"/>
      <xsl:with-param name="hStr" select="$hStr"/>
    </xsl:call-template>

    <xsl:text>,</xsl:text>
    <xsl:value-of select="format-number(number(ComputedGrid/North), $DecPl3, 'Standard')"/>
    <xsl:text>,</xsl:text>
    <xsl:value-of select="format-number(number(ComputedGrid/East), $DecPl3, 'Standard')"/>
    <xsl:text>,</xsl:text>
    <xsl:value-of select="format-number(number(ComputedGrid/Elevation), $DecPl3, 'Standard')"/>
    <xsl:text>,</xsl:text>
    <xsl:value-of select="$elMask"/>
    <xsl:text>,</xsl:text>
    <xsl:value-of select="QualityControl1/NumberOfSatellites"/>
    <xsl:text>,</xsl:text>
    <xsl:value-of select="format-number(number(QualityControl1/PDOP), $DecPl2, 'Standard')"/>
    <xsl:text>,</xsl:text>
    <xsl:choose>
      <xsl:when test="Precision/Horizontal">
        <xsl:value-of select="format-number(number(Precision/Horizontal), $DecPl3, 'Standard')"/>
      </xsl:when>
      <xsl:otherwise></xsl:otherwise>
    </xsl:choose>
    <xsl:text>,</xsl:text>
    <xsl:choose>
      <xsl:when test="Precision/Vertical">
        <xsl:value-of select="format-number(number(Precision/Vertical), $DecPl3, 'Standard')"/>
      </xsl:when>
      <xsl:otherwise></xsl:otherwise>
    </xsl:choose>
    <xsl:text>,</xsl:text>
    <xsl:value-of select="$antHt"/>
    <xsl:call-template name="NewLine"/>
  </xsl:template>

  <!-- WGS84: FieldBook first, then Reductions/Point by ID -->
  <xsl:template name="ResolveWgs84Latitude">
    <xsl:param name="pr"/>
    <xsl:choose>
      <xsl:when test="$pr/WGS84/Latitude">
        <xsl:value-of select="normalize-space($pr/WGS84/Latitude)"/>
      </xsl:when>
      <xsl:when test="key('reductionPointById', string($pr/@ID))/WGS84/Latitude">
        <xsl:value-of select="normalize-space(key('reductionPointById', string($pr/@ID))/WGS84/Latitude)"/>
      </xsl:when>
    </xsl:choose>
  </xsl:template>
  <xsl:template name="ResolveWgs84Longitude">
    <xsl:param name="pr"/>
    <xsl:choose>
      <xsl:when test="$pr/WGS84/Longitude">
        <xsl:value-of select="normalize-space($pr/WGS84/Longitude)"/>
      </xsl:when>
      <xsl:when test="key('reductionPointById', string($pr/@ID))/WGS84/Longitude">
        <xsl:value-of select="normalize-space(key('reductionPointById', string($pr/@ID))/WGS84/Longitude)"/>
      </xsl:when>
    </xsl:choose>
  </xsl:template>
  <xsl:template name="ResolveWgs84Height">
    <xsl:param name="pr"/>
    <xsl:choose>
      <xsl:when test="$pr/WGS84/Height">
        <xsl:value-of select="normalize-space($pr/WGS84/Height)"/>
      </xsl:when>
      <xsl:when test="key('reductionPointById', string($pr/@ID))/WGS84/Height">
        <xsl:value-of select="normalize-space(key('reductionPointById', string($pr/@ID))/WGS84/Height)"/>
      </xsl:when>
    </xsl:choose>
  </xsl:template>

  <!-- Stored ECEF, else computed from resolved WGS84 -->
  <xsl:template name="EmitEcefTriple">
    <xsl:param name="pr"/>
    <xsl:param name="latStr"/>
    <xsl:param name="lonStr"/>
    <xsl:param name="hStr"/>
    <xsl:choose>
      <xsl:when test="$pr/ECEF/X and $pr/ECEF/Y and $pr/ECEF/Z">
        <xsl:value-of select="format-number(number($pr/ECEF/X), $DecPl3, 'Standard')"/>
        <xsl:text>,</xsl:text>
        <xsl:value-of select="format-number(number($pr/ECEF/Y), $DecPl3, 'Standard')"/>
        <xsl:text>,</xsl:text>
        <xsl:value-of select="format-number(number($pr/ECEF/Z), $DecPl3, 'Standard')"/>
      </xsl:when>
      <xsl:when test="string-length(normalize-space($latStr)) &gt; 0
          and string-length(normalize-space($lonStr)) &gt; 0
          and string-length(normalize-space($hStr)) &gt; 0">
        <xsl:variable name="ex">
          <xsl:call-template name="Wgs84ToEcefX">
            <xsl:with-param name="lat_deg" select="number($latStr)"/>
            <xsl:with-param name="lon_deg" select="number($lonStr)"/>
            <xsl:with-param name="h_m" select="number($hStr)"/>
          </xsl:call-template>
        </xsl:variable>
        <xsl:variable name="ey">
          <xsl:call-template name="Wgs84ToEcefY">
            <xsl:with-param name="lat_deg" select="number($latStr)"/>
            <xsl:with-param name="lon_deg" select="number($lonStr)"/>
            <xsl:with-param name="h_m" select="number($hStr)"/>
          </xsl:call-template>
        </xsl:variable>
        <xsl:variable name="ez">
          <xsl:call-template name="Wgs84ToEcefZ">
            <xsl:with-param name="lat_deg" select="number($latStr)"/>
            <xsl:with-param name="lon_deg" select="number($lonStr)"/>
            <xsl:with-param name="h_m" select="number($hStr)"/>
          </xsl:call-template>
        </xsl:variable>
        <xsl:value-of select="format-number(number($ex), $DecPl3, 'Standard')"/>
        <xsl:text>,</xsl:text>
        <xsl:value-of select="format-number(number($ey), $DecPl3, 'Standard')"/>
        <xsl:text>,</xsl:text>
        <xsl:value-of select="format-number(number($ez), $DecPl3, 'Standard')"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:text>,,</xsl:text>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <!-- WGS84 geodetic (deg) -> ECEF (m) -->
  <xsl:template name="Wgs84ToEcefX">
    <xsl:param name="lat_deg"/>
    <xsl:param name="lon_deg"/>
    <xsl:param name="h_m"/>
    <xsl:variable name="phi">
      <xsl:call-template name="DegToRad">
        <xsl:with-param name="deg" select="$lat_deg"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="lam">
      <xsl:call-template name="DegToRad">
        <xsl:with-param name="deg" select="$lon_deg"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="sphi">
      <xsl:call-template name="SinRad">
        <xsl:with-param name="x" select="number($phi)"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="cphi">
      <xsl:call-template name="CosRad">
        <xsl:with-param name="x" select="number($phi)"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="slam">
      <xsl:call-template name="SinRad">
        <xsl:with-param name="x" select="number($lam)"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="clam">
      <xsl:call-template name="CosRad">
        <xsl:with-param name="x" select="number($lam)"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="sp2" select="number($sphi) * number($sphi)"/>
    <xsl:variable name="denArg" select="1 - $WGS84_e2 * $sp2"/>
    <xsl:variable name="rootDen">
      <xsl:call-template name="Sqrt">
        <xsl:with-param name="x" select="$denArg"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="N" select="$WGS84_a div number($rootDen)"/>
    <xsl:value-of select="($N + $h_m) * number($cphi) * number($clam)"/>
  </xsl:template>

  <xsl:template name="Wgs84ToEcefY">
    <xsl:param name="lat_deg"/>
    <xsl:param name="lon_deg"/>
    <xsl:param name="h_m"/>
    <xsl:variable name="phi">
      <xsl:call-template name="DegToRad">
        <xsl:with-param name="deg" select="$lat_deg"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="lam">
      <xsl:call-template name="DegToRad">
        <xsl:with-param name="deg" select="$lon_deg"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="sphi">
      <xsl:call-template name="SinRad">
        <xsl:with-param name="x" select="number($phi)"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="cphi">
      <xsl:call-template name="CosRad">
        <xsl:with-param name="x" select="number($phi)"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="slam">
      <xsl:call-template name="SinRad">
        <xsl:with-param name="x" select="number($lam)"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="clam">
      <xsl:call-template name="CosRad">
        <xsl:with-param name="x" select="number($lam)"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="sp2" select="number($sphi) * number($sphi)"/>
    <xsl:variable name="denArg" select="1 - $WGS84_e2 * $sp2"/>
    <xsl:variable name="rootDen">
      <xsl:call-template name="Sqrt">
        <xsl:with-param name="x" select="$denArg"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="N" select="$WGS84_a div number($rootDen)"/>
    <xsl:value-of select="($N + $h_m) * number($cphi) * number($slam)"/>
  </xsl:template>

  <xsl:template name="Wgs84ToEcefZ">
    <xsl:param name="lat_deg"/>
    <xsl:param name="lon_deg"/>
    <xsl:param name="h_m"/>
    <xsl:variable name="phi">
      <xsl:call-template name="DegToRad">
        <xsl:with-param name="deg" select="$lat_deg"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="sphi">
      <xsl:call-template name="SinRad">
        <xsl:with-param name="x" select="number($phi)"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="sp2" select="number($sphi) * number($sphi)"/>
    <xsl:variable name="denArg" select="1 - $WGS84_e2 * $sp2"/>
    <xsl:variable name="rootDen">
      <xsl:call-template name="Sqrt">
        <xsl:with-param name="x" select="$denArg"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="N" select="$WGS84_a div number($rootDen)"/>
    <xsl:value-of select="($N * (1 - $WGS84_e2) + $h_m) * number($sphi)"/>
  </xsl:template>

  <xsl:template name="DegToRad">
    <xsl:param name="deg"/>
    <xsl:value-of select="$deg * 0.017453292519943295"/>
  </xsl:template>

  <xsl:template name="Sqrt">
    <xsl:param name="x"/>
    <xsl:choose>
      <xsl:when test="number($x) &lt;= 0">0</xsl:when>
      <xsl:otherwise>
        <xsl:call-template name="SqrtIter">
          <xsl:with-param name="x" select="number($x)"/>
          <xsl:with-param name="g" select="number($x) * 0.5"/>
          <xsl:with-param name="n" select="22"/>
        </xsl:call-template>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
  <xsl:template name="SqrtIter">
    <xsl:param name="x"/>
    <xsl:param name="g"/>
    <xsl:param name="n"/>
    <xsl:choose>
      <xsl:when test="$n &lt;= 0">
        <xsl:value-of select="$g"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:call-template name="SqrtIter">
          <xsl:with-param name="x" select="$x"/>
          <xsl:with-param name="g" select="($g + $x div $g) * 0.5"/>
          <xsl:with-param name="n" select="$n - 1"/>
        </xsl:call-template>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <!-- Taylor series, |x| &lt; ~1.5 rad sufficient for lat/lon in radians -->
  <xsl:template name="SinRad">
    <xsl:param name="x"/>
    <xsl:variable name="x2" select="$x * $x"/>
    <xsl:variable name="x3" select="$x2 * $x"/>
    <xsl:variable name="x5" select="$x3 * $x2"/>
    <xsl:variable name="x7" select="$x5 * $x2"/>
    <xsl:variable name="x9" select="$x7 * $x2"/>
    <xsl:variable name="x11" select="$x9 * $x2"/>
    <xsl:value-of
        select="$x - $x3*0.16666666666666666 + $x5*0.008333333333333333 - $x7*0.0001984126984126984 + $x9*2.755731922398589e-6 - $x11*2.5052108385441717e-8"/>
  </xsl:template>

  <xsl:template name="CosRad">
    <xsl:param name="x"/>
    <xsl:variable name="x2" select="$x * $x"/>
    <xsl:variable name="x4" select="$x2 * $x2"/>
    <xsl:variable name="x6" select="$x4 * $x2"/>
    <xsl:variable name="x8" select="$x6 * $x2"/>
    <xsl:variable name="x10" select="$x8 * $x2"/>
    <xsl:value-of
        select="1 - $x2*0.5 + $x4*0.041666666666666664 - $x6*0.001388888888888889 + $x8*2.4801587301587302e-5 - $x10*2.7557319223985893e-7"/>
  </xsl:template>

  <xsl:template name="FormatDateDMY">
    <xsl:param name="ts"/>
    <xsl:if test="string-length($ts) &gt;= 10">
      <xsl:value-of select="substring($ts, 9, 2)"/>
      <xsl:text>-</xsl:text>
      <xsl:value-of select="substring($ts, 6, 2)"/>
      <xsl:text>-</xsl:text>
      <xsl:value-of select="substring($ts, 1, 4)"/>
    </xsl:if>
  </xsl:template>

  <xsl:template name="FormatTimeHMS">
    <xsl:param name="ts"/>
    <xsl:variable name="tpart" select="substring-after($ts, 'T')"/>
    <xsl:choose>
      <xsl:when test="string-length($tpart) &gt;= 8">
        <xsl:value-of select="substring($tpart, 1, 8)"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$tpart"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <xsl:template name="FormatUtcTimeHMS">
    <xsl:param name="ts"/>
    <xsl:variable name="tpart" select="substring-after($ts, 'T')"/>
    <xsl:variable name="h" select="number(substring($tpart, 1, 2))"/>
    <xsl:variable name="m" select="number(substring($tpart, 4, 2))"/>
    <xsl:variable name="s" select="number(substring($tpart, 7, 2))"/>
    <xsl:variable name="adjH" select="$h + $hoursToUtc"/>
    <xsl:variable name="hOut">
      <xsl:choose>
        <xsl:when test="$adjH &lt; 0">
          <xsl:value-of select="$adjH + 24"/>
        </xsl:when>
        <xsl:when test="$adjH &gt;= 24">
          <xsl:value-of select="$adjH - 24"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="$adjH"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:variable>
    <xsl:value-of select="format-number($hOut, '00', 'Standard')"/>
    <xsl:text>:</xsl:text>
    <xsl:value-of select="format-number($m, '00', 'Standard')"/>
    <xsl:text>:</xsl:text>
    <xsl:value-of select="format-number($s, '00', 'Standard')"/>
  </xsl:template>

  <!-- Emit file with full path + extension.
       Behaviour:
         - If the JOB value already contains a path separator ('/' or '\'),
           it is used as-is (we trust what the JOB stored).
         - Otherwise the configured default path is prepended.
         - If the file name has no extension, the configured default extension
           is appended. -->
  <xsl:template name="EmitFileWithPath">
    <xsl:param name="name"/>
    <xsl:param name="defaultPath"/>
    <xsl:param name="defaultExt"/>
    <xsl:variable name="trimmed" select="normalize-space($name)"/>
    <xsl:variable name="hasPath" select="contains($trimmed, '/') or contains($trimmed, '\')"/>
    <xsl:variable name="lastSegment">
      <xsl:call-template name="LastPathSegment">
        <xsl:with-param name="p" select="$trimmed"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="withExt">
      <xsl:choose>
        <xsl:when test="contains($lastSegment, '.')">
          <xsl:value-of select="$trimmed"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="concat($trimmed, $defaultExt)"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:variable>
    <xsl:choose>
      <xsl:when test="$hasPath">
        <xsl:value-of select="$withExt"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="concat($defaultPath, $withExt)"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <!-- Returns the substring after the final '/' or '\' (file name only). -->
  <xsl:template name="LastPathSegment">
    <xsl:param name="p"/>
    <xsl:choose>
      <xsl:when test="contains($p, '/')">
        <xsl:call-template name="LastPathSegment">
          <xsl:with-param name="p" select="substring-after($p, '/')"/>
        </xsl:call-template>
      </xsl:when>
      <xsl:when test="contains($p, '\')">
        <xsl:call-template name="LastPathSegment">
          <xsl:with-param name="p" select="substring-after($p, '\')"/>
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$p"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <xsl:template name="NewLine">
    <xsl:text>&#10;</xsl:text>
  </xsl:template>

</xsl:stylesheet>
