展平 XML 文档

Posted

技术标签:

【中文标题】展平 XML 文档【英文标题】:Flattening an XML document 【发布时间】:2012-03-02 04:25:00 【问题描述】:

我目前正在尝试在 C# 中展平深度结构化的 XML 文档,以便将元素的每个值都转换为属性。

XML结构如下:

<members>
    <member xmlns="mynamespace" id="1" status="1">
        <sensitiveData>
            <notes/>
            <url>someurl</url>
            <altUrl/>
            <date1>somedate</date1>
            <date2>someotherdate</date2>
            <description>some description</description>
            <tags/>
            <category>some category</category>
        </sensitiveData>
        <contacts>
            <contact contactId="1">
                <contactPerson>some contact person</contactPerson>
                <phone/>
                <mobile>mobile number</mobile>
                <email>some@email.com</email>
            </contact>
        </contacts>
    </member>
</members>

我希望它看起来像这样:

<members>
    <member xmlns="mynamespace" id="1" status="1" notes="" url="someurl" altUrl="" date1="somedate" date2="someotherdate" description="some description" tags="" category="some category" contactId="1" contactPerson="some contact person" phone="" mobile="mobile number" email="some@email.com" />
</members>

我可以只解析元素名称及其属性,但由于这个 XML 来自我无法控制的 web 服务,我必须创建某种动态解析器​​来将其展平,因为结构 可以 在某个时候改变。

值得注意的是,XML 结构来自 web 服务的 XElement。

以前有没有人尝试过这样做,并且可以帮助分享一下方法? :-) 将不胜感激!

非常感谢。

一切顺利,

【问题讨论】:

您显示的 XML 无效(/kontakter 没有开始标签?)...至于您的问题,没有一般答案,因为这完全取决于您要应用的规则(例如如果有多个contact 等会发生什么)。我的问题是:你为什么要“扁平化”这个 XML? 嗨 Yahia,谢谢 - 这只是一个错字 :) 我需要将其展平才能将其导入 CMS。 那么恕我直言,XSLT 是可行的方法,因为它允许在部署后更改转换规则... 【参考方案1】:

试试这个:

var doc = XDocument.Parse(@"<members>...</members>");

var result = new XDocument(
    new XElement(doc.Root.Name,
        from x in doc.Root.Elements()
        select new XElement(x.Name,
            from y in x.Descendants()
            where !y.HasElements
            select new XAttribute(y.Name.LocalName, y.Value))));

结果:

<members>
  <member notes="" url="someurl" altUrl="" date1="somedate" date2="someotherdate" description="some description" tags="" category="some category" contactPerson="some contact person" phone="" mobile="mobile number" email="some@email.com" xmlns="mynamespace" />
</members>

【讨论】:

【参考方案2】:

您可以使用这个 XSLT 1.0 样式表。您可能想要修改它处理多个 &lt;contact&gt; 元素的方式。

输入 XML

<members>
  <member xmlns="mynamespace" id="1" status="1">
    <sensitiveData>
      <notes/>
      <url>someurl</url>
      <altUrl/>
      <date1>somedate</date1>
      <date2>someotherdate</date2>
      <description>some description</description>
      <tags/>
      <category>some category</category>
    </sensitiveData>
    <contacts>
      <contact contactId="1">
        <contactPerson>some contact person</contactPerson>
        <phone/>
        <mobile>mobile number</mobile>
        <email>some@email.com</email>
      </contact>
      <contact contactId="2">
        <contactPerson>second contact person</contactPerson>
        <phone/>
        <mobile>second mobile number</mobile>
        <email>second some@email.com</email>
      </contact>
    </contacts>
  </member>
</members>

XSLT 1.0

<xsl:stylesheet version="1.0" xmlns:my="mynamespace" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes"/>
  <xsl:strip-space elements="*"/>

  <xsl:template match="node()|@*">
    <xsl:apply-templates select="node()|@*"/>
  </xsl:template>

  <xsl:template match="members|my:member">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>  
    </xsl:copy>
  </xsl:template>

  <xsl:template match="node()[text()][ancestor::my:member]|@*[ancestor::my:member]">
    <xsl:variable name="vContact">
      <xsl:if test="ancestor-or-self::my:contact">
        <xsl:value-of select="count(ancestor-or-self::my:contact/preceding-sibling::my:contact) + 1"/>
      </xsl:if>
    </xsl:variable>
    <xsl:attribute name="name()$vContact">
      <xsl:value-of select="."/>
    </xsl:attribute>
  </xsl:template>

</xsl:stylesheet>

XML 输出

<members>
   <member xmlns="mynamespace" id="1" status="1" url="someurl" date1="somedate"
           date2="someotherdate"
           description="some description"
           category="some category"
           contactId1="1"
           contactPerson1="some contact person"
           mobile1="mobile number"
           email1="some@email.com"
           contactId2="2"
           contactPerson2="second contact person"
           mobile2="second mobile number"
           email2="second some@email.com"/>
</members>

【讨论】:

【参考方案3】:

我认为 dtb 答案是最好的方法。但是,您必须注意一个重要问题。尝试添加另一个联系信息,dtb 代码会崩溃。因为一个成员可以有多个联系信息,但不能有重复的属性。为了解决这个问题,我更新了代码以仅选择不同的属性。为此,我实施了IEqualityComparer&lt;XAttribute&gt;。 更新后的 linq 表达式如下所示

var result = new XDocument(new XElement(doc.Root.Name, 
                from x in doc.Root.Elements() 
                select new XElement(x.Name, (from y in x.Descendants() 
                                            where !y.HasElements
                                            select new XAttribute(y.Name.LocalName, y.Value)).Distinct(new XAttributeEqualityComparer())
                                            )));

如您所见,添加了一个带有自定义平等比较器重载(XAttributeEqualityComparer) 的 Distinct 调用

    class XAttributeEqualityComparer : IEqualityComparer<XAttribute>
    
        public bool Equals(XAttribute x, XAttribute y)
        
            return x.Name == y.Name; 
        

        public int GetHashCode(XAttribute obj)
        
            return obj.Name.GetHashCode(); 
        
    

【讨论】:

【参考方案4】:

您可以编写一个 XSLT 转换来将元素转换为属性。

【讨论】:

【参考方案5】:

您这样做是为了创建另一个 XML 文档,还是只是为了让您的处理更简单?如果是前者,那么当您遇到叶节点时,您只需将所有值放入映射中即可。然后,您实际上可以遍历映射中的键值对以仅使用属性重构 xml 标记。

【讨论】:

以上是关于展平 XML 文档的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Java 手动展平 Elasticsearch 嵌套的 JSON 文档?

如何在 MongoDB 中将子文档展平为根级别?

通过按键过滤数组,使用 jq 展平 JSON 文档

使用 spark 展平嵌套的 json 文档并加载到 Elasticsearch

将 JSON 文档展平为单行

在输出前拼合邮件合并的文档