Browse Source

Added a robust CSV parser. Localisation file is read at load time.

master
chrisgregan 10 years ago
parent
commit
e063596031
  1. 218
      Assets/CsvParser.cs
  2. 12
      Assets/CsvParser.cs.meta
  3. 25
      Assets/Fungus/Flowchart/Scripts/CSVSupport.cs
  4. 131
      Assets/Fungus/Flowchart/Scripts/Language.cs
  5. 29
      Assets/Fungus/Narrative/Scripts/Character.cs
  6. 29
      Assets/Fungus/Narrative/Scripts/Commands/Menu.cs
  7. 29
      Assets/Fungus/Narrative/Scripts/Commands/Say.cs

218
Assets/CsvParser.cs

@ -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();
}
}
}

12
Assets/CsvParser.cs.meta

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 803c0d4d8bc9447d1b20e1f4fb86120f
timeCreated: 1428171346
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

25
Assets/Fungus/Flowchart/Scripts/CSVSupport.cs

@ -40,31 +40,6 @@ namespace Fungus
return s;
}
public static string[] SplitCSVLine(string line)
{
// '(?<val>[^'\\]*(?:\\[\S\s][^'\\]*)*)' # Either $1: Single quoted string,
string pattern = @"
# Match one value in valid CSV string.
(?!\s*$) # Don't match empty last value.
\s* # Strip whitespace before value.
(?: # Group for value alternatives.
| ""(?<val>[^""\\]*(?:\\[\S\s][^""\\]*)*)"" # or $2: Double quoted string,
| (?<val>[^,'""\s\\]*(?:\s+[^,'""\s\\]+)*) # or $3: Non-comma, non-quote stuff.
) # End group of value alternatives.
\s* # Strip whitespace after value.
(?:,|$) # Field ends on comma or EOS.
";
string[] values = (from Match m in Regex.Matches(line, pattern,
RegexOptions.ExplicitCapture | RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline)
select m.Groups[1].Value).ToArray();
return values;
}
private const string QUOTE = "\"";
private const string ESCAPED_QUOTE = "\"\"";
// private static char[] CHARACTERS_THAT_MUST_BE_QUOTED = { ',', '"', '\n' };

131
Assets/Fungus/Flowchart/Scripts/Language.cs

@ -2,23 +2,12 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using Ideafixxxer.CsvParser;
namespace Fungus
{
interface ILocalizable
{
string GetLocalizationID();
string GetStandardText();
void SetStandardText(string standardText);
string GetTimestamp();
string GetDescription();
}
/**
* Multi-language localization support.
*/
@ -36,7 +25,6 @@ namespace Fungus
*/
protected class LanguageItem
{
public string timeStamp;
public string description;
public string standardText;
public Dictionary<string, string> localizedStrings = new Dictionary<string, string>();
@ -73,7 +61,7 @@ namespace Fungus
}
// Build CSV header row and a list of the language codes currently in use
string csvHeader = "Key,Timestamp,Description,Standard";
string csvHeader = "Key,Description,Standard";
List<string> languageCodes = new List<string>();
foreach (LanguageItem languageItem in languageItems.Values)
{
@ -94,7 +82,6 @@ namespace Fungus
LanguageItem languageItem = languageItems[stringId];
string row = CSVSupport.Escape(stringId);
row += "," + CSVSupport.Escape(languageItem.timeStamp);
row += "," + CSVSupport.Escape(languageItem.description);
row += "," + CSVSupport.Escape(languageItem.standardText);
@ -121,6 +108,7 @@ namespace Fungus
Dictionary<string, LanguageItem> languageItems = new Dictionary<string, LanguageItem>();
// 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)
{
@ -131,6 +119,7 @@ namespace Fungus
{
string stringID = "";
string standardText = "";
string description = "";
System.Type type = command.GetType();
if (type == typeof(Say))
@ -138,12 +127,14 @@ namespace Fungus
stringID = "SAY." + flowchart.name + "." + command.itemId;
Say sayCommand = command as Say;
standardText = sayCommand.storyText;
description = sayCommand.description;
}
else if (type == typeof(Menu))
{
stringID = "MENU." + flowchart.name + "." + command.itemId;
Menu menuCommand = command as Menu;
standardText = menuCommand.text;
description = menuCommand.description;
}
else
{
@ -162,9 +153,8 @@ namespace Fungus
}
// Update basic properties,leaving localised strings intact
languageItem.timeStamp = "10/10/2015";
languageItem.description = "Note";
languageItem.standardText = standardText;
languageItem.description = description;
}
}
}
@ -174,33 +164,37 @@ namespace Fungus
protected virtual void AddLocalisedStrings(Dictionary<string, LanguageItem> languageItems, string csvData)
{
// Split into lines
// Excel on Mac exports csv files with \r line endings, so we need to support that too.
string[] lines = csvData.Split('\n', '\r');
CsvParser csvParser = new CsvParser();
string[][] csvTable = csvParser.Parse(csvData);
if (lines.Length == 0)
if (csvTable.Length <= 1)
{
// Early out if no data in file
// No data rows in file
return;
}
// Parse header row
string[] columnNames = CSVSupport.SplitCSVLine(lines[0]);
string[] columnNames = csvTable[0];
for (int i = 1; i < lines.Length; ++i)
for (int i = 1; i < csvTable.Length; ++i)
{
string line = lines[i];
string[] fields = CSVSupport.SplitCSVLine(line);
string[] fields = csvTable[i];
if (fields.Length < 4)
{
// No localized string fields present
continue;
}
string stringId = fields[0];
if (!languageItems.ContainsKey(stringId))
{
continue;
}
// Store localized strings for this string id
for (int j = 4; j < fields.Length; ++j)
LanguageItem languageItem = languageItems[stringId];
for (int j = 3; j < fields.Length; ++j)
{
if (j >= columnNames.Length)
{
@ -211,10 +205,7 @@ namespace Fungus
if (languageEntry.Length > 0)
{
if (languageItems.ContainsKey(stringId))
{
languageItems[stringId].localizedStrings[languageCode] = languageEntry;
}
languageItem.localizedStrings[languageCode] = languageEntry;
}
}
}
@ -230,18 +221,17 @@ namespace Fungus
localizedStrings.Clear();
// Split into lines
// Excel on Mac exports csv files with \r line endings, so we need to support that too.
string[] lines = csvData.Split('\n', '\r');
CsvParser csvParser = new CsvParser();
string[][] csvTable = csvParser.Parse(csvData);
if (lines.Length == 0)
if (csvTable.Length <= 1)
{
// No data rows in file
return;
}
// Parse header row
string[] columnNames = CSVSupport.SplitCSVLine(lines[0]);
string[] columnNames = csvTable[0];
if (columnNames.Length < 5)
{
@ -250,7 +240,7 @@ namespace Fungus
}
int languageIndex = -1;
for (int i = 4; i < columnNames.Length; ++i)
for (int i = 3; i < columnNames.Length; ++i)
{
if (columnNames[i] == languageCode)
{
@ -265,11 +255,10 @@ namespace Fungus
return;
}
for (int i = 1; i < lines.Length; ++i)
for (int i = 1; i < csvTable.Length; ++i)
{
string line = lines[i];
string[] fields = csvTable[i];
string[] fields = CSVSupport.SplitCSVLine(line);
if (fields.Length < languageIndex + 1)
{
continue;
@ -342,64 +331,6 @@ namespace Fungus
}
}
}
/**
* Import strings from a CSV file.
* 1. Any changes to standard text items will be applied to the corresponding scene object.
* 2. Any localized strings will be added to the localization dictionary.
*/
public virtual void ImportCSV(string csvData)
{
/*
// Split into lines
// Excel on Mac exports csv files with \r line endings, so we need to support that too.
string[] lines = csvData.Split('\n', '\r');
if (lines.Length == 0)
{
return;
}
localizedStrings.Clear();
// Parse header row
string[] columnNames = CSVSupport.SplitCSVLine(lines[0]);
for (int i = 1; i < lines.Length; ++i)
{
string line = lines[i];
string[] fields = CSVSupport.SplitCSVLine(line);
if (fields.Length < 4)
{
continue;
}
string stringId = fields[0];
// Ignore timestamp & notes fields
string standardText = CSVSupport.Unescape(fields[3]);
PopulateGameString(stringId, standardText);
// Store localized string in stringDict
for (int j = 4; j < fields.Length; ++j)
{
if (j >= columnNames.Length)
{
continue;
}
string languageCode = columnNames[j];
string languageEntry = CSVSupport.Unescape(fields[j]);
if (languageEntry.Length > 0)
{
// The dictionary key is the basic string id with .<LanguageCode> appended
localizedStrings[stringId + "." + languageCode] = languageEntry;
}
}
}
*/
}
}
}

29
Assets/Fungus/Narrative/Scripts/Character.cs

@ -8,7 +8,7 @@ namespace Fungus
{
[ExecuteInEditMode]
public class Character : MonoBehaviour, ILocalizable
public class Character : MonoBehaviour
{
public string nameText; // We need a separate name as the object name is used for character variations (e.g. "Smurf Happy", "Smurf Sad")
public Color nameColor = Color.white;
@ -36,33 +36,6 @@ namespace Fungus
{
activeCharacters.Remove(this);
}
// ILocalizable methods
public virtual string GetLocalizationID()
{
return "CHARACTER." + nameText;
}
public virtual string GetStandardText()
{
return nameText;
}
public virtual void SetStandardText(string standardText)
{
nameText = standardText;
}
public virtual string GetTimestamp()
{
return DateTime.Now.ToShortDateString();
}
public virtual string GetDescription()
{
return description;
}
}
}

29
Assets/Fungus/Narrative/Scripts/Commands/Menu.cs

@ -11,7 +11,7 @@ namespace Fungus
"Menu",
"Displays a multiple choice menu")]
[AddComponentMenu("")]
public class Menu : Command, ILocalizable
public class Menu : Command
{
// Menu displays a menu button which will execute the target block when clicked
@ -116,33 +116,6 @@ namespace Fungus
{
return false;
}
// ILocalizable methods
public virtual string GetLocalizationID()
{
return "MENU." + itemId.ToString();
}
public virtual string GetStandardText()
{
return text;
}
public virtual void SetStandardText(string standardText)
{
text = standardText;
}
public virtual string GetTimestamp()
{
return DateTime.Now.ToShortDateString();
}
public virtual string GetDescription()
{
return description;
}
}
}

29
Assets/Fungus/Narrative/Scripts/Commands/Say.cs

@ -9,7 +9,7 @@ namespace Fungus
"Say",
"Writes text in a dialog box.")]
[AddComponentMenu("")]
public class Say : Command, ILocalizable
public class Say : Command
{
[TextArea(5,10)]
public string storyText = "";
@ -164,33 +164,6 @@ namespace Fungus
{
executionCount = 0;
}
// ILocalizable methods
public virtual string GetLocalizationID()
{
return "SAY." + itemId.ToString();
}
public virtual string GetStandardText()
{
return storyText;
}
public virtual void SetStandardText(string standardText)
{
storyText = standardText;
}
public virtual string GetTimestamp()
{
return DateTime.Now.ToShortDateString();
}
public virtual string GetDescription()
{
return description;
}
}
}
Loading…
Cancel
Save