跟进我之前的答案(XSLT For-Each 循环从枚举标签中获取数据?),这需要一个简单的修改。就是说,既然你说你在某些时候迷路了,我会在最后添加一个解释。
当这个 XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable
name="vNums"
select="'0123456789'"/>
<xsl:key
name="kElemByNumber"
match="Demographic_Information/*"
use="translate(name(), translate(name(), $vNums, ''), '')"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="Demographic_Information">
<Demographic_Information>
<xsl:apply-templates
select="*[generate-id() =
generate-id(key(
'kElemByNumber',
translate(name(), translate(name(), $vNums, ''),
''
))[1])][normalize-space()]">
<xsl:sort
select="translate(name(), translate(name(), $vNums, ''), '')"
data-type="number"/>
</xsl:apply-templates>
</Demographic_Information>
</xsl:template>
<xsl:template match="Demographic_Information/*">
<Person subject="{position()}">
<xsl:apply-templates
select="key('kElemByNumber', position())"
mode="children">
<xsl:sort select="name()"/>
</xsl:apply-templates>
</Person>
</xsl:template>
<xsl:template match="Demographic_Information/*" mode="children">
<xsl:element name="{translate(name(), $vNums, '')}">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
...应用于源 XML(稍作修改,以便“其他/更多 XML 数据”的实例是注释 [从而确保正确的 XML 文档]):
<Root>
<Subjects>
<!--<...more XML Data>-->
<Data>
<!--<...other XML Data>-->
<Demographic_Information>
<Age1>33</Age1>
<Age2>66</Age2>
<Age3/>
<Age4/>
<Age5/>
<Age6/>
<Age7/>
<Age8/>
<Age9/>
<Age10/>
<Gender1>M</Gender1>
<Gender2>F</Gender2>
<Gender3/>
<Gender4/>
<Gender5/>
<Gender6/>
<Gender7/>
<Gender8/>
<Gender9/>
<Gender10/>
<Race1>W</Race1>
<Race2>H</Race2>
<Race3/>
<Race4/>
<Race5/>
<Race6/>
<Race7/>
<Race8/>
<Race9/>
<Race10/>
</Demographic_Information>
<!--</...other XML Data>-->
</Data>
<!--</...more XML Data>-->
</Subjects>
</Root>
...产生了想要的结果:
<Root>
<Subjects>
<!--<...more XML Data>-->
<Data>
<!--<...other XML Data>-->
<Demographic_Information>
<Person subject="1">
<Age>33</Age>
<Gender>M</Gender>
<Race>W</Race>
</Person>
<Person subject="2">
<Age>66</Age>
<Gender>F</Gender>
<Race>H</Race>
</Person>
</Demographic_Information>
<!--</...other XML Data>-->
</Data>
<!--</...more XML Data>-->
</Subjects>
</Root>
解释:
让我们一步一步地完成这个过程,首先看一下每个<xsl:template>
声明。
模板#1:身份模板
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()" />
</xsl:copy>
</xsl:template>
这个模板(顺便说一下,这是这个答案和我之前给你的答案之间的唯一变化),它有一个工作:将所有节点(元素、文本节点、注释和处理指令)和所有属性从源 XML 复制到结果 XML。因此,不管怎样,这个模板创建了原始 XML 的精确副本。用其他模板覆盖这个模板是 XSLT 中最基本的设计模式之一。
在我们的例子中,这个模板确保我们不直接匹配的任何节点/属性(例如不属于<Demographic_Information>
或其子级的所有节点/属性)将按原样复制到结果文档中。
模板#2:修改<Demographic_Information>
<xsl:template match="Demographic_Information">
<Demographic_Information>
<xsl:apply-templates
select="*[generate-id() =
generate-id(key(
'kElemByNumber',
translate(name(), translate(name(), $vNums, ''),
''
))[1])][normalize-space()]">
<xsl:sort
select="translate(name(), translate(name(), $vNums, ''), '')"
data-type="number"/>
</xsl:apply-templates>
</Demographic_Information>
</xsl:template>
这个模板(匹配所有<Demographic_Information>
元素)使用了一种称为Muenchian Grouping的技术,这是一种在 XSLT 1.0 中将相似元素组合在一起的方法。我不打算详细介绍这种技术。对 SO 的快速搜索揭示了几个显示 该过程的问题 。
一旦您掌握了 Muenchian 分组,请记下我在 XSLT 顶部使用的键:
<xsl:key
name="kElemByNumber"
match="Demographic_Information/*"
use="translate(name(), translate(name(), $vNums, ''), '')"/>
实际上,这就是说,“<Demographic_Information>
根据他们名字的数字部分给我所有的子元素”。因此,所有“1”元素将被组合在一起,所有“2”元素将被组合在一起,依此类推。
“他们名字的数字部分”部分是通过使用双重翻译方法完成的,这是一种只保留特定字符集的方法。在我们的例子中,我们使用该方法只为我们的键选择那些数字字符。
模板#3:修改子元素<Demographic_Information>
<xsl:template match="Demographic_Information/*">
<Person subject="{position()}">
<xsl:apply-templates
select="key('kElemByNumber', position())"
mode="children">
<xsl:sort select="name()"/>
</xsl:apply-templates>
</Person>
</xsl:template>
注意这个模板是从前一个模板执行的;因此,它是 Muenchian Grouping 序列的一部分。从策略上讲,这意味着该模板将仅针对其数字部分唯一的节点运行(即,它将针对第一个“1”元素、第一个“2”元素等)运行。
在找到 的任何子元素后<Demographic_Information>
,将指示处理器创建一个新<Person>
元素并为其赋予一个subject
属性,该属性具有工作树中当前位置的值(在这种情况下,它是 的所有子元素<Demographic_Information>
)。然后,我们将模板应用于匹配正确条件的键中的所有元素(记住,由 的子元素组成<Demographic_Information>
),并在处理它们时按名称对它们进行排序。
注意mode="children"
我们分配给这个<xsl:apply-templates>
调用的属性。这在我们当前的设置中是必要的:如果我们没有它,那么我们将创建一个无限循环(一个匹配子级的模板<Demographic_Children>
被告知将模板应用于<Demographic_Information>
匹配当前模板的所有子级,等等等等.)。
模板#4:修改<Demographic_Information>
(在“children”模式下)的子元素
<xsl:template match="Demographic_Information/*" mode="children">
<xsl:element name="{translate(name(), $vNums, '')}">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
这个相当简单:对于<Demographic_Information>
found 的每个子元素(在该children
模式下),都会创建一个新元素并赋予与当前元素相同的名称,但删除了数字部分。最后,通过将模板应用于当前节点剩下的任何子节点来填充该元素的内容(在这种情况下,只剩下text()
节点;因此,<xsl:apply-templates>
使用处理器的默认模板,输出文本)。这产生了我们的最终输出。
即使有了这个解释,这里还有很多事情要做。让我知道我是否可以提供更清晰的信息。