private IEnumerable <uSyncChange> CalculateChanges(TrackedItem change, XElement current, XElement target, string name, string path) { if (change == null) { return(Enumerable.Empty <uSyncChange>()); } var changePath = GetChangePath(path, change.Path); var changeName = GetChangeName(name, change.Name); if (change.Repeating == null) { if (change.HasChildProperties) { return(CalculatePropertyChanges(change, current, target, changeName, changePath)); } else { return(CalculateSingleChange(change, current, target, changeName, changePath)); } } return(CalculateRepeatingChanges(change, current, target, changeName, changePath)); }
/// <summary> /// works out changes, when the child elements are all properties (and have their own node names) /// this only works when each property is unique (no duplicates in a list) /// </summary> private IEnumerable <uSyncChange> CalculatePropertyChanges(TrackedItem change, XElement current, XElement target, string name, string path) { var updates = new List <uSyncChange>(); var currentNode = current.XPathSelectElement(path); var targetNode = target.XPathSelectElement(path); foreach (var childNode in currentNode.Elements()) { var currentNodePath = GetChangePath(change.Path, $"/{childNode.Name.LocalName}"); var currentNodeName = GetChangeName(change.Name, childNode.Name.LocalName); // we basically compare to target now. var targetChildNode = targetNode.Element(childNode.Name.LocalName); if (targetChildNode == null) { // no target, this element will get deleted updates.Add(uSyncChange.Delete(path, $"{name} [{childNode.Name.LocalName}]", GetElementValues(childNode))); continue; } // check all the children of the current and target for changes if (change.Children != null && change.Children.Any()) { foreach (var child in change.Children) { updates.AddRange(CalculateChanges(child, currentNode, targetNode, currentNodeName, currentNodePath)); } } else { var childValue = childNode.ValueOrDefault(string.Empty); var targetChildValue = targetChildNode.ValueOrDefault(string.Empty); if (!childValue.Equals(targetChildValue)) { // if there are no children, they we are comparing the actual text of the nodes updates.AddNotNull(Compare(currentNodePath, currentNodeName, childValue, targetChildValue, change.MaskValue)); } } } // missing from current (so new) foreach (var targetChild in targetNode.Elements()) { var currentChildNode = currentNode.Element(targetChild.Name.LocalName); if (currentChildNode == null) { // not in current, its a new property. updates.Add(uSyncChange.Create(path, $"{name} [{targetChild.Name.LocalName}]", GetElementValues(targetChild))); } } return(updates); }
/// <summary> /// works out changes when we have a repeating block (e.g all the properties on a content type) /// </summary> private IEnumerable <uSyncChange> CalculateRepeatingChanges(TrackedItem change, XElement current, XElement target, string name, string path) { var updates = new List <uSyncChange>(); var currentItems = current.XPathSelectElements(path); var targetItems = target.XPathSelectElements(path); var currentIndex = 0; // loop through the nodes in the current item foreach (var currentNode in currentItems) { var currentNodePath = path; var currentNodeName = name; XElement targetNode = null; // if the key is blank we just compare the values in the elements if (string.IsNullOrWhiteSpace(change.Repeating.Key)) { if (change.Repeating.ElementsInOrder) { if (targetItems.Count() > currentIndex) { targetNode = targetItems.ElementAt(currentIndex); } } else { // if the element isn't the key, then we get the first one (by value) // if the value is different in this case we will consider this a delete targetNode = targetItems.FirstOrDefault(x => x.Value == currentNode.Value); } } else { // we need to find the current key value var currentKey = GetKeyValue(currentNode, change.Repeating.Key, change.Repeating.KeyIsAttribute); if (currentKey == string.Empty) { continue; } // now we need to make the XPath for the children this will be [key = ''] or [@key =''] // depending if its an attribute or element key currentNodePath += MakeKeyPath(change.Repeating.Key, currentKey, change.Repeating.KeyIsAttribute); if (!string.IsNullOrWhiteSpace(change.Repeating.Name)) { var itemName = GetKeyValue(currentNode, change.Repeating.Name, change.Repeating.NameIsAttribute); if (!string.IsNullOrWhiteSpace(itemName)) { currentNodeName += $": {itemName}"; } } // now see if we can find that node in the target elements we have loaded targetNode = GetTarget(targetItems, change.Repeating.Key, currentKey, change.Repeating.KeyIsAttribute); } if (targetNode == null) { // no target, this element will get deleted var oldValue = currentNode.Value; if (!string.IsNullOrWhiteSpace(change.Repeating.Name)) { oldValue = GetKeyValue(currentNode, change.Repeating.Name, change.Repeating.NameIsAttribute); } updates.Add(uSyncChange.Delete(path, name, oldValue)); continue; } // check all the children of the current and target for changes if (change.Children != null && change.Children.Any()) { foreach (var child in change.Children) { updates.AddRange(CalculateChanges(child, currentNode, targetNode, currentNodeName, currentNodePath)); } } else { // if there are no children, they we are comparing the actual text of the nodes updates.AddNotNull(Compare(currentNodePath, currentNodeName, currentNode.ValueOrDefault(string.Empty), targetNode.ValueOrDefault(string.Empty), change.MaskValue)); } currentIndex++; } if (!change.Repeating.ElementsInOrder) { // look for things in target but not current (for they will be removed) List <XElement> missing = new List <XElement>(); if (string.IsNullOrWhiteSpace(change.Repeating.Key)) { missing = targetItems.Where(x => !currentItems.Any(t => t.Value == x.Value)) .ToList(); } else { foreach (var targetItem in targetItems) { var targetNodePath = path; var targetKey = GetKeyValue(targetItem, change.Repeating.Key, change.Repeating.KeyIsAttribute); if (string.IsNullOrEmpty(targetKey)) { continue; } targetNodePath += MakeKeyPath(change.Repeating.Key, targetKey, change.Repeating.KeyIsAttribute); var currentNode = GetTarget(currentItems, change.Repeating.Key, targetKey, change.Repeating.KeyIsAttribute); if (currentNode == null) { missing.Add(targetItem); } } } if (missing.Any()) { foreach (var missingItem in missing) { var oldValue = missingItem.Value; if (!string.IsNullOrWhiteSpace(change.Repeating.Name)) { oldValue = GetKeyValue(missingItem, change.Repeating.Name, change.Repeating.NameIsAttribute); } updates.Add(uSyncChange.Create(path, name, oldValue)); } } } return(updates); }
private IEnumerable <uSyncChange> CalculateSingleChange(TrackedItem change, XElement current, XElement target, string name, string path) { var updates = new List <uSyncChange>(); var currentNode = current; var targetNode = target; if (!string.IsNullOrEmpty(path)) { currentNode = current.XPathSelectElement(path); targetNode = target.XPathSelectElement(path); if (currentNode == null) { if (targetNode != null) { return(uSyncChange.Create(path, name, targetNode.ValueOrDefault(string.Empty), change.CompareValue) .AsEnumerableOfOne()); } // if both are null, just return nothing. return(updates); } if (targetNode == null) { // its a delete (not in target) return(uSyncChange.Delete(path, name, currentNode.ValueOrDefault(string.Empty), change.CompareValue) .AsEnumerableOfOne()); } // this happens if both exist, we compare values in them. if (change.CompareValue) { // actual change updates.AddNotNull(Compare(path, name, currentNode.ValueOrDefault(string.Empty), targetNode.ValueOrDefault(string.Empty), change.MaskValue)); } if (change.Attributes != null && change.Attributes.Any()) { foreach (var attribute in change.Attributes) { var currentValue = currentNode.Attribute(attribute).ValueOrDefault(string.Empty); var targetValue = targetNode.Attribute(attribute).ValueOrDefault(string.Empty); updates.AddNotNull(Compare(path, $"{name} [{attribute}]", currentValue, targetValue, change.MaskValue)); } } } if (change.Children != null && change.Children.Any()) { foreach (var child in change.Children) { updates.AddRange(CalculateChanges(child, currentNode, targetNode, name, path)); } } return(updates); }