QuickImage – 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 QuickImage.Utilities.Serialization.Comma_Separated_Values
{
    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

        private readonly List<string> _store;

        #endregion

        #region Constructors, Destructors and Finalizers

        public Csv(string csv) => _store = FromStringCsv(csv)
            .ToList();

        public Csv(IEnumerable<string> csv) : this(ToStringCsv(csv)) { }

        public Csv() => _store = new List<string>();

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

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

        #endregion

        #region Interface

        public IEnumerator<string> GetEnumerator() => _store.GetEnumerator();

        IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_store).GetEnumerator();

        public int Count => _store.Count;

        #endregion

        #region Public Overrides

        public override string ToString() => ToStringCsv(_store);

        #endregion

        #region Operators

        public static implicit operator string(Csv csv) => ToStringCsv(csv);

        public static implicit operator Csv(string @string) => new Csv(@string);

        public static implicit operator Csv(Dictionary<string, string> dictionary) =>
            new Csv(FromDictionary(dictionary));

        public static implicit operator string[](Csv csv) => csv.ToArray();

        public static implicit operator Csv(string[] array) => new Csv(array);

        #endregion

        #region Public Methods

        public string[] ToArray() => _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>
        private 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>
        private 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>
        private 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.