Home > Software design >  Using XSLT to calculate start times of a video playlist
Using XSLT to calculate start times of a video playlist

Time:10-26

I have the following XML representing a list of videos (Titles) and a playlist using these videos (Clock) :

<?xml version="1.0" encoding="UTF-8"?>
<myXML>
<Titles>
    <Title>
        <name>Video1</name>
        <duration>200</duration>
    </Title>
    <Title>
        <name>Video2</name>
        <duration>300</duration>
    </Title>
    <Title>
        <name>Video3</name>
        <duration>100</duration>
    </Title>
</Titles>
<Clock>
    <Video>
        <name>Video3</name>
    </Video>
    <Video>
        <name>Video1</name>
    </Video>
    <Video>
        <name>Video3</name>
    </Video>
    <Video>
        <name>Video2</name>
    </Video>
</Clock>
</myXML>

I want to use XSLT to create a new XML playlist with the same order and the start time of each video (assuming the first video in Clock starts at time 0). As you can see, a video can be played several times in the playlist (here Video3 is played twice).

I tried this :

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

<Playlist>
<xsl:for-each select="myXML/Clock/Video">
<Video>
    <name><xsl:value-of select="name"/></name>
    <start_time><xsl:value-of select="sum(/myXML/Titles/Title[name=current()/preceding-sibling::*/name]/duration)" /></start_time>
</Video>
</xsl:for-each>
</Playlist>

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

The idea would be, for each Video in Clock, to calculate the sum of durations of the preceding videos in Clock, which would be the start time of the Video, using the join operation "[name=current()/preceding-sibling::*/name]". But it does not work because if a video is played twice in Clocks (or multiple times), it is only counted once in the sum. The XML output with this solution is :

<?xml version="1.0" encoding="UTF-8"?>
<Playlist>
<Video>
    <name>Video3</name>
    <start_time>0</start_time>
</Video>
<Video>
    <name>Video1</name>
    <start_time>100</start_time></Video>
<Video>
    <name>Video3</name>
    <start_time>300</start_time></Video>
<Video>
    <name>Video2</name>
    <start_time>300</start_time></Video>
</Playlist>

while Video2's start_time should be 400.

I tried to manipulate XSLT but did not find a satisfying solution. I feel stuck because of the lack of mutable variables in XSLT. Do you have any idea how to achieve this ? Any help is welcome, thanks.

CodePudding user response:

Here is one way you could look at it:

XSLT 1.0

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

<xsl:key name="title" match="Title" use="name" />

<xsl:template match="/myXML">
    <Playlist>
        <xsl:call-template name="build-list">
            <xsl:with-param name="items" select="Clock/Video"/>
        </xsl:call-template>
    </Playlist>
</xsl:template>

<xsl:template name="build-list">
    <xsl:param name="items"/>
    <xsl:param name="start" select="0"/>
    <xsl:if test="$items">
        <xsl:variable name="name" select="$items[1]/name" />
        <Video>
            <xsl:copy-of select="$name"/>
            <start_time>
                <xsl:value-of select="$start" />
            </start_time>
        </Video>
        <!-- recursive call -->
        <xsl:call-template name="build-list">
            <xsl:with-param name="items" select="$items[position() > 1]"/>
            <xsl:with-param name="start" select="$start   key('title', $name)/duration"/>
        </xsl:call-template>
    </xsl:if>
</xsl:template>

</xsl:stylesheet>

Another option is to use a so-called "sibling recursion" (look it up).

  • Related