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}");
}
}
}