English 中文(简体)
Merging an XML file with a list of changes
原标题:
  • 时间:2009-11-09 20:24:48
  •  标签:
  • c#
  • xml
  • merge

I have two XML files that are generated by another application I have no control over. The first is a settings file, and the second is a list of changes that should be applied to the first.

Main settings file:

<?xml version="1.0"?>
<preset>
  <var id="9" opt="0" val="6666666"/>
  <var id="9" opt="1" val="10000000"/>
  <var id="9" opt="2" val="10000000"/>
  <var id="9" opt="3" val="10000000"/>
  <var id="9" opt="4" val="0"/>
  <var id="10" opt="0" val="4"/>
  <var id="11" opt="0" val="0"/>
  <var id="15" opt="0" val="75"/>
  <var id="22" opt="0" val="0,0,127,516" type="rect(l,t,r,b)"/>
  <var id="23" opt="0" val="27,18,92,66" type="rect(l,t,r,b)"/>
  <var id="24" opt="0" val="320"/>
  ... Skip 300 lines ...
</preset>

And here is an example of the changes:

<?xml version="1.0"?>
<preset>
  <var id="15" opt="0" val="425"/>
  <var id="22" opt="0" val="0,0,127,776" type="rect(l,t,r,b)"/>
  <var id="26" opt="0" val="147"/>
  <var id="27" opt="0" val="147"/>
  <var id="109" opt="1" val="7"/>
  <var id="126" opt="0" val="6,85,85,59" type="crv(t,m,b,vm)"/>
  <var id="157" opt="0" val="1"/>
  ... Skip 10 lines ...
</preset>

Each variable has an ID and an Optimization that ID applies to. Basically, I m looking to replace the lines where the id="#" and opt="#" are the same with the version from the "change" file. In the example above, the value for id="15" opt="0" would change from 75 to 425.

Would there be any clean way in doing this in C#? At first thought, reading as text and stepping through the changes using a find-replace type of method seems the cleanest. An approach handling this as an XmlDocument seems like much more work.

最佳回答

This would be terribly inefficient if the files get very big, but this is how you can do it with XmlDocuments:

XmlDocument main = new XmlDocument();
main.Load( "main.xml" );

XmlDocument changes = new XmlDocument();
changes.Load( "changes.xml" );

foreach ( XmlNode mainNode in main.SelectNodes( "preset/var" ) )
{
    string mainId = mainNode.Attributes[ "id" ].Value;
    string mainOpt = mainNode.Attributes[ "opt" ].Value;

    foreach ( XmlNode changeNode in changes.SelectNodes( "preset/var" ) )
    {
        if ( mainId == changeNode.Attributes[ "id" ].Value &&
            mainOpt == changeNode.Attributes[ "opt" ].Value )
        {
            mainNode.Attributes[ "val" ].Value = changeNode.Attributes[ "val" ].Value;
        }
    }
}

// save the updated main document
main.Save( "updated_main.xml" );
问题回答

Not sure about the efficiency, but this is straightforward with Linq to XML - the follwing was a bit rough - but having remembered that the very wonderful LinqPAD will let you run programs... herewith a complete lump of code that would do the job:

void Main()
{
    XDocument settingsXML = XDocument.Load(@"c:	empsettings.xml");
    XDocument updateXML = XDocument.Load(@"c:	empupdates.xml");

    Console.WriteLine("Processing");

    // Loop through the updates
    foreach(XElement update in updateXML.Element("preset").Elements("var"))
    {    
        // Find the element to update    
        XElement settingsElement = 
            (from s in settingsXML.Element("preset").Elements("var")
             where s.Attribute("id").Value == update.Attribute("id").Value &&
                   s.Attribute("opt").Value == update.Attribute("opt").Value
             select s).FirstOrDefault();    
        if (settingsElement != null)    
        {      
            settingsElement.Attribute("val").Value = update.Attribute("val").Value;    
            // Handling for additional attributes here
        }    
        else    
        {   
            // not found handling    
            Console.WriteLine("Not found {0},{1}", update.Attribute("id").Value,
                                                   update.Attribute("opt").Value);
        }
    }
    Console.WriteLine("Saving");
    settingsXML.Save(@"c:	empupdatedSettings.xml");
    Console.WriteLine("Finis!");
}

Addition of using clauses is left as an exercise :)

There s another example here but its in VB which has more capabilities in terms of XML.

I also think that it might be possible to do something seriously elegant by way of a query with a join of the two sets of XML data generating a list of dynamic types containing an XElement and the value (or values) that it needs to be updated with. But I ve had enough fun (spent enough time) with this one already for one evening

Example of doing it in Linq to XML using joins to relate the documents together. First I select the elements that match on the two attributes and then I update those to the new value from the change file.

XDocument main = XDocument.Load("XMLFile1.xml");
XDocument changes = XDocument.Load("XMLFile2.xml");

var merge = from entry in main.Descendants("preset").Descendants("var")
            join change in changes.Descendants("preset").Descendants("var")
            on 
               new {a=entry.Attribute("id").Value, b=entry.Attribute("opt").Value}
            equals 
               new {a=change.Attribute("id").Value, b=change.Attribute("opt").Value}
            select new
            {
               Element = entry,
               newValue = change.Attribute("val").Value                       
            };

            merge.ToList().ForEach(i => i.Element.Attribute("val").Value = i.newValue);

            main.Save("XMLFile3.xml");

XmlDocument would be ideal for this process and is far less work than the other method you suggested. You might want to look at other methods to approach this if your using LARGE Xml files as XmlDocument loads the whole document into memory!

Anyways the XmlDocument approach would be something like:

  1. Load both files into their respective XmlDocument objects.
  2. Iterate through the list of var nodes in the changes file XmlDocument object and foreach one run a xpath query on the original file XmlDocument object to search for the node with the matching id (using the SelectSingleNode() method on the original file XmlDocument object) when found edit the attribute you need edited in the node.
  3. Save the file after all the edits.

I know what i m telling you to do works but i might not be explaining it clearly. I can complete a very rough version of this program in under 30mins and i ve only got about a years experience in c#.





相关问题
Anyone feel like passing it forward?

I m the only developer in my company, and am getting along well as an autodidact, but I know I m missing out on the education one gets from working with and having code reviewed by more senior devs. ...

NSArray s, Primitive types and Boxing Oh My!

I m pretty new to the Objective-C world and I have a long history with .net/C# so naturally I m inclined to use my C# wits. Now here s the question: I feel really inclined to create some type of ...

C# Marshal / Pinvoke CBitmap?

I cannot figure out how to marshal a C++ CBitmap to a C# Bitmap or Image class. My import looks like this: [DllImport(@"test.dll", CharSet = CharSet.Unicode)] public static extern IntPtr ...

How to Use Ghostscript DLL to convert PDF to PDF/A

How to user GhostScript DLL to convert PDF to PDF/A. I know I kind of have to call the exported function of gsdll32.dll whose name is gsapi_init_with_args, but how do i pass the right arguments? BTW, ...

Linqy no matchy

Maybe it s something I m doing wrong. I m just learning Linq because I m bored. And so far so good. I made a little program and it basically just outputs all matches (foreach) into a label control. ...

热门标签