Chris Falter

.NET Design and Best Practices
posts - 38, comments - 59, trackbacks - 24

My Links

News

All source code published on this blog is placed in the public domain.

Archives

Post Categories

Image Galleries

About Me

How To: Modify an Existing Xml File

The past couple of days I've been working on a command-line config file parser that will enable our build process to emit the correct web.config for any given environment.  For example, we define environments called “model,” “fqa” (final qa) and “prod” (production).  The idea is to embed the environment-specific settings in comments at the end of the web.config file, then run the parser, passing in the environment name as a command-line parameter.  Here is a sample of what the environment-specific settings might look like:

<!-- [PROD]
<configuration>
    <system.web>
        <customErrors mode="On" defaultRedirect="Error.htm" replaceHere="true"/>
        <authentication >
            <forms loginUrl="login.aspx" name="PROD" timeout="60" path="/" replaceHere="true">
            </forms>
        </authentication>
        <httpCookies httpOnlyCookies="false" requireSSL="false" domain="seibelsonline.com"  replaceHere="true"/>
    </system.web>
</configuration>
-->

<!--
[FQA]
<configuration>
    <system.web>
        <customErrors mode="RemoteOnly" defaultRedirect="Error.htm" replaceHere="true"/>
        <authentication >
            <forms loginUrl="login.aspx" name="FQA" timeout="60" path="/" replaceHere="true">
            </forms>
        </authentication>
    </system.web>
</configuration>
-->


The replaceHere="true" attribute marks the location at which the parser is supposed to insert a replacement node.  Once I have the code fully debugged, I hope to be able to post it here.  (Please leave a comment if you are interested in seeing it.)

If you're like me, you check the Microsoft QuickStart samples when you want to do a task for the first time.  The XML file samples Microsoft provides are quite straightforward.  To read the document, you call the XmlDocument.Load(fileName) method; to save it, you simply call the XmlDocument.Save(fileName) method.  So to read, modify, and save the document, you should be able to write code like this:

string fileName = args[0];

// load the XML document
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(fileName);

// TODO: parse the special comments and modify the in-memory document

// write the document back to the file system
xmlDoc.Save(fileName);


Wouldn't life be wonderful if code this simple would just work?  Well, as my kids are probably tired of hearing, life is often unfair.  When your code tries to call xmlDoc.Save(), it will throw a System.IOException in a pique of outrage.  The exception message will be something like “another process has the file open.”

What? Another process?  I'm looking at my code, and all I see is one process!  How can another process have it open?  Well, it's time to learn something about the NTFS file system.  The Windows operating systems all have a special pool of kernel-mode threads that handle I/O requests asynchronously.  So while the appearance of your code is that one process is doing the file operations synchronously, the reality is that the System.Xml code calls System.IO code which calls Windows API code which calls the IO subsystem asynchronously and waits for completion signals.  You know, the knee bone's connected to the thigh bone and all that.  The important point is that the kernel-mode threads that handle the xmlDoc .Load and .Save operations do not know how to cooperate with each other unless given specific instructions otherwise.

So how do we get the .Load and .Save methods to cooperate?  The answer is to create a FileStream object that both methods can use.  Your new and improved code might look something like this:

//  load the XML document
XmlDocument configFile = new XmlDocument();
FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
configFile.Load(fs);
 
// TODO: parse the special comments and modify the in-memory document

// write the document back to the file system
configFile.Save(fs);

And that, friends, should be the end of the story.

Only it's not.  Examine the output, and you'll be tempted to schedule an appointment with your optometrist because you're seeing double.  The config file now has two root nodes; the old section was preserved and a new one got added at the end.  Fortunately, it doesn't take too much effort to fix this little problem.  You need to reset the FileStream object before you write to it, that's all.  Your final code should look something like this:

//  load the XML document
XmlDocument configFile = new XmlDocument();
FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
configFile.Load(fs);
 
// TODO: parse the special comments and modify the in-memory document

// write the document back to the file system
fs.Seek(0, SeekOrigin.Begin);
fs.SetLength(0);

configFile.Save(fs);


Pop the cork on the champagne bottle, folks, you've got a solution.  And please do not hesitate to leave a comment if you've got any related insights to share with other readers.

Print | posted on Monday, March 27, 2006 6:30 PM | Filed Under [ .NET Gotchas ]


Feedback

# re: How To: Modify an Existing Xml File

Is the 'replaceHere' supposed to be the insertion point for new xml tags? Or does it mean that the whole section, e.g. [PROD] needs to be replaced? 3/28/2006 11:21 AM | Brian

# re: How To: Modify an Existing Xml File

It's supposed to be the insertion point; it's hard to imagine a situation in which you would want to replace the entire <system.web> element!

I can see I'll have to post my solution when it's completed. :) 3/28/2006 11:49 AM | Chris Falter

# re: How To: Modify an Existing Xml File

Chris,
If I got a dollar for every time I recommend people look at the Quickstarts first for answers, I wouldn't need to come to work every day.
Cheers,
Peter
4/12/2006 11:25 AM | Peter Bromberg

# re: How To: Modify an Existing Xml File

THANK YOU! THANK YOU! THANK YOU!

I thought I was going nuts! It's stuff like this that really needs to be in the community comments section on MSDN. Unfortunately THIS topic doesn't have anywhere to put in a comment. GRRRRRR

Thanks Again
Brad
4/14/2007 1:30 PM | Brad Bruce

# re: How To: Modify an Existing Xml File

Hi Chris,

Could you give me an example of how you would modify the in-memory document? Thanks, I'm new at this. 5/5/2008 2:58 AM | Trung

# re: How To: Modify an Existing Xml File

please send it as soon as possible 5/27/2008 6:11 PM | swapna

# re: How To: Modify an Existing Xml File

@Trung and swapna -

Probably the simplest way to modify an in-memory document is to use the XmlDocument class' capabilities.

string theXml; // initialized before we get here
XmlDocument doc = new XmlDocument();
doc.LoadXml(theXml);

// find the element(s) we want to modify, using an XPath query
XmlNode geekNode = doc.SelectSingleNode("/OrgChart/Techies/Geek");

// modify the node
geekNode.InnerText = "Fred";
geekNode.Attributes.RemoveAll();
XmlAttribute attr = doc.CreateAttribute("surname");
attr.Value = "Flintstone";
geekNode.Attributes.Append(attr);

- Chris 5/28/2008 9:54 AM | Chris Falter

# re: How To: Modify an Existing Xml File

Hey thanks! I've been searching everywhere trying to figure out WHY I am getting extra tags in my XML after editing a document in Open Xml. Your solution fixed it.

Thanks Again! 7/10/2008 6:48 AM | jhamlett

# re: How To: Modify an Existing Xml File

i need to modify values for attributes in the xml and save it back. plz help 7/29/2008 4:39 PM | prakasam

# re: How To: Modify an Existing Xml File

@prakasam -

Please see my comment @Trung and swapna above. You'd have to modify it slightly, of course. You will probably end up with something like this:

// load the XML document
string fileName = @"c:\Mydir\Mysubdir\Mydoc.xml";
XmlDocument myDoc = new XmlDocument();
FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
myDoc.Load(fs);

// find the element(s) we want to modify, using an XPath query
XmlNode geekNode = doc.SelectSingleNode("/OrgChart/Techies/Geek");

// modify the node
XmlAttribute surNameAttr = geekNode.Attributes["surname"];
surNameAttr.Value = "Flintstone";

// write the document back to the file system
fs.Seek(0, SeekOrigin.Begin);
fs.SetLength(0);
myDoc.Save(fs);

HTH -

Chris

7/30/2008 6:08 AM | Chris Falter

# re: How To: Modify an Existing Xml File

Hey Guys.. I am trying to use Transformers to save the XML file

but after looking at this I assume I don't need to do anything of that sort..can somebody tell me how Transformers can be used instead of this and which one will be better? 8/5/2008 5:31 AM | Apoorv

# re: How To: Modify an Existing Xml File

@apoorv - I assume you are talking about applying an XSL Transform to an XML document. To do so, you would follow the same strategies for opening/loading the XML file and writing it back to the file system as I listed in my 7/30/08 comment. The only change is that you would modify the document by applying an XSL Transform in the area with the comments "find the element(s)" and "modify the node". The .NET XSL engine will use the XSL instructions to find and modify the appropriate nodes/attributes.

- Chris 8/6/2008 9:49 AM | Chris Falter

Post Comment

Title  
Name  
Email
Url
Comment   
Please add 1 and 6 and type the answer here:

Powered by: