chrisgregan
10 years ago
29 changed files with 1334 additions and 422 deletions
@ -1,187 +0,0 @@ |
|||||||
using UnityEditor; |
|
||||||
using UnityEngine; |
|
||||||
using System.Collections; |
|
||||||
using System.Collections.Generic; |
|
||||||
using System.Linq; |
|
||||||
using System.Reflection; |
|
||||||
using System.IO; |
|
||||||
|
|
||||||
namespace Fungus |
|
||||||
{ |
|
||||||
|
|
||||||
/** |
|
||||||
* Import and export a Fungus story in the .fountain screenplay format. |
|
||||||
* The exported file contains special tags in note blocks which map the |
|
||||||
* story text to the corresponding commands. |
|
||||||
*/ |
|
||||||
public class FountainExporter |
|
||||||
{ |
|
||||||
|
|
||||||
public static void ExportStrings(Flowchart flowchart) |
|
||||||
{ |
|
||||||
if (flowchart == null) |
|
||||||
{ |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
string path = EditorUtility.SaveFilePanel("Export strings", "", |
|
||||||
flowchart.name + ".txt", ""); |
|
||||||
|
|
||||||
if(path.Length == 0) |
|
||||||
{ |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
// Write out character names |
|
||||||
|
|
||||||
string exportText = "Title: " + flowchart.name + "\n"; |
|
||||||
exportText += "Draft date: " + System.DateTime.Today.ToString("d") + "\n"; |
|
||||||
exportText += "\n"; |
|
||||||
|
|
||||||
// In every block, write out Say & Menu text in order |
|
||||||
Block[] blocks = flowchart.GetComponentsInChildren<Block>(); |
|
||||||
foreach (Block block in blocks) |
|
||||||
{ |
|
||||||
// Check for any Say, Menu or Comment commands |
|
||||||
bool hasText = false; |
|
||||||
foreach (Command c in block.commandList) |
|
||||||
{ |
|
||||||
System.Type t = c.GetType(); |
|
||||||
if (t == typeof(Say) || |
|
||||||
t == typeof(Menu) || |
|
||||||
t == typeof(Comment)) |
|
||||||
{ |
|
||||||
hasText = true; |
|
||||||
} |
|
||||||
} |
|
||||||
if (!hasText) |
|
||||||
{ |
|
||||||
continue; |
|
||||||
} |
|
||||||
|
|
||||||
exportText += "." + block.blockName.ToUpper() + "\n\n"; |
|
||||||
|
|
||||||
foreach (Command c in block.commandList) |
|
||||||
{ |
|
||||||
if (c.GetType() == typeof(Say)) |
|
||||||
{ |
|
||||||
string idText = ""; |
|
||||||
Say say = c as Say; |
|
||||||
|
|
||||||
if (say.character == null) |
|
||||||
{ |
|
||||||
exportText += "NO CHARACTER\n"; |
|
||||||
} |
|
||||||
else |
|
||||||
{ |
|
||||||
exportText += say.character.nameText.ToUpper() + "\n"; |
|
||||||
} |
|
||||||
|
|
||||||
idText += "[[Say," + c.itemId + "]]\n"; |
|
||||||
|
|
||||||
exportText += idText; |
|
||||||
|
|
||||||
// Fountain requires blank dialogue lines to contain 2 spaces or else |
|
||||||
// they will be interpreted as ACTION text. |
|
||||||
string trimmedText = say.storyText.Trim(); |
|
||||||
string[] lines = trimmedText.Split(new [] { '\r', '\n' }); |
|
||||||
foreach (string line in lines) |
|
||||||
{ |
|
||||||
string trimmed = line.Trim(); |
|
||||||
if (line.Length == 0) |
|
||||||
{ |
|
||||||
exportText += " \n"; |
|
||||||
} |
|
||||||
else |
|
||||||
{ |
|
||||||
exportText += trimmed + "\n"; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
exportText += "\n"; |
|
||||||
} |
|
||||||
else if (c.GetType() == typeof(Menu)) |
|
||||||
{ |
|
||||||
exportText += "MENU\n"; |
|
||||||
|
|
||||||
string idText = ""; |
|
||||||
Menu menu = c as Menu; |
|
||||||
idText += "[[Menu," + c.itemId + "]]\n"; |
|
||||||
|
|
||||||
exportText += idText + menu.text.Trim() + "\n\n"; |
|
||||||
} |
|
||||||
else if (c.GetType() == typeof(Comment)) |
|
||||||
{ |
|
||||||
string idText = ""; |
|
||||||
Comment comment = c as Comment; |
|
||||||
idText += "[[Comment," + c.itemId + "]]\n"; |
|
||||||
|
|
||||||
exportText += idText + comment.commentText.Trim() + "\n\n"; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
File.WriteAllText(path, exportText); |
|
||||||
} |
|
||||||
|
|
||||||
public static void ImportStrings(Flowchart flowchart) |
|
||||||
{ |
|
||||||
string path = EditorUtility.OpenFilePanel("Import strings", "", ""); |
|
||||||
|
|
||||||
if(path.Length == 0) |
|
||||||
{ |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
string stringsFile = File.ReadAllText(path); |
|
||||||
|
|
||||||
StringsParser parser = new StringsParser(); |
|
||||||
List<StringsParser.StringItem> items = parser.ProcessText(stringsFile); |
|
||||||
|
|
||||||
// Build dict of commands |
|
||||||
Dictionary<int, Command> commandDict = new Dictionary<int, Command>(); |
|
||||||
foreach (Command c in flowchart.gameObject.GetComponentsInChildren<Command>()) |
|
||||||
{ |
|
||||||
commandDict.Add (c.itemId, c); |
|
||||||
} |
|
||||||
|
|
||||||
foreach (StringsParser.StringItem item in items) |
|
||||||
{ |
|
||||||
if (item.parameters.Length != 2) |
|
||||||
{ |
|
||||||
continue; |
|
||||||
} |
|
||||||
|
|
||||||
string stringType = item.parameters[0]; |
|
||||||
if (stringType == "Say") |
|
||||||
{ |
|
||||||
int itemId = int.Parse(item.parameters[1]); |
|
||||||
Say sayCommand = commandDict[itemId] as Say; |
|
||||||
if (sayCommand != null) |
|
||||||
{ |
|
||||||
sayCommand.storyText = item.bodyText; |
|
||||||
} |
|
||||||
} |
|
||||||
else if (stringType == "Menu") |
|
||||||
{ |
|
||||||
int itemId = int.Parse(item.parameters[1]); |
|
||||||
Menu menuCommand = commandDict[itemId] as Menu; |
|
||||||
if (menuCommand != null) |
|
||||||
{ |
|
||||||
menuCommand.text = item.bodyText; |
|
||||||
} |
|
||||||
} |
|
||||||
else if (stringType == "Comment") |
|
||||||
{ |
|
||||||
int itemId = int.Parse(item.parameters[1]); |
|
||||||
Comment commentCommand = commandDict[itemId] as Comment; |
|
||||||
if (commentCommand != null) |
|
||||||
{ |
|
||||||
commentCommand.commentText = item.bodyText; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,96 +0,0 @@ |
|||||||
using UnityEngine; |
|
||||||
using System.Collections; |
|
||||||
using System.Collections.Generic; |
|
||||||
using System.Text.RegularExpressions; |
|
||||||
using Fungus; |
|
||||||
|
|
||||||
namespace Fungus |
|
||||||
{ |
|
||||||
/** |
|
||||||
* Parses an exported strings file using the Fountain file format for screenplays |
|
||||||
* See http://fountain.io for details. |
|
||||||
* We only support a small subset of Fountain markup, and use note tags to embed meta data to |
|
||||||
* bind dialogue text to the corresponding Say / Menu commands. |
|
||||||
*/ |
|
||||||
public class StringsParser |
|
||||||
{ |
|
||||||
public class StringItem |
|
||||||
{ |
|
||||||
public string[] parameters; |
|
||||||
public string bodyText; |
|
||||||
} |
|
||||||
|
|
||||||
public virtual List<StringItem> ProcessText(string text) |
|
||||||
{ |
|
||||||
List<StringItem> items = new List<StringItem>(); |
|
||||||
|
|
||||||
// Split text into lines. Add a newline at end to ensure last command is always parsed |
|
||||||
string[] lines = Regex.Split(text + "\n", "(?<=\n)"); |
|
||||||
|
|
||||||
int i = 0; |
|
||||||
while (i < lines.Length) |
|
||||||
{ |
|
||||||
string line = lines[i].Trim(); |
|
||||||
|
|
||||||
if (!(line.StartsWith("[[") && line.EndsWith("]]"))) |
|
||||||
{ |
|
||||||
i++; |
|
||||||
continue; |
|
||||||
} |
|
||||||
|
|
||||||
string blockTag = line.Substring(2, line.Length - 4); |
|
||||||
|
|
||||||
// Find next empty line, #, [[ or eof |
|
||||||
int start = i + 1; |
|
||||||
int end = lines.Length - 1; |
|
||||||
for (int j = start; j <= end; ++j) |
|
||||||
{ |
|
||||||
string line2 = lines[j].Trim(); |
|
||||||
|
|
||||||
if (line2.Length == 0 || |
|
||||||
line2.StartsWith("#") || |
|
||||||
line2.StartsWith("[[")) |
|
||||||
{ |
|
||||||
end = j; |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if (end > start) |
|
||||||
{ |
|
||||||
string blockBuffer = ""; |
|
||||||
for (int j = start; j <= end; ++j) |
|
||||||
{ |
|
||||||
blockBuffer += lines[j].Trim() + "\n"; |
|
||||||
} |
|
||||||
|
|
||||||
blockBuffer = blockBuffer.Trim(); |
|
||||||
|
|
||||||
StringItem item = CreateItem(blockTag, blockBuffer); |
|
||||||
if (item != null) |
|
||||||
{ |
|
||||||
items.Add(item); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
i = end + 1; |
|
||||||
} |
|
||||||
|
|
||||||
return items; |
|
||||||
} |
|
||||||
|
|
||||||
protected StringItem CreateItem(string commandInfo, string bodyText) |
|
||||||
{ |
|
||||||
string[] parameters = commandInfo.Split(new char[] { ',' }); |
|
||||||
if (parameters.Length > 0) |
|
||||||
{ |
|
||||||
StringItem item = new StringItem(); |
|
||||||
item.parameters = parameters; |
|
||||||
item.bodyText = bodyText; |
|
||||||
return item; |
|
||||||
} |
|
||||||
|
|
||||||
return null; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,106 @@ |
|||||||
|
using UnityEditor; |
||||||
|
using UnityEngine; |
||||||
|
using System.Collections; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.IO; |
||||||
|
using Rotorz.ReorderableList; |
||||||
|
|
||||||
|
namespace Fungus |
||||||
|
{ |
||||||
|
|
||||||
|
[CustomEditor(typeof(Localization))] |
||||||
|
public class LocalizationEditor : Editor |
||||||
|
{ |
||||||
|
protected SerializedProperty activeLanguageProp; |
||||||
|
protected SerializedProperty localizationFileProp; |
||||||
|
|
||||||
|
protected virtual void OnEnable() |
||||||
|
{ |
||||||
|
activeLanguageProp = serializedObject.FindProperty("activeLanguage"); |
||||||
|
localizationFileProp = serializedObject.FindProperty("localizationFile"); |
||||||
|
} |
||||||
|
|
||||||
|
public override void OnInspectorGUI() |
||||||
|
{ |
||||||
|
serializedObject.Update(); |
||||||
|
|
||||||
|
Localization localization = target as Localization; |
||||||
|
|
||||||
|
EditorGUILayout.PropertyField(activeLanguageProp); |
||||||
|
EditorGUILayout.PropertyField(localizationFileProp); |
||||||
|
|
||||||
|
if (GUILayout.Button(new GUIContent("Export Localization File"))) |
||||||
|
{ |
||||||
|
ExportLocalizationFile(localization); |
||||||
|
} |
||||||
|
|
||||||
|
if (GUILayout.Button(new GUIContent("Export Standard Text"))) |
||||||
|
{ |
||||||
|
ExportStandardText(localization); |
||||||
|
} |
||||||
|
|
||||||
|
if (GUILayout.Button(new GUIContent("Import Standard Text"))) |
||||||
|
{ |
||||||
|
ImportStandardText(localization); |
||||||
|
} |
||||||
|
|
||||||
|
serializedObject.ApplyModifiedProperties(); |
||||||
|
} |
||||||
|
|
||||||
|
public virtual void ExportLocalizationFile(Localization localization) |
||||||
|
{ |
||||||
|
string path = EditorUtility.SaveFilePanel("Export Localization File", "Assets/", |
||||||
|
"localization.csv", ""); |
||||||
|
if (path.Length == 0) |
||||||
|
{ |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
string csvData = localization.GetCSVData(); |
||||||
|
File.WriteAllText(path, csvData); |
||||||
|
AssetDatabase.Refresh(); |
||||||
|
|
||||||
|
ShowNotification(localization); |
||||||
|
} |
||||||
|
|
||||||
|
public virtual void ExportStandardText(Localization localization) |
||||||
|
{ |
||||||
|
string path = EditorUtility.SaveFilePanel("Export Standard Text", "Assets/", "standard.txt", ""); |
||||||
|
if (path.Length == 0) |
||||||
|
{ |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
string textData = localization.GetStandardText(); |
||||||
|
File.WriteAllText(path, textData); |
||||||
|
AssetDatabase.Refresh(); |
||||||
|
|
||||||
|
ShowNotification(localization); |
||||||
|
} |
||||||
|
|
||||||
|
public virtual void ImportStandardText(Localization localization) |
||||||
|
{ |
||||||
|
string path = EditorUtility.OpenFilePanel("Import Standard Text", "Assets/", "txt"); |
||||||
|
if (path.Length == 0) |
||||||
|
{ |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
string textData = File.ReadAllText(path); |
||||||
|
localization.SetStandardText(textData); |
||||||
|
|
||||||
|
ShowNotification(localization); |
||||||
|
} |
||||||
|
|
||||||
|
protected virtual void ShowNotification(Localization localization) |
||||||
|
{ |
||||||
|
EditorWindow window = EditorWindow.GetWindow(typeof(FlowchartWindow), false, "Flowchart"); |
||||||
|
if (window != null) |
||||||
|
{ |
||||||
|
window.ShowNotification(new GUIContent(localization.notificationText)); |
||||||
|
localization.notificationText = ""; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -1,6 +1,6 @@ |
|||||||
fileFormatVersion: 2 |
fileFormatVersion: 2 |
||||||
guid: c91ad6ef6a0734046bd93dde4b0e59d1 |
guid: ab0caac085485491fb32dbf86efefef1 |
||||||
timeCreated: 1426502899 |
timeCreated: 1428581512 |
||||||
licenseType: Free |
licenseType: Free |
||||||
MonoImporter: |
MonoImporter: |
||||||
serializedVersion: 2 |
serializedVersion: 2 |
@ -0,0 +1,54 @@ |
|||||||
|
%YAML 1.1 |
||||||
|
%TAG !u! tag:unity3d.com,2011: |
||||||
|
--- !u!1 &149266 |
||||||
|
GameObject: |
||||||
|
m_ObjectHideFlags: 0 |
||||||
|
m_PrefabParentObject: {fileID: 0} |
||||||
|
m_PrefabInternal: {fileID: 100100000} |
||||||
|
serializedVersion: 4 |
||||||
|
m_Component: |
||||||
|
- 4: {fileID: 480768} |
||||||
|
- 114: {fileID: 11438504} |
||||||
|
m_Layer: 0 |
||||||
|
m_Name: Localization |
||||||
|
m_TagString: Untagged |
||||||
|
m_Icon: {fileID: 0} |
||||||
|
m_NavMeshLayer: 0 |
||||||
|
m_StaticEditorFlags: 0 |
||||||
|
m_IsActive: 1 |
||||||
|
--- !u!4 &480768 |
||||||
|
Transform: |
||||||
|
m_ObjectHideFlags: 1 |
||||||
|
m_PrefabParentObject: {fileID: 0} |
||||||
|
m_PrefabInternal: {fileID: 100100000} |
||||||
|
m_GameObject: {fileID: 149266} |
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} |
||||||
|
m_LocalPosition: {x: 2.05546069, y: -3.16485739, z: 0} |
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1} |
||||||
|
m_Children: [] |
||||||
|
m_Father: {fileID: 0} |
||||||
|
m_RootOrder: 0 |
||||||
|
--- !u!114 &11438504 |
||||||
|
MonoBehaviour: |
||||||
|
m_ObjectHideFlags: 1 |
||||||
|
m_PrefabParentObject: {fileID: 0} |
||||||
|
m_PrefabInternal: {fileID: 100100000} |
||||||
|
m_GameObject: {fileID: 149266} |
||||||
|
m_Enabled: 1 |
||||||
|
m_EditorHideFlags: 0 |
||||||
|
m_Script: {fileID: 11500000, guid: e5724422a635e425bae0af9ffe2615d6, type: 3} |
||||||
|
m_Name: |
||||||
|
m_EditorClassIdentifier: |
||||||
|
activeLanguage: |
||||||
|
localizationFile: {fileID: 0} |
||||||
|
--- !u!1001 &100100000 |
||||||
|
Prefab: |
||||||
|
m_ObjectHideFlags: 1 |
||||||
|
serializedVersion: 2 |
||||||
|
m_Modification: |
||||||
|
m_TransformParent: {fileID: 0} |
||||||
|
m_Modifications: [] |
||||||
|
m_RemovedComponents: [] |
||||||
|
m_ParentPrefab: {fileID: 0} |
||||||
|
m_RootGameObject: {fileID: 149266} |
||||||
|
m_IsPrefabParent: 1 |
@ -0,0 +1,8 @@ |
|||||||
|
fileFormatVersion: 2 |
||||||
|
guid: ffbd0831d997545eab75c364da082c1b |
||||||
|
timeCreated: 1428580452 |
||||||
|
licenseType: Free |
||||||
|
NativeFormatImporter: |
||||||
|
userData: |
||||||
|
assetBundleName: |
||||||
|
assetBundleVariant: |
@ -0,0 +1,35 @@ |
|||||||
|
using UnityEngine; |
||||||
|
using System.Collections; |
||||||
|
|
||||||
|
namespace Fungus |
||||||
|
{ |
||||||
|
[CommandInfo("Narrative", |
||||||
|
"Set Language", |
||||||
|
"Set the active language for the scene. A Localization object with a localization file must be present in the scene.")] |
||||||
|
[AddComponentMenu("")] |
||||||
|
public class SetLanguage : Command |
||||||
|
{ |
||||||
|
public string languageCode; |
||||||
|
|
||||||
|
public override void OnEnter() |
||||||
|
{ |
||||||
|
Localization localization = GameObject.FindObjectOfType<Localization>(); |
||||||
|
if (localization != null) |
||||||
|
{ |
||||||
|
localization.SetActiveLanguage(languageCode); |
||||||
|
} |
||||||
|
|
||||||
|
Continue(); |
||||||
|
} |
||||||
|
|
||||||
|
public override string GetSummary() |
||||||
|
{ |
||||||
|
return languageCode; |
||||||
|
} |
||||||
|
|
||||||
|
public override Color GetButtonColor() |
||||||
|
{ |
||||||
|
return new Color32(184, 210, 235, 255); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -1,8 +1,12 @@ |
|||||||
fileFormatVersion: 2 |
fileFormatVersion: 2 |
||||||
guid: 0f02aedc631824200a4abe95774a44f5 |
guid: 3fc625e237d6048bf86f34835d8266d9 |
||||||
|
timeCreated: 1428591017 |
||||||
|
licenseType: Free |
||||||
MonoImporter: |
MonoImporter: |
||||||
serializedVersion: 2 |
serializedVersion: 2 |
||||||
defaultReferences: [] |
defaultReferences: [] |
||||||
executionOrder: 0 |
executionOrder: 0 |
||||||
icon: {instanceID: 0} |
icon: {instanceID: 0} |
||||||
userData: |
userData: |
||||||
|
assetBundleName: |
||||||
|
assetBundleVariant: |
@ -0,0 +1,601 @@ |
|||||||
|
/** |
||||||
|
* CSVParser by Ideafixxxer. http://www.codeproject.com/Tips/741941/CSV-Parser-Csharp |
||||||
|
* This code is licensed under the CPOL open source license. |
||||||
|
* http://www.codeproject.com/info/cpol10.aspx |
||||||
|
*/ |
||||||
|
|
||||||
|
using UnityEngine; |
||||||
|
#if UNITY_EDITOR |
||||||
|
using UnityEditor; |
||||||
|
#endif |
||||||
|
using System; |
||||||
|
using System.Collections; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.IO; |
||||||
|
using Ideafixxxer.CsvParser; |
||||||
|
|
||||||
|
namespace Fungus |
||||||
|
{ |
||||||
|
|
||||||
|
/** |
||||||
|
* Multi-language localization support. |
||||||
|
*/ |
||||||
|
public class Localization : MonoBehaviour |
||||||
|
{ |
||||||
|
/** |
||||||
|
* Currently active language, usually defined by a two letter language code (e.g DE = German) |
||||||
|
*/ |
||||||
|
public string activeLanguage = ""; |
||||||
|
|
||||||
|
protected static Dictionary<string, string> localizedStrings = new Dictionary<string, string>(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Temp storage for a single item of standard text and its localizations |
||||||
|
*/ |
||||||
|
protected class TextItem |
||||||
|
{ |
||||||
|
public string description = ""; |
||||||
|
public string standardText = ""; |
||||||
|
public Dictionary<string, string> localizedStrings = new Dictionary<string, string>(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* CSV file containing localization data |
||||||
|
*/ |
||||||
|
public TextAsset localizationFile; |
||||||
|
|
||||||
|
/** |
||||||
|
* Stores any notification message from export / import methods. |
||||||
|
*/ |
||||||
|
[NonSerialized] |
||||||
|
public string notificationText = ""; |
||||||
|
|
||||||
|
public virtual void Start() |
||||||
|
{ |
||||||
|
if (localizationFile != null && |
||||||
|
localizationFile.text.Length > 0) |
||||||
|
{ |
||||||
|
SetActiveLanguage(activeLanguage); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Looks up the specified string in the localized strings table. |
||||||
|
* For this to work, a localization file and active language must have been set previously. |
||||||
|
* Return null if the string is not found. |
||||||
|
*/ |
||||||
|
public static string GetLocalizedString(string stringId) |
||||||
|
{ |
||||||
|
if (localizedStrings == null) |
||||||
|
{ |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
if (localizedStrings.ContainsKey(stringId)) |
||||||
|
{ |
||||||
|
return localizedStrings[stringId]; |
||||||
|
} |
||||||
|
|
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Convert all text items and localized strings to an easy to edit CSV format. |
||||||
|
*/ |
||||||
|
public virtual string GetCSVData() |
||||||
|
{ |
||||||
|
// Collect all the text items present in the scene |
||||||
|
Dictionary<string, TextItem> textItems = FindTextItems(); |
||||||
|
|
||||||
|
// Update text items with localization data from CSV file |
||||||
|
if (localizationFile != null && |
||||||
|
localizationFile.text.Length > 0) |
||||||
|
{ |
||||||
|
AddCSVDataItems(textItems, localizationFile.text); |
||||||
|
} |
||||||
|
|
||||||
|
// Build CSV header row and a list of the language codes currently in use |
||||||
|
string csvHeader = "Key,Description,Standard"; |
||||||
|
List<string> languageCodes = new List<string>(); |
||||||
|
foreach (TextItem textItem in textItems.Values) |
||||||
|
{ |
||||||
|
foreach (string languageCode in textItem.localizedStrings.Keys) |
||||||
|
{ |
||||||
|
if (!languageCodes.Contains(languageCode)) |
||||||
|
{ |
||||||
|
languageCodes.Add(languageCode); |
||||||
|
csvHeader += "," + languageCode; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Build the CSV file using collected text items |
||||||
|
int rowCount = 0; |
||||||
|
string csvData = csvHeader + "\n"; |
||||||
|
foreach (string stringId in textItems.Keys) |
||||||
|
{ |
||||||
|
TextItem textItem = textItems[stringId]; |
||||||
|
|
||||||
|
string row = CSVSupport.Escape(stringId); |
||||||
|
row += "," + CSVSupport.Escape(textItem.description); |
||||||
|
row += "," + CSVSupport.Escape(textItem.standardText); |
||||||
|
|
||||||
|
foreach (string languageCode in languageCodes) |
||||||
|
{ |
||||||
|
if (textItem.localizedStrings.ContainsKey(languageCode)) |
||||||
|
{ |
||||||
|
row += "," + CSVSupport.Escape(textItem.localizedStrings[languageCode]); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
row += ","; // Empty field |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
csvData += row + "\n"; |
||||||
|
rowCount++; |
||||||
|
} |
||||||
|
|
||||||
|
notificationText = "Exported " + rowCount + " localization text items."; |
||||||
|
|
||||||
|
return csvData; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Buidls a dictionary of localizable text items in the scene. |
||||||
|
*/ |
||||||
|
protected Dictionary<string, TextItem> FindTextItems() |
||||||
|
{ |
||||||
|
Dictionary<string, TextItem> textItems = new Dictionary<string, TextItem>(); |
||||||
|
|
||||||
|
// Export all character names |
||||||
|
foreach (Character character in GameObject.FindObjectsOfType<Character>()) |
||||||
|
{ |
||||||
|
// String id for character names is CHARACTER.<Character Name> |
||||||
|
TextItem textItem = new TextItem(); |
||||||
|
textItem.standardText = character.nameText; |
||||||
|
textItem.description = character.description; |
||||||
|
string stringId = "CHARACTER." + character.nameText; |
||||||
|
textItems[stringId] = textItem; |
||||||
|
} |
||||||
|
|
||||||
|
// Export all Say and Menu commands in the scene |
||||||
|
// To make it easier to localize, we preserve the command order in each exported block. |
||||||
|
Flowchart[] flowcharts = GameObject.FindObjectsOfType<Flowchart>(); |
||||||
|
foreach (Flowchart flowchart in flowcharts) |
||||||
|
{ |
||||||
|
// Have to set a unique localization id to export strings |
||||||
|
if (flowchart.localizationId.Length == 0) |
||||||
|
{ |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
Block[] blocks = flowchart.GetComponentsInChildren<Block>(); |
||||||
|
foreach (Block block in blocks) |
||||||
|
{ |
||||||
|
foreach (Command command in block.commandList) |
||||||
|
{ |
||||||
|
string stringId = ""; |
||||||
|
string standardText = ""; |
||||||
|
string description = ""; |
||||||
|
|
||||||
|
System.Type type = command.GetType(); |
||||||
|
if (type == typeof(Say)) |
||||||
|
{ |
||||||
|
// String id for Say commands is SAY.<Flowchart id>.<Command id>.<Character Name> |
||||||
|
Say sayCommand = command as Say; |
||||||
|
standardText = sayCommand.storyText; |
||||||
|
description = sayCommand.description; |
||||||
|
stringId = "SAY." + flowchart.localizationId + "." + sayCommand.itemId + "."; |
||||||
|
if (sayCommand.character != null) |
||||||
|
{ |
||||||
|
stringId += sayCommand.character.nameText; |
||||||
|
} |
||||||
|
} |
||||||
|
else if (type == typeof(Menu)) |
||||||
|
{ |
||||||
|
// String id for Menu commands is MENU.<Flowchart id>.<Command id> |
||||||
|
Menu menuCommand = command as Menu; |
||||||
|
standardText = menuCommand.text; |
||||||
|
description = menuCommand.description; |
||||||
|
stringId = "MENU." + flowchart.localizationId + "." + menuCommand.itemId; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
TextItem textItem = null; |
||||||
|
if (textItems.ContainsKey(stringId)) |
||||||
|
{ |
||||||
|
textItem = textItems[stringId]; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
textItem = new TextItem(); |
||||||
|
textItems[stringId] = textItem; |
||||||
|
} |
||||||
|
|
||||||
|
// Update basic properties,leaving localised strings intact |
||||||
|
textItem.standardText = standardText; |
||||||
|
textItem.description = description; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return textItems; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds localized strings from CSV file data to a dictionary of text items in the scene. |
||||||
|
*/ |
||||||
|
protected virtual void AddCSVDataItems(Dictionary<string, TextItem> textItems, string csvData) |
||||||
|
{ |
||||||
|
CsvParser csvParser = new CsvParser(); |
||||||
|
string[][] csvTable = csvParser.Parse(csvData); |
||||||
|
|
||||||
|
if (csvTable.Length <= 1) |
||||||
|
{ |
||||||
|
// No data rows in file |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// Parse header row |
||||||
|
string[] columnNames = csvTable[0]; |
||||||
|
|
||||||
|
for (int i = 1; i < csvTable.Length; ++i) |
||||||
|
{ |
||||||
|
string[] fields = csvTable[i]; |
||||||
|
if (fields.Length < 3) |
||||||
|
{ |
||||||
|
// No standard text or localized string fields present |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
string stringId = fields[0]; |
||||||
|
|
||||||
|
if (!textItems.ContainsKey(stringId)) |
||||||
|
{ |
||||||
|
if (stringId.StartsWith("CHARACTER.") || |
||||||
|
stringId.StartsWith("SAY.") || |
||||||
|
stringId.StartsWith("MENU.")) |
||||||
|
{ |
||||||
|
// If it's a 'built-in' type this probably means that item has been deleted from its flowchart, |
||||||
|
// so there's no need to add a text item for it. |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
// Key not found. Assume it's a custom string that we want to retain, so add a text item for it. |
||||||
|
TextItem newTextItem = new TextItem(); |
||||||
|
newTextItem.description = CSVSupport.Unescape(fields[1]); |
||||||
|
newTextItem.standardText = CSVSupport.Unescape(fields[2]); |
||||||
|
textItems[stringId] = newTextItem; |
||||||
|
} |
||||||
|
|
||||||
|
TextItem textItem = textItems[stringId]; |
||||||
|
|
||||||
|
for (int j = 3; j < fields.Length; ++j) |
||||||
|
{ |
||||||
|
if (j >= columnNames.Length) |
||||||
|
{ |
||||||
|
continue; |
||||||
|
} |
||||||
|
string languageCode = columnNames[j]; |
||||||
|
string languageEntry = CSVSupport.Unescape(fields[j]); |
||||||
|
|
||||||
|
if (languageEntry.Length > 0) |
||||||
|
{ |
||||||
|
textItem.localizedStrings[languageCode] = languageEntry; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Scan a localization CSV file and copies the strings for the specified language code |
||||||
|
* into the text properties of the appropriate scene objects. |
||||||
|
*/ |
||||||
|
public virtual void SetActiveLanguage(string languageCode) |
||||||
|
{ |
||||||
|
if (!Application.isPlaying) |
||||||
|
{ |
||||||
|
// This function should only ever be called when the game is playing (not in editor). |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (localizationFile == null) |
||||||
|
{ |
||||||
|
// No localization file set |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
localizedStrings.Clear(); |
||||||
|
|
||||||
|
CsvParser csvParser = new CsvParser(); |
||||||
|
string[][] csvTable = csvParser.Parse(localizationFile.text); |
||||||
|
|
||||||
|
if (csvTable.Length <= 1) |
||||||
|
{ |
||||||
|
// No data rows in file |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// Parse header row |
||||||
|
string[] columnNames = csvTable[0]; |
||||||
|
|
||||||
|
if (columnNames.Length < 3) |
||||||
|
{ |
||||||
|
// No languages defined in CSV file |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// First assume standard text column and then look for a matching language column |
||||||
|
int languageIndex = 2; |
||||||
|
for (int i = 3; i < columnNames.Length; ++i) |
||||||
|
{ |
||||||
|
if (columnNames[i] == languageCode) |
||||||
|
{ |
||||||
|
languageIndex = i; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (languageIndex == 2) |
||||||
|
{ |
||||||
|
// Using standard text column |
||||||
|
// Add all strings to the localized strings dict, but don't replace standard text in the scene. |
||||||
|
// This allows string substitution to work for both standard and localized text strings. |
||||||
|
for (int i = 1; i < csvTable.Length; ++i) |
||||||
|
{ |
||||||
|
string[] fields = csvTable[i]; |
||||||
|
if (fields.Length < 3) |
||||||
|
{ |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
localizedStrings[fields[0]] = fields[languageIndex]; |
||||||
|
} |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// Using a localized language text column |
||||||
|
// 1. Add all localized text to the localized strings dict |
||||||
|
// 2. Update all scene text properties with localized versions |
||||||
|
|
||||||
|
// Cache a lookup table of characters in the scene |
||||||
|
Dictionary<string, Character> characterDict = new Dictionary<string, Character>(); |
||||||
|
foreach (Character character in GameObject.FindObjectsOfType<Character>()) |
||||||
|
{ |
||||||
|
characterDict[character.nameText] = character; |
||||||
|
} |
||||||
|
|
||||||
|
// Cache a lookup table of flowcharts in the scene |
||||||
|
Dictionary<string, Flowchart> flowchartDict = new Dictionary<string, Flowchart>(); |
||||||
|
foreach (Flowchart flowChart in GameObject.FindObjectsOfType<Flowchart>()) |
||||||
|
{ |
||||||
|
flowchartDict[flowChart.localizationId] = flowChart; |
||||||
|
} |
||||||
|
|
||||||
|
for (int i = 1; i < csvTable.Length; ++i) |
||||||
|
{ |
||||||
|
string[] fields = csvTable[i]; |
||||||
|
|
||||||
|
if (fields.Length < languageIndex + 1) |
||||||
|
{ |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
string stringId = fields[0]; |
||||||
|
string languageEntry = CSVSupport.Unescape(fields[languageIndex]); |
||||||
|
|
||||||
|
if (languageEntry.Length > 0) |
||||||
|
{ |
||||||
|
localizedStrings[stringId] = languageEntry; |
||||||
|
PopulateTextProperty(stringId, languageEntry, flowchartDict, characterDict); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Populates the text property of a single scene object with a new text value. |
||||||
|
*/ |
||||||
|
public virtual bool PopulateTextProperty(string stringId, |
||||||
|
string newText, |
||||||
|
Dictionary<string, Flowchart> flowchartDict, |
||||||
|
Dictionary<string, Character> characterDict) |
||||||
|
{ |
||||||
|
string[] idParts = stringId.Split('.'); |
||||||
|
if (idParts.Length == 0) |
||||||
|
{ |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
string stringType = idParts[0]; |
||||||
|
if (stringType == "SAY") |
||||||
|
{ |
||||||
|
if (idParts.Length != 4) |
||||||
|
{ |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
string flowchartId = idParts[1]; |
||||||
|
if (!flowchartDict.ContainsKey(flowchartId)) |
||||||
|
{ |
||||||
|
return false; |
||||||
|
} |
||||||
|
Flowchart flowchart = flowchartDict[flowchartId]; |
||||||
|
|
||||||
|
int itemId = int.Parse(idParts[2]); |
||||||
|
|
||||||
|
if (flowchart != null) |
||||||
|
{ |
||||||
|
foreach (Say say in flowchart.GetComponentsInChildren<Say>()) |
||||||
|
{ |
||||||
|
if (say.itemId == itemId && |
||||||
|
say.storyText != newText) |
||||||
|
{ |
||||||
|
#if UNITY_EDITOR |
||||||
|
Undo.RecordObject(say, "Set Text"); |
||||||
|
#endif |
||||||
|
|
||||||
|
say.storyText = newText; |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
else if (stringType == "MENU") |
||||||
|
{ |
||||||
|
if (idParts.Length != 3) |
||||||
|
{ |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
string flowchartId = idParts[1]; |
||||||
|
if (!flowchartDict.ContainsKey(flowchartId)) |
||||||
|
{ |
||||||
|
return false; |
||||||
|
} |
||||||
|
Flowchart flowchart = flowchartDict[flowchartId]; |
||||||
|
|
||||||
|
int itemId = int.Parse(idParts[2]); |
||||||
|
|
||||||
|
if (flowchart != null) |
||||||
|
{ |
||||||
|
foreach (Menu menu in flowchart.GetComponentsInChildren<Menu>()) |
||||||
|
{ |
||||||
|
if (menu.itemId == itemId && |
||||||
|
menu.text != newText) |
||||||
|
{ |
||||||
|
#if UNITY_EDITOR |
||||||
|
Undo.RecordObject(menu, "Set Text"); |
||||||
|
#endif |
||||||
|
|
||||||
|
menu.text = newText; |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
else if (stringType == "CHARACTER") |
||||||
|
{ |
||||||
|
if (idParts.Length != 2) |
||||||
|
{ |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
string characterName = idParts[1]; |
||||||
|
if (!characterDict.ContainsKey(characterName)) |
||||||
|
{ |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
Character character = characterDict[characterName]; |
||||||
|
if (character != null && |
||||||
|
character.nameText != newText) |
||||||
|
{ |
||||||
|
#if UNITY_EDITOR |
||||||
|
Undo.RecordObject(character, "Set Text"); |
||||||
|
#endif |
||||||
|
|
||||||
|
character.nameText = newText; |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns all standard text for SAY & MENU commands in the scene using an |
||||||
|
* easy to edit custom text format. |
||||||
|
*/ |
||||||
|
public virtual string GetStandardText() |
||||||
|
{ |
||||||
|
// Collect all the text items present in the scene |
||||||
|
Dictionary<string, TextItem> textItems = FindTextItems(); |
||||||
|
|
||||||
|
string textData = ""; |
||||||
|
int rowCount = 0; |
||||||
|
foreach (string stringId in textItems.Keys) |
||||||
|
{ |
||||||
|
if (!stringId.StartsWith("SAY.") && !(stringId.StartsWith("MENU."))) |
||||||
|
{ |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
TextItem languageItem = textItems[stringId]; |
||||||
|
|
||||||
|
textData += "#" + stringId + "\n"; |
||||||
|
textData += languageItem.standardText.Trim() + "\n\n"; |
||||||
|
rowCount++; |
||||||
|
} |
||||||
|
|
||||||
|
notificationText = "Exported " + rowCount + " standard text items."; |
||||||
|
|
||||||
|
return textData; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets standard text on scene objects by parsing a text data file. |
||||||
|
*/ |
||||||
|
public virtual void SetStandardText(string textData) |
||||||
|
{ |
||||||
|
// Cache a lookup table of characters in the scene |
||||||
|
Dictionary<string, Character> characterDict = new Dictionary<string, Character>(); |
||||||
|
foreach (Character character in GameObject.FindObjectsOfType<Character>()) |
||||||
|
{ |
||||||
|
characterDict[character.nameText] = character; |
||||||
|
} |
||||||
|
|
||||||
|
// Cache a lookup table of flowcharts in the scene |
||||||
|
Dictionary<string, Flowchart> flowchartDict = new Dictionary<string, Flowchart>(); |
||||||
|
foreach (Flowchart flowChart in GameObject.FindObjectsOfType<Flowchart>()) |
||||||
|
{ |
||||||
|
flowchartDict[flowChart.localizationId] = flowChart; |
||||||
|
} |
||||||
|
|
||||||
|
string[] lines = textData.Split('\n'); |
||||||
|
|
||||||
|
int updatedCount = 0; |
||||||
|
|
||||||
|
string stringId = ""; |
||||||
|
string buffer = ""; |
||||||
|
foreach (string line in lines) |
||||||
|
{ |
||||||
|
// Check for string id line |
||||||
|
if (line.StartsWith("#")) |
||||||
|
{ |
||||||
|
if (stringId.Length > 0) |
||||||
|
{ |
||||||
|
// Write buffered text to the appropriate text property |
||||||
|
if (PopulateTextProperty(stringId, buffer.Trim(), flowchartDict, characterDict)) |
||||||
|
{ |
||||||
|
updatedCount++; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Set the string id for the follow text lines |
||||||
|
stringId = line.Substring(1, line.Length - 1); |
||||||
|
buffer = ""; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
buffer += line; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Handle last buffered entry |
||||||
|
if (stringId.Length > 0) |
||||||
|
{ |
||||||
|
if (PopulateTextProperty(stringId, buffer.Trim(), flowchartDict, characterDict)) |
||||||
|
{ |
||||||
|
updatedCount++; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
notificationText = "Updated " + updatedCount + " standard text items."; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
fileFormatVersion: 2 |
||||||
|
guid: e5724422a635e425bae0af9ffe2615d6 |
||||||
|
timeCreated: 1427886378 |
||||||
|
licenseType: Free |
||||||
|
MonoImporter: |
||||||
|
serializedVersion: 2 |
||||||
|
defaultReferences: [] |
||||||
|
executionOrder: 0 |
||||||
|
icon: {instanceID: 0} |
||||||
|
userData: |
||||||
|
assetBundleName: |
||||||
|
assetBundleVariant: |
@ -0,0 +1,9 @@ |
|||||||
|
fileFormatVersion: 2 |
||||||
|
guid: 3469ab31c1c9d4c2da1ae42edc001ded |
||||||
|
folderAsset: yes |
||||||
|
timeCreated: 1428523768 |
||||||
|
licenseType: Free |
||||||
|
DefaultImporter: |
||||||
|
userData: |
||||||
|
assetBundleName: |
||||||
|
assetBundleVariant: |
@ -0,0 +1,48 @@ |
|||||||
|
using UnityEngine; |
||||||
|
using System.Collections; |
||||||
|
using System.Text.RegularExpressions; |
||||||
|
using System.Linq; |
||||||
|
using System; |
||||||
|
|
||||||
|
namespace Fungus |
||||||
|
{ |
||||||
|
|
||||||
|
// Some CSV utilities cobbled together from stack overflow answers |
||||||
|
// CSV escape & unescape from http://stackoverflow.com/questions/769621/dealing-with-commas-in-a-csv-file |
||||||
|
// http://answers.unity3d.com/questions/144200/are-there-any-csv-reader-for-unity3d-without-needi.html |
||||||
|
public static class CSVSupport |
||||||
|
{ |
||||||
|
public static string Escape( string s ) |
||||||
|
{ |
||||||
|
s = s.Replace("\n", "\\n"); |
||||||
|
|
||||||
|
if ( s.Contains( QUOTE ) ) |
||||||
|
s = s.Replace( QUOTE, ESCAPED_QUOTE ); |
||||||
|
|
||||||
|
if ( s.IndexOfAny( CHARACTERS_THAT_MUST_BE_QUOTED ) > -1 ) |
||||||
|
s = QUOTE + s + QUOTE; |
||||||
|
|
||||||
|
return s; |
||||||
|
} |
||||||
|
|
||||||
|
public static string Unescape( string s ) |
||||||
|
{ |
||||||
|
s = s.Replace("\\n", "\n"); |
||||||
|
|
||||||
|
if ( s.StartsWith( QUOTE ) && s.EndsWith( QUOTE ) ) |
||||||
|
{ |
||||||
|
s = s.Substring( 1, s.Length - 2 ); |
||||||
|
|
||||||
|
if ( s.Contains( ESCAPED_QUOTE ) ) |
||||||
|
s = s.Replace( ESCAPED_QUOTE, QUOTE ); |
||||||
|
} |
||||||
|
|
||||||
|
return s; |
||||||
|
} |
||||||
|
|
||||||
|
private const string QUOTE = "\""; |
||||||
|
private const string ESCAPED_QUOTE = "\"\""; |
||||||
|
private static char[] CHARACTERS_THAT_MUST_BE_QUOTED = { ',', '"', '\n' }; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
fileFormatVersion: 2 |
||||||
|
guid: 4468f4dcfdfbf46b088949ea57ed6135 |
||||||
|
timeCreated: 1427897861 |
||||||
|
licenseType: Free |
||||||
|
MonoImporter: |
||||||
|
serializedVersion: 2 |
||||||
|
defaultReferences: [] |
||||||
|
executionOrder: 0 |
||||||
|
icon: {instanceID: 0} |
||||||
|
userData: |
||||||
|
assetBundleName: |
||||||
|
assetBundleVariant: |
@ -0,0 +1,218 @@ |
|||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.IO; |
||||||
|
using System.Text; |
||||||
|
|
||||||
|
namespace Ideafixxxer.CsvParser |
||||||
|
{ |
||||||
|
public class CsvParser |
||||||
|
{ |
||||||
|
private const char CommaCharacter = ','; |
||||||
|
private const char QuoteCharacter = '"'; |
||||||
|
|
||||||
|
#region Nested types |
||||||
|
|
||||||
|
private abstract class ParserState |
||||||
|
{ |
||||||
|
public static readonly LineStartState LineStartState = new LineStartState(); |
||||||
|
public static readonly ValueStartState ValueStartState = new ValueStartState(); |
||||||
|
public static readonly ValueState ValueState = new ValueState(); |
||||||
|
public static readonly QuotedValueState QuotedValueState = new QuotedValueState(); |
||||||
|
public static readonly QuoteState QuoteState = new QuoteState(); |
||||||
|
|
||||||
|
public abstract ParserState AnyChar(char ch, ParserContext context); |
||||||
|
public abstract ParserState Comma(ParserContext context); |
||||||
|
public abstract ParserState Quote(ParserContext context); |
||||||
|
public abstract ParserState EndOfLine(ParserContext context); |
||||||
|
} |
||||||
|
|
||||||
|
private class LineStartState : ParserState |
||||||
|
{ |
||||||
|
public override ParserState AnyChar(char ch, ParserContext context) |
||||||
|
{ |
||||||
|
context.AddChar(ch); |
||||||
|
return ValueState; |
||||||
|
} |
||||||
|
|
||||||
|
public override ParserState Comma(ParserContext context) |
||||||
|
{ |
||||||
|
context.AddValue(); |
||||||
|
return ValueStartState; |
||||||
|
} |
||||||
|
|
||||||
|
public override ParserState Quote(ParserContext context) |
||||||
|
{ |
||||||
|
return QuotedValueState; |
||||||
|
} |
||||||
|
|
||||||
|
public override ParserState EndOfLine(ParserContext context) |
||||||
|
{ |
||||||
|
context.AddLine(); |
||||||
|
return LineStartState; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private class ValueStartState : LineStartState |
||||||
|
{ |
||||||
|
public override ParserState EndOfLine(ParserContext context) |
||||||
|
{ |
||||||
|
context.AddValue(); |
||||||
|
context.AddLine(); |
||||||
|
return LineStartState; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private class ValueState : ParserState |
||||||
|
{ |
||||||
|
public override ParserState AnyChar(char ch, ParserContext context) |
||||||
|
{ |
||||||
|
context.AddChar(ch); |
||||||
|
return ValueState; |
||||||
|
} |
||||||
|
|
||||||
|
public override ParserState Comma(ParserContext context) |
||||||
|
{ |
||||||
|
context.AddValue(); |
||||||
|
return ValueStartState; |
||||||
|
} |
||||||
|
|
||||||
|
public override ParserState Quote(ParserContext context) |
||||||
|
{ |
||||||
|
context.AddChar(QuoteCharacter); |
||||||
|
return ValueState; |
||||||
|
} |
||||||
|
|
||||||
|
public override ParserState EndOfLine(ParserContext context) |
||||||
|
{ |
||||||
|
context.AddValue(); |
||||||
|
context.AddLine(); |
||||||
|
return LineStartState; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private class QuotedValueState : ParserState |
||||||
|
{ |
||||||
|
public override ParserState AnyChar(char ch, ParserContext context) |
||||||
|
{ |
||||||
|
context.AddChar(ch); |
||||||
|
return QuotedValueState; |
||||||
|
} |
||||||
|
|
||||||
|
public override ParserState Comma(ParserContext context) |
||||||
|
{ |
||||||
|
context.AddChar(CommaCharacter); |
||||||
|
return QuotedValueState; |
||||||
|
} |
||||||
|
|
||||||
|
public override ParserState Quote(ParserContext context) |
||||||
|
{ |
||||||
|
return QuoteState; |
||||||
|
} |
||||||
|
|
||||||
|
public override ParserState EndOfLine(ParserContext context) |
||||||
|
{ |
||||||
|
context.AddChar('\r'); |
||||||
|
context.AddChar('\n'); |
||||||
|
return QuotedValueState; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private class QuoteState : ParserState |
||||||
|
{ |
||||||
|
public override ParserState AnyChar(char ch, ParserContext context) |
||||||
|
{ |
||||||
|
//undefined, ignore " |
||||||
|
context.AddChar(ch); |
||||||
|
return QuotedValueState; |
||||||
|
} |
||||||
|
|
||||||
|
public override ParserState Comma(ParserContext context) |
||||||
|
{ |
||||||
|
context.AddValue(); |
||||||
|
return ValueStartState; |
||||||
|
} |
||||||
|
|
||||||
|
public override ParserState Quote(ParserContext context) |
||||||
|
{ |
||||||
|
context.AddChar(QuoteCharacter); |
||||||
|
return QuotedValueState; |
||||||
|
} |
||||||
|
|
||||||
|
public override ParserState EndOfLine(ParserContext context) |
||||||
|
{ |
||||||
|
context.AddValue(); |
||||||
|
context.AddLine(); |
||||||
|
return LineStartState; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private class ParserContext |
||||||
|
{ |
||||||
|
private readonly StringBuilder _currentValue = new StringBuilder(); |
||||||
|
private readonly List<string[]> _lines = new List<string[]>(); |
||||||
|
private readonly List<string> _currentLine = new List<string>(); |
||||||
|
|
||||||
|
public void AddChar(char ch) |
||||||
|
{ |
||||||
|
_currentValue.Append(ch); |
||||||
|
} |
||||||
|
|
||||||
|
public void AddValue() |
||||||
|
{ |
||||||
|
_currentLine.Add(_currentValue.ToString()); |
||||||
|
_currentValue.Remove(0, _currentValue.Length); |
||||||
|
} |
||||||
|
|
||||||
|
public void AddLine() |
||||||
|
{ |
||||||
|
_lines.Add(_currentLine.ToArray()); |
||||||
|
_currentLine.Clear(); |
||||||
|
} |
||||||
|
|
||||||
|
public List<string[]> GetAllLines() |
||||||
|
{ |
||||||
|
if (_currentValue.Length > 0) |
||||||
|
{ |
||||||
|
AddValue(); |
||||||
|
} |
||||||
|
if (_currentLine.Count > 0) |
||||||
|
{ |
||||||
|
AddLine(); |
||||||
|
} |
||||||
|
return _lines; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#endregion |
||||||
|
|
||||||
|
public string[][] Parse(string csvData) |
||||||
|
{ |
||||||
|
var context = new ParserContext(); |
||||||
|
|
||||||
|
string[] lines = csvData.Split('\n'); |
||||||
|
|
||||||
|
ParserState currentState = ParserState.LineStartState; |
||||||
|
foreach (string next in lines) |
||||||
|
{ |
||||||
|
foreach (char ch in next) |
||||||
|
{ |
||||||
|
switch (ch) |
||||||
|
{ |
||||||
|
case CommaCharacter: |
||||||
|
currentState = currentState.Comma(context); |
||||||
|
break; |
||||||
|
case QuoteCharacter: |
||||||
|
currentState = currentState.Quote(context); |
||||||
|
break; |
||||||
|
default: |
||||||
|
currentState = currentState.AnyChar(ch, context); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
currentState = currentState.EndOfLine(context); |
||||||
|
} |
||||||
|
List<string[]> allLines = context.GetAllLines(); |
||||||
|
return allLines.ToArray(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
fileFormatVersion: 2 |
||||||
|
guid: 803c0d4d8bc9447d1b20e1f4fb86120f |
||||||
|
timeCreated: 1428171346 |
||||||
|
licenseType: Free |
||||||
|
MonoImporter: |
||||||
|
serializedVersion: 2 |
||||||
|
defaultReferences: [] |
||||||
|
executionOrder: 0 |
||||||
|
icon: {instanceID: 0} |
||||||
|
userData: |
||||||
|
assetBundleName: |
||||||
|
assetBundleVariant: |
Loading…
Reference in new issue