Mike Pliam
Mike Pliam

Reputation: 57

How to replace specific xml node element using C#

Given the local xml file:

<products>
  <product id="p1">
    <name>Delta</name>
    <price>800</price>
    <stock>4</stock>
    <country>Denmark</country>
  </product>
  <product id="p2">
    <name>Golf</name>
    <price>1000</price>
    <stock>5</stock>
    <country>Germany</country>
  </product>
  <product id="p3">
    <name>Alfa</name>
    <price>1200</price>
    <stock>19</stock>
    <country>Germany</country>
  </product>
  <product id="p4">
    <name>Foxtrot</name>
    <price>1500</price>
    <stock>5</stock>
    <country>Australia</country>
  </product>
  <product id="p5">
    <name>Tango</name>
    <price>1225</price>
    <stock>3</stock>
    <country>Japan</country>
  </product>
</products>

I have attempted to replace the price element in product node 'p1' as follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using System;
using System.Xml.XPath;
using System.Xml.Linq;

XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(@"products.xml");

Console.WriteLine("\n\nDisplay the initial XML...");
xmlDoc.Save(Console.Out);

//Create an XmlNamespaceManager for resolving namespaces.
XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmlDoc.NameTable);
nsmgr.AddNamespace("products", "product");

// replace the node with a new one
//Select the profile node with the matching attribute value.
XmlNode product;
XmlElement root = xmlDoc.DocumentElement;
product = root.SelectSingleNode("descendant::product[id='p1']", nsmgr);

//Create a new price element.
XmlElement oldElem = xmlDoc.CreateElement("price");
oldElem.InnerText = "800";

//Create a new price element.
XmlElement newElem = xmlDoc.CreateElement("price");
newElem.InnerText = "125";

//Replace the price element.
root.ReplaceChild(newElem, root.FirstChild);
Console.WriteLine("\n\nDisplay the modified XML...");
xmlDoc.Save(Console.Out);

// save the document with the revised node
xmlDoc.Save(@"products2.xml");

Problem is that the new node (price) element is simply added to the product p1 node which when saved to disk drops all of p1. What am I doing wrong?

Upvotes: 4

Views: 15380

Answers (3)

Thomas Weller
Thomas Weller

Reputation: 59208

The main issue is in

product = root.SelectSingleNode("descendant::product[id='p1']", nsmgr);

because you're not using the variable in the following.

Next issue is in [id='p1'], because you're accessing ID like an element, but it should be an attribute instead. Use [@id='p1'] instead.

Other things:

  • one would probably update the inner text of the <price> node directly instead of replacing a whole element.
  • there's no need for a Namespace manager, since there are no namespaces in your example.
  • there's no need to create a node oldNode. That node already exists.
  • newElem is unused.

Suggested fix in your style:

using System;
using System.Xml;

namespace XmlUpdateNode
{
    class Program
    {
        static void Main()
        {
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.Load(@"products.xml");

            Console.WriteLine("\n\nDisplay the initial XML...");
            xmlDoc.Save(Console.Out);

            // replace the node with a new one
            //Select the profile node with the matching attribute value.
            var product = xmlDoc.SelectSingleNode("descendant::product[@id='p1']");

            //Create a new price element.
            XmlElement elem = xmlDoc.CreateElement("price");
            elem.InnerText = "125";

            //Replace the price element.
            product.ReplaceChild(elem, product.FirstChild.NextSibling);
            Console.WriteLine("\n\nDisplay the modified XML...");
            xmlDoc.Save(Console.Out);

            // save the document with the revised node
            xmlDoc.Save(@"products2.xml");
        }
    }
}

Even shorter by directly replacing the text inside the price element:

using System;
using System.Xml;

namespace XmlUpdateNode
{
    class Program
    {
        static void Main()
        {
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.Load(@"products.xml");

            Console.WriteLine("\n\nDisplay the initial XML...");
            xmlDoc.Save(Console.Out);

            // replace the price directly
            var product = xmlDoc.SelectSingleNode("descendant::product[@id='p1']/price");
            product.InnerText = "125";

            Console.WriteLine("\n\nDisplay the modified XML...");
            xmlDoc.Save(Console.Out);

            // save the document with the revised node
            xmlDoc.Save(@"products2.xml");
        }
    }
}

Upvotes: 2

keyboardP
keyboardP

Reputation: 69362

If I understand correctly, you want to update the value of price in p1. If so, it's straightforward with LINQ.

XDocument xmlDoc = XDocument.Load(@"products.xml");
var product = xmlDoc.Descendants("product")
                  .Where(item => item.Attribute("id").Value == "p1").FirstOrDefault();
product.Element("price").Value = "125"; //new price will be 125

If you then examine the xmlDoc variable, it'll be updated with p1 having a price of 125 so you can then save that out. Naturally, you can create generic methods using LINQ to make it easier to replace any node but the above should give an idea of one way of doing it.

Upvotes: 0

Durga Nunna
Durga Nunna

Reputation: 51

Instead of creating an element, create a node and append it to the product node you have in your code.

        XmlNode priceNode = xmlDoc.CreateNode(XmlNodeType.Element, "price", string.Empty);
        priceNode.InnerText = "125";
        product.AppendChild(priceNode);

You may repeat the above code the no. of nodes you want under the product node.

Upvotes: 0

Related Questions