Click here to Skip to main content
15,879,535 members
Articles / Desktop Programming / WPF
Tip/Trick

Rewriting "Rewriting XML Files"

Rate me:
Please Sign up or sign in to vote.
5.00/5 (3 votes)
1 Apr 2014CPOL6 min read 17.5K   126   5   2
Sometimes better isn't, necessarily
Image 1

Introduction

Recently, after submitting a Beginner's tip here at CodeProject on rewriting XML files, someone who follows my work suggested I use XLINQ (now in System.Xml.Linq). I decided to take him up on that offer. I would classify this tip as Intermediate and suggest checking the earlier tip as needed.

Thank You, Microsoft

Microsoft has indeed made wonderful LINQ extensions to handle XML. The trick is knowing whether these extensions really buy you that much, given your requirements and objectives. I'd like to elaborate somewhat on these from my previous tip:

  • Suppose you have Windows machines with existing XML files set for a particular customer environment, that is, the settings in the XML files are not just some set of standard or default values. Suppose you want to change a few settings to run a test. When you are through, you want to restore the existing XML files to what they were before the test.

  • An objective is to leave no or as few changes as possible, down to even the whitespace, after the 'restore'. Another objective is to avoid making any temporary files on the test system.

  • Some means of specifying values to be changed and restored is needed. It has to be general enough to specify any element value in your XML files. This is where XLINQ should have helped, but I found building the queries to find this element Name, then from there find that attribute value, then from there set the next element's value to "X" to be less intuitive and efficient than the Dictionary's and reader/writer code used in my previous tip. More about this later.

  • For CodeProject, another objective was to show a tested, simple solution using easy to follow code.

Let's get into XLINQ then. There are two basic approaches to modifying an XML file. One uses streams where you parse each XML node and handle state and value changes from beginning to end. This is the world of XmlReader and XmlWriter that were used in the helper classes in the previous tip.

The other approach is a document object model (little d.o.m.) where the entire XML file is represented in memory. This in-memory object can be queried something like a database to get and set values.

XLINQ uses both, but to get an XDocument into memory from a file, you pretty much have to use XDocument.Load. Here is a blurb from MSDN regarding XDocument.Load(String) used in this tip:

This method uses an underlying XmlReader to read the XML into an XML tree.

The Load(Stream) method has this blurb (Yes, we have to use XmlReaderSettings since one of our goals is to PreserveWhitespace):

If you have to modify XmlReaderSettings, follow these steps:

  1. Create an XmlReader by calling one of the Create overloads that take XmlReaderSettings as a parameter.

  2. Pass the XmlReader to one of the Load overloads of XDocument that takes XmlReader as a parameter.

The Load(TextReader) method has this blurb:

LINQ to XML's loading functionality is built upon XmlReader. Therefore, you might catch any exceptions that are thrown by the XmlReader.Create overload methods and the XmlReader methods that read and parse the document.

The Load(XmlReader) method has, well, my point is this: Since we need to create readers and writers to get and save XDocuments, we should be convinced XLINQ provides enough incentive for its use when our goal is to simply replace and restore some XML values.

Some of the Rewrites

I'd like to point out some of the differences used in this tip. For the wpml-config.xml sample, I have used 'full-blown' XLINQ. Keep in mind that I am still learning the true power and glory of System.Xml.Linq, but I think this is what my follower had in mind. Instead of using Dictionarys and since we are dealing with a 'database', we use a 'Stored Procedure' as if it were part of the database:

C#
private string SaveValue3;
private string ReplValue3 = "#fabdec";
// 1: save, 2: replace, 3: restore
private bool StoredProcedure3(XDocument xdoc, int caseNum)
{
   IEnumerable<xelement> els =
      (from el in xdoc.Root.Elements("language-switcher-settings")
       select el);

   var xx = els.First();
   IEnumerable<xelement> keys =
      (from key in xx.Descendants()
       where key.Attribute("name").Value == "background-other-normal"
       select key).Skip(1).Take(1);

   switch (caseNum)
   {
      case 1:
         SaveValue3 = keys.First().Value; break;
      case 2:
         keys.First().Value = ReplValue3; break;
      case 3:
         keys.First().Value = SaveValue3; break;
   }
   return true; // how could we fail
}>

Note this is for just one element value change. The previous tip replaced and restored two different elements starting from two named elements ("wpml-config" - one occurrence, and "key" - two occurrences) using helper classes. These helper classes are retained in this tip's solution for your inspection.

This stored procedure does however address a point made in the previous tip. Given:

XML
<?xml version="1.0" encoding="UTF-8"?>
  <root>
    <element>One</element>
    <element>Two</element>
  </root>

"Without any attributes to hang its hat on, it's not possibly[e] with this current code to change just the second element's text value of Two. You would need to change the code to replace only the Nth matching element."

If you look at StoredProcedure3 above, you will see the second query is qualified with ".Skip(1).Take(1)". We only replace the value of the second occurrence of the "background-other-normal" "name"-attributed "key" element. Which is kind'a nice I have to admit. Grudgingly.

For the other samples, in place of the XmlSaveReader and XmlReWriter helper classes, we have these two methods in the MainWindow class:

C#
// Fairly similar to XmlSaveReader
private bool Save(XDocument xdoc, Dictionary<string, Tuple<string, string, int>> saveElemVals)
{
   bool inElem = false, getNextText = false;
   int nesting = 0;
   ...
   return success;
}
// Somewhat similar to XmlReWriter
private bool ReWrite(string fname, Dictionary<string, Tuple<string, string, int>> replElemVals)
{
   try
   {
      ...
   }
   catch (Exception ex)
   {
      throw ex; // chance to set a breakpoint
   }

   return true;
}

These handle XML files as XDocuments. You will notice ReWrite (and the full-blown stored procedure) handle conformance level on their own. Each uses this method:

C#
private void SaveDocOrFrag(XDocument xdoc, string fname)
{
   if (xdoc.Declaration == null)
   {
      System.Xml.XmlWriterSettings xws =
         new System.Xml.XmlWriterSettings() { Indent = true, OmitXmlDeclaration = true };

      using (var fs = new FileStream(fname, FileMode.Create))
      using (System.Xml.XmlWriter xw = System.Xml.XmlWriter.Create(fs, xws))
      {
         xdoc.Save(xw);
      }
   }
   else
      new XDocument(xdoc.Declaration, xdoc.Root).Save(fname);
}

Old Wrinkles and New Wrinkles

Both the old solution and this one have common wrinkles I haven't mentioned before. They both make these changes:

  • ANSI line endings (single line feed) are converted to the PC's carriage return / line feed pairs.
  • Byte Order Marks (BOM) are added to saved files (probably a good thing).

I made some effort in the previous solution to retain XML file contents down to the original whitespace. Using XLINQ is a tradeoff that increases file differences. The tradeoff is managing less state but more dependence on the faithfulness of XDocument's Load and Save methods. Using XDocuments creates the following changes:

  • Partial declarations are replaced with declarations giving both version and encoding.
  • Uppercase encoding values "UTF-8" are set to lower case "utf-8".
  • Root element indentations are removed (Actually improves Config2.xml's style, but it's still an unwanted change).

7 Or 8 Years Ago

The link to Microsoft Visual Studio Code Name “Orcas” Language-Integrated Query, May 2006 Community Technology Preview still works; however, when you try to apply it today, you get:

Install fails

When you resurrect Visual Studio 2005 on your machine, this preview installs OK. When this is done, building and running this CodeProject article's demo application1 works. I would say that this app is susceptible to extraneous button pushing crashes, but only if I were mean spirited.

Bottom Line

My previous tip is small and adequate for the task I had at hand. I shared it here at CodeProject in hopes this simple approach could help beginners. If you are comfortable with Linq you can use this tip's solution as a starting point. Or, I notice searching for "XLINQ" on CodeProject there are about 50 articles from 24 May 2007 (yes, the very first one. big deal. but I'm not being personal) to 1 Jan 2011. Searching for "Xml.Linq" matches two articles (a Tip and a Technical Blog) dated 23 Aug 2011 and 20 Mar 2013, so I don't feel too bad showing up here in 2014.

I want to thank Sacha Barber for pointing out XLINQ. He's good reading and I used his class diagram in making this rewrite.

History

  • Submitted to CodeProject on Sunday, 30th March, 2014

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
CEO
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Volynsky Alex1-Apr-14 5:17
professionalVolynsky Alex1-Apr-14 5:17 
AnswerMy vote of 5 Pin
Christian Amado1-Apr-14 3:42
professionalChristian Amado1-Apr-14 3:42 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.