using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Xml.Linq; using JetBrains.Annotations; using NugetForUnity.Models; namespace NugetForUnity.PackageSource { /// /// Provides helper methods for parsing a NuGet server OData response (NuGet API v2). /// OData is a superset of the Atom API. /// internal static class NugetODataResponse { private const string AtomNamespace = "http://www.w3.org/2005/Atom"; private const string DataServicesNamespace = "http://schemas.microsoft.com/ado/2007/08/dataservices"; private const string MetaDataNamespace = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"; /// /// Parses the given and returns the list of s contained within. /// /// The that is the OData XML response from the NuGet server. /// The source this package was downloaded with / provided by. /// The list of s read from the given XML. [NotNull] [ItemNotNull] public static List Parse([NotNull] XDocument document, [NotNull] NugetPackageSourceV2 packageSource) { var packages = new List(); if (document.Root == null) { return packages; } IEnumerable packageEntries; if (document.Root.Name.Equals(XName.Get("entry", AtomNamespace))) { packageEntries = Enumerable.Repeat(document.Root, 1); } else { packageEntries = document.Root.Elements(XName.Get("entry", AtomNamespace)); } foreach (var entry in packageEntries) { var package = new NugetPackageV2(packageSource) { Id = entry.GetAtomElement("title").Value, DownloadUrl = entry.GetAtomElement("content").Attribute("src")?.Value, }; var entryProperties = entry.Element(XName.Get("properties", MetaDataNamespace)) ?? throw new InvalidOperationException("Missing 'properties' element."); package.Title = entryProperties.GetProperty("Title"); package.Version = entryProperties.GetProperty("Version"); package.Description = entryProperties.GetProperty("Description"); package.Summary = entryProperties.GetProperty("Summary"); package.ReleaseNotes = entryProperties.GetProperty("ReleaseNotes"); package.LicenseUrl = entryProperties.GetProperty("LicenseUrl"); package.ProjectUrl = entryProperties.GetProperty("ProjectUrl"); package.Authors = entryProperties.GetProperty("Authors").Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); package.TotalDownloads = long.Parse(entryProperties.GetProperty("DownloadCount"), CultureInfo.InvariantCulture); package.IconUrl = entryProperties.GetProperty("IconUrl"); // if there is no title, just use the ID as the title if (string.IsNullOrEmpty(package.Title)) { package.Title = package.Id; } // Get dependencies var rawDependencies = entryProperties.GetProperty("Dependencies"); if (!string.IsNullOrEmpty(rawDependencies)) { var dependencyGroups = new Dictionary(); var dependencies = rawDependencies.Split('|'); foreach (var dependencyString in dependencies) { var details = dependencyString.Split(':'); var framework = string.Empty; if (details.Length > 2) { framework = details[2]; } if (!dependencyGroups.TryGetValue(framework, out var group)) { group = new NugetFrameworkGroup { TargetFramework = framework }; dependencyGroups.Add(framework, group); } var dependency = new NugetPackageIdentifier(details[0], details[1]); // some packages (ex: FSharp.Data - 2.1.0) have improper "semi-empty" dependencies such as: // "Zlib.Portable:1.10.0:portable-net40+sl50+wp80+win80|::net40" // so we need to only add valid dependencies and skip invalid ones if (!string.IsNullOrEmpty(dependency.Id) && !string.IsNullOrEmpty(dependency.Version)) { group.Dependencies.Add(dependency); } } foreach (var group in dependencyGroups.Values) { package.Dependencies.Add(group); } } packages.Add(package); } return packages; } /// /// Gets the string value of a NuGet metadata property from the given properties element and property name. /// /// The properties element. /// The name of the property to get. /// The string value of the property. [NotNull] private static string GetProperty([NotNull] this XElement properties, [NotNull] string name) { return (string)properties.Element(XName.Get(name, DataServicesNamespace)) ?? string.Empty; } /// /// Gets the within the Atom namespace with the given name. /// /// The element containing the Atom element. /// The name of the Atom element. /// The Atom element. [NotNull] private static XElement GetAtomElement([NotNull] this XElement element, [NotNull] string name) { return element.Element(XName.Get(name, AtomNamespace)) ?? throw new InvalidOperationException($"Can't find element with name {name} inside:\n{element}"); } } }