XSLT Group for half an hour

Given this XML:

<root> <row> <time>08:00</time> <sales>800</sales> </row> <row> <time>08:15</time> <sales>815</sales> </row> <row> <time>08:30</time> <sales>830</sales> </row> <row> <time>08:45</time> <sales>845</sales> </row> <row> <time>11:00</time> <sales>1100</sales> </row> <row> <time>11:45</time> <sales>1145</sales> </row> <row> <time>14:15</time> <sales>1415</sales> </row> <row> <time>14:30</time> <sales>1430</sales> </row> </root> 

I am trying to find a way to convert XSLT by summarizing sales at 30 minute intervals. I can summarize hourly intervals for 60 minutes using the MUENCHIAN method, but I canโ€™t use it for 30 minutes since I need a custom function for this (but I canโ€™t use XSLT 2.0 and non-standard .Net functions). Please, help!

Expected Result:

 30 minute 08:00 $1600 08:30 $1675 11:00 $1100 11:30 $1145 14:00 $1415 14:30 $1430 
+4
source share
2 answers

Solution 1

This conversion is :

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="text"/> <xsl:key name="krowsInHalfHour" match= "row[not((substring-after(time,':')+30) mod 30 = 0)]" use="generate-id( preceding-sibling::row [60*substring-before(time,':') + substring-after(time,':') >= 60*substring-before(current()/time,':') + substring-after(current()/time,':') - substring-after(current()/time,':') mod 30 ] [1] ) "/> <xsl:template match= "row[(substring-after(time,':')+30) mod 30 = 0 or not( 60*substring-before(preceding-sibling::row[1]/time,':') + substring-after(preceding-sibling::row[1]/time,':') >= 60*substring-before(time,':') + substring-after(time,':') - substring-after(time,':') mod 30 ) ] "> <xsl:variable name="vPrevStartMins" select= "60*substring-before(time,':') + substring-after(time,':') - substring-after(time,':') mod 30 "/> <xsl:value-of select= "concat('&#xA;',floor($vPrevStartMins div 60), ':', concat(substring('0',($vPrevStartMins mod 60 >0)+1), $vPrevStartMins mod 60 ) ) "/> <xsl:text> $</xsl:text> <xsl:value-of select= "sum(sales | key('krowsInHalfHour',generate-id())/sales)"/> </xsl:template> <xsl:template match="text()"/> </xsl:stylesheet> 

when applied to the provided XML document:

 <root> <row> <time>08:00</time> <sales>800</sales> </row> <row> <time>08:15</time> <sales>815</sales> </row> <row> <time>08:30</time> <sales>830</sales> </row> <row> <time>08:45</time> <sales>845</sales> </row> <row> <time>11:00</time> <sales>1100</sales> </row> <row> <time>11:45</time> <sales>1145</sales> </row> <row> <time>14:15</time> <sales>1415</sales> </row> <row> <time>14:30</time> <sales>1430</sales> </row> </root> 

creates the desired, correct result:

 8:00 $1615 8:30 $1675 11:00 $1100 11:30 $1145 14:00 $1415 14:30 $1430 

Solution 2 :

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:my="my:my" > <xsl:output method="text"/> <xsl:strip-space elements="*"/> <my:halfHours> <t>00:00</t><t>00:30</t><t>01:00</t><t>01:30</t> <t>02:00</t><t>02:30</t><t>03:00</t><t>03:30</t> <t>04:00</t><t>04:30</t><t>05:00</t><t>05:30</t> <t>06:00</t><t>06:30</t><t>07:00</t><t>07:30</t> <t>08:00</t><t>08:30</t><t>09:00</t><t>09:30</t> <t>10:00</t><t>10:30</t><t>11:00</t><t>11:30</t> <t>12:00</t><t>12:30</t><t>13:00</t><t>13:30</t> <t>14:00</t><t>14:30</t><t>15:00</t><t>15:30</t> <t>16:00</t><t>16:30</t><t>17:00</t><t>17:30</t> <t>18:00</t><t>18:30</t><t>19:00</t><t>19:30</t> <t>20:00</t><t>20:30</t><t>21:00</t><t>21:30</t> <t>22:00</t><t>22:30</t><t>23:00</t><t>23:30</t> <t>24:00</t> </my:halfHours> <xsl:variable name="vhalfHrs" select= "document('')/*/my:halfHours/*"/> <xsl:template match="row"> <xsl:variable name="vStart" select= "$vhalfHrs[translate(.,':','') > translate(current()/time,':','') ][1] /preceding-sibling::*[1] "/> <xsl:variable name="vprecRow" select= "preceding-sibling::*[1]"/> <xsl:if test= "not(translate($vprecRow/time,':','') >= translate($vStart,':','') )"> <xsl:value-of select="concat('&#xA;',$vStart, ' $')"/> <xsl:value-of select= "sum(sales|following-sibling::* [not(translate(time,':','') >= translate($vStart/following-sibling::*,':','') ) ] /sales ) "/> </xsl:if> </xsl:template> </xsl:stylesheet> 

when this conversion is applied to the same XML document, the desired, correct result is again obtained :

 08:00 $1615 08:30 $1675 11:00 $1100 11:30 $1145 14:00 $1415 14:30 $1430 

Explanation

  • The variable $vhalfHrs contains elements whose values โ€‹โ€‹are the starting time of each half-hour period during the day.

  • In the template that corresponds to each row , the $vStart variable is set at this half-hour start time, at which the current node ( row )'s time falls.

  • The $vprecRow set to the previous sibling ( row ) of the current row .

  • If the time of the previous row no later than the beginning of half an hour (in $ vStart ), then the current row` is the first for this half hour period.

  • We display the initial time of half an hour.

  • We calculate and display the sum of all row elements whose time is in the same half-hour period. They follow the brothers and sisters of the current row , and their time not greater than or equal to the beginning of the next half-hour period.

Solution 3 (XSLT 2.0):

 <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xsl:output method="text"/> <xsl:template match="/*"> <xsl:for-each-group select="row" group-adjacent= "(xs:integer(substring-before(time,':'))*60 + xs:integer(substring-after(time,':')) ) idiv 30 "> <xsl:variable name="vStartMinutes" select="current-grouping-key()*30"/> <xsl:value-of separator="" select= "'&#xA;', format-number($vStartMinutes idiv 60, '00'), ':', format-number($vStartMinutes mod 60,'00'), ' $', sum(current-group()/sales/xs:integer(.)) "/> </xsl:for-each-group> </xsl:template> </xsl:stylesheet> 

when this conversion is applied to the same XML document as above, the same desired, correct result is obtained :

 08:00 $1615 08:30 $1675 11:00 $1100 11:30 $1145 14:00 $1415 14:30 $1430 

Explanation

  • We use <xsl:for-each-group> with the group-adjacent attribute as an expression evaluating the position of the 1/2 hour period in which a row/time .

  • Heavy use of standard functions current-group() and current-grouping-key() .

+6
source

Without a preceding axis, this stylesheet:

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="text"/> <xsl:key name="kRowByHalfHour" match="row" use="floor((substring(time,1,2)*60+substring(time,4))div 30)"/> <xsl:template match="row[count(.|key('kRowByHalfHour', floor((substring(time,1,2) * 60 + substring(time,4)) div 30) )[1] )=1 ]"> <xsl:variable name="vKey" select="floor((substring(time,1,2)*60+substring(time,4))div 30)"/> <xsl:value-of select="concat(format-number(floor($vKey * 30 div 60),'00'),':', format-number(($vKey * 30) mod 60,'00'),' $', sum(key('kRowByHalfHour',$vKey)/sales),'&#xA;' )"/> </xsl:template> <xsl:template match="text()"/> </xsl:stylesheet> 

Output:

 08:00 $1615 08:30 $1675 11:00 $1100 11:30 $1145 14:00 $1415 14:30 $1430 

Edit 2 : an even better key value with normalized time allowing parameterization (by means of the document() function or two-phase conversion): if we take the key calculation as a measure of complexity, it will be O (N)

+3
source

Source: https://habr.com/ru/post/1340403/


All Articles