File: Core\NuspecUtility.cs
Web Access
Project: src\src\nuget-client\src\NuGet.Core\NuGet.Packaging\NuGet.Packaging.csproj (NuGet.Packaging)
// 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.Globalization;
using System.Linq;
using System.Xml.Linq;
using NuGet.Common;
using NuGet.Frameworks;

namespace NuGet.Packaging.Core
{
    /// <summary>
    /// Until NuspecReader and Manifest are unified, this is a place to share implementations of
    /// reading and parsing specific elements out of the .nuspec XML.
    /// </summary>
    public static class NuspecUtility
    {
        public static readonly string PackageTypes = "packageTypes";
        public static readonly string PackageType = "packageType";
        public static readonly string Name = "name";
        public static readonly string Version = "version";
        public static readonly string Serviceable = "serviceable";
        public static readonly string Repository = "repository";
        public static readonly string Type = "type";
        public static readonly string RepositoryUrl = "url";
        public static readonly string RepositoryBranch = "branch";
        public static readonly string RepositoryCommit = "commit";
        public static readonly string License = "license";
        public static readonly string Group = "group";
        public static readonly string FrameworkReferences = "frameworkReferences";
        public static readonly string FrameworkReference = "frameworkReference";
        public static readonly string TargetFramework = "targetFramework";

        /// <summary>
        /// Gets the package types from a .nuspec metadata XML element.
        /// </summary>
        /// <param name="metadataNode">The metadata XML element.</param>
        /// <param name="useMetadataNamespace">
        /// Whether or not to use the metadata element's namespace when finding the package type
        /// nodes. If false is specified, only the local names of the package type nodes are used
        /// for comparison. If true is specified, the package type nodes must have the same
        /// namespace as the metadata node.
        /// </param>
        /// <returns>
        /// A list of package types. If no package types are found in the metadata node, an empty
        /// list is returned.
        /// </returns>
        public static IReadOnlyList<PackageType> GetPackageTypes(XElement metadataNode, bool useMetadataNamespace)
        {
            IEnumerable<XElement> nodes;
            if (useMetadataNamespace)
            {
                var metadataNamespace = metadataNode.GetDefaultNamespace().NamespaceName;
                nodes = metadataNode
                    .Elements(XName.Get(PackageTypes, metadataNamespace))
                    .SelectMany(x => x.Elements(XName.Get(PackageType, metadataNamespace)));
            }
            else
            {
                nodes = metadataNode
                    .Elements()
                    .Where(x => x.Name.LocalName == PackageTypes)
                    .SelectMany(x => x.Elements())
                    .Where(x => x.Name.LocalName == PackageType);
            }

            var packageTypes = new List<PackageType>();
            foreach (var node in nodes)
            {
                // Get the required package type name.
                var nameAttribute = node.Attribute(XName.Get(Name));

                if (nameAttribute == null || string.IsNullOrWhiteSpace(nameAttribute.Value))
                {
                    throw new PackagingException(Strings.MissingPackageTypeName);
                }

                var name = nameAttribute.Value.Trim();

                // Get the optional package type version.
                var versionAttribute = node.Attribute(XName.Get(Version));
                Version? version;

                if (versionAttribute != null)
                {
                    if (!System.Version.TryParse(versionAttribute.Value, out version))
                    {
                        throw new PackagingException(string.Format(
                            CultureInfo.CurrentCulture,
                            Strings.InvalidPackageTypeVersion,
                            versionAttribute.Value));
                    }
                }
                else
                {
                    version = Core.PackageType.EmptyVersion;
                }

                packageTypes.Add(new PackageType(name, version));
            }

            return packageTypes;
        }

        /// <summary>
        /// Gets the value of serviceable element from a .nuspec metadata XML element.
        /// </summary>
        /// <param name="metadataNode">The metadata XML element.</param>
        /// <returns>
        /// true if the serviceable element is set in the .nuspec file as true, else false.
        /// </returns>
        public static bool IsServiceable(XElement metadataNode)
        {
            var metadataNamespace = metadataNode.GetDefaultNamespace().NamespaceName;
            var element = metadataNode.Elements(XName.Get(Serviceable, metadataNamespace)).FirstOrDefault();
            if (element == null)
            {
                return false;
            }

            if (element.Value == null)
            {
                return false;
            }

            var value = element.Value.Trim();
            return System.Xml.XmlConvert.ToBoolean(value);
        }

        /// <summary>
        /// Gets the FrameworkReference groups. This refers to the FrameworkReference concept added in .NET Core 3.0
        /// </summary>
        /// <param name="metadataNode">The metadata node</param>
        /// <param name="frameworkProvider">A FrameworkNameProvider</param>
        /// <param name="useMetadataNamespace">Whether or not to use the metadata element's namespace when getting the framework reference nodes.
        /// If false is specified, only the local names of the FrameworkReference nodes are used
        /// for comparison. If true is specified, the fremeworkreference nodes must have the same
        /// namespace as the metadata node.</param>
        /// <returns></returns>
        internal static IEnumerable<FrameworkReferenceGroup> GetFrameworkReferenceGroups(XElement metadataNode, IFrameworkNameProvider frameworkProvider, bool useMetadataNamespace)
        {
            var ns = useMetadataNamespace ? metadataNode.GetDefaultNamespace().NamespaceName : null;
            IEnumerable<XElement> frameworkReferenceGroups;
            if (useMetadataNamespace)
            {
                var frameworkReferencesNode = metadataNode
                    .Elements(XName.Get(FrameworkReferences, ns!));
                frameworkReferenceGroups = frameworkReferencesNode
                    .Elements(XName.Get(Group, ns!));
            }
            else
            {
                frameworkReferenceGroups = metadataNode
                    .Elements()
                    .Where(x => x.Name.LocalName == Group);
            }

            foreach (var frameworkRefGroup in frameworkReferenceGroups)
            {
                var groupFramework = GetAttributeValue(frameworkRefGroup, TargetFramework);

                var frameworkReferences = useMetadataNamespace ?
                    frameworkRefGroup.Elements(XName.Get(FrameworkReference, ns!)) :
                    frameworkRefGroup.Elements().Where(x => x.Name.LocalName == FrameworkReference);

                var framework = NuGetFramework.Parse(groupFramework!, frameworkProvider);
                var frameworkRefs = GetFrameworkReferences(frameworkReferences);

                yield return new FrameworkReferenceGroup(framework, frameworkRefs.Select(e => new FrameworkReference(e)));
            }
        }

        private static IEnumerable<string> GetFrameworkReferences(IEnumerable<XElement> nodes)
        {
            return new HashSet<string>(nodes.Select(e => GetAttributeValue(e, Name)!), ComparisonUtility.FrameworkReferenceNameComparer);
        }

        private static string? GetAttributeValue(XElement element, string attributeName)
        {
            var attribute = element.Attribute(XName.Get(attributeName));
            return attribute == null ? null : attribute.Value;
        }
    }
}