【问题标题】:Need to parse out Numbered Tags data and group the data appropriately using XSLT需要解析出编号标签数据并使用 XSLT 对数据进行适当的分组
【发布时间】:2013-04-29 13:34:04
【问题描述】:

对于我之前发布的场景,我得到了很好的答案,但该解决方案不起作用(或者至少我无法让它适用于完整的 XML 文件)。

我需要一些东西来保持我当前的 XSLT 工作得很好,并添加能力来获取这些非常缩进的信息。

 <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>

我需要人口统计信息的输出像

<Person subject="1">
    <Age>33</Age>
    <Gender>M</Gender>
    <Race>W</Race>
</Person>
<Person subject="2">
    <Age>66</Age>
    <Gender>F</Gender>
    <Race>A</Race>
</Person>

此外,如果还对代码中发生的事情进行了一些解释,则更容易学习。我试图逐步浏览提供的最后一个版本,但在某些时候我迷路了,真的不知道它在做什么。

我一如既往地感谢您的帮助,并向提供最佳答案的发帖人表示感谢。

【问题讨论】:

  • 提供指向您所指的上一个场景的链接会很有帮助。

标签: xml xslt foreach tags


【解决方案1】:

如果您要分组的元素的名称事先已知,您可以使用其中一个编号的元素作为 for-each 循环或模板的“锚”,并轻松提取编号。然后您可以检索所有其他元素:

<xsl:variable name="demo-info-elements">
    <Age/><Gender/><Race/>
</xsl:variable>

<xsl:template match="Demographic_Information">
    <xsl:variable name="parent" select="."/>
    <xsl:for-each select="*[substring(local-name(), 1, 3)=local-name($demo-info-elements/*[1])]">
        <xsl:variable name="number" select="substring(local-name(), 4)"/>
        <Person name="{$number}">
            <xsl:for-each select="$demo-info-elements/*">
                <xsl:call-template name="ungrouped-demographic-element">
                    <xsl:with-param name="name" select="."/>
                    <xsl:with-param name="number" select="$number"/>
                    <xsl:with-param name="parent" select="$parent"/>
                </xsl:call-template>
            </xsl:for-each>
        </Person>
    </xsl:for-each>
</xsl:template>

<xsl:template name="ungrouped-demographic-element">
    <xsl:param name="name"/><xsl:param name="number"/><xsl:param name="parent"/>
    <xsl:copy><xsl:value-of select="$parent/*[local-name()=concat(local-name($name), $number)]"/></xsl:copy>
</xsl:template>

如果事先知道元素,则转换会比较棘手。您将需要使用 xsl:key 按尾随数字索引元素。

【讨论】:

  • 当我运行它时,它会给出错误“要在路径表达式中使用结果树片段,首先使用 msxsl:node-set() 函数将其转换为节点集。”在&lt;xsl:for-each select="*[substring(local-name(), 1, 3)=local-name($demo-info-elements/*[1])]"&gt; 线上。任何想法。
  • 所以您使用的是 XSLT-1.0。按照错误消息中的说明执行操作:msxsl:node-set($demo-info-elements)/*[1]。您也可以展开循环,而不是使用 $demo-info-elements 作为元素列表。
【解决方案2】:

跟进我之前的回答 (XSLT For-Each loop to get data from enumerated tags?),这个需要简单的修改。话虽如此,既然你说你在某些时候迷路了,我会在最后添加一个解释。

当这个 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 数据”的实例是 cmets [从而确保正确的 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>

说明:

让我们一步一步来看看这个&lt;xsl:template&gt;声明。

模板 #1:The Identity Template

<xsl:template match="@*|node()">
  <xsl:copy>
    <xsl:apply-templates select="@*|node()" />
  </xsl:copy>
</xsl:template> 

这个模板(顺便说一下,这是这个答案和我之前给你的答案之间的唯一变化),有一个工作:复制所有节点(元素、文本节点、cmets 和处理指令)和所有属性源 XML 到结果 XML。因此,不管怎样,这个模板创建了原始 XML 的精确副本。用其他模板覆盖这个模板是 XSLT 中最基本的设计模式之一。

在我们的例子中,这个模板确保我们不直接匹配的任何节点/属性(例如&lt;Demographic_Information&gt;或其子级的所有节点/属性部分)将按原样复制到结果文档。

模板#2:修改&lt;Demographic_Information&gt;

<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>

这个模板(匹配所有&lt;Demographic_Information&gt; 元素)使用了一种称为Muenchian Grouping 的技术,这是一种在XSLT 1.0 中将相似元素组合在一起的方法。我不打算详细介绍这种技术。快速搜索 SO 会发现 several questions that show the procedure

一旦您掌握了 Muenchian Grouping,请记下我在 XSLT 顶部使用的键:

<xsl:key
  name="kElemByNumber"
  match="Demographic_Information/*"
  use="translate(name(), translate(name(), $vNums, ''), '')"/>

实际上,这表示“根据名称的数字部分将&lt;Demographic_Information&gt; 的所有子元素给我”。因此,所有“1”元素将被组合在一起,所有“2”元素将被组合在一起,依此类推。

“他们名字的数字部分”部分是通过使用Double Translate Method 完成的,这是一种只保留特定字符集的方法。在我们的例子中,我们使用该方法只为我们的键选择那些数字字符。

模板#3:修改&lt;Demographic_Information&gt;的子元素

<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”元素等)运行。

在找到&lt;Demographic_Information&gt; 的任何子元素后,会指示处理器创建一个新的&lt;Person&gt; 元素并为其赋予subject 属性,该属性具有工作树中当前位置的值(在本例中为, 是&lt;Demographic_Information&gt;) 的所有子代。然后,我们将模板应用于键中所有符合正确条件的元素(请记住,这些元素由&lt;Demographic_Information&gt; 的子元素组成),并在处理它们时按名称对其进行排序。

注意我们分配给这个&lt;xsl:apply-templates&gt; 调用的mode="children" 属性。这在我们当前的设置中是必要的:如果我们没有它,那么我们将创建一个无限循环(与 &lt;Demographic_Children&gt; 的子代匹配的模板被告知将模板应用于与当前模板匹配的 &lt;Demographic_Information&gt; 的所有子代等等等等)。

模板#4:修改&lt;Demographic_Information&gt;的子元素(在“子”模式下)

<xsl:template match="Demographic_Information/*" mode="children">
  <xsl:element name="{translate(name(), $vNums, '')}">
    <xsl:apply-templates/>
  </xsl:element>
</xsl:template>

这个相当简单:对于找到的每个&lt;Demographic_Information&gt; 的子元素(在children 模式下),都会创建一个新元素并赋予与当前元素相同的名称,但删除了数字部分。最后,通过将模板应用于当前节点的任何剩余子节点来填充此元素的内容(在这种情况下,只剩下text() 节点;因此,&lt;xsl:apply-templates&gt; 使用处理器的默认模板,它输出文本) .这产生了我们的最终输出。


即使有这样的解释,这里还有很多事情要做。让我知道我是否可以提供更清晰的说明。

【讨论】:

  • 很棒的解释,非常感谢。多亏了你,我让它运行起来了,但它把所有子节点信息都放到了一个 Person 节点中。 &lt;Person subject="1"&gt;&lt;Age&gt;22&lt;/Age&gt;&lt;Age&gt;33&lt;/Age&gt;&lt;Gender&gt;F&lt;/Gender&gt;&lt;Gender&gt;M&lt;/Gender&gt;&lt;Race&gt;A&lt;/Race&gt;&lt;Race&gt;W&lt;/Race&gt;&lt;/Person&gt;。有什么想法吗?
  • 我再次未能提供完整信息。 Demographic_Information 可以在提供的 XML 中重复。这是在工作中扔一个小扳手。
  • @Mac-Gon - 我不完全理解。我的解决方案适用于您提供给我的 XML;我知道这并不容易,但您能否提出一个新问题并提供 XML 尽可能接近您的来源?告诉我,我会提供帮助。
猜你喜欢
  • 2014-04-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-30
  • 1970-01-01
相关资源
最近更新 更多