public void Compare(Stream stream1, Stream stream2, IXmlCompareHandler callback) { var loadOptions = LoadOptions.SetBaseUri | LoadOptions.SetLineInfo; const int leftId = 1; const int rightId = 2; var doc1 = XDocument.Load(stream1, loadOptions); var doc2 = XDocument.Load(stream2, loadOptions); var nsm1 = new XmlNamespaceManagerEnhanced(doc1); var nsm2 = new XmlNamespaceManagerEnhanced(doc2); var doc1Descendants = doc1.Descendants() .Where(a => a != doc1.Root) .Select(a => new { Source = leftId, Element = a }); var doc2Descendants = doc2.Descendants() .Where(a => a != doc2.Root) .Select(a => new { Source = rightId, Element = a }); var doc1DescendantsDiff = doc1Descendants .Where(a => !doc2Descendants.Any(a2 => ElementsAreEqual(a2.Element, a.Element))) .Select(a => Elem.Create(a.Element, a.Source)) .ToList(); var doc2DescendantsDiff = doc2Descendants .Where(a => !doc1Descendants.Any(a1 => ElementsAreEqual(a1.Element, a.Element))) .Select(a => Elem.Create(a.Element, a.Source)) .ToList(); var itemsToProcess = doc1DescendantsDiff .Concat(doc2DescendantsDiff) .OrderBy(a => a.LineNumber).ThenBy(a => a.Source) .ToList(); var xPathsAdded = new List <string>(); var xPathsRemoved = new List <string>(); var xPathsChanged = new List <string>(); var additions = new List <ElementAddedEventArgs>(); var changes = new List <ElementChangedEventArgs>(); var removals = new List <ElementRemovedEventArgs>(); foreach (var item in itemsToProcess) { if (xPathsAdded.Any(a => a == item.XPath) || xPathsChanged.Any(a => a == item.XPath) || xPathsRemoved.Any(a => a == item.XPath) ) { continue; } //Console.WriteLine(item.XPath); var node1 = item.Source != leftId ? default(XElement) : item.Element; var node2 = item.Source != rightId ? default(XElement) : item.Element; // now get item from other side switch (item.Source) { case leftId: { // get node2 var e = itemsToProcess.FirstOrDefault(a => a.Source == rightId && a.XPath == item.XPath); if (e != null) { node2 = e.Element; } break; } case rightId: { // get node1 var e = itemsToProcess.FirstOrDefault(a => a.Source == leftId && a.XPath == item.XPath); if (e != null) { node1 = e.Element; } break; } default: { throw new Exception("Invalid Source " + item + " for item : " + item.XPath); } } if (node1 != null && node2 != null) { CompareAttributes(node1, node2, callback); // if there are sub-elements, those will be handled separately if (node1.HasElements || node2.HasElements) { continue; } } if (node1 == null && node2 != null) { //added if (xPathsAdded.Any(a => item.XPath.StartsWith(a))) { // if the node's parent exists in the list of items, // there is no need to call the callback continue; } xPathsAdded.Add(item.XPath); additions.Add(new ElementAddedEventArgs(item.XPath, node2, node2.LineNumber())); continue; } if (node1 != null && node2 == null) { //removed if (xPathsRemoved.Any(a => item.XPath.StartsWith(a))) { // if the node's parent exists in the list of items, // there is no need to call the callback continue; } xPathsRemoved.Add(item.XPath); removals.Add(new ElementRemovedEventArgs(item.XPath, node1, node1.LineNumber())); continue; } if (node1 != null && node2 != null) { //might have changed //compare values if (xPathsChanged.Any(a => item.XPath.StartsWith(a))) { // if the node's parent exists in the list of items, // there is no need to call the callback continue; } xPathsChanged.Add(item.XPath); var val1 = node1.Value; var val2 = node2.Value; if (string.Equals(val1, val2)) { continue; } changes.Add(new ElementChangedEventArgs(item.XPath, node1, node1.LineNumber(), node2, node2.LineNumber())); continue; } throw new Exception("Invalid scenario while comparing elements: " + item.XPath); } foreach (var item in removals) { callback.ElementRemoved(item); } foreach (var item in additions) { callback.ElementAdded(item); } foreach (var item in changes) { callback.ElementChanged(item); } }
public void Compare(Stream stream1, Stream stream2, IXmlCompareHandler callback) { var stopWatch = new Stopwatch(); var loadOptions = LoadOptions.SetBaseUri | LoadOptions.SetLineInfo; const int leftId = 1; const int rightId = 2; var doc1 = XDocument.Load(stream1, loadOptions); var doc2 = XDocument.Load(stream2, loadOptions); var nsm1 = new XmlNamespaceManagerEnhanced(doc1); var nsm2 = new XmlNamespaceManagerEnhanced(doc2); var doc1Descendants = doc1.Descendants() .Where(a => a != doc1.Root).Select(a => Elem.Create(a, leftId)).ToList(); var doc2Descendants = doc2.Descendants() .Where(a => a != doc2.Root).Select(a => Elem.Create(a, rightId)).ToList(); var xPathsAdded = new List <string>(); var xPathsRemoved = new List <string>(); var xPathsChanged = new List <string>(); stopWatch.Start(); foreach (var item1 in doc1Descendants) { //taken same levels var levelCollection = doc2Descendants.Where(x => x.Level == item1.Level); //taken same parents var groupCollection = levelCollection.Where(x => XPathComparer.CompareParent(item1.XPath, x.XPath)); //take matched children var itemCollection = groupCollection.Where(a => XPathComparer.Compare(item1.XPath, a.XPath)).ToList(); //handle properties used in a list if (itemCollection.Count > 1) { var isExist = false; foreach (var prop in itemCollection) { if (item1.XPath == prop.XPath) { isExist = true; prop.isChecked = true; } } if (!isExist) { xPathsChanged.Add(item1.XPath); changes.Add(new ElementChangedEventArgs(item1.XPath, item1.Element, item1.Element.LineNumber(), item1.Element, item1.LineNumber)); } } else { var item2 = itemCollection.FirstOrDefault(); if (!groupCollection.ToList().Any() && item2 == null) // group collection will be empty when parent property has changed { var changedParentCollection = levelCollection.Where(x => item1.XPath.CompareImmediateParent(x.XPath) && XPathComparer.Compare(item1.XPath, x.XPath)); var isPresent = changedParentCollection.Any(); if (isPresent) { changedParentCollection.ToList().ForEach(x => x.isChecked = true); continue; } xPathsRemoved.Add(item1.XPath); removals.Add(new ElementRemovedEventArgs(item1.XPath, item1.Element, item1.LineNumber)); continue; } if (item2 == null) { if (xPathsRemoved.Any(a => item1.XPath.StartsWith(a))) { continue; } xPathsRemoved.Add(item1.XPath); removals.Add(new ElementRemovedEventArgs(item1.XPath, item1.Element, item1.LineNumber)); continue; } else if (item1.XPath != item2.XPath) { if (!xPathsChanged.Any(a => item1.XPath.StartsWith(a))) { xPathsChanged.Add(item1.XPath); changes.Add(new ElementChangedEventArgs(item1.XPath, item1.Element, item1.LineNumber, item2.Element, item2.LineNumber)); } } foreach (var item in itemCollection)// marking searchable objects { item.isChecked = true; } } } var addedCollection = doc2Descendants.Where(x => !x.isChecked).ToList(); foreach (var item1 in addedCollection) { if (xPathsAdded.Any(a => item1.XPath.StartsWith(a))) { continue; } xPathsAdded.Add(item1.XPath); additions.Add(new ElementAddedEventArgs(item1.XPath, item1.Element, item1.LineNumber)); } stopWatch.Stop(); Console.WriteLine("Elapsed Time {0} ms", stopWatch.ElapsedMilliseconds.ToString()); foreach (var item in removals) { callback.ElementRemoved(item); } foreach (var item in additions) { callback.ElementAdded(item); } foreach (var item in changes) { callback.ElementChanged(item); } }