16

I'm trying to look at the best way of changing the value of an element in XML.

<MyXmlType>
   <MyXmlElement>Value</MyXmlElement>
</MyXmlType>

What is the easiest and/or best way to change "Value" in C#?

I've looked at XMLDocument and it will cause a load of the whole XML document to memory. Could you do it safely with the XMLReader. The problem is changing the value and emitting it back seems like an interesting conundrum.

Cheers :D

Spence
  • 28,526
  • 15
  • 68
  • 103
  • Did you find your effective solution for this problem? – Amnesh Goel Dec 19 '19 at 07:48
  • 1
    I had to write an XML Reader that streamed in the XML, search for the appropriate element, then modify it and stream it back out. Essentially read in the XML and output it, with a special case when I hit my search criteria with hand crafted XML to push it back out :(. My case with memory usage prevented me from using the nicely formed classes to do it... – Spence Dec 31 '19 at 00:37

9 Answers9

39

You could use the System.Xml.Linq namespace stuff for the easiest to read code. This will load the full file into memory.

XDocument xdoc = XDocument.Load("file.xml");
var element = xdoc.Elements("MyXmlElement").Single();
element.Value = "foo";
xdoc.Save("file.xml");
Tassisto
  • 9,877
  • 28
  • 100
  • 157
Ben Robbins
  • 2,600
  • 2
  • 24
  • 26
  • XML Namespaces gave me grief, but it got me to where I needed to be. Thanks, plus linqy stuff is nice :). Based on the xml I provided your example already works. – Spence Feb 13 '09 at 03:29
  • this is great if the xml file is not too large – cyclical Jan 29 '20 at 19:10
  • System.InvalidOperationException: 'Sequence contains no elements' from this answer. – Lightsout Dec 28 '21 at 03:08
  • @bakalolo - Single() throws if there isn't EXACTLY 1 item (ie. element) in the collection (ie the Elements("MyXmlElement"). See SingleOrDefault() or FirstOrDefault() if you're dealing with lists where count <> 1 is not cause for exception in your logic. This is a basic concept that needs to be understood for effective use of Linq. – Bryan W Apr 08 '22 at 20:21
6

EDIT: didn't see your clause about XmlDocument. XmlReader does just that. You can't edit xml files with this class.

You want XmlWriter. However, in case it's still helpful, here's the code for XmlDocument.

private void changeXMLVal(string element, string value)
{
    try
    {
        string fileLoc = "PATH_TO_XML_FILE";
        XmlDocument doc = new XmlDocument();
        doc.Load(fileLoc);
        XmlNode node = doc.SelectSingleNode("/MyXmlType/" + element);
        if (node != null)
        {
            node.InnerText = value;
        }
        else
        {
            XmlNode root = doc.DocumentElement;
            XmlElement elem;
            elem = doc.CreateElement(element);
            elem.InnerText = value;
            root.AppendChild(elem);
        }
        doc.Save(fileLoc);
        doc = null;
    }
    catch (Exception)
    {
        /*
         * Possible Exceptions:
         *  System.ArgumentException
         *  System.ArgumentNullException
         *  System.InvalidOperationException
         *  System.IO.DirectoryNotFoundException
         *  System.IO.FileNotFoundException
         *  System.IO.IOException
         *  System.IO.PathTooLongException
         *  System.NotSupportedException
         *  System.Security.SecurityException
         *  System.UnauthorizedAccessException
         *  System.UriFormatException
         *  System.Xml.XmlException
         *  System.Xml.XPath.XPathException
        */
    }
}
Andrew Ensley
  • 11,611
  • 16
  • 61
  • 73
2

This uses an old file and creates a new one with updated value. It will throw an exception if it can't find the element

{
    XDocument newSettingFile =  new XDocument(settingFile);
    //Root element
    var newSetting = newSettingFile.Element("MyXmlType");
    //Update childelement with new value
    newSetting.Element("MyXmlElement").Value = "NewValue";   
    return newSettingFile;
}
jonsca
  • 10,218
  • 26
  • 54
  • 62
Haze
  • 21
  • 1
2

You could use an XmlReader to read into a class that pumps the data back out through an XmlWriter and scan for the element between the read/write, changing the value as necessary.

Honestly, I'm a little surprised that your XML file is so huge you are worried about memory consumption... not saying it's never an issue. Without more info, I can't say your hypothetical XML file isn't 50gb, but in many cases loading files that seem large into memory long enough to manipulate isn't quite as big a deal as you might think.

Rex M
  • 142,167
  • 33
  • 283
  • 313
2

Have you thought about using Linq to XML? (if you are using .Net 3.0+)

public static XElement ChangeValue(string xmlSnippet, 
    string nodeName,
    string newValue)
{
    XElement snippet = XElement.Parse(xmlSnippet);
    if (snippet != null)
    {
        snippet.Element(nodeName).Value = newValue;
    }
    return snippet;
}

I am guessing XElement will perform better than XmlDocument (althought not sure), the base object for XElement is XObject, and yes, it will have to load the whole document.

Vin
  • 6,115
  • 4
  • 41
  • 55
2

Using a forward-only reader is definitely going to be the most efficient approach, in which case a XmlReader derivation seems appropriate, to be sure, though it's still more work than using DOM approach that loads the entire file at once.

XmlReader is supposedly an improvement over the SAX XML parser API that originated in the Java world, but which has become a de facto standard in the industry (outside of Microsoft).

If you just want to get the job done quickly, the XmlTextReader exists for that purpose (in .NET).

If you want to learn a de facto standard that is stable (and also available in many programming languages) and which will force you to code very efficiently and elegantly, but which is also extremely flexible, then look into SAX. However, do not bother with SAX itself unless you're going to be creating highly esoteric XML parsers. There are plenty of parsers out there that use SAX under the covers.

Please check out my response about SAX here for a list of SAX resources and a really creative .NET XML parser idea that uses XmlTextReader as its foundation: SAX vs XmlTextReader - SAX in C#

Community
  • 1
  • 1
1

I ran some tests on a document that was 10.6 K. The parsing the XmlDocument always comes out faster than the Linq query, by about 50%.

       var stopwatch2 = Stopwatch.StartNew();
        XmlDocument xd = new XmlDocument();
        xd.LoadXml(instanceXML);
        XmlNode node = xd.SelectSingleNode("//figures/figure[@id='" + stepId + "']/properties/property[@name='" + fieldData + "']");
            node.InnerXml = "<![CDATA[ " + valData + " ]]>";  
        stopwatch2.Stop();
        var xmlDocTicks = stopwatch2.ElapsedTicks;

        Stopwatch stopwatch1 = Stopwatch.StartNew(); 
        XDocument doc = XDocument.Parse(instanceXML);
        XElement prop =
        (from el in doc.Descendants("figure")
         where (string)el.Attribute("id") == stepId
            select el).FirstOrDefault();
        prop.Value = valData;
        stopwatch1.Stop();
        var linqTicks = stopwatch1.ElapsedTicks;

The results are as follows (xmlDocTicks,linqTicks):

  • run1:(1258,1581)
  • run2:(2667,3463)
  • run3:(1416,2626)
  • run4:(1231,2383)
  • avg: (1643,2513)
ItsAllABadJoke
  • 129
  • 2
  • 4
  • for sure it does, but the application of either choice certainly depends on the size of the hammer needed. – cyclical Jan 29 '20 at 19:10
1
using System;
using System.Xml;
using System.Linq;
using System.Xml.Linq;

namespace ReandAndWriteXML
{
    class MainClass
    {
        public static void Main (string[] args)
        {
            XDocument xdoc = XDocument.Load(@"file.xml");
            var element = xdoc.Root.Elements("MyXmlElement").Single();
            element.Value = "This wasn't nearly as hard as the internet tried to make it!";
            xdoc.Save(@"file.xml");
        }
    }
}

This is much like Ben Robin's example, except it works (although his does too, now that it has been edited). And I even gave you the using directives!

  • 1
    The problem is the XML file is over 100MB and I have to do this often. So if I can avoid using the XDocument class then the document can be streamed in and out as I change it, without requiring a full load into memory. – Spence Oct 06 '11 at 22:31
  • Unfortunately I can't help you there... I'm just learning this stuff myself. I was just bothered by the example that didn't work when I initially came here from Google, so I felt the need to make a post on here that was a working version. – Noelle Hockaday Oct 07 '11 at 12:56
  • Randomly coming back here over ten years later to see that you noted your eventual solution in 2019 was amusing. :) – Noelle Hockaday Mar 27 '23 at 21:09
0

Load and Save

public XDocument XDocument { get; set; }
    private async Task OpenResWFileAsync()
    {
        List<XElement> dataElements;
        var reswFile = await StorageHelper.PickSingleFileAsync(".resw");
        if (reswFile == null) return;
        using (Stream fileStream = await reswFile.OpenStreamForReadAsync())
        {
            this.XDocument = XDocument.Load(fileStream);
            dataElements = this.XDocument.Root.Elements("data").ToList();
            this.DataElements = dataElements;
        }
    }
    #region
    private List<string> GetValues()
    {
        if (this.XDocument == null) return new List<string>();
        return this.XDocument.Root.Elements("data").Select(e => e.Attribute("name").Value).ToList();

    }
    public void ChangeValue(string resourceKey, string newValue)
    {
        if (this.DataElements == null) return;
        var element = this.DataElements.Where(e => e.Name == resourceKey).Single();
        element.Value = newValue;
    }
    #endregion
Minute V
  • 25
  • 3