我们如何将同一层次结构级别的重复相关项转换为嵌套良好的元素?
我有类似这样的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>
...
对原始问题示例不充分道歉。
答案 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) > 1][1]),6) <
substring( local-name(), 6))
and substring( local-name(), 6) > 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) > 1][1]),6) <
substring( local-name(), 6))][1])]
[substring( local-name( preceding-sibling::*
[substring( local-name(), 6) > 1][1]),6) <
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) > 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) > 1]"
use="
generate-id( (.|preceding-sibling::*)
[starts-with(local-name(),'Field')]
[substring( local-name(), 6) > 1]
[
(position() = 1)
or
( substring( local-name(), 6)
<
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) > 1]
[
(position() = 1)
or
( substring( local-name(), 6)
<
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) > 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>