【问题标题】:What is the best solution to merge multiple Xml合并多个 Xml 的最佳解决方案是什么
【发布时间】:2012-02-13 17:34:17
【问题描述】:

我在 Sql 中有一个带有 Xml 列的表。所有 Xml 文件都有相同的架构,我想将其中的一些 Xml 合并在一起。

以 X1 为例:

 <A>
     <B>
         <C id='101'>
             <D id='102'>abcd</D>
         </C>
         <C id='103'>
             <D id='104'>zxcv</D>
         </C>
     </B>
 </A>

和 X2:

 <A>
     <B>
         <C id='101'>
             <D id='102'>abcd</D>
             <D id='501'>abef</D>
         </C>
         <C id='502'>
             <D id='503'>efgh</D>
         </C>
     </B>
 </A>

X1+X2=...

 <A>
     <B>
         <C id='101'>
             <D id='102'>abcd</D>
             <D id='501'>abef</D>
         </C>
         <C id='103'>
             <D id='104'>zxcv</D>
         </C>            
         <C id='502'>
             <D id='503'>efgh</D>
         </C>
     </B>
 </A>

那么哪种选择是最好的以及如何选择:

  • Sql 中的 XQuery
  • C# XDocument 和 XPath
  • ...

【问题讨论】:

  • 你想要通过 ID 连接的元素的联合吗?
  • @ARZ,如果 X2 中的 &lt;D id='102'&gt; 具有值 12345,是否可能出现这种情况?如果是,如何合并这些文件?
  • @ARZ,Kirill Polishchuk 重申这一点,如果匹配 ids 的元素具有不同的值,那会怎样?
  • 架构是否像您发布的 XML 一样简单?或者您是否需要一些通常可以做到这一点的代码?
  • 我说首先预处理两个 xml 文件并将文本节点映射到路径。然后遍历其中一个文件,查看另一个xml的映射中是否存在crt节点路径

标签: c# sql xml xquery linq-to-xml


【解决方案1】:

我认为解决这个问题的最好方法是编写一个使用the visitor pattern 合并两个XDocuments 的类,区别在于我们总是同时访问第一个文档中的节点与第二个文档中的节点。

整体设计如下:

class XmlMerger
{
    public XDocument Merge(XDocument first, XDocument second);

    private XElement MergeElements(XElement first, XElement second);

    private XAttribute MergeAttributes(XAttribute first, XAttribute second);

    private XText MergeTexts(XText first, XText second);
}

具体的实现可能如下所示:

class XmlMerger
{
    public XDocument Merge(XDocument first, XDocument second)
    {
        return new XDocument(MergeElements(first.Root, second.Root));
    }

    private XElement MergeElements(XElement first, XElement second)
    {
        if (first == null)
            return second;

        if (second == null)
            return first;

        if (first.Name != second.Name)
            throw new InvalidOperationException();

        var firstId = (string)first.Attribute("id");
        var secondId = (string)second.Attribute("id");

        // different ids
        if (firstId != secondId)
            throw new InvalidOperationException();

        var result = new XElement(first.Name);

        var attributeNames = first.Attributes()
            .Concat(second.Attributes())
            .Select(a => a.Name)
            .Distinct();

        foreach (var attributeName in attributeNames)
            result.Add(
                MergeAttributes(
                    first.Attribute(attributeName),
                    second.Attribute(attributeName)));

        // text-only elements
        if (first.Nodes().OfType<XText>().Any() ||
            second.Nodes().OfType<XText>().Any())
        {
            var firstText = first.Nodes().OfType<XText>().FirstOrDefault();
            var secondText = second.Nodes().OfType<XText>().FirstOrDefault();

            // we're not handling mixed elements
            if (first.Nodes().Any(n => n != firstText) ||
                second.Nodes().Any(n => n != secondText))
                throw new InvalidOperationException();

            result.Add(MergeTexts(firstText, secondText));
        }
        else
        {
            var elementNames = first.Elements()
                .Concat(second.Elements())
                .Select(e => e.Name)
                .Distinct();

            foreach (var elementName in elementNames)
            {
                var ids = first.Elements(elementName)
                    .Concat(second.Elements(elementName))
                    .Select(e => (string)e.Attribute("id"))
                    .Distinct();

                foreach (var id in ids)
                {
                    XElement firstElement = first.Elements(elementName)
                        .SingleOrDefault(e => (string)e.Attribute("id") == id);
                    XElement secondElement = second.Elements(elementName)
                        .SingleOrDefault(e => (string)e.Attribute("id") == id);

                    result.Add(MergeElements(firstElement, secondElement));
                }
            }
        }

        return result;
    }

    private XAttribute MergeAttributes(XAttribute first, XAttribute second)
    {
        if (first == null)
            return second;

        if (second == null)
            return first;

        if (first.Name != second.Name)
            throw new InvalidOperationException();

        if (first.Value == second.Value)
            return new XAttribute(first);

        // can't merge attributes with different values
        throw new InvalidOperationException();
    }

    private XText MergeTexts(XText first, XText second)
    {
        if (first == null)
            return second;

        if (second == null)
            return first;

        if (first.Value == second.Value)
            return new XText(first);

        // can't merge texts with different values
        throw new InvalidOperationException();
    }
}

如果此代码遇到无法处理的问题(例如,具有相同 id 但文本不同的节点;或 cmets),则会引发异常。

【讨论】:

  • 谢谢,但这种方法仅适用于两个 XDoc,必须扩展以合并多个 XDoc。
  • 如果可以合并 2 则可以全部合并
  • @ARZ,Jodrell 是对的。只需将文件 A 和 B 合并在一起,然后将结果与文件 C 合并等。
【解决方案2】:

我会在 XQuery 中完成。它的代码少得多。下面的示例是使用纯 XQuery 1.0 完成的。使用 XQuery 3.0(因为它支持分组依据)或使用 XQuery Scripting 会更容易。

declare variable $sequence := (
   <A> 
       <B> 
           <C id='101'>
               <D id='102'>abcd</D>
           </C>
           <C id='103'>
               <D id='104'>zxcv</D>
           </C>
       </B>
   </A>
  ,
   <A> 
       <B> 
           <C id='101'>
               <D id='102'>abcd</D>
               <D id='501'>abef</D>
           </C>
           <C id='502'>
               <D id='503'>efgh</D>
           </C>
       </B>
   </A>
  );  

declare function local:merge($dsequence) {
  let $dfirst := $dsequence[1]
  let $dextended := <D cid="{$dfirst/../@id}" id="{$dfirst/@id}">{$dfirst/text()}</D>
  return
    if (count($dsequence) eq 1) then
      (: nothing to merge :)
      $dextended 
    else
      (: merging :)
      let $tomerge := local:merge(fn:subsequence($dsequence, 2)) 
      return
        if ($tomerge[@cid eq $dextended/@cid] and $tomerge[@id eq $dextended/id]) then
          $tomerge
        else
          ($tomerge, $dextended)
};

<A><B> {
  let $merged := local:merge($sequence/B/C/D)
  let $ckeys := fn:distinct-values(fn:data($merged/@cid))
  for $ckey in $ckeys
  return
  <C id="{$ckey}"> {
    for $dkey in fn:distinct-values(data($merged[@cid eq $ckey]/@id))
    let $d := ($merged[@cid eq $ckey and @id eq $dkey])[1]
    return <D id="{$d/@id}">{$d/text()}</D>
  }</C>
}
</B></A>

【讨论】:

  • 对不起,但是如何在 sql 表上运行呢? ;)
  • 我对 SQL 数据库中的 XQuery 不是很熟悉。如何从 XQuery 中访问 XML 表?是否必须从 PL/SQL 传递?它是如何工作的?
猜你喜欢
  • 1970-01-01
  • 2013-05-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多