|
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
namespace NuGet.Packaging
{
public static class XElementExtensions
{
public static string? GetOptionalAttributeValue(this XElement element, string localName, string? namespaceName = null)
{
XAttribute? attr;
if (String.IsNullOrEmpty(namespaceName))
{
attr = element.Attribute(localName);
}
else
{
attr = element.Attribute(XName.Get(localName, namespaceName!));
}
return attr?.Value;
}
public static IEnumerable<XElement> ElementsNoNamespace(this XContainer container, string localName)
{
return container.Elements().Where(e => e.Name.LocalName == localName);
}
public static XElement Except(this XElement source, XElement? target)
{
if (target == null)
{
return source;
}
var attributesToRemove = from e in source.Attributes()
where AttributeEquals(e, target.Attribute(e.Name))
select e;
// Remove the attributes
foreach (var a in attributesToRemove.ToList())
{
a.Remove();
}
foreach (var sourceChildNode in source.Nodes().ToList())
{
var sourceChildComment = sourceChildNode as XComment;
if (sourceChildComment != null)
{
var hasMatchingComment = HasComment(target, sourceChildComment);
if (hasMatchingComment)
{
sourceChildComment.Remove();
}
continue;
}
var sourceChild = sourceChildNode as XElement;
if (sourceChild != null)
{
var targetChild = FindElement(target, sourceChild);
if (targetChild != null
&& !HasConflict(sourceChild, targetChild))
{
Except(sourceChild, targetChild);
var hasContent = sourceChild.HasAttributes || sourceChild.HasElements;
if (!hasContent)
{
// Remove the element if there is no content
sourceChild.Remove();
targetChild.Remove();
}
}
}
}
return source;
}
private static XElement? FindElement(XElement source, XElement targetChild)
{
// Get all of the elements in the source that match this name
var sourceElements = source.Elements(targetChild.Name).ToList();
// Try to find the best matching element based on attribute names and values
sourceElements.Sort((a, b) => Compare(targetChild, a, b));
return sourceElements.FirstOrDefault();
}
private static int Compare(XElement target, XElement left, XElement right)
{
// First check how much attribute names and values match
var leftExactMathes = CountMatches(left, target, AttributeEquals);
var rightExactMathes = CountMatches(right, target, AttributeEquals);
if (leftExactMathes == rightExactMathes)
{
// Then check which names match
var leftNameMatches = CountMatches(left, target, (a, b) => a.Name == b.Name);
var rightNameMatches = CountMatches(right, target, (a, b) => a.Name == b.Name);
return rightNameMatches.CompareTo(leftNameMatches);
}
return rightExactMathes.CompareTo(leftExactMathes);
}
private static int CountMatches(XElement left, XElement right, Func<XAttribute, XAttribute, bool> matcher)
{
return (from la in left.Attributes()
from ta in right.Attributes()
where matcher(la, ta)
select la).Count();
}
private static bool HasComment(XElement element, XComment comment)
{
return element.Nodes().Any(node => node.NodeType == XmlNodeType.Comment &&
((XComment)node).Value.Equals(comment.Value, StringComparison.Ordinal));
}
private static bool HasConflict(XElement source, XElement target)
{
// Get all attributes as name value pairs
var sourceAttr = source.Attributes().ToDictionary(a => a.Name, a => a.Value);
// Loop over all the other attributes and see if there are
foreach (var targetAttr in target.Attributes())
{
string? sourceValue;
// if any of the attributes are in the source (names match) but the value doesn't match then we've found a conflict
if (sourceAttr.TryGetValue(targetAttr.Name, out sourceValue)
&& sourceValue != targetAttr.Value)
{
return true;
}
}
return false;
}
private static bool AttributeEquals(XAttribute? source, XAttribute? target)
{
if (source == null
&& target == null)
{
return true;
}
if (source == null
|| target == null)
{
return false;
}
return source.Name == target.Name && source.Value == target.Value;
}
}
}
|