< Summary

Class:GDX.DataTables.DataBinding.Formats.CommaSeperatedValueFormat
Assembly:GDX
File(s):./Packages/com.dotbunny.gdx/GDX/DataTables/DataBinding/Formats/CommaSeperatedValueFormat.cs
Covered lines:10
Uncovered lines:136
Coverable lines:146
Total lines:278
Line coverage:6.8% (10 of 146)
Covered branches:0
Total branches:0
Covered methods:3
Total methods:15
Method coverage:20% (3 of 15)

Coverage History

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
CommaSeperatedValueFormat()0%110100%
Finalize()0%2100%
GetBindingTimestamp(...)0%2100%
GetFilePreferredExtension()0%2100%
GetFriendlyName()0%110100%
IsFileHeader(...)0%2100%
GetImportDialogExtensions()0%2100%
IsOnDiskFormat()0%110100%
IsUri(...)0%2100%
Pull(...)0%12300%
Push(...)0%6200%
Generate(...)0%56700%
MakeCommaSeperatedValue(...)0%30500%
Parse(...)0%12300%
ParseCommaSeperatedValues(...)0%42600%

File(s)

./Packages/com.dotbunny.gdx/GDX/DataTables/DataBinding/Formats/CommaSeperatedValueFormat.cs

#LineLine coverage
 1// Copyright (c) 2020-2024 dotBunny Inc.
 2// dotBunny licenses this file to you under the BSL-1.0 license.
 3// See the LICENSE file in the project root for more information.
 4
 5using System;
 6using System.Collections.Generic;
 7using System.IO;
 8using System.Runtime.CompilerServices;
 9using System.Text;
 10using UnityEngine;
 11using TextGenerator = GDX.Developer.TextGenerator;
 12
 13namespace GDX.DataTables.DataBinding.Formats
 14{
 15    /// <summary>
 16    ///     A comma-seperated values format.
 17    /// </summary>
 18    class CommaSeperatedValueFormat : FormatBase
 19    {
 120        public CommaSeperatedValueFormat()
 121        {
 22            // We can register the format this way because we automatically include these formats in the
 23            // DataBindingsProvider as private members.
 124            DataBindingProvider.RegisterFormat(this);
 125        }
 26
 27        ~CommaSeperatedValueFormat()
 028        {
 029            DataBindingProvider.UnregisterFormat(this);
 030        }
 31
 32        /// <inheritdoc />
 33        public override DateTime GetBindingTimestamp(string uri)
 034        {
 035            return File.GetLastWriteTimeUtc(Path.Combine(Application.dataPath, uri));
 036        }
 37
 38        /// <inheritdoc />
 39        public override string GetFilePreferredExtension()
 040        {
 041            return "csv";
 042        }
 43
 44        /// <inheritdoc />
 45        public override string GetFriendlyName()
 446        {
 447            return "CSV";
 448        }
 49
 50        /// <inheritdoc />
 51        public override bool IsFileHeader(string header)
 052        {
 053            return header.StartsWith("Row Identifier, Row Name,", StringComparison.OrdinalIgnoreCase);
 054        }
 55
 56        /// <inheritdoc />
 57        public override string[] GetImportDialogExtensions()
 058        {
 059            return new[] { GetFriendlyName(), GetFilePreferredExtension() };
 060        }
 61
 62        /// <inheritdoc />
 63        public override bool IsOnDiskFormat()
 464        {
 465            return true;
 466        }
 67
 68        /// <inheritdoc />
 69        public override bool IsUri(string uri)
 070        {
 071            return uri.EndsWith(GetFilePreferredExtension(), StringComparison.OrdinalIgnoreCase);
 072        }
 73
 74        /// <inheritdoc />
 75        public override SerializableTable Pull(string uri, ulong currentDataVersion, int currentStructuralVersion)
 076        {
 077            if (uri == null || !File.Exists(uri))
 078            {
 079                return null;
 80            }
 81
 082            return Parse(File.ReadAllLines(uri));
 083        }
 84
 85        /// <inheritdoc />
 86        public override bool Push(string uri, SerializableTable serializableTable)
 087        {
 088            if (uri == null)
 089            {
 090                return false;
 91            }
 92
 093            File.WriteAllText(uri, Generate(serializableTable), new UTF8Encoding());
 094            return File.Exists(uri);
 095        }
 96
 97        /// <summary>
 98        ///     Creates the content for a Comma Seperated Values file from a <see cref="SerializableTable" />.
 99        /// </summary>
 100        /// <param name="serializableTable">The target to create the file from.</param>
 101        /// <returns>The content of the file.</returns>
 102        static string Generate(SerializableTable serializableTable)
 0103        {
 0104            int rowCount = serializableTable.Rows.Length;
 0105            int columnCount = serializableTable.Types.Length;
 106
 0107            TextGenerator generator = new TextGenerator();
 108
 109            // Build first line
 0110            generator.Append("Row Identifier, Row Name");
 0111            for (int i = 0; i < columnCount; i++)
 0112            {
 0113                generator.Append(", ");
 0114                generator.Append(serializableTable.Headers[i]);
 0115            }
 116
 0117            generator.NextLine();
 118
 119            // Build info line
 0120            generator.Append(
 121                $"{serializableTable.DataVersion.ToString()}, {serializableTable.StructureVersion.ToString()}");
 0122            for (int i = 0; i < columnCount; i++)
 0123            {
 0124                generator.Append(", ");
 0125                generator.Append(serializableTable.Types[i]);
 0126            }
 127
 0128            generator.NextLine();
 129
 130            // Build lines for rows
 0131            for (int r = 0; r < rowCount; r++)
 0132            {
 0133                SerializableRow transferRow = serializableTable.Rows[r];
 134
 0135                generator.Append($"{transferRow.Identifier.ToString()}, {MakeCommaSeperatedValue(transferRow.Name)}");
 0136                for (int c = 0; c < columnCount; c++)
 0137                {
 0138                    generator.Append(", ");
 139
 0140                    if (serializableTable.Types[c] == Serializable.SerializableTypes.String.GetLabel() ||
 141                        serializableTable.Types[c] == Serializable.SerializableTypes.Char.GetLabel())
 0142                    {
 0143                        generator.Append(MakeCommaSeperatedValue(transferRow.Data[c]));
 0144                    }
 145                    else
 0146                    {
 0147                        generator.Append(transferRow.Data[c]);
 0148                    }
 0149                }
 150
 0151                generator.NextLine();
 0152            }
 153
 0154            return generator.ToString();
 0155        }
 156
 157        /// <summary>
 158        ///     Make a CSV safe version of the provided content.
 159        /// </summary>
 160        /// <param name="content">The content which needs to be made safe for CSV.</param>
 161        /// <returns>A CSV safe value string.</returns>
 162        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 163        static string MakeCommaSeperatedValue(string content)
 0164        {
 0165            if (content == null)
 0166            {
 0167                return string.Empty;
 168            }
 169
 170            // Double quote quotes
 0171            if (content.IndexOf('"') != -1)
 0172            {
 0173                content = content.Replace("\"", "\"\"");
 0174            }
 175
 176            // Ensure quotes for commas
 0177            return content.IndexOf(',') != -1 ? $"\"{content}\"" : content;
 0178        }
 179
 180        /// <summary>
 181        ///     Creates a <see cref="SerializableTable" /> from a CSV files contents.
 182        /// </summary>
 183        /// <param name="fileContent">An array of lines from a csv file.</param>
 184        /// <returns>An object if it successfully parses, or null if it fails.</returns>
 185        static SerializableTable Parse(string[] fileContent)
 0186        {
 187            try
 0188            {
 0189                SerializableTable returnSerializableTable = new SerializableTable();
 190
 0191                int rowCount = fileContent.Length - 2;
 0192                if (rowCount <= 0)
 0193                {
 0194                    return null;
 195                }
 196
 197                // Build headers
 0198                string[] headers = ParseCommaSeperatedValues(fileContent[0]);
 0199                int actualHeaderCount = headers.Length - 2;
 0200                returnSerializableTable.Headers = new string[actualHeaderCount];
 0201                Array.Copy(headers, 2, returnSerializableTable.Headers, 0, actualHeaderCount);
 202
 203                // Build types plus additional packed versions
 0204                string[] types = ParseCommaSeperatedValues(fileContent[1]);
 0205                int actualTypesCount = types.Length - 2;
 0206                returnSerializableTable.Types = new string[actualTypesCount];
 0207                returnSerializableTable.DataVersion = ulong.Parse(types[0]);
 0208                returnSerializableTable.StructureVersion = int.Parse(types[1]);
 0209                Array.Copy(types, 2, returnSerializableTable.Types, 0, actualTypesCount);
 210
 211                // Extract rows
 0212                returnSerializableTable.Rows = new SerializableRow[rowCount];
 0213                int rowIndex = 0;
 0214                for (int i = 2; i < fileContent.Length; i++)
 0215                {
 0216                    string[] rowData = ParseCommaSeperatedValues(fileContent[i]);
 217
 0218                    SerializableRow transferRow = new SerializableRow(actualTypesCount)
 219                    {
 220                        Identifier = int.Parse(rowData[0]), Name = rowData[1], Data = new string[actualTypesCount]
 221                    };
 0222                    Array.Copy(rowData, 2, transferRow.Data, 0, actualTypesCount);
 223
 0224                    returnSerializableTable.Rows[rowIndex] = transferRow;
 0225                    rowIndex++;
 0226                }
 227
 228                // Return our built object from CSV
 0229                return returnSerializableTable;
 230            }
 0231            catch (Exception e)
 0232            {
 0233                Debug.LogWarning($"Unable to parse provided CVS\n{e.Message}");
 0234                return null;
 235            }
 0236        }
 237
 238        /// <summary>
 239        ///     Parse a given <paramref name="line" /> into seperated values.
 240        /// </summary>
 241        /// <param name="line">The CSV line.</param>
 242        /// <returns>An array of string values.</returns>
 243        static string[] ParseCommaSeperatedValues(string line)
 0244        {
 0245            List<string> returnStrings = new List<string>();
 0246            int lastIndex = -1;
 0247            int currentIndex = 0;
 0248            bool isQuoted = false;
 0249            int length = line.Length;
 0250            while (currentIndex < length)
 0251            {
 0252                switch (line[currentIndex])
 253                {
 254                    case '"':
 0255                        isQuoted = !isQuoted;
 0256                        break;
 257                    case ',':
 0258                        if (!isQuoted)
 0259                        {
 0260                            returnStrings.Add(line.Substring(lastIndex + 1, currentIndex - lastIndex).Trim(' ', ','));
 0261                            lastIndex = currentIndex;
 0262                        }
 263
 0264                        break;
 265                }
 266
 0267                currentIndex++;
 0268            }
 269
 0270            if (lastIndex != line.Length - 1)
 0271            {
 0272                returnStrings.Add(line.Substring(lastIndex + 1).Trim());
 0273            }
 274
 0275            return returnStrings.ToArray();
 0276        }
 277    }
 278}