/* Yarn Spinner is licensed to you under the terms found in the file LICENSE.md. */ using CsvHelper; using System.Collections.Generic; using System.Linq; #nullable enable namespace Yarn.Unity { /// /// Struct holding information about a line and its associated metadata. /// Only used internally as an intermediary before persisting information /// in either a `YarnProject` or a CSV file. /// internal struct LineMetadataTableEntry { /// /// The line ID for this line. /// public string ID; /// /// The name of the Yarn script in which this line was originally /// found. /// public string File; /// /// The name of the node in which this line was originally found. /// /// /// This node can be found in the file indicated by . /// public string Node; /// /// The line number in the file indicated by at /// which the original version of this line can be found. /// public string LineNumber; /// /// Additional metadata included in this line. /// public string[] Metadata; /// /// Initializes a new instance of the /// struct, copying values from an existing instance. /// /// The instance to copy values from. public LineMetadataTableEntry(LineMetadataTableEntry s) { ID = s.ID; File = s.File; Node = s.Node; LineNumber = s.LineNumber; Metadata = s.Metadata; } private static CsvHelper.Configuration.Configuration? CsvConfiguration; private static CsvHelper.Configuration.Configuration GetConfiguration() { if (CsvConfiguration == null) { CsvConfiguration = new CsvHelper.Configuration.Configuration(System.Globalization.CultureInfo.InvariantCulture) { MemberTypes = CsvHelper.Configuration.MemberTypes.Fields, }; } return CsvConfiguration; } /// /// Reads comma-separated value data from , /// and produces a collection of structs. /// /// A string containing CSV-formatted /// data. /// The parsed collection of /// structs. /// Thrown when an error occurs when /// parsing the string. internal static IEnumerable ParseFromCSV(string sourceText) { try { using (var stringReader = new System.IO.StringReader(sourceText)) using (var csv = new CsvReader(stringReader, GetConfiguration())) { /* Do the below instead of GetRecords due to incompatibility with IL2CPP See more: https://github.com/YarnSpinnerTool/YarnSpinner-Unity/issues/36#issuecomment-691489913 */ var records = new List(); csv.Read(); csv.ReadHeader(); while (csv.Read()) { // Fetch values; if they can't be found, they'll be // defaults. csv.TryGetField("id", out var id); csv.TryGetField("file", out var file); csv.TryGetField("node", out var node); csv.TryGetField("lineNumber", out var lineNumber); csv.TryGetField("metadata", out var metadata); var record = new LineMetadataTableEntry { ID = id ?? string.Empty, File = file ?? string.Empty, Node = node ?? string.Empty, LineNumber = lineNumber ?? string.Empty, Metadata = metadata?.Split(' ') ?? new string[] { }, }; records.Add(record); } return records; } } catch (CsvHelperException e) { throw new System.ArgumentException($"Error reading CSV file: {e}"); } } /// /// Creates a CSV-formatted string containing data from . /// /// The values to /// generate the spreadsheet from. /// A string containing CSV-formatted data. public static string CreateCSV(IEnumerable entries) { using (var textWriter = new System.IO.StringWriter()) { // Use the invariant culture when writing the CSV var csv = new CsvWriter( textWriter, // write into this stream GetConfiguration() // use this configuration ); var fieldNames = new[] { "id", "file", "node", "lineNumber", "metadata", }; foreach (var field in fieldNames) { csv.WriteField(field); } csv.NextRecord(); foreach (var entry in entries) { var values = new[] { entry.ID, entry.File, entry.Node, entry.LineNumber, string.Join(" ", entry.Metadata), }; foreach (var value in values) { csv.WriteField(value); } csv.NextRecord(); } return textWriter.ToString(); } } /// public override string ToString() { return $"LineMetadataTableEntry: id={ID} file={File} node={Node} line={LineNumber} metadata={string.Join(" ", Metadata)}"; } /// public override bool Equals(object obj) { return obj is LineMetadataTableEntry entry && ID == entry.ID && File == entry.File && Node == entry.Node && LineNumber == entry.LineNumber && Enumerable.SequenceEqual(Metadata, entry.Metadata); } /// public override int GetHashCode() { var result = ID.GetHashCode() ^ File.GetHashCode() ^ Node.GetHashCode() ^ LineNumber.GetHashCode(); foreach (var piece in Metadata) { result ^= piece.GetHashCode(); } return result; } } }