|
// 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.IO;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using NuGet.Versioning;
namespace NuGet.Packaging.Core
{
/// <summary>
/// A very basic Nuspec reader that understands the Id, Version, PackageType, and MinClientVersion of a package.
/// </summary>
public abstract class NuspecCoreReaderBase : INuspecCoreReader
{
private readonly XDocument _xml;
private XElement? _metadataNode;
private Dictionary<string, string>? _metadataValues;
protected const string Metadata = "metadata";
protected const string Id = "id";
protected const string Version = "version";
protected const string MinClientVersion = "minClientVersion";
protected const string DevelopmentDependency = "developmentDependency";
/// <summary>
/// Read a nuspec from a path.
/// </summary>
public NuspecCoreReaderBase(string path)
{
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
_xml = LoadXml(File.OpenRead(path), leaveStreamOpen: false);
}
/// <summary>
/// Read a nuspec from a stream.
/// </summary>
public NuspecCoreReaderBase(Stream stream)
: this(stream, leaveStreamOpen: false)
{
}
/// <summary>
/// Read a nuspec from a stream.
/// </summary>
public NuspecCoreReaderBase(Stream stream, bool leaveStreamOpen)
{
if (stream == null)
{
throw new ArgumentNullException(nameof(stream));
}
_xml = LoadXml(stream, leaveStreamOpen);
}
/// <summary>
/// Reads a nuspec from XML
/// </summary>
public NuspecCoreReaderBase(XDocument xml)
{
if (xml == null)
{
throw new ArgumentNullException(nameof(xml));
}
_xml = xml;
}
/// <summary>
/// Id of the package
/// </summary>
/// <remarks>NU_NULL_INC :This method is annotated as not nullable intentionally.
/// The null return is possible only with malformed nuspecs and practically illegal in cases dealing with an actual package.</remarks>
public virtual string GetId()
{
var node = MetadataNode.Elements(XName.Get(Id, MetadataNode.GetDefaultNamespace().NamespaceName)).FirstOrDefault();
return node == null ? null! : node.Value;
}
/// <summary>
/// Version of the package
/// </summary>
/// <remarks>NU_NULL_INC :This method is annotated as not nullable intentionally.
/// The null return is possible only with malformed nuspecs and practically illegal in cases dealing with an actual package.</remarks>
public virtual NuGetVersion GetVersion()
{
var node = MetadataNode.Elements(XName.Get(Version, MetadataNode.GetDefaultNamespace().NamespaceName)).FirstOrDefault();
return node == null ? null! : NuGetVersion.Parse(node.Value);
}
/// <summary>
/// The minimum client version this package supports.
/// </summary>
public virtual NuGetVersion? GetMinClientVersion()
{
var node = MetadataNode.Attribute(XName.Get(MinClientVersion));
return node == null ? null : NuGetVersion.Parse(node.Value);
}
/// <summary>
/// Gets zero or more package types from the .nuspec.
/// </summary>
public virtual IReadOnlyList<PackageType> GetPackageTypes()
{
return NuspecUtility.GetPackageTypes(MetadataNode, useMetadataNamespace: true);
}
/// <summary>
/// Returns if the package is serviceable.
/// </summary>
public virtual bool IsServiceable()
{
return NuspecUtility.IsServiceable(MetadataNode);
}
/// <summary>
/// The developmentDependency attribute
/// </summary>
public virtual bool GetDevelopmentDependency()
{
var node = MetadataNode.Elements(XName.Get(DevelopmentDependency, MetadataNode.GetDefaultNamespace().NamespaceName)).FirstOrDefault();
return node == null ? false : bool.Parse(node.Value);
}
/// <summary>
/// Nuspec Metadata
/// </summary>
public virtual IEnumerable<KeyValuePair<string, string>> GetMetadata()
{
return MetadataNode
.Elements()
.Where(e => !e.HasElements && !string.IsNullOrEmpty(e.Value))
.Select(e => new KeyValuePair<string, string>(e.Name.LocalName, e.Value));
}
/// <summary>
/// Returns a nuspec metadata value or string.Empty.
/// </summary>
public virtual string GetMetadataValue(string name)
{
string? metadataValue;
MetadataValues.TryGetValue(name, out metadataValue);
return metadataValue ?? string.Empty;
}
/// <summary>
/// Indexed metadata values of the XML elements in the nuspec.
/// If duplicate keys exist only the first is used.
/// </summary>
protected Dictionary<string, string> MetadataValues
{
get
{
if (_metadataValues == null)
{
var metadataValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var pair in GetMetadata())
{
if (!metadataValues.ContainsKey(pair.Key))
{
metadataValues.Add(pair.Key, pair.Value);
}
}
_metadataValues = metadataValues;
}
return _metadataValues;
}
}
protected XElement MetadataNode
{
get
{
if (_metadataNode == null)
{
// find the metadata node regardless of the NS, some legacy packages have the NS here instead of on package
_metadataNode = _xml.Root!.Elements().FirstOrDefault(e => StringComparer.Ordinal.Equals(e.Name.LocalName, Metadata));
if (_metadataNode == null)
{
throw new PackagingException(string.Format(
CultureInfo.CurrentCulture,
Strings.MissingMetadataNode,
Metadata));
}
}
return _metadataNode;
}
}
/// <summary>
/// Raw XML doc
/// </summary>
public XDocument Xml
{
get { return _xml; }
}
public virtual PackageIdentity GetIdentity()
{
return new PackageIdentity(GetId(), GetVersion());
}
private static XDocument LoadXml(Stream stream, bool leaveStreamOpen)
{
using (var xmlReader = XmlReader.Create(stream, new XmlReaderSettings
{
CloseInput = !leaveStreamOpen,
IgnoreWhitespace = true,
IgnoreComments = true,
IgnoreProcessingInstructions = true
}))
{
return XDocument.Load(xmlReader, LoadOptions.None);
}
}
}
}
|