XML LINQ 转换

3
尝试使用LINQ将扁平的XML转换为层次结构的XML......有人愿意尝试吗?我真的很困惑 :(
我有一个带有以下内容的XML文档:
<DriverDetails>
    <Index>0</Index>
    <DriverTitle>Mr</DriverTitle>
    <DriverFirstName>Something</DriverFirstName>
    <DriverSurname>SOMETHING</DriverSurname>
    <DriverTelephone>01234 123123</DriverTelephone>
    <DriverMobile />
    <DriverEmail>something@something.co.uk</DriverEmail>
    <Index>1</Index>
    <DriverTitle>Mr</DriverTitle>
    <DriverFirstName>Something</DriverFirstName>
    <DriverSurname>Something</DriverSurname>
    <DriverTelephone>01234 123456</DriverTelephone>
    <DriverMobile />
    <DriverEmail>something@something.co.uk</DriverEmail>
</DriverDetails>

我试图将这个转换为XML格式:

索引是新数据集的标识符

<driverContacts>
    <addressType>Something</addressType>
    <surname>something</surname>
    <forename>something</forename>
    <title>Mr</title>
    <phoneNo />
    <mobileNo />
    <eMail>something@something.co.uk</eMail>
    <fax />
</driverContacts>

<driverContacts>
    <addressType>Something</addressType>
    <surname>something</surname>
    <forename>something</forename>
    <title>Mr</title>
    <phoneNo />
    <mobileNo />
    <eMail>something@something.co.uk</eMail>
    <fax />
</driverContacts>

到目前为止我已经得到了这个:
XElement driverContacts = 
          new XElement("driverContacts",
                from driverDetails in loaded.Descendants("DriverDetails")
                select new XElement("driverContacts",
                            new XElement("surname",
                            driverDetails.Element("surname").Value)));

4
写出生成第一个XML文档的代码的人应该被解雇。 - Jon
1
你的第一次Linq尝试是什么?让我们看看你已经做到了什么。另外,我不太确定你将如何从集合中获取<fax/>属性,因为它在原始代码中不存在!(而且地址类型似乎也是硬编码的) - jim tollan
传入的代码来自外部系统,因此无法控制输入。 - user203538
抱歉,我的意思是,您是如何得出传真和地址类型属性的,因为它们不是原始输入代码的一部分。 - jim tollan
XElement driverContacts = new XElement("driverContacts", from driverDetails in loaded.Descendants("DriverDetails") select new XElement("driverContacts", new XElement("surname", driverDetails.Element("surname").Value) ));(注:此为程序代码,无法进行翻译) - user203538
显示剩余2条评论
3个回答

3

请注意,我永远不会使用这段代码,因为它多次解析XML,但是你要求使用XLINQ,那就用XLINQ吧。

var res = (from p in doc.Descendants("DriverDetails").Elements("Index")
            select new XElement("driverContacts",
                                new XElement("surname", p.ElementsAfterSelf("DriverSurname").First().Value),
                                new XElement("forename", p.ElementsAfterSelf("DriverFirstName").First().Value)
                ));

或者更好的方法是,如果您不确定所有字段都有值:

Func<XElement, string> getValue = p => p != null ? p.Value : String.Empty;
Func<XElement, string, string> getSibling = (p, q) => getValue(p.ElementsAfterSelf().TakeWhile(r => r.Name != "Index").FirstOrDefault(r => r.Name == q));

var res = from p in doc.Descendants("DriverDetails").Elements("Index")
            select new XElement("driverContacts",
                new XElement("surname", getSibling(p, "DriverFirstName")),
                new XElement("forename", getSibling(p, "DriverSurname"))
            );

+1 我不玩得够 - "ElementsAfterSelf" 部分是渲染查询而不是循环所需的技巧。然而,断言“我永远不会使用”并不是很有帮助,因为问题不在解决方案上,而在于源数据以及你如何处理它? - Murph
@Murph 我会像Murph建议的那样做...哦等等...你就是Murph :-) 我会创建一个类来存储数据(或者我可以使用Dictionary<string,string>),循环每个元素,每次找到一个索引时,我都会基于类中累积的数据创建一个新元素,并重置该类。 - xanatos
我会即兴构建元素 - 目前没有中间类 - 除非性能成为问题。 - Murph
@Murph 如果你直接构建它,你会失去顺序,除非你的顺序与“源”顺序相同。通过进行“完整”的读取和“完整”的写入,您可以重新排序元素。 - xanatos
@xantos - 哎呀!你说得完全正确!不按照你的方式做的唯一理由是,如果你决定只做最少的工作将输入转换为稍微合理的东西(基本上在每个索引处进行包装),然后使用linq在XML结果上推导输出XML。 - Murph

1

在XSLT中,这个操作相当简单。只需将以下内容添加到标识转换中:

<xsl:template match="DriverDetails">
   <xsl:apply-templates select="Index"/>
</xsl:template>

<xsl:template match="Index">
   <driverContacts>
      <addressType>something</addressType>
      <surname><xsl:value-of select="following-sibling::DriverName[1]"/></surname>
      <forename><xsl:value-of select="following-sibling::DriverFirstName[1]"/></forename>
      <!-- repeat for remaining desired elements -->
   </driverContacts>
</xsl:template>

这将从每个Index元素创建一个driverContacts元素,并从以下DriverNameDriverFirstName等元素填充其子元素。

请注意,如果其中任何一个元素缺失,XPath将沿着following-sibling轴搜索下一个Index元素,直到找到一个。只有在源XML的结构一致时才应使用此方法。

如果不是这样,你仍然可以做到这一点,但它更加棘手。你基本上必须像这样做:

<xsl:variable name="nextIndex" select="following-sibling::Index[1]"/>
<xsl:variable name="elements" select="following-sibling::*[not($nextIndex) or . = $nextIndex/preceding-sibling::*]"/>

该代码限制$elements仅包含那些在下一个Index元素之前(或所有后续元素,如果没有下一个Index元素)的元素。然后您可以像这样设置结果元素:

<surname><xsl:value-of select="$elements[name() = 'DriverSurname'][1]"/></surname>

1

在这里,你有点陷入困境 - 至少在以适当的基于集合的方式处理时,必要的层次结构信息没有编码在输入数据中。

你所拥有的是一个用XML样式标记编码的顺序文件结构,这反过来表明,除非存在显著的性能问题,否则最好只是通过循环滚动。

它有点笨重,因为循环将包含一个开关或类似的东西,当你遇到索引元素时触发创建新的driverContacts,并且否则从源元素映射到所需的等效目标元素(在当前新联系人中)。

这不会很优雅 - 但它易于实现,相对容易理解,并且可以解决问题 - 即将结构应用于当前未结构化的数据,以便充分利用你所拥有的工具。


感谢您的所有评论,我会看看自己的情况 :) - user203538

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接