0

I want to convert a time-format like "HH:MM:SS.SSS" into Milliseconds using tokenize and/or analyze-string() with XSLT 1.0.

The following xsl-Stylesheet:

 <?xml version="1.0"?>
<xsl:stylesheet version="1.0" 
    xmlns:pc="https://purl.org/net/hbuschme/teaching/2019ws-infostruk/podcast/0.3" 
    xmlns:pt="https://purl.org/net/hbuschme/teaching/2019ws-infostruk/podcast-transcript/0.1" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:exsl="http://exslt.org/common"
    exclude-result-prefixes="exsl">
    <xsl:output method="xhtml"/>






    <xsl:template match="pc:podcast">
            <html>
                <body>
                    <h1>Episoden des Podcasts <i><xsl:value-of select="pc:title"/></i></h1>
                    <xsl:apply-templates select="pc:episode"/>
                </body>
            </html>
        </xsl:template>


        <xsl:template match="pc:episode">
            <p>
                   Episode <xsl:value-of select="@episode"/> <xsl:text> </xsl:text> <xsl:number format="1. "/> 
                               <xsl:text></xsl:text>
                               <b><xsl:value-of select="@title"/></b> <br/> 
                               <xsl:value-of select="pt:transcript"/>
                               <xsl:for-each select="pc:chapter"><ul>
                                <xsl:number count="pc:episode|pc:chapter" level="multiple" format="1.1. "/>
                                <xsl:value-of select="@title" />
                               </ul></xsl:for-each>  
                               <xsl:for-each select="@url">
                                 <xsl:sort select="@episode" order="descending" /><a href="{@url}"><xsl:apply-templates/></a>
                               </xsl:for-each>
                               <xsl:call-template name="time-to-milliseconds">
                                 <xsl:with-param name="time" select="@duration"/>
                               </xsl:call-template>

                               <xsl:call-template name="mt">
                                 <xsl:with-param name="time" select="@duration"/>
                               </xsl:call-template>
            </p>
      </xsl:template>

      <xsl:template name="time-to-milliseconds">
        <xsl:param name="time"/>
        <xsl:param name="h" select="substring-before($time, ':')"/>
        <xsl:param name="m" select="substring-before(substring-after($time,':'),':')"/>
        <xsl:param name="s" select="substring-after(substring-after($time,':'),':')"/>  
        <xsl:choose>
         <xsl:when test="contains($h, '00')">
            <xsl:value-of select="1000*(60*$m + $s)"/>
        </xsl:when>
         <xsl:otherwise>
          <xsl:value-of select="1000*(3600*$h + 60*$m + $s)"/>
         </xsl:otherwise>
          <xsl:when test="contains($m, '00')">
            <xsl:value-of select="1000*(3600*$h + $s)"/>
          </xsl:when>
          <xsl:otherwise>
          <xsl:value-of select="1000*(3600*$h + 60*$m + $s)"/>
         </xsl:otherwise>
         <xsl:when test="contains($s, '00')">
            <xsl:value-of select="1000*(3600*$h + 60*$m)"/>
          </xsl:when>
          <xsl:otherwise>
          <xsl:value-of select="1000*(3600*$h + 60*$m + $s)"/>
         </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

    <xsl:template name="mt">
        <xsl:param name="time"/>
        <xsl:param name="h" select="floor($time div 3600000)"/>
        <xsl:param name="m" select="floor($time mod 3600000 div 60000)"/>
        <xsl:param name="s" select="floor($time mod 60000 div 1000)"/>
        <xsl:value-of select="concat($h,':',$m,':',$s,':')"/>
    </xsl:template>

    </xsl:stylesheet>

Expected output:

00:42 -> 4200

Any advice? I do not know how or if I can even use the analyze-string function in XSLT 1.0. Can you provide me with an example of how to handle this problem?

ASCIIIIIII
  • 21
  • 5
  • The `analyze-string` function is part of XPath 3 and later so normally used with XSLT 3, therefore I don't understand why you tag the question as XSLT 2. It is not clear what you are aiming at, XPath is the expression language in XSLT, it is not clear what you mean by "implement". – Martin Honnen Jan 18 '20 at 22:08
  • I want to use the above expression in XSLT (3.0). Please forgive me if I misunderstood something. – ASCIIIIIII Jan 18 '20 at 22:47
  • @MartinHonnen: Despite the mistagging, I'm guessing that ASCIIIIIII wants to port to XSLT the above code for parsing and converting hours, minutes, seconds, and milliseconds to just milliseconds, [something like this](https://stackoverflow.com/a/15348806/290085). – kjhughes Jan 19 '20 at 00:31
  • XPath is the expression language used in XSLT -- so just make the provided XPath 3.1 expression --> the value of a "select" attribute of an `` inside a template or of the `select` attribute of a global variable. Of course, you need an XSLT 3.0 processor. In XSLT 2.0 one can use the `` instruction: https://www.w3.org/TR/2007/REC-xslt20-20070123/#analyze-string – Dimitre Novatchev Jan 19 '20 at 04:26
  • Thanks but I need a way to use the expression with xsl:call-template – ASCIIIIIII Jan 19 '20 at 08:58
  • Yes, in this case specify the expression as the value of the `select` attribute of the `` child of `` -- why do you think there is any problem with this? – Dimitre Novatchev Jan 19 '20 at 17:31
  • I'm using XML-Copy editor. Does anybody know if this software supports XPATH 3.1 expressions and XSLT 3.0? – ASCIIIIIII Jan 20 '20 at 10:13
  • I apologize. As you can see I'm using version 1.0 of XSLT, so I can not use analyze string or tokenize for instance. – ASCIIIIIII Jan 20 '20 at 12:15
  • @ASCIIIIIII See here how to find out which version your processor supports: https://stackoverflow.com/a/25245033/3016153 – michael.hor257k Jan 20 '20 at 15:52

2 Answers2

0

One way to do it, given XSLT 3.0, is just to take your entire expression and wrap it into an XSLT select attribute, but with adjustment of single-vs-double quotes, e.g.

<xsl:variable name="result" select='
 let
    $duration := string(/test/@duration),
    $components := analyze-string(
            $duration, 
            "(((\d*):)?([0-5]?[0-9]):)?([0-5]?[0-9])(\.([0-9]?[0-9]?[0-9]?))?"
        ),
    $hours := $components//fn:group[@nr="3"]/text(),
    $hours := if ($hours != "") then $hours else 0,
    $minutes := $components//fn:group[@nr="4"]/text(),
    $minutes := if ($minutes != "") then $minutes else 0,
    $seconds := $components//fn:group[@nr="5"]/text(),
    $seconds := if ($seconds != "") then $seconds else 0,
    $millis := $components//fn:group[@nr="7"]/text(),
    $millis := if ($millis != "") then $millis else 0
return
    xs:int($hours * 60 * 60 * 1000
    + $minutes * 60 * 1000
    + $seconds * 1000
    + $millis)'/>

Another way is to rewrite it using XSLT constructs at a finer-grained level:

<xsl:variable name="duration" select="string(/test/@duration)"/>
<xsl:variable name="components" select='analyze-string(
            $duration, 
            "(((\d*):)?([0-5]?[0-9]):)?([0-5]?[0-9])(\.([0-9]?[0-9]?[0-9]?))?"
        )'/>
<xsl:variable name="hours" select="$components//fn:group[@nr='3']/text()'/>

etc

A third way would be to replace the call on fn:analyze-string() with something that uses the XSLT xsl:analyze-string instruction; you would then potentially have something that works under XSLT 2.0.

Michael Kay
  • 156,231
  • 11
  • 92
  • 164
0

This may be a bit tedious, but still trivial to do in pure XSLT 1.0:

<xsl:template name="time-to-milliseconds">
    <xsl:param name="time"/>
    <xsl:param name="h" select="substring-before($time, ':')"/>
    <xsl:param name="m" select="substring-before(substring-after($time,':'),':')"/>
    <xsl:param name="s" select="substring-after(substring-after($time,':'),':')"/>  
    <xsl:value-of select="1000*(3600*$h + 60*$m + $s)"/>
</xsl:template>

Demo: https://xsltfiddle.liberty-development.net/6rexjhN


To convert in the opposite direction, you can use:

<xsl:template name="milliseconds-to-time">
    <xsl:param name="milliseconds"/>
    <xsl:variable name="h" select="floor($milliseconds div 3600000)"/>
    <xsl:variable name="m" select="floor($milliseconds div 60000) mod 60"/>
    <xsl:variable name="s" select="$milliseconds mod 60000 div 1000"/>
    <xsl:value-of select="format-number($h, '00')" />
    <xsl:value-of select="format-number($m, ':00')" />
    <xsl:value-of select="format-number($s, ':00.###')" />
</xsl:template>
michael.hor257k
  • 113,275
  • 6
  • 33
  • 51