/* Yarn Spinner is licensed to you under the terms found in the file LICENSE.md. */ using CsvHelper; using System.Collections.Generic; #nullable enable namespace Yarn.Unity { public struct StringTableEntry { /// /// The language that the line is written in. /// public string Language; /// /// The line ID for this line. This value will be the same across all /// localizations. /// public string ID; /// /// The text of this line, in the language specified by . /// public string? Text; /// /// 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; /// /// A string used as part of a mechanism for checking if translated /// versions of this string are out of date. /// /// /// /// This field contains the first 8 characters of the SHA-256 hash of /// the line's text as it appeared in the base localization CSV file. /// /// /// When a new StringTableEntry is created in a localized CSV file for a /// .yarn file, the Lock value is copied over from the base CSV file, /// and used for the translated entry. /// /// /// Because the base localization CSV is regenerated every time the /// .yarn file is imported, the base localization Lock value will change /// if a line's text changes. This means that if the base lock and /// translated lock differ, the translated line is out of date, and /// needs to be updated. /// /// public string Lock; /// /// A comment used to describe this line to translators. /// public string Comment; /// /// Initializes a new instance of the /// struct, copying values from an existing instance. /// /// The instance to copy values from. public StringTableEntry(StringTableEntry s) { ID = s.ID; Text = s.Text; File = s.File; Node = s.Node; LineNumber = s.LineNumber; Lock = s.Lock; Comment = s.Comment; Language = s.Language; } 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. public 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("language", out var language); csv.TryGetField("lock", out var lockString); csv.TryGetField("comment", out var comment); csv.TryGetField("id", out var id); csv.TryGetField("text", out var text); csv.TryGetField("file", out var file); csv.TryGetField("node", out var node); csv.TryGetField("lineNumber", out var lineNumber); var record = new StringTableEntry { Language = language ?? string.Empty, ID = id ?? string.Empty, Text = text ?? string.Empty, File = file ?? string.Empty, Node = node ?? string.Empty, LineNumber = lineNumber ?? string.Empty, Lock = lockString ?? string.Empty, Comment = comment ?? string.Empty, }; 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()) { // Generate the localised .csv file // Use the invariant culture when writing the CSV var csv = new CsvHelper.CsvWriter( textWriter, // write into this stream GetConfiguration() // use this configuration ); var fieldNames = new[] { "language", "id", "text", "file", "node", "lineNumber", "lock", "comment", }; foreach (var field in fieldNames) { csv.WriteField(field); } csv.NextRecord(); foreach (var entry in entries) { var values = new[] { entry.Language, entry.ID, entry.Text, entry.File, entry.Node, entry.LineNumber, entry.Lock, entry.Comment, }; foreach (var value in values) { csv.WriteField(value); } csv.NextRecord(); } return textWriter.ToString(); } } /// public override string ToString() { return $"StringTableEntry: lang={Language} id={ID} text=\"{Text}\" file={File} node={Node} line={LineNumber} lock={Lock} comment={Comment}"; } /// public override bool Equals(object obj) { return obj is StringTableEntry entry && Language == entry.Language && ID == entry.ID && Text == entry.Text && File == entry.File && Node == entry.Node && LineNumber == entry.LineNumber && Lock == entry.Lock && Comment == entry.Comment; } /// public override int GetHashCode() { return Language.GetHashCode() ^ ID.GetHashCode() ^ (Text?.GetHashCode() ?? 1) ^ File.GetHashCode() ^ Node.GetHashCode() ^ LineNumber.GetHashCode() ^ Lock.GetHashCode() ^ Comment.GetHashCode(); } } }