Korero – Rev 1

Subversion Repositories:
Rev:
///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2013 - License: GNU GPLv3      //
//  Please see: http://www.gnu.org/licenses/gpl.html for legal details,  //
//  rights of fair usage, the disclaimer and warranty conditions.        //
///////////////////////////////////////////////////////////////////////////

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Korero.Serialization
{
    public class CSV : IReadOnlyCollection<string>
    {
        #region Static Fields and Constants

        /// <summary>
        ///     Special CSV characters.
        /// </summary>
        private static readonly char[] CsvEscapeCharacters = {'"', ' ', ',', '\r', '\n'};

        #endregion

        #region Public Enums, Properties and Fields

        public int Length => Store.Count;

        #endregion

        #region Private Delegates, Events, Enums, Properties, Indexers and Fields

        protected List<string> Store = new List<string>();

        #endregion

        #region Constructors, Destructors and Finalizers

        public CSV(string csv)
        {
            Store = FromStringCSV(csv)
                .ToList();
        }

        public CSV(IEnumerable<string> csv) : this(ToStringCSV(csv))
        {
        }

        public CSV()
        {
        }

        public CSV(IDictionary<string, string> dictionary) : this(FromDictionary(dictionary))
        {
        }

        public CSV(ISet<string> set) : this(set.ToList())
        {
        }

        #endregion

        #region Interface

        public IEnumerator<string> GetEnumerator()
        {
            return Store.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return ((IEnumerable) Store).GetEnumerator();
        }

        public int Count => Store.Count;

        #endregion

        #region Public Overrides

        public override string ToString()
        {
            return ToStringCSV(Store);
        }

        #endregion

        #region Operators

        public static implicit operator string(CSV csv)
        {
            return ToStringCSV(csv);
        }

        public static implicit operator CSV(string @string)
        {
            return new CSV(@string);
        }

        public static implicit operator CSV(Dictionary<string, string> dictionary)
        {
            return new CSV(FromDictionary(dictionary));
        }

        public static implicit operator string[](CSV csv)
        {
            return csv.ToArray();
        }

        public static implicit operator CSV(string[] array)
        {
            return new CSV(array);
        }

        #endregion

        #region Public Methods

        public string[] ToArray()
        {
            return Store.ToArray();
        }

        #endregion

        #region Private Methods

        ///////////////////////////////////////////////////////////////////////////
        //    Copyright (C) 2016 Wizardry and Steamworks - License: GNU GPLv3    //
        ///////////////////////////////////////////////////////////////////////////
        /// <summary>
        ///     Converts a dictionary of strings to a comma-separated values string.
        /// </summary>
        /// <returns>a comma-separated list of values</returns>
        /// <remarks>compliant with RFC 4180</remarks>
        internal static string FromDictionary<TK, TV>(IDictionary<TK, TV> input)
        {
            return string.Join(",",
                input.Keys.Select(o => o.ToString())
                    .Zip(input.Values.Select(o => o.ToString()),
                        (o, p) =>
                            string.Join(",",
                                o.Replace("\"", "\"\"")
                                    .IndexOfAny(CsvEscapeCharacters) ==
                                -1
                                    ? o
                                    : $"\"{o}\"",
                                p.Replace("\"", "\"\"")
                                    .IndexOfAny(CsvEscapeCharacters) ==
                                -1
                                    ? p
                                    : $"\"{p}\"")));
        }

        ///////////////////////////////////////////////////////////////////////////
        //    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    //
        ///////////////////////////////////////////////////////////////////////////
        /// <summary>
        ///     Converts a list of strings to a comma-separated values string.
        /// </summary>
        /// <returns>a comma-separated list of values</returns>
        /// <remarks>compliant with RFC 4180</remarks>
        protected static string ToStringCSV(IEnumerable<string> csv)
        {
            var list = csv.ToList();

            /*
             * This is How the Cookie Crumbles (yes, it follows RFC 4180 as intended
             * but not as designed):
             *
             * The CSV escape symbol is the double-quote and, as per RFC 4180, it
             * the double-quote is used to escape cells by surrounding cells that
             * contain line-breaks, double quotes and commas.
             *
             * The purpose for escaping CSV cells conforming with RFC 4180:
             *   * cells with double-quotes - general protection from clashes
             *     with CSV cell escape symbols but since there is only one single
             *     cell, there are no clashes possible since no other cells precede
             *     or follow.
             *   * cells with commas - the comma is used as a delimiter between
             *     cells, such that if a comma is part of a cell's contents, the
             *     cell must be escaped but since if there is only one single cell,
             *     then there is no reason to escape the cell if it contains a comma.
             *
             */
            if (list.Count == 1)
            {
                return list.Single();
            }

            return string.Join(",",
                list
                    .Select(o => string.IsNullOrEmpty(o) ? string.Empty : o.Replace("\"", "\"\""))
                    .Select(o => o.IndexOfAny(CsvEscapeCharacters) == -1 ? o : $"\"{o}\""));
        }

        ///////////////////////////////////////////////////////////////////////////
        //    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    //
        ///////////////////////////////////////////////////////////////////////////
        /// <summary>
        ///     Converts a comma-separated list of values to a list of strings.
        /// </summary>
        /// <param name="csv">a comma-separated list of values</param>
        /// <returns>a list of strings</returns>
        /// <remarks>compliant with RFC 4180</remarks>
        protected static IEnumerable<string> FromStringCSV(string csv)
        {
            if (string.IsNullOrEmpty(csv))
            {
                yield break;
            }

            var s = new Stack<char>();
            var m = new StringBuilder();

            for (var i = 0; i < csv.Length; ++i)
            {
                switch (csv[i])
                {
                    case ',':

                        if (!s.Any() ||
                            !s.Peek()
                                .Equals('"'))
                        {
                            yield return m.ToString();

                            m = new StringBuilder();

                            continue;
                        }

                        m.Append(csv[i]);

                        continue;

                    case '"':

                        if (i + 1 < csv.Length &&
                            csv[i]
                                .Equals(csv[i + 1]))
                        {
                            m.Append(csv[i]);
                            ++i;

                            continue;
                        }

                        if (!s.Any() ||
                            !s.Peek()
                                .Equals(csv[i]))
                        {
                            s.Push(csv[i]);

                            continue;
                        }

                        s.Pop();

                        continue;
                }

                m.Append(csv[i]);
            }

            yield return m.ToString();
        }

        #endregion
    }
}

Generated by GNU Enscript 1.6.5.90.