<|start_filename|>OaiPmhNet.Test/SampleMetadataFormatRepository.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using OaiPmhNet.Models;

namespace OaiPmhNet.Test
{
    public class SampleMetadataFormatRepository : IMetadataFormatRepository
    {
        private readonly Dictionary<string, MetadataFormat> _dictionary;

        public SampleMetadataFormatRepository(IList<MetadataFormat> metadataFormats)
        {
            _dictionary = metadataFormats.ToDictionary(f => f.Prefix, f => f);
        }

        public MetadataFormat GetMetadataFormat(string prefix)
        {
            if (_dictionary.TryGetValue(prefix, out MetadataFormat format))
                return format;
            else
                return null;
        }

        public IEnumerable<MetadataFormat> GetMetadataFormats()
        {
            return _dictionary.Select(o => o.Value);
        }
    }
}


<|start_filename|>OaiPmhNet/Models/RecordContainer.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;

namespace OaiPmhNet.Models
{
    public class RecordContainer
    {
        public IEnumerable<Record> Records { get; set; } = Enumerable.Empty<Record>();

        public IResumptionToken ResumptionToken { get; set; }
    }
}


<|start_filename|>OaiPmhNet/Converters/DublinCoreMetadataConverter.cs<|end_filename|>
﻿using System;
using System.Xml.Linq;
using OaiPmhNet.Models;

namespace OaiPmhNet.Converters
{
    public class DublinCoreMetadataConverter : MetadataConverter<DublinCoreMetadata>, IDublinCoreMetadataConverter
    {
        public DublinCoreMetadataConverter(IOaiConfiguration configuration, IDateConverter dateConverter)
            : base(configuration, dateConverter)
        {

        }

        public override DublinCoreMetadata Decode(XElement metadata)
        {
            XElement root = metadata.Element(OaiNamespaces.OaiDcNamespace + "dc");

            if (root == null)
                root = metadata;

            return new DublinCoreMetadata()
            {
                Title = DecodeList(root, OaiNamespaces.DcNamespace + "title", (e) => { return e; }),
                Creator = DecodeList(root, OaiNamespaces.DcNamespace + "creator", (e) => { return e; }),
                Subject = DecodeList(root, OaiNamespaces.DcNamespace + "subject", (e) => { return e; }),
                Description = DecodeList(root, OaiNamespaces.DcNamespace + "description", (e) => { return e; }),
                Publisher = DecodeList(root, OaiNamespaces.DcNamespace + "publisher", (e) => { return e; }),
                Contributor = DecodeList(root, OaiNamespaces.DcNamespace + "contributor", (e) => { return e; }),
                Date = DecodeList(root, OaiNamespaces.DcNamespace + "date", (date) =>
                {
                    if (_dateConverter.TryDecode(date, out DateTime dateTime))
                        return dateTime;
                    else
                        return DateTime.MinValue;
                }),
                Type = DecodeList(root, OaiNamespaces.DcNamespace + "type", (e) => { return e; }),
                Format = DecodeList(root, OaiNamespaces.DcNamespace + "format", (e) => { return e; }),
                Identifier = DecodeList(root, OaiNamespaces.DcNamespace + "identifier", (e) => { return e; }),
                Source = DecodeList(root, OaiNamespaces.DcNamespace + "source", (e) => { return e; }),
                Language = DecodeList(root, OaiNamespaces.DcNamespace + "language", (e) => { return e; }),
                Relation = DecodeList(root, OaiNamespaces.DcNamespace + "relation", (e) => { return e; }),
                Coverage = DecodeList(root, OaiNamespaces.DcNamespace + "coverage", (e) => { return e; }),
                Rights = DecodeList(root, OaiNamespaces.DcNamespace + "rights", (e) => { return e; })
            };
        }

        public override XElement Encode(DublinCoreMetadata metadata)
        {
            return new XElement(OaiNamespaces.OaiDcNamespace + "dc",
                    new XAttribute(XNamespace.Xmlns + "oai_dc", OaiNamespaces.OaiDcNamespace),
                    new XAttribute(XNamespace.Xmlns + "dc", OaiNamespaces.DcNamespace),
                    new XAttribute(XNamespace.Xmlns + "xsi", OaiNamespaces.XsiNamespace),
                    new XAttribute(OaiNamespaces.XsiNamespace + "schemaLocation", OaiNamespaces.OaiDcSchemaLocation),
                    EncodeList(OaiNamespaces.DcNamespace + "title", metadata.Title),
                    EncodeList(OaiNamespaces.DcNamespace + "creator", metadata.Creator),
                    EncodeList(OaiNamespaces.DcNamespace + "subject", metadata.Subject),
                    EncodeList(OaiNamespaces.DcNamespace + "description", metadata.Description),
                    EncodeList(OaiNamespaces.DcNamespace + "publisher", metadata.Publisher),
                    EncodeList(OaiNamespaces.DcNamespace + "contributor", metadata.Contributor),
                    EncodeList(OaiNamespaces.DcNamespace + "date", metadata.Date, (date) =>
                    {
                        return _dateConverter.Encode(_configuration.Granularity, date);
                    }),
                    EncodeList(OaiNamespaces.DcNamespace + "type", metadata.Type),
                    EncodeList(OaiNamespaces.DcNamespace + "format", metadata.Format),
                    EncodeList(OaiNamespaces.DcNamespace + "identifier", metadata.Identifier),
                    EncodeList(OaiNamespaces.DcNamespace + "source", metadata.Source),
                    EncodeList(OaiNamespaces.DcNamespace + "language", metadata.Language),
                    EncodeList(OaiNamespaces.DcNamespace + "relation", metadata.Relation),
                    EncodeList(OaiNamespaces.DcNamespace + "coverage", metadata.Coverage),
                    EncodeList(OaiNamespaces.DcNamespace + "rights", metadata.Rights));
        }
    }
}


<|start_filename|>OaiPmhNet/Models/ArgumentContainer.cs<|end_filename|>
﻿namespace OaiPmhNet.Models
{
    public class ArgumentContainer
    {
        public string Verb { get; private set; }
        public string MetadataPrefix { get; private set; }
        public string ResumptionToken { get; private set; }
        public string Identifier { get; private set; }
        public string From { get; private set; }
        public string Until { get; private set; }
        public string Set { get; private set; }

        public ArgumentContainer(string verb,
            string metadataPrefix = null,
            string resumptionToken = null,
            string identifier = null,
            string from = null,
            string until = null,
            string set = null)
        {
            Verb = verb;
            MetadataPrefix = metadataPrefix;
            ResumptionToken = resumptionToken;
            Identifier = identifier;
            From = from;
            Until = until;
            Set = set;
        }
    }
}


<|start_filename|>OaiPmhNet.Test/SampleSetRepositoryTest.cs<|end_filename|>
﻿using NUnit.Framework;
using OaiPmhNet.Models;
using System.Collections.Generic;
using System.Linq;

namespace OaiPmhNet.Test
{
    [TestFixture]
    public class SampleSetRepositoryTest
    {
        private SampleSetRepository _repository;

        public static SampleSetRepository Create()
        {
            IList<Set> sets = new List<Set>()
            {
                new Set()
                {
                    Spec = "video",
                    Name = "Video Collection",
                    Description = "This set contains videos."
                },
                new Set()
                {
                    Spec = "image",
                    Name = "Image Collection"
                }
            };

            IOaiConfiguration configuration = OaiConfiguration.Instance;

            return new SampleSetRepository(configuration, sets);
        }

        [OneTimeSetUp]
        public void Init()
        {
            OaiConfigurationTest.Init();

            _repository = Create();
        }

        [Test]
        public void SampleSetRepository_GetSets()
        {
            var expected = 2;

            var actual = _repository.GetSets(new ArgumentContainer(OaiVerb.ListSets.ToString())).Sets.Count();

            Assert.AreEqual(expected, actual);
        }
    }
}


<|start_filename|>OaiPmhNet.Test/SampleRecordRepositoryTest.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using NUnit.Framework;
using OaiPmhNet.Converters;
using OaiPmhNet.Models;
using OaiPmhNet.Test.Converters;

namespace OaiPmhNet.Test
{
    [TestFixture]
    public class SampleRecordRepositoryTest
    {
        private SampleRecordRepository _repository;

        public static SampleRecordRepository Create()
        {
            IList<SampleRecord> records = new List<SampleRecord>()
            {
                new SampleRecord()
                {
                    Id = 11,
                    Date = new DateTime(2018, 2, 16, 1, 0, 0, DateTimeKind.Utc),
                    Title = "Title 1",
                    Owner = "Owner 1",
                    Contributors = new List<string>() { "Contributor 1", "Contributor 1.1" }
                },
                new SampleRecord()
                {
                    Id = 13,
                    Date = new DateTime(2018, 2, 16, 3, 0, 0, DateTimeKind.Utc),
                    Title = "Title 3"
                },
                new SampleRecord()
                {
                    Id = 12,
                    Date = new DateTime(2018, 2, 16, 2, 0, 0, DateTimeKind.Utc),
                    Title = "Title 2",
                    Owner = "Owner 2"
                },
                new SampleRecord()
                {
                    Id = 14,
                    Date = new DateTime(2018, 2, 16, 4, 0, 0, DateTimeKind.Utc),
                    Title = "Title 4",
                    Owner = "Owner 4"
                },
                new SampleRecord()
                {
                    Id = 15,
                    Date = new DateTime(2018, 2, 16, 5, 0, 0, DateTimeKind.Utc),
                    Title = "Title 5",
                    Owner = "Owner 5",
                    Contributors = new List<string>() { "Contributor 5" }
                }
            };

            IOaiConfiguration configuration = OaiConfiguration.Instance;
            DateConverter dateConverter = new DateConverter();
            DublinCoreMetadataConverter dublinCoreMetadataConverter = new DublinCoreMetadataConverter(configuration, dateConverter);

            return new SampleRecordRepository(configuration, records, dateConverter, dublinCoreMetadataConverter);
        }

        [OneTimeSetUp]
        public void Init()
        {
            OaiConfigurationTest.Init();

            _repository = Create();
        }

        [Test]
        public void SampleRecordRepository_GetRecords()
        {
            var expected1 = new RecordContainer()
            {
                Records = new List<Record>()
                {
                    new Record()
                    {
                        Header = new RecordHeader()
                        {
                            Identifier = "11"
                        },
                        Metadata = new RecordMetadata()
                        {
                            Content = XElement.Parse($@"{DublinCoreMetadataConverterTest.OAI_DC_ROOT}
  <dc:title>Title 1</dc:title>
  <dc:creator>Owner 1</dc:creator>
  <dc:contributor>Contributor 1</dc:contributor>
  <dc:contributor>Contributor 1.1</dc:contributor>
  <dc:date>2018-02-16T01:00:00Z</dc:date>
  <dc:identifier>11</dc:identifier>
</oai_dc:dc>")
                        }
                    },
                    new Record()
                    {
                        Header = new RecordHeader()
                        {
                            Identifier = "12"
                        }
                    }
                },
                ResumptionToken = new ResumptionToken()
                {
                    CompleteListSize = 5,
                    Cursor = 0,
                    Custom = new Dictionary<string, string>() { { "offset", "12" } }
                }
            };

            var actual1 = _repository.GetRecords(new ArgumentContainer(OaiVerb.ListRecords.ToString()));

            Assert.IsNotNull(actual1.ResumptionToken);
            Assert.AreEqual(expected1.ResumptionToken.CompleteListSize, actual1.ResumptionToken.CompleteListSize);
            Assert.AreEqual(expected1.ResumptionToken.Cursor, actual1.ResumptionToken.Cursor);
            Assert.AreEqual(expected1.Records.ToList()[0].Header.Identifier, actual1.Records.ToList()[0].Header.Identifier);
            Assert.AreEqual(expected1.Records.ToList()[1].Header.Identifier, actual1.Records.ToList()[1].Header.Identifier);
            Assert.AreEqual(expected1.Records.ToList()[0].Metadata.Content.ToString(), actual1.Records.ToList()[0].Metadata.Content.ToString());
            Assert.AreEqual(expected1.ResumptionToken.Custom["offset"], actual1.ResumptionToken.Custom["offset"]);

            var expected2 = new RecordContainer()
            {
                Records = new List<Record>()
                {
                    new Record()
                    {
                        Header = new RecordHeader()
                        {
                            Identifier = "13"
                        }
                    },
                    new Record()
                    {
                        Header = new RecordHeader()
                        {
                            Identifier = "14"
                        }
                    }
                },
                ResumptionToken = new ResumptionToken()
                {
                    CompleteListSize = 5,
                    Cursor = 2,
                    Custom = new Dictionary<string, string>() { { "offset", "14" } }
                }
            };

            var actual2 = _repository.GetRecords(new ArgumentContainer(OaiVerb.ListRecords.ToString()), actual1.ResumptionToken);

            Assert.IsNotNull(actual2.ResumptionToken);
            Assert.AreEqual(expected2.ResumptionToken.CompleteListSize, actual2.ResumptionToken.CompleteListSize);
            Assert.AreEqual(expected2.ResumptionToken.Cursor, actual2.ResumptionToken.Cursor);
            Assert.AreEqual(expected2.Records.ToList()[0].Header.Identifier, actual2.Records.ToList()[0].Header.Identifier);
            Assert.AreEqual(expected2.Records.ToList()[1].Header.Identifier, actual2.Records.ToList()[1].Header.Identifier);
            Assert.AreEqual(expected2.ResumptionToken.Custom["offset"], actual2.ResumptionToken.Custom["offset"]);
        }
    }
}


<|start_filename|>OaiPmhNet/Models/MetadataFormat.cs<|end_filename|>
﻿using System.Xml.Linq;

namespace OaiPmhNet.Models
{
    public class MetadataFormat
    {
        public string Prefix { get; private set; }

        public XNamespace Namespace { get; private set; }

        public XNamespace Schema { get; private set; }

        public XNamespace SchemaLocation { get; private set; }

        public MetadataFormat(string prefix, XNamespace ns, XNamespace schema, XNamespace schemaLocation)
        {
            Prefix = prefix;
            Namespace = ns;
            Schema = schema;
            SchemaLocation = schemaLocation;
        }
    }
}


<|start_filename|>OaiPmhNet/Models/ResumptionToken.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;

namespace OaiPmhNet.Models
{
    public class ResumptionToken : IResumptionToken
    {
        public DateTime? From { get; set; }
        public DateTime? Until { get; set; }
        public string MetadataPrefix { get; set; }
        public string Set { get; set; }
        public DateTime? ExpirationDate { get; set; }
        public int? CompleteListSize { get; set; }
        public int? Cursor { get; set; }
        public IDictionary<string, string> Custom { get; set; } = new Dictionary<string, string>();
    }
}


<|start_filename|>OaiPmhNet/Converters/DateConverter.cs<|end_filename|>
﻿using System;
using System.Globalization;

namespace OaiPmhNet.Converters
{
    public sealed class DateConverter : IDateConverter
    {
        public bool TryDecode(string date, out DateTime dateTime)
        {
            if (DateTimeOffset.TryParse(date, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out DateTimeOffset offset))
            {
                dateTime = offset.UtcDateTime;
                return true;
            }
            dateTime = DateTime.MinValue.ToUniversalTime();
            return false;
        }

        public string Encode(string granularity, DateTime? date)
        {
            if (date.HasValue)
                return date.Value.ToUniversalTime().ToString(granularity);
            else
                return string.Empty;
        }
    }
}


<|start_filename|>OaiPmhNet/OaiArgument.cs<|end_filename|>
﻿using System;

namespace OaiPmhNet
{
    [Flags]
    public enum OaiArgument
    {
        None = 0,
        MetadataPrefix = 1,
        ResumptionToken = 2,
        Identifier = 4,
        From = 8,
        Until = 16,
        Set = 32
    }
}


<|start_filename|>OaiPmhNet/IMetadataFormatRepository.cs<|end_filename|>
﻿using System.Collections.Generic;
using OaiPmhNet.Models;

namespace OaiPmhNet
{
    /// <summary>
    /// The interface representing the repository for metadata formats.
    /// </summary>
    public interface IMetadataFormatRepository
    {
        /// <summary>
        /// Gets the metadata format corresponding to the provided prefix.
        /// </summary>
        /// <param name="prefix">The prefix to specify the metadata format.</param>
        /// <returns>The metadata format corresponding to the provided prefix.</returns>
        MetadataFormat GetMetadataFormat(string prefix);

        /// <summary>
        /// Gets a list of available metadata formats.
        /// </summary>
        /// <returns>A list of available metadata formats.</returns>
        IEnumerable<MetadataFormat> GetMetadataFormats();
    }
}


<|start_filename|>OaiPmhNet.Test/SampleMetadataFormatRepositoryTest.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using OaiPmhNet.Models;

namespace OaiPmhNet.Test
{
    [TestFixture]
    public class SampleMetadataFormatRepositoryTest
    {
        private SampleMetadataFormatRepository _repository;

        public static SampleMetadataFormatRepository Create()
        {
            IList<MetadataFormat> metadataFormats = new List<MetadataFormat>()
            {
                new MetadataFormat(
                    "oai_dc",
                    OaiNamespaces.OaiDcNamespace,
                    OaiNamespaces.OaiDcSchema,
                    OaiNamespaces.OaiDcSchemaLocation),
                new MetadataFormat(
                    "rdf",
                    "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
                    "http://www.openarchives.org/OAI/2.0/rdf.xsd",
                    "http://www.w3.org/1999/02/22-rdf-syntax-ns# http://www.openarchives.org/OAI/2.0/rdf.xsd")
            };
            return new SampleMetadataFormatRepository(metadataFormats);
        }

        [OneTimeSetUp]
        public void Init()
        {
            OaiConfigurationTest.Init();

            _repository = Create();
        }

        [Test]
        public void SampleMetadataFormatRepository_GetByPrefix()
        {
            var expected = new MetadataFormat(
                    "oai_dc",
                    OaiNamespaces.OaiDcNamespace,
                    OaiNamespaces.OaiDcSchema,
                    OaiNamespaces.OaiDcSchemaLocation);

            var actual = _repository.GetMetadataFormat("oai_dc");

            Assert.AreEqual(expected.Prefix, actual.Prefix);
            Assert.AreEqual(expected.Namespace, actual.Namespace);
            Assert.AreEqual(expected.Schema, actual.Schema);
            Assert.AreEqual(expected.SchemaLocation, actual.SchemaLocation);
        }

        [Test]
        public void SampleMetadataFormatRepository_GetQuery()
        {
            var expected = 2;

            var actual = _repository.GetMetadataFormats().Count();

            Assert.AreEqual(expected, actual);
        }
    }
}


<|start_filename|>OaiPmhNet/Models/IResumptionToken.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;

namespace OaiPmhNet.Models
{
    public interface IResumptionToken
    {
        // A UTC datetime value, which specifies a lower bound for datestamp-based selective harvesting.
        DateTime? From { get; }

        // A UTC datetime value, which specifies a upper bound for datestamp-based selective harvesting.
        DateTime? Until { get; }

        // A required argument, which specifies that headers should be returned 
        // only if the metadata format matching the supplied metadataPrefix is available or, 
        // depending on the repository's support for deletions, has been deleted. 
        string MetadataPrefix { get; }

        // An optional argument which specifies set criteria for selective harvesting.
        string Set { get; }

        // A UTC datetime indicating when the resumptionToken ceases to be valid.
        DateTime? ExpirationDate { get; }

        // An integer indicating the cardinality of the complete list 
        // (i.e., the sum of the cardinalities of the incomplete lists).
        int? CompleteListSize { get; }

        // A count of the number of elements of the complete list thus far returned 
        // (i.e. cursor starts at 0).
        int? Cursor { get; }

        // Optional custom parameters not part of the specification.
        IDictionary<string, string> Custom { get; }
    }
}


<|start_filename|>OaiPmhNet/OaiNamespaces.cs<|end_filename|>
﻿using System.Xml.Linq;

namespace OaiPmhNet
{
    public class OaiNamespaces
    {
        // Common
        public static XNamespace XsiNamespace = "http://www.w3.org/2001/XMLSchema-instance";

        // OAI
        public static XNamespace OaiNamespace = "http://www.openarchives.org/OAI/2.0/";
        public static XNamespace OaiSchema = "http://www.openarchives.org/OAI/2.0/OAI-PMH.xsd";
        public static XNamespace OaiSchemaLocation = "http://www.openarchives.org/OAI/2.0/ " +
                                                     "http://www.openarchives.org/OAI/2.0/OAI-PMH.xsd";
        public static XNamespace OaiStaticRepositoryNamespace = "http://www.openarchives.org/OAI/2.0/static-repository";

        // Dublin Core
        public static XNamespace DcNamespace = "http://purl.org/dc/elements/1.1/";
        public static XNamespace OaiDcNamespace = "http://www.openarchives.org/OAI/2.0/oai_dc/";
        public static XNamespace OaiDcSchema = "http://www.openarchives.org/OAI/2.0/oai_dc.xsd";
        public static XNamespace OaiDcSchemaLocation = "http://www.openarchives.org/OAI/2.0/oai_dc/ " +
                                                       "http://www.openarchives.org/OAI/2.0/oai_dc.xsd";
    }
}


<|start_filename|>OaiPmhNet/Converters/IResumptionTokenConverter.cs<|end_filename|>
﻿using System.Xml.Linq;
using OaiPmhNet.Models;

namespace OaiPmhNet.Converters
{
    public interface IResumptionTokenConverter
    {
        IResumptionToken Decode(string resumptionToken);

        string Encode(IResumptionToken resumptionToken);

        XElement ToXElement(IResumptionToken resumptionToken);
    }
}


<|start_filename|>OaiPmhNet/Models/SetContainer.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;

namespace OaiPmhNet.Models
{
    public class SetContainer
    {
        public IEnumerable<Set> Sets { get; set; } = Enumerable.Empty<Set>();

        public IResumptionToken ResumptionToken { get; set; }
    }
}


<|start_filename|>OaiPmhNet/ISetRepository.cs<|end_filename|>
﻿using OaiPmhNet.Models;

namespace OaiPmhNet
{
    /// <summary>
    /// The interface representing the repository for sets.
    /// </summary>
    public interface ISetRepository
    {
        /// <summary>
        /// Gets an incomplete list of sets corresponding to the provided arguments and resumption token.
        /// </summary>
        /// <param name="arguments">The OAI-PMH arguments.</param>
        /// <param name="resumptionToken">The optional token used for resumption.</param>
        /// <returns>An incomplete list of sets corresponding to the provided arguments and resumption token.</returns>
        SetContainer GetSets(ArgumentContainer arguments, IResumptionToken resumptionToken = null);
    }
}


<|start_filename|>OaiPmhNet/OaiConfiguration.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;

namespace OaiPmhNet
{
    public class OaiConfiguration : IOaiConfiguration
    {
        private static volatile IOaiConfiguration instance;
        private static readonly object syncRoot = new object();

        private OaiConfiguration()
        {

        }

        /// <summary>
        /// The singleton instance.
        /// </summary>
        public static IOaiConfiguration Instance
        {
            get
            {
                if (instance == null)
                {
                    lock (syncRoot)
                    {
                        if (instance == null)
                            instance = new OaiConfiguration();
                    }
                }

                return instance;
            }
        }

        #region Identify configuration

        public string RepositoryName { get; set; } = "MyRepository";

        public Func<string> BaseUrl { get; set; } = () => { return "http://localhost/oai2"; };

        public string ProtocolVersion { get; set; } = "2.0";

        public string EarliestDatestamp { get; set; } = "1987-02-16T00:00:00Z";

        public string DeletedRecord { get; set; } = "no";

        public string Granularity { get; set; } = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'";

        public string[] AdminEmails { get; set; } = new string[] { "<EMAIL>" };

        public string[] Compressions { get; set; } = new string[] { };

        public string[] Descriptions { get; set; } = new string[] { };

        #endregion

        #region Data Provider configuration

        public int PageSize { get; set; } = 10;

        public bool SupportSets { get; set; } = false;

        public TimeSpan? ExpirationTimeSpan { get; set; }

        #endregion

        #region Custom configuration

        public ISet<string> ResumptionTokenCustomParameterNames { get; set; } = new HashSet<string>();

        #endregion
    }
}


<|start_filename|>OaiPmhNet/Models/RecordHeader.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace OaiPmhNet.Models
{
    public class RecordHeader
    {
        /// <summary>
        /// The unique identifier of an item in a repository.
        /// </summary>
        public string Identifier { get; set; }

        /// <summary>
        /// The date of creation, modification or deletion of the record for the purpose of selective harvesting.
        /// </summary>
        public DateTime? Datestamp { get; set; }

        /// <summary>
        /// The set memberships of the item for the purpose of selective harvesting.
        /// </summary>
        public IEnumerable<string> SetSpecs { get; set; } = Enumerable.Empty<string>();

        /// <summary>
        /// Optional with a value of deleted indicating the withdrawal of availability of the specified metadata 
        /// format for the item, dependent on the repository support for deletions.
        /// </summary>
        public string Status { get; set; }
    }
}


<|start_filename|>OaiPmhNet.Test/Converters/DublinCoreMetadataConverterTest.cs<|end_filename|>
﻿using System;
using System.Xml.Linq;
using NUnit.Framework;
using OaiPmhNet.Converters;
using OaiPmhNet.Models;

namespace OaiPmhNet.Test.Converters
{
    [TestFixture]
    public class DublinCoreMetadataConverterTest
    {
        private DublinCoreMetadataConverter _converter;
        internal const string OAI_DC_ROOT = @"<oai_dc:dc xmlns:oai_dc=""http://www.openarchives.org/OAI/2.0/oai_dc/"" xmlns:dc=""http://purl.org/dc/elements/1.1/"" xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xsi:schemaLocation=""http://www.openarchives.org/OAI/2.0/oai_dc/ http://www.openarchives.org/OAI/2.0/oai_dc.xsd"">";

        [OneTimeSetUp]
        public void Init()
        {
            OaiConfigurationTest.Init();
            _converter = new DublinCoreMetadataConverter(OaiConfiguration.Instance, new DateConverter());
        }

        [Test]
        public void DublinCoreMetadataConverter_Decode()
        {
            DublinCoreMetadata expected = new DublinCoreMetadata();
            expected.Title.Add("Title1");
            expected.Title.Add("Title2");
            expected.Creator.Add("Creator 1");
            expected.Creator.Add("Creator 2");
            expected.Date.Add(new DateTime(1987, 2, 16, 0, 0, 0, DateTimeKind.Utc));

            XElement metadata = XElement.Parse($@"{OAI_DC_ROOT}
  <dc:title>Title1</dc:title>
  <dc:title>Title2</dc:title>
  <dc:creator>Creator 1</dc:creator>
  <dc:creator>Creator 2</dc:creator>
  <dc:date>1987-02-16T00:00:00Z</dc:date>
</oai_dc:dc>");

            DublinCoreMetadata actual = _converter.Decode(metadata);

            Assert.AreEqual(expected.Title.Count, actual.Title.Count);
            Assert.AreEqual(expected.Title[0], actual.Title[0]);
            Assert.AreEqual(expected.Title[1], actual.Title[1]);
            Assert.AreEqual(expected.Creator[0], actual.Creator[0]);
            Assert.AreEqual(expected.Creator[1], actual.Creator[1]);
            Assert.AreEqual(expected.Date[0], actual.Date[0]);
        }

        [Test]
        public void DublinCoreMetadataConverter_Encode()
        {
            string expected = $@"{OAI_DC_ROOT}
  <dc:title>Title1</dc:title>
  <dc:title>Title2</dc:title>
  <dc:creator>Creator 1</dc:creator>
  <dc:creator>Creator 2</dc:creator>
  <dc:date>1987-02-16T00:00:00Z</dc:date>
</oai_dc:dc>";

            DublinCoreMetadata metadata = new DublinCoreMetadata();
            metadata.Title.Add("Title1");
            metadata.Title.Add("Title2");
            metadata.Creator.Add("Creator 1");
            metadata.Creator.Add("Creator 2");
            metadata.Date.Add(new DateTime(1987, 2, 16, 0, 0, 0, DateTimeKind.Utc));

            string actual = _converter.Encode(metadata).ToString();

            Assert.AreEqual(expected, actual);
        }
    }
}


<|start_filename|>OaiPmhNet.Test/SampleRecord.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;

namespace OaiPmhNet.Test
{
    public class SampleRecord
    {
        public int Id { get; set; }
        public DateTime Date { get; set; }
        public string Title { get; set; }
        public string Owner { get; set; }
        public IList<string> Contributors { get; set; } = new List<string>();
    }
}


<|start_filename|>OaiPmhNet/Models/RecordMetadata.cs<|end_filename|>
﻿using System.Xml.Linq;

namespace OaiPmhNet.Models
{
    public class RecordMetadata
    {
        public XElement Content { get; set; }
    }
}


<|start_filename|>OaiPmhNet.Test/Converters/ResumptionTokenConverterTest.cs<|end_filename|>
﻿using System;
using NUnit.Framework;
using OaiPmhNet.Converters;
using OaiPmhNet.Models;

namespace OaiPmhNet.Test.Converters
{
    [TestFixture]
    public class ResumptionTokenConverterTest
    {
        private IResumptionTokenConverter _converter;

        [OneTimeSetUp]
        public void Init()
        {
            OaiConfigurationTest.Init();
            _converter = new ResumptionTokenConverter(OaiConfiguration.Instance, new DateConverter());
        }

        [Test]
        public void ResumptionTokenConverter_Decode()
        {
            string token = "from=198<PASSWORD>-16T00%3a00%3a00Z&until=2018-02-16T00%3a00%3a00Z&metadataPrefix=oai_dc" +
                "&set=test123&expirationDate=2019-02-16T00%3a00%3a00Z&completeListSize=123&cursor=321&placeholder=123&test=123";

            IResumptionToken actual = _converter.Decode(token);
            IResumptionToken expected = new ResumptionToken()
            {
                From = new DateTime(1987, 2, 16, 0, 0, 0, DateTimeKind.Utc),
                Until = new DateTime(2018, 2, 16, 0, 0, 0, DateTimeKind.Utc),
                MetadataPrefix = "oai_dc",
                Set = "test123",
                ExpirationDate = new DateTime(2019, 2, 16, 0, 0, 0, DateTimeKind.Utc),
                CompleteListSize = 123,
                Cursor = 321
            };
            expected.Custom["placeholder"] = "123";

            Assert.AreEqual(expected.From.Value.Kind, actual.From.Value.Kind);
            Assert.AreEqual(expected.From, actual.From);
            Assert.AreEqual(expected.Until.Value.Kind, actual.Until.Value.Kind);
            Assert.AreEqual(expected.Until, actual.Until);
            Assert.AreEqual(expected.MetadataPrefix, actual.MetadataPrefix);
            Assert.AreEqual(expected.Set, actual.Set);
            Assert.AreEqual(expected.ExpirationDate.Value.Kind, actual.ExpirationDate.Value.Kind);
            Assert.AreEqual(expected.ExpirationDate, actual.ExpirationDate);
            Assert.AreEqual(expected.CompleteListSize, actual.CompleteListSize);
            Assert.AreEqual(expected.Cursor, actual.Cursor);
            Assert.AreEqual(expected.Custom["placeholder"], actual.Custom["placeholder"]);
            Assert.IsFalse(actual.Custom.ContainsKey("test"));
        }

        [Test]
        public void ResumptionTokenConverter_Encode()
        {
            ResumptionToken token = new ResumptionToken()
            {
                From = new DateTime(1987, 2, 16, 0, 0, 0, DateTimeKind.Utc),
                Until = new DateTime(2018, 2, 16, 0, 0, 0, DateTimeKind.Utc),
                MetadataPrefix = "oai_dc",
                Set = "test123",
                ExpirationDate = new DateTime(2019, 2, 16, 0, 0, 0, DateTimeKind.Utc),
                CompleteListSize = 123,
                Cursor = 321
            };
            token.Custom.Add("placeholder", "123");
            token.Custom.Add("test", "123");

            string actual = _converter.Encode(token);
            string expected = "metadataPrefix%3doai_dc%26from%3d1987-02-16T00%3a00%3a00Z%26until%3d2018-02-16T00%3a00%3a00Z%26" +
                "set%3dtest123%26placeholder%3d123";

            Assert.AreEqual(expected, actual);
        }

        [Test]
        public void ResumptionTokenConverter_ToXElement()
        {
            ResumptionToken token = new ResumptionToken()
            {
                From = new DateTime(1987, 2, 16, 0, 0, 0, DateTimeKind.Utc),
                Until = new DateTime(2018, 2, 16, 0, 0, 0, DateTimeKind.Utc),
                MetadataPrefix = "oai_dc",
                Set = "test123",
                ExpirationDate = new DateTime(2019, 2, 16, 0, 0, 0, DateTimeKind.Utc),
                CompleteListSize = 123,
                Cursor = 321
            };
            token.Custom.Add("placeholder", "123");
            token.Custom.Add("test", "123");

            var actual = _converter.ToXElement(token).ToString();
            var expected = "<resumptionToken expirationDate=\"2019-02-16T00:00:00Z\" completeListSize=\"123\" cursor=\"321\">" +
                "metadataPrefix%3doai_dc%26from%3d1987-02-16T00%3a00%3a00Z%26until%3d2018-02-16T00%3a00%3a00Z%26set%3dtest123%26placeholder%3d123</resumptionToken>";

            Assert.AreEqual(expected, actual);
        }
    }
}


<|start_filename|>OaiPmhNet/IRecordRepository.cs<|end_filename|>
﻿using OaiPmhNet.Models;

namespace OaiPmhNet
{
    /// <summary>
    /// The interface representing the repository for records.
    /// </summary>
    public interface IRecordRepository
    {
        /// <summary>
        /// Gets the record corresponding to the provided identifier.
        /// </summary>
        /// <param name="identifier">The identifier to specify the record.</param>
        /// <param name="metadataPrefix">The prefix to specify the metadata format.</param>
        /// <returns>The record corresponding to the provided identifier and prefix.</returns>
        Record GetRecord(string identifier, string metadataPrefix);

        /// <summary>
        /// Gets an incomplete list of records corresponding to the provided arguments and resumption token.
        /// </summary>
        /// <param name="arguments">The OAI-PMH arguments.</param>
        /// <param name="resumptionToken">The optional token used for resumption.</param>
        /// <returns>An incomplete list of records corresponding to the provided arguments and resumption token.</returns>
        RecordContainer GetRecords(ArgumentContainer arguments, IResumptionToken resumptionToken = null);

        /// <summary>
        /// Gets an incomplete list of identifiers corresponding to the provided arguments and resumption token.
        /// </summary>
        /// <param name="arguments">The OAI-PMH arguments.</param>
        /// <param name="resumptionToken">The optional token used for resumption.</param>
        /// <returns>An incomplete list of identifiers corresponding to the provided arguments and resumption token.</returns>
        RecordContainer GetIdentifiers(ArgumentContainer arguments, IResumptionToken resumptionToken = null);
    }
}


<|start_filename|>OaiPmhNet/Models/DublinCoreMetadata.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;

namespace OaiPmhNet.Models
{
    /// <summary>
    /// Dublin Core Metadata Element Set, Version 1.1
    /// http://dublincore.org/documents/dces/
    /// </summary>
    public class DublinCoreMetadata
    {
        public IList<string> Title { get; set; } = new List<string>();
        public IList<string> Creator { get; set; } = new List<string>();
        public IList<string> Subject { get; set; } = new List<string>();
        public IList<string> Description { get; set; } = new List<string>();
        public IList<string> Publisher { get; set; } = new List<string>();
        public IList<string> Contributor { get; set; } = new List<string>();
        public IList<DateTime> Date { get; set; } = new List<DateTime>();
        public IList<string> Type { get; set; } = new List<string>();
        public IList<string> Format { get; set; } = new List<string>();
        public IList<string> Identifier { get; set; } = new List<string>();
        public IList<string> Source { get; set; } = new List<string>();
        public IList<string> Language { get; set; } = new List<string>();
        public IList<string> Relation { get; set; } = new List<string>();
        public IList<string> Coverage { get; set; } = new List<string>();
        public IList<string> Rights { get; set; } = new List<string>();
    }
}


<|start_filename|>OaiPmhNet/Converters/IDateConverter.cs<|end_filename|>
﻿using System;

namespace OaiPmhNet.Converters
{
    public interface IDateConverter
    {
        bool TryDecode(string date, out DateTime dateTime);

        string Encode(string granularity, DateTime? date);
    }
}


<|start_filename|>OaiPmhNet/IOaiConfiguration.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;

namespace OaiPmhNet
{
    public interface IOaiConfiguration
    {
        #region Identify configuration

        /// <summary>
        /// A human readable name for the repository.
        /// </summary>
        string RepositoryName { get; set; }

        /// <summary>
        /// The base URL of the repository.
        /// </summary>
        Func<string> BaseUrl { get; set; }

        /// <summary>
        /// The version of the OAI-PMH supported by the repository.
        /// </summary>
        string ProtocolVersion { get; set; }

        /// <summary>
        /// A UTCdatetime that is the guaranteed lower limit of all datestamps recording changes, modifications, or deletions in the repository.
        /// </summary>
        string EarliestDatestamp { get; set; }

        /// <summary>
        /// The manner in which the repository supports the notion of deleted records.
        /// </summary>
        string DeletedRecord { get; set; }

        /// <summary>
        /// The finest harvesting granularity supported by the repository. 
        /// </summary>
        string Granularity { get; set; }

        /// <summary>
        /// The e-mail addresses of the administrators of the repository.
        /// </summary>
        string[] AdminEmails { get; set; }

        /// <summary>
        /// Compression encodings supported by the repository.
        /// </summary>
        string[] Compressions { get; set; }

        /// <summary>
        /// An extensible mechanism for communities to describe their repositories.
        /// </summary>
        string[] Descriptions { get; set; }

        #endregion

        #region Data Provider configuration

        /// <summary>
        /// A numeric value indicating the page size used for pagination.
        /// </summary>
        int PageSize { get; set; }

        /// <summary>
        /// A boolean value indicating whether sets are supported.
        /// </summary>
        bool SupportSets { get; set; }

        /// <summary>
        /// An optional time interval indicating when the resumption token ceases to be valid.
        /// </summary>
        TimeSpan? ExpirationTimeSpan { get; set; }

        #endregion

        #region Custom configuration

        /// <summary>
        /// Optional custom parameter names in the resumption token.
        /// </summary>
        ISet<string> ResumptionTokenCustomParameterNames { get; set; }

        #endregion
    }
}


<|start_filename|>OaiPmhNet.Test/SampleSetRepository.cs<|end_filename|>
﻿using OaiPmhNet.Converters;
using OaiPmhNet.Models;
using System.Collections.Generic;
using System.Linq;

namespace OaiPmhNet.Test
{
    public class SampleSetRepository : ISetRepository
    {
        private readonly IOaiConfiguration _configuration;
        private readonly IList<Set> _sets;

        public SampleSetRepository(IOaiConfiguration configuration, IList<Set> sets)
        {
            _configuration = configuration;
            _sets = sets;
        }

        public SetContainer GetSets(ArgumentContainer arguments, IResumptionToken resumptionToken = null)
        {
            SetContainer container = new SetContainer();

            IQueryable<Set> sets = _sets.AsQueryable().OrderBy(s => s.Name);

            int totalCount = sets.Count();

            container.Sets = sets.Take(_configuration.PageSize);

            return container;
        }
    }
}


<|start_filename|>OaiPmhNet/Converters/IDublinCoreMetadataConverter.cs<|end_filename|>
﻿using OaiPmhNet.Models;

namespace OaiPmhNet.Converters
{
    public interface IDublinCoreMetadataConverter : IMetadataConverter<DublinCoreMetadata>
    {
    }
}


<|start_filename|>OaiPmhNet/Models/Set.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;

namespace OaiPmhNet.Models
{
    public class Set
    {
        /// <summary>
        /// A colon [:] separated list indicating the path from the root of the set hierarchy to the respective node.
        /// </summary>
        public string Spec { get; set; }

        /// <summary>
        /// A short human-readable string naming the set.
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// An optional description of the set.
        /// </summary>
        public string Description { get; set; }

        /// <summary>
        /// An optional and repeatable container that may hold community-specific XML-encoded data about the set.
        /// </summary>
        public IEnumerable<XElement> AdditionalDescriptions { get; set; } = Enumerable.Empty<XElement>();
    }
}


<|start_filename|>OaiPmhNet/OaiVerb.cs<|end_filename|>
﻿namespace OaiPmhNet
{
    public enum OaiVerb
    {
        None,
        Identify,
        ListMetadataFormats,
        ListRecords,
        ListIdentifiers,
        GetRecord,
        ListSets
    }
}


<|start_filename|>OaiPmhNet.Test/OaiErrorsTest.cs<|end_filename|>
﻿using System.Xml.Linq;
using NUnit.Framework;
using OaiPmhNet.Models;

namespace OaiPmhNet.Test
{
    [TestFixture]
    public class OaiErrorsTest
    {
        [TestCase(true, null, OaiArgument.None)]
        [TestCase(true, null, OaiArgument.MetadataPrefix, "metadataPrefix123")]
        [TestCase(true, null, OaiArgument.MetadataPrefix | OaiArgument.ResumptionToken, "metadataPrefix123", "token123")]
        [TestCase(false, @"<error code=""badArgument"">The request includes a 'metadataPrefix' argument that is not allowed for this verb.</error>", OaiArgument.None, "metadataPrefix123")]
        [TestCase(false, @"<error code=""badArgument"">The request includes a 'resumptionToken' argument that is not allowed for this verb.</error>", OaiArgument.MetadataPrefix, null, "token123")]
        [TestCase(false, @"<error code=""badArgument"">The request includes a 'identifier' argument that is not allowed for this verb.</error>", OaiArgument.MetadataPrefix | OaiArgument.ResumptionToken, null, null, "identifier123")]
        public void OaiErrors_ValidateArguments(bool expected, string expectedElement, OaiArgument allowedArguments, 
            string metadataPrefix = null,
            string resumptionToken = null,
            string identifier = null,
            string from = null,
            string until = null,
            string set = null)
        {
            var arguments = new ArgumentContainer(OaiVerb.None.ToString(), metadataPrefix, resumptionToken, identifier, from, until, set);
            var actual = OaiErrors.ValidateArguments(arguments, allowedArguments, out XElement errorElement);

            Assert.AreEqual(expected, actual);
            Assert.AreEqual(expectedElement, errorElement?.ToString());
        }
    }
}


<|start_filename|>OaiPmhNet.Test/OaiConfigurationTest.cs<|end_filename|>
﻿using NUnit.Framework;

namespace OaiPmhNet.Test
{
    [TestFixture]
    public class OaiConfigurationTest
    {
        [OneTimeSetUp]
        public static void Init()
        {
            var config = OaiConfiguration.Instance;

            config.BaseUrl = () => { return "http://localhost/test"; };
            config.PageSize = 2;
            config.SupportSets = true;
            config.ResumptionTokenCustomParameterNames.Clear();
            config.ResumptionTokenCustomParameterNames.Add("placeholder");
        }
    }
}


<|start_filename|>OaiPmhNet.Test/SampleRecordRepository.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Linq;
using OaiPmhNet.Converters;
using OaiPmhNet.Models;

namespace OaiPmhNet.Test
{
    public class SampleRecordRepository : IRecordRepository
    {
        private readonly IOaiConfiguration _configuration;
        private readonly IList<SampleRecord> _records;
        private readonly IDateConverter _dateConverter;
        private readonly IDublinCoreMetadataConverter _dublinCoreMetadataConverter;

        public SampleRecordRepository(IOaiConfiguration configuration, IList<SampleRecord> records, 
            IDateConverter dateConverter, IDublinCoreMetadataConverter dublinCoreMetadataConverter)
        {
            _configuration = configuration;
            _records = records;
            _dateConverter = dateConverter;
            _dublinCoreMetadataConverter = dublinCoreMetadataConverter;
        }

        public Record GetRecord(string identifier, string metadataPrefix)
        {
            if (int.TryParse(identifier, out int parsedIdentifier))
                return ToRecord(_records.FirstOrDefault(r => r.Id == parsedIdentifier));
            else
                return null;
        }

        public RecordContainer GetRecords(ArgumentContainer arguments, IResumptionToken resumptionToken = null)
        {
            RecordContainer container = new RecordContainer();

            IQueryable<SampleRecord> records = _records.AsQueryable().OrderBy(r => r.Id);

            int totalCount = records.Count();

            if (resumptionToken != null)
            {
                if (resumptionToken.From.HasValue)
                    records = records.Where(r => r.Date >= resumptionToken.From.Value);
                if (resumptionToken.Until.HasValue)
                    records = records.Where(r => r.Date <= resumptionToken.Until.Value);
                if (resumptionToken.Custom.ContainsKey("offset") && resumptionToken.Custom.TryGetValue("offset", out string offset))
                {
                    if (int.TryParse(offset, out int parsedOffset))
                        records = records.Where(r => r.Id > parsedOffset);
                }
            }

            ResumptionToken newResumptionToken = null;

            if (records.Count() > _configuration.PageSize)
            {
                newResumptionToken = new ResumptionToken();
                container.ResumptionToken = newResumptionToken;

                if (_dateConverter.TryDecode(arguments.From, out DateTime from))
                    newResumptionToken.From = from;
                if (_dateConverter.TryDecode(arguments.Until, out DateTime until))
                    newResumptionToken.Until = until;
                newResumptionToken.MetadataPrefix = arguments.MetadataPrefix;
                newResumptionToken.Set = arguments.Set;
                if (_configuration.ExpirationTimeSpan.HasValue)
                    newResumptionToken.ExpirationDate = DateTime.UtcNow.Add(_configuration.ExpirationTimeSpan.Value);
                newResumptionToken.CompleteListSize = totalCount;
                newResumptionToken.Cursor = 0;
            }

            records = records.Take(_configuration.PageSize);

            if (newResumptionToken != null)
            {
                if (resumptionToken != null)
                {
                    // Increase the cursor value
                    newResumptionToken.Cursor = resumptionToken.Cursor.HasValue ?
                        resumptionToken.Cursor.Value + _configuration.PageSize : _configuration.PageSize;
                }

                // Add custom offset value
                var lastRecord = records.LastOrDefault();
                if (lastRecord != null)
                    newResumptionToken.Custom.Add("offset", lastRecord.Id.ToString());
            }

            container.Records = records.Select(r => ToRecord(r));

            return container;
        }

        public RecordContainer GetIdentifiers(ArgumentContainer arguments, IResumptionToken resumptionToken = null)
        {
            return GetRecords(arguments, resumptionToken);
        }

        #region private methods

        private Record ToRecord(SampleRecord sampleRecord)
        {
            if (sampleRecord == null)
                return null;

            return new Record()
            {
                Header = new RecordHeader()
                {
                    Identifier = sampleRecord.Id.ToString(),
                    Datestamp = sampleRecord.Date
                },
                Metadata = new RecordMetadata()
                {
                    Content = _dublinCoreMetadataConverter.Encode(new DublinCoreMetadata()
                    {
                        Title = new List<string>() { sampleRecord.Title },
                        Creator = new List<string>() { sampleRecord.Owner },
                        Contributor = sampleRecord.Contributors,
                        Date = new List<DateTime>() { sampleRecord.Date },
                        Identifier = new List<string> { sampleRecord.Id.ToString() }
                    })
                }
            };
        }

        #endregion
    }
}


<|start_filename|>OaiPmhNet/Models/Record.cs<|end_filename|>
﻿namespace OaiPmhNet.Models
{
    public class Record
    {
        public RecordHeader Header { get; set; }

        public RecordMetadata Metadata { get; set; }
    }
}


<|start_filename|>OaiPmhNet/Converters/IMetadataConverter.cs<|end_filename|>
﻿using System.Xml.Linq;

namespace OaiPmhNet.Converters
{
    public interface IMetadataConverter<T>
    {
        T Decode(XElement metadata);
        XElement Encode(T metadata);
    }
}


<|start_filename|>OaiPmhNet/Converters/MetadataConverter.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Xml.Linq;

namespace OaiPmhNet.Converters
{
    public abstract class MetadataConverter<T> : IMetadataConverter<T>
    {
        protected readonly IOaiConfiguration _configuration;
        protected readonly IDateConverter _dateConverter;

        public MetadataConverter(IOaiConfiguration configuration, IDateConverter dateConverter)
        {
            _configuration = configuration;
            _dateConverter = dateConverter;
        }

        public abstract T Decode(XElement metadata);

        public abstract XElement Encode(T metadata);

        public virtual IList<T1> DecodeList<T1>(XElement root, XName element, Func<string, T1> toT1Func)
        {
            IList<T1> decodedList = new List<T1>();

            if (root == null || element == null || toT1Func == null)
                return decodedList;

            foreach (var item in root.Elements(element))
            {
                if (item != null)
                    decodedList.Add(toT1Func(item.Value));
            }

            return decodedList;
        }

        public virtual IList<XElement> EncodeList<T1>(XName name, IList<T1> list, Func<T1, string> toStringFunc = null)
        {
            IList<XElement> encodedList = new List<XElement>();

            if (list == null)
                return encodedList;

            foreach (var item in list)
            {
                if (item != null)
                    encodedList.Add(new XElement(name, toStringFunc == null ? item.ToString() : toStringFunc(item)));
            }

            return encodedList;
        }
    }
}
