4

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?

3
  • When you say "replace" the element, are you just trying to accomlish updating the value (innertext) from 800 to 125? Commented Jul 12, 2016 at 20:49
  • Please create a minimal reproducible example and remove all code that's not necessary to reproduce the problem Commented Jul 12, 2016 at 20:49
  • Can you provide us with your desired output? Commented Jul 12, 2016 at 20:50

3 Answers 3

2

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");
        }
    }
}
Sign up to request clarification or add additional context in comments.

4 Comments

I would thank you all but the moderator has warned against it. My purpose in asking this question was to develop a way to replace the elements of a longer node (36 elements) with a prepared string array. This particular target Xml file does not contain attributes, only two tiered: <patients><profile><ID>1</ID>...</profile>...</patients>
@MikePliam: you can upvote, there should not be a problem. You can also accept an answer if it solved your problem. Regarding your issue with the ID element: I can just answer what you have asked here on SO. I cannot guess what your real situation is. Above program works for the given XML. Ask a new question if your problem is not fully solved.
Not sure what 'upvote' means in this forum. In any case, you've answered the question as I posed it. Thanks.
To upvote (if it was helpful), press the triangle which points upwards. To downvote (it was not helpful or even wrong), press the triangle which points downwards. To accept, click the checkmark. All those items are left of the answers.
0

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.

Comments

0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.