1

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

我有类似这样的 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>
...

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

4

2 回答 2

0

如果您知道顺序,则可以在序列中的第一个进行匹配,然后使用 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>
于 2012-06-27T09:02:43.647 回答
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>
于 2012-06-27T15:37:58.887 回答