XSLT 转换与计数
Posted
技术标签:
【中文标题】XSLT 转换与计数【英文标题】:XSLT Transformation with count 【发布时间】:2010-11-06 00:55:39 【问题描述】:如何使用 xslt 转换以下内容
<blogger>
<post>
<text>...</text>
<categories>Engineering, Internet, Sausages</catgories>
</post>
<post>
<text>...</text>
<categories>Internet, Sausages</catgories>
</post>
<post>
<text>...</text>
<categories>Sausages</catgories>
</post>
</blogger>
进入
Sausages (3)
Internet (2)
Engineering (1)
【问题讨论】:
你为什么不发布有效的 XML?我的意思是......这是一个剪切和粘贴的问题...... 源代码是正确的; markdown 误解了缩进不足的行。 【参考方案1】:首先,改变你的 xml
创建数据.xml
<blogger>
<post>
<text>...</text>
<categories>
<category>Engineering</category>
<category>Internet</category>
<category>Sausages</category>
</categories>
</post>
<post>
<text>...</text>
<categories>
<category>Internet</category>
<category>Sausages</category>
</categories>
</post>
<post>
<text>...</text>
<categories>
<category>Sausages</category>
</categories>
</post>
</blogger>
然后编写你的 xslt,创建 transform.xslt
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:for-each select="//category">
<xsl:variable name="value" select="."/>
<xsl:if test="count(preceding::category[.=$value]) = 0">
<xsl:value-of select="."/>
<xsl:text> (</xsl:text>
<xsl:value-of select="count(//category[.=$value])"/>
<xsl:text>)</xsl:text><br/>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
然后你可以在 Internet Explorer 中打开 data.xml 并得到如下结果:
工程学(1)互联网(2)香肠(3)
【讨论】:
不幸的是,我无法更改 XML 的结构,因为它来自其他地方。 拥有现有的 XML 结构是不合逻辑的(至少对我而言)。您可以要求您的 xml 源提供者更改他们的 XML 吗?否则,您可以预处理/优化您的 XML 以获得运行所需的 xslt 所需的内容。 这很酷...只是最后一个想法想将它们从高到低排序? 3 2 1 等 Awww,对它们进行排序也不会那么困难,但我会将其作为练习留给其他人使用。 :-)【参考方案2】:你需要的是这个:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:template match="/">
<items>
<xsl:apply-templates select="/blogger/post/categories" />
</items>
</xsl:template>
<xsl:template match="categories">
<xsl:call-template name="split">
<xsl:with-param name="pString" select="." />
</xsl:call-template>
</xsl:template>
<!-- this splits a comma-delimited string into a series of <item>s -->
<xsl:template name="split">
<xsl:param name="pString" select="''" />
<xsl:variable name="vList" select="
concat($pString, ',')
" />
<xsl:variable name="vHead" select="
normalize-space(substring-before($vList ,','))
" />
<xsl:variable name="vTail" select="
normalize-space(substring-after($vList ,','))
" />
<xsl:if test="not($vHead = '')">
<item>
<xsl:value-of select="$vHead" />
</item>
<xsl:call-template name="split">
<xsl:with-param name="pString" select="$vTail" />
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
这会产生这个中间结果:
<items>
<item>Engineering</item>
<item>Internet</item>
<item>Sausages</item>
<item>Internet</item>
<item>Sausages</item>
<item>Sausages</item>
</items>
还有这个:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output method="text" />
<xsl:key name="kItem" match="item" use="." />
<xsl:template match="/items">
<xsl:apply-templates select="item">
<xsl:sort
select="count(key('kItem', .))"
data-type="number"
order="descending"
/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="item">
<xsl:if test="
generate-id() = generate-id(key('kItem', .)[1])
">
<xsl:value-of select="
concat(
., ' (', count(key('kItem', .)), ') '
)
" />
</xsl:if>
</xsl:template>
</xsl:stylesheet>
哪些输出:
Sausages (3)
Internet (2)
Engineering (1)
【讨论】:
【参考方案3】:其实是可以做到的,也不难。这将做你想做的事:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="fo msxsl">
<xsl:output encoding="UTF-8" indent="yes" method="xml"/>
<xsl:variable name="Separator">,</xsl:variable>
<xsl:template match="/">
<xsl:variable name="NodeList">
<xsl:apply-templates select="//categories"/>
</xsl:variable>
<xsl:variable name="Nodes" select="msxsl:node-set($NodeList)"/>
<html>
<head>
<title>Simple list</title>
</head>
<body>
<xsl:for-each select="$Nodes/Value">
<xsl:variable name="value" select="."/>
<xsl:if test="count(preceding::Value[.=$value]) = 0">
<xsl:value-of select="."/> (<xsl:value-of select="count($Nodes/Value[.=$value])"/>)<br/>
</xsl:if>
</xsl:for-each>
</body>
</html>
</xsl:template>
<xsl:template match="categories" name="Whole">
<xsl:call-template name="Substring">
<xsl:with-param name="Value" select="normalize-space(.)"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="Substring">
<xsl:param name="Value"/>
<xsl:choose>
<xsl:when test="contains($Value, $Separator)">
<xsl:variable name="Before" select="normalize-space(substring-before($Value, $Separator))"/>
<xsl:variable name="After" select="normalize-space(substring-after($Value, $Separator))"/>
<Value>
<xsl:value-of select="$Before"/>
</Value>
<xsl:call-template name="Substring">
<xsl:with-param name="Value" select="$After"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<Value>
<xsl:value-of select="$Value"/>
</Value>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
实际上,这是小菜一碟。 :-)
【讨论】:
必须添加此代码适用于 MXSML。如果您使用另一个 XSLT 处理器,那么您需要另一个解决方案来将变量转换为节点集。 (虽然有些处理器不需要这样的转换。) 但这也不是按组数排序 - 需要两步操作。 哦,我刚刚注意到:模板名称(“Whole”)是不必要的。 按组数分组并不难。只需将它们再次添加到新的节点集中。我的样式表已经以两步方式运行。首先,它拆分字符串并将结果存储在节点集中。然后它计算这个节点集中的元素。您可以将其添加到第二个节点集并按计数对其进行排序。基本上,您可以在一个样式表中执行两个步骤... 嗯...是的,你可以这样做。通常,在没有扩展功能的情况下进行分组和做事时,也许我太拘泥于键了。 ;-) 对于小输入,“node-set()”和“count(preceding...)”可能足够快,但我预计它的扩展性会非常糟糕。反正。 +1 来自我。 ^^以上是关于XSLT 转换与计数的主要内容,如果未能解决你的问题,请参考以下文章