Reputation: 45
I have an 2 xml files, the Current Version (B) and the previous version (A)
I want to check if any of the Attributes have changed based on the Id Attribute If so I want to grab that Element and also any new Elements that have been added to the current file (B)
So I would have a resulting xml file of any element with any changes and any new element
---- Version A
<Books>
<book id='1' image='C01' name='C# in Depth'/>
<book id='2' image='C02' name='ASP.NET'/>
<book id='3' image='C03' name='LINQ in Action '/>
<book id='4' image='C04' name='Architecting Applications'/>
</Books>
---- Version B
<Books>
<book id='1' image='C011' name='C# in Depth'/>
<book id='2' image='C02' name='ASP.NET 2.0'/>
<book id='3' image='XXXC03' name='XXXLINQ in Action '/>
<book id='4' image='C04' name='Architecting Applications'/>
<book id='5' image='C05' name='PowerShell in Action'/>
</Books>
I want to return the following
---- Results
<Books>
<book id='1' image='C011' name='C# in Depth'/>
<book id='2' image='C02' name='ASP.NET 2.0'/>
<book id='3' image='XXXC03' name='XXXLINQ in Action '/>
<book id='5' image='C05' name='PowerShell in Action'/>
</Books>
Here is my code so far. I can get the changes based on the ids but not any new ones and also I'm sure someone can get the whole lot out in one statment and also the attributes without having to parse again. Thanks
private void LinqCompareXMLFiles()
{
string oldXML = @"<Books>
<book id='1' image='C01' name='C# in Depth'/>
<book id='2' image='C02' name='ASP.NET'/>
<book id='3' image='C03' name='LINQ in Action '/>
<book id='4' image='C04' name='Architecting Applications'/>
</Books>";
string newXML = @"<Books>
<book id='1' image='C011' name='C# in Depth'/>
<book id='2' image='C02' name='ASP.NET 2.0'/>
<book id='3' image='XXXC03' name='XXXLINQ in Action '/>
<book id='4' image='C04' name='Architecting Applications'/>
<book id='5' image='C05' name='PowerShell in Action'/>
</Books>";
XDocument xmlOld = XDocument.Parse(oldXML);
XDocument xmlNew = XDocument.Parse(newXML);
var res = (from b1 in xmlOld.Descendants("book")
from b2 in xmlNew.Descendants("book")
let issues = from a1 in b1.Attributes()
join a2 in b2.Attributes()
on a1.Name equals a2.Name
select new
{
Id = a1.Parent.FirstAttribute.Value,
Name = a1.Name,
Value1 = a1.Value,
Value2 = a2.Value
}
where issues.Any(i => i.Value1 == i.Value2)
from issue in issues
where issue.Value1 != issue.Value2
select issue);
var reportXmlItems = (from rx in res select new XElement("book", new XAttribute("id", rx.Id))).Distinct(new MyComparer());
// This isn't excluding the ids that exist in theold book set because they are different elements I guess and I need to exclude based on the element Id
var res2 = (from b2 in xmlNew.Descendants("book") select new XElement("book", new XAttribute("id",b2.Attribute("id").Value))).Except(xmlOld.Descendants("book"));
var res3 = reportXmlItems.Union(res2);
var reportXml = new XElement("books", res3);
reportXml.Save(@"c:\test\result.xml");
}
public class MyComparer : IEqualityComparer<XElement>
{
public bool Equals(XElement x, XElement y)
{
return x.Attribute("id").Value == y.Attribute("id").Value;
}
public int GetHashCode(XElement obj)
{
return obj.Attribute("id").Value.GetHashCode();
}
}
Upvotes: 1
Views: 9434
Reputation: 9485
I think it's possible to do this less verbosely and slightly more in the declarative style of the original question, as below:
XDocument xdoc = new XDocument(new XElement("Books",
from newBook in XDocument.Parse(newXML).Descendants("book")
join oldBook in XDocument.Parse(oldXML).Descendants("book")
on newBook.Attributes("id").First().Value equals oldBook.Attributes("id").First().Value into oldBooks
where !oldBooks.Any()
|| newBook.Attributes().Any(a => a.Value != oldBooks.First().Attributes(a.Name).First().Value)
select newBook));
This gives the answer requested, although as is often the way with Linq doing a lot of stuff in one line can make it hard to understand and to debug. Note we do a group join on id and then just select those new items where there's no matching old item, or there's some different attribute value on the matching item.
Upvotes: 0
Reputation: 7830
I don't see the point to comparing nodes with same id - they can be changed directly. But having said that, you can compare and merge your XML documents using LINQ to XML as follows:
// XMLs
string oldXML = @"<Books>
<book id='1' image='C01' name='C# in Depth'/>
<book id='2' image='C02' name='ASP.NET'/>
<book id='3' image='C03' name='LINQ in Action '/>
<book id='4' image='C04' name='Architecting Applications'/>
</Books>";
string newXML = @"<Books>
<book id='1' image='C011' name='C# in Depth'/>
<book id='2' image='C02' name='ASP.NET 2.0'/>
<book id='3' image='XXXC03' name='XXXLINQ in Action '/>
<book id='4' image='C04' name='Architecting Applications'/>
<book id='5' image='C05' name='PowerShell in Action'/>
</Books>";
Code:
// xml documents
var xmlOld = XDocument.Parse(oldXML);
var xmlNew = XDocument.Parse(newXML);
// helper function to get the attribute value of the given element by attribute name
Func<XElement, string, string> getAttributeValue = (xElement, name) => xElement.Attribute(name).Value;
// nodes for which we are looking for
var nodeName = "book";
var sameNodes = new List<string>();
// iterate over all old nodes (this will replace all existing but changed nodes)
xmlOld.Descendants(nodeName).ToList().ForEach(item =>
{
var currentElementId = getAttributeValue(item, "id");
// find node with the same id in the new nodes collection
var toReplace = xmlNew.Descendants(nodeName).ToList().FirstOrDefault(n => getAttributeValue(n, "id") == currentElementId);
if (toReplace != null)
{
var aImageOldValue = getAttributeValue(item, "image");
var aImageNewValue = getAttributeValue(toReplace, "image");
var aNameOldValue = getAttributeValue(item, "name");
var aNameNewValue = getAttributeValue(toReplace, "name");
if ((aImageNewValue != aImageOldValue) || (aNameOldValue != aNameNewValue))
{
// replace attribute values
item.Attribute("image").Value = getAttributeValue(toReplace, "image");
item.Attribute("name").Value = getAttributeValue(toReplace, "name");
}
else if ((aImageNewValue == aImageOldValue) && (aNameOldValue == aNameNewValue))
{
// remove same nodes! can't remove the node yet, because it will be seen as new
sameNodes.Add(getAttributeValue(item, "id"));
}
}
});
// add new nodes
// id's of all old nodes
var oldNodes = xmlOld.Descendants(nodeName).Select (node => getAttributeValue(node, "id")).ToList();
// id's of all new nodes
var newNodes = xmlNew.Descendants(nodeName).Select (node => getAttributeValue(node, "id")).ToList();
// find new nodes that are not present in the old collection
var nodeIdsToAdd = newNodes.Except(oldNodes);
// add all new nodes to the already modified xml document
foreach (var newNodeId in nodeIdsToAdd)
{
var newNode = xmlNew.Descendants(nodeName).FirstOrDefault(node => getAttributeValue(node, "id") == newNodeId);
if (newNode != null)
{
xmlOld.Root.Add(newNode);
}
}
// remove unchanged nodes
foreach (var oldNodeId in sameNodes)
{
xmlOld.Descendants(nodeName).FirstOrDefault (node => getAttributeValue(node, "id") == oldNodeId).Remove();
}
xmlOld.Save(@"d:\temp\merged.xml");
The resulting XML looks like this:
<Books>
<book id="1" image="C011" name="C# in Depth" />
<book id="2" image="C02" name="ASP.NET 2.0" />
<book id="3" image="XXXC03" name="XXXLINQ in Action " />
<book id="5" image="C05" name="PowerShell in Action" />
</Books>
Upvotes: 1
Reputation: 2218
I would reccomend you to look at xmldiff before going for selfimplemented solution. some refs you can find here: http://msdn.microsoft.com/en-us/library/aa302294.aspx
Upvotes: 2