如何用XSLT实现XML按车型日期分组生成指定格式文本文件
解决XML按车型+日期分组生成指定格式文本问题
需求:将指定XML转换为文本文件,要求车型(Cars)和日期(Date)仅在每组头部出现一次,下方列出该组对应的时间(Time)与平均值(avg),按日期拆分头部,此前尝试分组方式未得到预期结果,现提供可行实现方案。
输入XML
<?xml version="1.0" encoding="UTF-8"?> <data> <item> <Date>2023-02-19</Date> <Cars>Ford</Cars> <SellTarget>A4+</SellTarget> <Time>10:40:09</Time> <avg>19.3464027998</avg> </item> <item> <Date>2023-02-19</Date> <Cars>Ford</Cars> <SellTarget>A4+</SellTarget> <Time>11:21:56</Time> <avg>32.7150023474</avg> </item> <item> <Date>2023-02-19</Date> <Cars>Ford</Cars> <SellTarget>A4+</SellTarget> <Time>19:01:27</Time> <avg>554.0289810087</avg> </item> <item> <Date>2023-02-19</Date> <Cars>Ford</Cars> <SellTarget>A4+</SellTarget> <Time>23:15:26</Time> <avg>46.1398232343</avg> </item> <item> <Date>2023-02-19</Date> <Cars>Opel</Cars> <SellTarget>A4+</SellTarget> <Time>14:05:51</Time> <avg>41.7428144493</avg> </item> <item> <Date>2023-02-19</Date> <Cars>Opel</Cars> <SellTarget>A4+</SellTarget> <Time>15:01:02</Time> <avg>65.6303001034</avg> </item> <item> <Date>2023-02-19</Date> <Cars>Opel</Cars> <SellTarget>A4+</SellTarget> <Time>02:00:00</Time> <avg>1.2954721559</avg> </item> </data>
当前使用的XSLT
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:output method="text" encoding="UTF-8"/> <xsl:key name="carsgroup" match="/data/item" use="concat(Cars, '|', Date)" /> <xsl:variable name="tab" select="'	'"/> <xsl:variable name="newLine" select="' '"/> <xsl:variable name="csvSeparator" select="$tab"/> <xsl:template match="/"> <xsl:call-template name="headerlines"/> <xsl:apply-templates select="data/item[generate-id() = generate-id(key('carsgroup', concat(Cars, '|', Date)))]" mode="dataRows"> </xsl:apply-templates> </xsl:template> <xsl:template name="headerlines"> <!-- 1. line is Skipped --> <xsl:call-template name="csv"> <xsl:with-param name="val" select="''"/> </xsl:call-template> <!-- 2. line is Skipped--> <xsl:call-template name="csv"> <xsl:with-param name="val" select="''"/> </xsl:call-template> <!-- 3. line is Skipped --> <xsl:call-template name="csv"> <xsl:with-param name="val" select="''"/> </xsl:call-template> <!-- 4. line is Skipped --> <xsl:call-template name="csv"> <xsl:with-param name="val" select="''"/> <xsl:with-param name="isLastCol" select="true()"/> </xsl:call-template> </xsl:template> <xsl:template match="item" mode="dataRows"> <!-- at 5. line the first 5 char is skipped --> <xsl:call-template name="csv"> <xsl:with-param name="val" select="concat(' ', Cars, ' / ', Date, ' [ 50 Entrants ]')"/> </xsl:call-template> <!-- sequence number --> <xsl:call-template name="csv"> <xsl:with-param name="val" select="position()"/> </xsl:call-template> <!-- Time HH:MM:SS --> <xsl:call-template name="csv"> <xsl:with-param name="val" select="Time"/> </xsl:call-template> <!-- avg --> <xsl:call-template name="csv"> <xsl:with-param name="val" select="avg"/> <xsl:with-param name="isLastCol" select="true()"/> <xsl:with-param name="isEOF" select="position()=last()"/> </xsl:call-template> </xsl:template> <xsl:template name="csv"> <xsl:param name="val"/> <xsl:param name="isLastCol" select="false()"/> <xsl:param name="isEOF" select="false()"/> <xsl:value-of select="$val"/> <xsl:choose> <xsl:when test="$isLastCol and not($isEOF)"> <xsl:value-of select="$newLine"/> </xsl:when> <xsl:when test="not($isEOF)"> <xsl:value-of select="$csvSeparator"/> </xsl:when> </xsl:choose> </xsl:template> </xsl:stylesheet>
当前生成的输出
Ford / 2023-02-19 [ 50 Entrants ] 1 10:40:09 19.3464027998 Opel / 2023-02-19 [ 50 Entrants ] 2 14:05:51 41.7428144493
期望的输出
Ford / 2023-02-19 [ 50 Entrants ] 1 10:40:09 19.3464027998 2 11:21:56 32.7150023474 3 19:01:27 554.0289810087 4 23:15:26 46.1398232343 Opel / 2023-02-19 [ 50 Entrants ] 1 02:00:00 1.2954721559 2 14:05:51 41.7428144493 3 15:01:02 65.6303001034
修改后的XSLT实现
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:output method="text" encoding="UTF-8"/> <xsl:key name="carsgroup" match="/data/item" use="concat(Cars, '|', Date)" /> <xsl:variable name="tab" select="'	'"/> <xsl:variable name="newLine" select="' '"/> <xsl:template match="/"> <!-- 遍历每个分组的第一个item,作为组的入口 --> <xsl:apply-templates select="data/item[generate-id() = generate-id(key('carsgroup', concat(Cars, '|', Date)))]" /> </xsl:template> <xsl:template match="item"> <!-- 输出组头,注意缩进和格式 --> <xsl:value-of select="concat(' ', Cars, ' / ', Date, ' [ 50 Entrants ]', $newLine)"/> <!-- 遍历当前组下的所有item --> <xsl:for-each select="key('carsgroup', concat(Cars, '|', Date))"> <!-- 按时间排序(匹配期望输出中Opel组的时间顺序) --> <xsl:sort select="Time" data-type="text"/> <!-- 输出序号、时间、平均值,用制表符分隔 --> <xsl:value-of select="concat(position(), $tab, Time, $tab, avg, $newLine)"/> </xsl:for-each> </xsl:template> </xsl:stylesheet>
关键修改说明
- 移除多余空行:删除原
headerlines模板,直接从组头开始输出,匹配期望格式 - 分组内遍历所有项:在分组模板中,通过
key('carsgroup', concat(Cars, '|', Date))获取当前组的所有item,而非仅处理分组的第一个item - 组内独立序号:使用
xsl:for-each的position()生成每组内部的序号,而非全局序号 - 时间排序:添加
<xsl:sort select="Time" data-type="text"/>,确保同组内按时间顺序输出,匹配期望的Opel组时间排序 - 简化格式输出:直接拼接文本和分隔符,避免复杂的
csv模板,更易维护
内容的提问来源于stack exchange,提问作者gyuszmok
相关产品推荐
相关产品推荐

