/// <summary>
        /// Writes a .csv file to disk at the path indicated by <paramref
        /// name="destination"/>, containing all of the lines found in the
        /// scripts referred to by <paramref name="yarnProjectImporter"/>.
        /// </summary>
        /// <remarks>
        /// The file generated is in a format ready to be added to the <see
        /// cref="YarnProjectImporter.languagesToSourceAssets"/> list.
        /// </remarks>
        /// <param name="yarnProjectImporter">The YarnProjectImporter to
        /// extract strings from.</param>
        /// <param name="destination">The path to write the file
        /// to.</param>
        /// <returns><see langword="true"/> if the file was written
        /// successfully, <see langword="false"/> otherwise.</returns>
        /// <exception cref="CsvHelper.CsvHelperException">Thrown when an
        /// error is encountered when generating the CSV data.</exception>
        /// <exception cref="IOException">Thrown when an error is
        /// encountered when writing the data to disk.</exception>
        internal static bool WriteStringsFile(string destination, YarnProjectImporter yarnProjectImporter)
        {
            // Perform a strings-only compilation to get a full strings
            // table, and generate the CSV.
            var stringTable = yarnProjectImporter.GenerateStringsTable();

            // If there was an error, bail out here
            if (stringTable == null)
            {
                return(false);
            }

            // Convert the string tables to CSV...
            var outputCSV = StringTableEntry.CreateCSV(stringTable);

            // ...and write it to disk.
            File.WriteAllText(destination, outputCSV);

            return(true);
        }
        /// <summary>
        /// Verifies the TextAsset referred to by <paramref
        /// name="destinationLocalizationAsset"/>, and updates it if
        /// necessary.
        /// </summary>
        /// <param name="baseLocalizationStrings">A collection of <see
        /// cref="StringTableEntry"/></param>
        /// <param name="language">The language that <paramref
        /// name="destinationLocalizationAsset"/> provides strings
        /// for.false</param>
        /// <param name="destinationLocalizationAsset">A TextAsset
        /// containing localized strings in CSV format.</param>
        /// <returns>Whether <paramref
        /// name="destinationLocalizationAsset"/> was modified.</returns>
        private static bool UpdateLocalizationFile(IEnumerable <StringTableEntry> baseLocalizationStrings, string language, TextAsset destinationLocalizationAsset)
        {
            var translatedStrings = StringTableEntry.ParseFromCSV(destinationLocalizationAsset.text);

            // Convert both enumerables to dictionaries, for easier lookup
            var baseDictionary       = baseLocalizationStrings.ToDictionary(entry => entry.ID);
            var translatedDictionary = translatedStrings.ToDictionary(entry => entry.ID);

            // The list of line IDs present in each localisation
            var baseIDs       = baseLocalizationStrings.Select(entry => entry.ID);
            var translatedIDs = translatedStrings.Select(entry => entry.ID);

            // The list of line IDs that are ONLY present in each
            // localisation
            var onlyInBaseIDs       = baseIDs.Except(translatedIDs);
            var onlyInTranslatedIDs = translatedIDs.Except(baseIDs);

            // Tracks if the translated localisation needed modifications
            // (either new lines added, old lines removed, or changed lines
            // flagged)
            var modificationsNeeded = false;

            // Remove every entry whose ID is only present in the
            // translated set. This entry has been removed from the base
            // localization.
            foreach (var id in onlyInTranslatedIDs.ToList())
            {
                translatedDictionary.Remove(id);
                modificationsNeeded = true;
            }

            // Conversely, for every entry that is only present in the base
            // localisation, we need to create a new entry for it.
            foreach (var id in onlyInBaseIDs)
            {
                StringTableEntry baseEntry = baseDictionary[id];
                var newEntry = new StringTableEntry(baseEntry)
                {
                    // Empty this text, so that it's apparent that a
                    // translated version needs to be provided.
                    Text     = string.Empty,
                    Language = language,
                };
                translatedDictionary.Add(id, newEntry);
                modificationsNeeded = true;
            }

            // Finally, we need to check for any entries in the translated
            // localisation that:
            // 1. have the same line ID as one in the base, but
            // 2. have a different Lock (the hash of the text), which
            //    indicates that the base text has changed.

            // First, get the list of IDs that are in both base and
            // translated, and then filter this list to any where the lock
            // values differ
            var outOfDateLockIDs = baseDictionary.Keys
                                   .Intersect(translatedDictionary.Keys)
                                   .Where(id => baseDictionary[id].Lock != translatedDictionary[id].Lock);

            // Now loop over all of these, and update our translated
            // dictionary to include a note that it needs attention
            foreach (var id in outOfDateLockIDs)
            {
                // Get the translated entry as it currently exists
                var entry = translatedDictionary[id];

                // Include a note that this entry is out of date
                entry.Text = $"(NEEDS UPDATE) {entry.Text}";

                // Update the lock to match the new one
                entry.Lock = baseDictionary[id].Lock;

                // Put this modified entry back in the table
                translatedDictionary[id] = entry;

                modificationsNeeded = true;
            }

            // We're all done!

            if (modificationsNeeded == false)
            {
                // No changes needed to be done to the translated string
                // table entries. Stop here.
                return(false);
            }

            // We need to produce a replacement CSV file for the translated
            // entries.

            var outputStringEntries = translatedDictionary.Values
                                      .OrderBy(entry => entry.File)
                                      .ThenBy(entry => int.Parse(entry.LineNumber));

            var outputCSV = StringTableEntry.CreateCSV(outputStringEntries);

            // Write out the replacement text to this existing file,
            // replacing its existing contents
            var outputFile = AssetDatabase.GetAssetPath(destinationLocalizationAsset);

            File.WriteAllText(outputFile, outputCSV, System.Text.Encoding.UTF8);

            // Tell the asset database that the file needs to be reimported
            AssetDatabase.ImportAsset(outputFile);

            // Signal that the file was changed
            return(true);
        }
示例#3
0
        private void ImportYarn(AssetImportContext ctx)
        {
            var    sourceText = File.ReadAllText(ctx.assetPath);
            string fileName   = System.IO.Path.GetFileNameWithoutExtension(ctx.assetPath);

            Yarn.Program compiledProgram = null;
            IDictionary <string, Yarn.Compiler.StringInfo> stringTable = null;

            compilationErrorMessage = null;

            try
            {
                // Compile the source code into a compiled Yarn program (or
                // generate a parse error)
                compilationStatus       = Yarn.Compiler.Compiler.CompileString(sourceText, fileName, out compiledProgram, out stringTable);
                isSuccesfullyCompiled   = true;
                compilationErrorMessage = string.Empty;
            }
            catch (Yarn.Compiler.ParseException e)
            {
                isSuccesfullyCompiled   = false;
                compilationErrorMessage = e.Message;
                ctx.LogImportError(e.Message);
            }

            // Create a container for storing the bytes
            if (programContainer == null)
            {
                programContainer = ScriptableObject.CreateInstance <YarnProgram>();
            }

            byte[] compiledBytes = null;

            if (compiledProgram != null)
            {
                using (var memoryStream = new MemoryStream())
                    using (var outputStream = new Google.Protobuf.CodedOutputStream(memoryStream))
                    {
                        // Serialize the compiled program to memory
                        compiledProgram.WriteTo(outputStream);
                        outputStream.Flush();

                        compiledBytes = memoryStream.ToArray();
                    }
            }


            programContainer.compiledProgram = compiledBytes;

            // Add this container to the imported asset; it will be
            // what the user interacts with in Unity
            ctx.AddObjectToAsset("Program", programContainer, YarnEditorUtility.GetYarnDocumentIconTexture());
            ctx.SetMainObject(programContainer);

            if (stringTable?.Count > 0)
            {
                var lines = stringTable.Select(x => new StringTableEntry
                {
                    ID         = x.Key,
                    Language   = baseLanguageID,
                    Text       = x.Value.text,
                    File       = x.Value.fileName,
                    Node       = x.Value.nodeName,
                    LineNumber = x.Value.lineNumber.ToString(),
                    Lock       = GetHashString(x.Value.text, 8),
                }).OrderBy(entry => int.Parse(entry.LineNumber));

                var stringTableCSV = StringTableEntry.CreateCSV(lines);

                var textAsset = new TextAsset(stringTableCSV);
                textAsset.name = $"{fileName} ({baseLanguageID})";

                ctx.AddObjectToAsset("Strings", textAsset);

                programContainer.baseLocalizationId = baseLanguageID;
                baseLanguage = textAsset;
                programContainer.localizations      = localizations.Append(new YarnProgram.YarnTranslation(baseLanguageID, textAsset)).ToArray();
                programContainer.baseLocalizationId = baseLanguageID;

                stringIDs = lines.Select(l => l.ID).ToArray();
            }
        }