【问题标题】:XSLT - converting list into nested elementsXSLT - 将列表转换为嵌套元素
【发布时间】:2012-06-28 16:10:11
【问题描述】:

我们如何将同一层次结构中重复的相关项转换为嵌套良好的元素?

我有类似这样的 XML 作为来自表单引擎的复杂 SQL 查询的输出,收集信息,如简历,其中某些字段(如以前的工作)可以重复。但是,它们不会以友好的方式输出:

<People>
  <Person>
    <Field1>Fred</Field1>
    <Field2>Head Chef</Field2>
    <Field3>The Ritz Hotel</Field3>
    <Field2>Bottle Washer</Field2>
    <Field3>Dog and Duck</Field3>
  </Person>
  <Person>
    <Field1>Mary</Field1>
    <Field2>Chief Executive</Field2>
    <Field3>BigCorp</Field3>
    <Field2>Manager</Field2>
    <Field3>LargeCorp</Field3>
    <Field2>Mail Clerk</Field2>
    <Field3>SmallCorp</Field3>
  </Person>
</People>

我需要做的是将其转换为其他 XML,将重复的 Field2 和 Field3 集合分离为单独的父元素,就像这样......

<People>
  <Person>
    <Name>Fred</Name>
    <Jobs>
      <Job>
        <JobTitle>Head Chef</JobTitle>
        <Employer>Ritz Hotel</Employer>
      </Job>
      <Job>
        <JobTitle>Bottle Washer</JobTitle>
        <Employer>Dog and Duck</Employer>
      </Job>
    </Jobs>
  </Person>

  <Person>
    <Name>Mary</Name>
    <Jobs>
      <Job>
        <JobTitle>Chief Executive</JobTitle>
        <Employer>BigCorp</Employer>
      </Job>
      <Job>
        <JobTitle>Manager</JobTitle>
        <Employer>LargeCorp</Employer>
      </Job>
      <Job>
        <JobTitle>Mail Clerk</JobTitle>
        <Employer>SmallCorp</Employer>
      </Job>
    </Jobs>
  </Person>
</People>

这些字段将始终按顺序排列 - 即 Field1 然后是 Field2,然后是可选的另一个 Field1 和 Field2,可选地重复ad nauseum。 我有代码可以为单次出现的字段(当然有很多)做简单的事情,但是我可能需要像这样的几个重复的字段集(教育历史、就业历史等)整理。不要担心从哪里获得翻译的名称(例如从“Field3”到“Employer”),因为我可以解决这个问题,我想。 如果它有助于解析,我可以调整输入元素的名称 - 例如附加序列序号以对相关元素进行分组,例如:

...
    <Field2_Seq1>Head Chef</Field2_Seq1>
    <Field3_Seq1>The Ritz Hotel</Field3_Seq1>
    <Field2_Seq2>Bottle Washer</Field2_Seq2>
    <Field3_Seq2>Dog and Duck</Field3_Seq2>
....

我为此苦苦挣扎 - 我是 XSL 新手,并且承受着让它工作的压力(我们什么时候不工作?),因此非常感谢任何帮助。
谢谢,基思。

编辑:实际上,重复集中有更多字段(例如 Field4、Field5...FieldN)并且它们是可选的,因此我们可能在序列中省略了项目:那些存在的将按升序排列,但不会必然顺序。新 Job 元素的触发器是数字倒退(例如从 Field6 到 Field3)。 所以我们需要应对,例如

...
<Field1>John</Field1>
<Field4>Something</Field4>
<Field6>Missed some more</Field6>
<Field2>New Job</Field2>
...

产生类似(部分)的东西

...
<Person>
  <Name>John</Name>
  <Job>
    <SomeElement>Something</SomeElement>
    <YetAnother>Missed some more</YetAnother>
  </Job>
  <Job>
    <JobTitle>New Job</JobTitle>
...

对原始问题示例不充分表示歉意。

【问题讨论】:

  • 那么您希望如何处理Field4Field5Field5?应该为他们每个人生成什么?
  • 您可以使用 XSLT 2.0 还是仅限于 XSLT 1.0?
  • 你能选择一个答案吗?

标签: xslt


【解决方案1】:

这个 XSLT 1.0 样式表应该可以解决问题...

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>

<xsl:template match="/">
 <People>
   <xsl:apply-templates select="People/Person"/>
 </People>
</xsl:template>

<xsl:template match="Person">
 <xsl:copy>
  <Name><xsl:value-of select="Field1"/></Name>
  <jobs>
   <xsl:for-each select="*[not( substring( local-name( preceding-sibling::*
                     [substring( local-name(), 6) &gt; 1][1]),6) &lt;
                    substring( local-name(), 6))
                    and substring( local-name(), 6) &gt; 1]">
    <job>
      <xsl:variable name="id-of-head" select="generate-id()" />
      <xsl:apply-templates select="
       .|(following-sibling::*[
         $id-of-head = generate-id(
      preceding-sibling::*[not( substring( local-name( preceding-sibling::*
                     [substring( local-name(), 6) &gt; 1][1]),6) &lt;
                    substring( local-name(), 6))][1])]
      [substring( local-name( preceding-sibling::*
                     [substring( local-name(), 6) &gt; 1][1]),6) &lt;
                    substring( local-name(), 6)])" />
    </job>  
   </xsl:for-each> 
  </jobs>
 </xsl:copy>
</xsl:template>

<xsl:template match="Field2" priority="1">
  <JobTitle><xsl:value-of select="." /></JobTitle> 
</xsl:template>

<xsl:template match="Field3" priority="1">
  <Employer><xsl:value-of select="." /></Employer> 
</xsl:template>

<xsl:template match="*[substring( local-name(), 6) &gt; 1]">
  <xsl:copy>
    <xsl:value-of select="." />
    <xsl:comment>You haven't defined a translation for this field yet.
To do so, just add another template.
See templates for Fields 2 and 3 for example.</xsl:comment>
  </xsl:copy> 
</xsl:template>

</xsl:stylesheet>

应该有聪明的方法来做等效的键。让我们知道您是否可以使用 XSLT 2.0,因为这将使解决方案更小更简单

更新

我将此样式表转换为使用键的形式,它可能更具可读性。我不确定它是否符合 muenchian 或准 muenchian 的条件?这是...

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>

<xsl:key name="job"
  match="
  Person/*
    [starts-with(local-name(),'Field')]
    [substring( local-name(), 6) &gt; 1]"
  use="
  generate-id( (.|preceding-sibling::*)
    [starts-with(local-name(),'Field')]
    [substring( local-name(), 6) &gt; 1]
    [
     (position() = 1)
      or
     ( substring( local-name(), 6)
        &lt;
     substring(
       local-name( preceding-sibling::*
       [starts-with(local-name(),'Field')]
       [1]
           ), 6
          )
     )
    ]
    [last()])"
  />

<xsl:template match="/">
 <People>
   <xsl:apply-templates select="People/Person"/>
 </People>
</xsl:template>

<xsl:template match="Person">
 <xsl:copy>
  <Name><xsl:value-of select="Field1"/></Name>
  <jobs>
   <xsl:for-each select="
    *
    [starts-with(local-name(),'Field')]
    [substring( local-name(), 6) &gt; 1]
    [
     (position() = 1)
        or
     ( substring( local-name(), 6)
        &lt;
       substring(
       local-name( preceding-sibling::*
         [starts-with(local-name(),'Field')]
         [1]
             ), 6
          )
      )
     ]">
    <job>
    <xsl:apply-templates select="key('job',generate-id(.))" />
  </job>
   </xsl:for-each>
  </jobs>
 </xsl:copy>
</xsl:template>

<xsl:template match="Field2" priority="1">
  <JobTitle><xsl:value-of select="." /></JobTitle>
</xsl:template>

<xsl:template match="Field3" priority="1">
  <Employer><xsl:value-of select="." /></Employer>
</xsl:template>

<xsl:template match="*[substring( local-name(), 6) &gt; 1]">
  <xsl:copy>
    <xsl:value-of select="." />
    <xsl:comment>You haven't defined a translation for this field yet.
To do so, just add another template.
See templates for Fields 2 and 3 for example.</xsl:comment>
  </xsl:copy>
</xsl:template>

</xsl:stylesheet>

【讨论】:

  • 哇,这看起来棒极了 - 为延迟回复道歉,我昨天不在办公室并离线。我会试试这个,让你知道。 XSLT 肯定比我想象的要多得多(我太天真了)。非常感谢您为此付出的努力,非常感谢。我稍后再评论。
  • 效果很好——我现在需要努力理解它,所以我从中学习。非常感谢 Sean 的帮助,也感谢 actionhrimp!
【解决方案2】:

如果你知道顺序,你可以在序列中的第一个匹配,然后使用following-sibling获取后续元素(如果不确定它们是否会出现,请检查以下元素的名称与否)。

下面的 XSLT 会将您的第一个 XML 转换为您的第一个输出 XML。尝试将 following-sibling::*[1] 更改为 [2] 等以获得更多兄弟姐妹:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:template match="Person">
        <Person>
            <Name><xsl:value-of select="Field1"/></Name>
            <Jobs>
                <xsl:apply-templates select="Field2"/>
            </Jobs>
        </Person>
    </xsl:template>
    <xsl:template match="Field2">
        <Job>
            <JobTitle><xsl:value-of select="."/></JobTitle>
            <xsl:if test="./following-sibling::*[1][name() = 'Field3']">
                <Employer><xsl:value-of select="./following-sibling::*[1]"/></Employer>
            </xsl:if>
        </Job>
    </xsl:template>
</xsl:stylesheet>

【讨论】:

  • 谢谢 - 这看起来可以解决问题。我会用真实的东西做一些实验,以确保我理解如何让它充分发挥作用。我可以控制顺序(使用 SQL),因此顺序将始终按顺序排列,尽管字段编号可能很稀疏(即并非所有表单字段都是必填字段)。我会在标记为“正确答案”之前试一试,但到目前为止看起来真的很好 - 非常感谢。
  • 承认我的无知,我无法将其扩展到真实情况。因此,对于原始示例过于简化表示歉意,如果我有超过 2 个字段,是否有更通用的解决方案。由于它们是可选的,是否有允许省略 Field2 到 FieldN 的任何解决方案?新的“工作”元素的触发器是字段编号小于上一个。也就是说,如果我们有 Field1、Field 4、Field 6、Field5,我们会得到一个包含 Fields 4 和 6 的 Job,然后另一个 Job 从 Field5 开始(因为 5 小于 6)?很抱歉打扰您,感谢您的帮助。
  • 条件肯定是'5小于6'还是4总是映射到一个标签,5映射到另一个,6映射到另一个等等,你只是不知道标签是否会是否在场?
  • 字段编号与表单编辑器的问题内部 ID 相关,我可以确保它们在组内按升序排列,或者每个组都有不同的后缀/前缀,例如字段N_GroupX。 XSLT 需要具有硬编码的名称,因此将 Field1 输出为 Name 并将 Field2 输出为 JobTitle 等,因此每个 FieldN 标记都需要在 XSLT 中进行明确的相应处理才能输出为适当的实际名称。实际上,有些字段是强制性的,但有些不是,因此一般情况下都是可选的。不确定我是否理解并回答了您的问题。
猜你喜欢
  • 2014-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-11
相关资源
最近更新 更多