【问题标题】:How can I create/update an XML node that may or may not exist?如何创建/更新可能存在或不存在的 XML 节点?
【发布时间】:2012-07-05 18:59:12
【问题描述】:

是否有可用的方法(即无需我创建自己的递归方法),用于给定的 xpath(或识别分层位置的其他方法)来创建/更新 XML 节点,如果这样做,将在其中创建节点不存在?如果父节点也不存在,则需要创建它。我确实有一个包含所有可能节点的 XSD。

即 之前:

<employee>
   <name>John Smith</name>
</employee>

想这样称呼:

CoolXmlUpdateMethod("/employee/address/city", "Los Angeles");

之后:

  <employee>
       <name>John Smith</name>
       <address>
         <city>Los Angeles</city>
       </address>
    </employee>

或者甚至是一种创建节点的方法,给定一个 xpath,如果父节点不存在,它将递归地创建它们?

就应用程序而言(如果重要的话),这是采用仅包含填充节点的现有 XML 文档,并从另一个系统向其添加数据。新数据可能已经在源 XML 中填充了值,也可能没有。

这肯定不是罕见的情况吗?

【问题讨论】:

  • XPath 是一种用于 XML 文档的 查询 语言,因此它不能修改文档的结构(包括删除或创建新节点)。 XSLT 正是为编程 XML 文档转换而创建的——这个特殊的转换与 XSLT 无关。您对 XSLT 解决方案感兴趣吗?

标签: .net xml linq xpath


【解决方案1】:

好吧,我们所做的是创建一个表示 XML 的类(我们使用 XSD2Code 从 XSD 生成一个),当它被反序列化/序列化时,它可以为您做那种事情(XMLSerializer)。

【讨论】:

  • 这实际上是“B 计划”,但考虑到 XSD2Code 从我的 6MB XSD(EDI 837P 医疗索赔)生成了 312,000 行类,并且这需要在一个过程。我认为即使是序列化/反序列化也会非常缓慢......但对于所展示的示例来说,它是一个可行的解决方案 - 也许只是不适用于我的生产范围。
  • 我将把它标记为答案,因为我认为这是最好的方法,实际上我最终这样做了,但是仅为我真正需要的节点创建自己的 XML 序列化类,这只是自动化工具直接从 XSD 创建的一小部分。
【解决方案2】:

Chris Knight 的解决方案存在错误。如果你有:

<a></a>
<b>
  <a>
  </a>
</b> 

and do 
UpdateOrCreate ("<b><a>") 
it will update first node , not nested one.

这是我的功能:

/// <summary>
/// Creates nessecary parent nodes using the provided Queue, and assigns the value to the last child node.
/// </summary>
/// <param name="ele">XElement to take action on</param>
/// <param name="nodes">Queue of node names</param>
/// <param name="value">Value for last child node</param>
/// <param name="attr1Name">Optional name for an attribute, can be null</param>
/// <param name="attr1Val">Optional value for an attribute, can be null</param>
/// returns created/updated element        
public static XElement UpdateOrCreateXmlNode(XElement ele, Queue<string> nodes, string value, string attr1Name = null, string attr1Val = null)
{            
    int fullQueueCOunt = nodes.Count;
    for (int i = 0; i < fullQueueCOunt; i++)
    {
        string node = nodes.Dequeue();
        XElement firstChildMatch = ele.Elements(node).FirstOrDefault();
        if (firstChildMatch == null)
        {
            XElement newChlid = new XElement(node);
            ele.Add(newChlid);
            ele = newChlid;
        }
        else
            ele = firstChildMatch;
    }
    if (attr1Name != null && attr1Val != null)
    {
        if (ele.Attribute(attr1Name) == null)
            ele.Add(new XAttribute(attr1Name, attr1Val));
        else
            ele.Attribute(attr1Name).Value = attr1Val;
    }
    ele.Value = value;
    return ele;
}

【讨论】:

    【解决方案3】:

    我以前做过类似的事情。我正在使用 LINQ to XML。我为 XElement 创建了一个扩展方法,它采用节点名称队列和列表中最后一个节点的值。这是我做的扩展方法:

    /// <summary>
        /// Creates nessecary parent nodes using the provided Queue, and assigns the value to the last child node.
        /// </summary>
        /// <param name="ele">XElement to take action on</param>
        /// <param name="nodes">Queue of node names</param>
        /// <param name="value">Value for last child node</param>
        public static void UpdateOrCreate(this XElement ele, Queue<string> nodes, string value)
        {
            string previousNodeName = "";
            int fullQueueCOunt = nodes.Count;
            for (int i = 0; i < fullQueueCOunt; i++)
            {
                string node = nodes.Dequeue();
                if (ele.Descendants(node).FirstOrDefault() == null)
                {
                    if (!string.IsNullOrEmpty(previousNodeName))
                    {
                        ele.Element(previousNodeName).Add(new XElement(node));
                    }
                    else
                    {
                        // use main parent node if this is the first iteration
                        ele.Add(new XElement(node));
                    }
                }
                previousNodeName = node;
            }
            // assign the value of the last child element
            ele.Descendants(previousNodeName).First().Value = value;
        }
    

    这是一个示例实现:

    XElement element = XElement.Parse(
                    "<employee>" +
                       "<name>John Smith</name>" +
                    "</employee>");
                Queue<string> nodeQueue = new Queue<string>();
                nodeQueue.Enqueue("address");
                nodeQueue.Enqueue("city");
                element.UpdateOrCreate(nodeQueue, "myValue");
    

    这将采用输入 XML:

    <employee>
      <name>John Smith</name>
    </employee>
    

    并将其更改为:

    <employee>
      <name>John Smith</name>
      <address>
        <city>myValue</city>
      </address>
    </employee>
    

    如果“地址”和/或“城市”节点已经存在,这也将起作用。

    【讨论】:

      【解决方案4】:

      我自己也在为此苦苦挣扎,所以我想添加一个使用 C# 与 .Net 2.0 一起使用的答案。

      private static void addOrUpdateNode(XmlDocument xmlDoc, string xpath, string value)
      {
          XmlNode node = xmlDoc.SelectSingleNode(xpath);
          if (node == null)
          {
              //node does not exist, so create it
              string newNodeString = String.Format(
                  "<city>{0}</city>", value); //as per OP's example
              StringReader sr = new StringReader(newNodeString);
              XmlTextReader reader = new XmlTextReader(sr);
              XmlNode newNode = xmlDoc.ReadNode(reader);
              //adding to root of document, you may want to
              //navigate to a different part of the doc
              xmlDoc.AppendChild(newNode);
          }
          else
          {
              node.Value = value;
          }
      }
      

      请原谅我把它写得很粗糙和未经测试,任何想要清理它的人都可以随时编辑。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-11-25
        • 2017-08-30
        • 1970-01-01
        • 2011-07-17
        • 2019-03-26
        • 1970-01-01
        • 2017-05-01
        相关资源
        最近更新 更多