XSLT - 将列表转换为嵌套元素

时间:2012-06-27 08:55:03

标签: xslt

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

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

<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,可选地重复广告。 我有一些代码可以为单一事件字段(其中有许多字段)做简单的事情,但是我可能需要这样的几个重复的字段集(教育历史,就业历史等)整理。不要担心从哪里获取翻译的名称(例如从“Field3”到“雇主”),我认为可以解决这个问题。 如果它有助于解析我可以调整输入元素的名称 - 例如附加序列序号以对相关元素进行分组,如:

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

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

2 个答案:

答案 0 :(得分:0)

如果您知道顺序,则可以匹配序列中的第一个,然后使用follow-sibling来获取后续元素(如果您不确定它们是否存在,则检查以下元素的名称或不)。

下面的XSLT会将您的第一个XML转换为您的第一个输出XML。尝试将以下兄弟:: * [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>

答案 1 :(得分:0)

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