[gbrainy] Support for plural strings in games.xml



commit 2bb249c3ce3f7999dae1fd2d8c255fcab4713321
Author: Jordi Mas <jmas softcatala org>
Date:   Sat Jul 17 19:14:03 2010 +0200

    Support for plural strings in games.xml

 Makefile.am                            |    1 +
 configure.ac                           |    1 +
 data/games.xml                         |   29 +++++---
 po/POTFILES.in                         |    1 +
 src/Core/Main/Xml/GameXml.cs           |   57 +++++++++++---
 src/Core/Main/Xml/GameXmlDefinition.cs |   22 +++++-
 src/Core/Main/Xml/GameXmlFactory.cs    |   68 ++++++++++++++---
 tools/GameXmlGetStringTemplate.cs      |   33 ++++++++
 tools/GameXmlToGetString.cs            |  128 ++++++++++++++++++++++++++++++++
 tools/Makefile.am                      |   23 ++++++
 10 files changed, 328 insertions(+), 35 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index 151a302..886abef 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2,6 +2,7 @@ SUBDIRS = 		\
 	src		\
 	tests		\
 	data		\
+	tools		\
 	po		\
 	help
 
diff --git a/configure.ac b/configure.ac
index c6810d7..b086178 100644
--- a/configure.ac
+++ b/configure.ac
@@ -182,6 +182,7 @@ src/Games/AssemblyInfo.cs
 Makefile
 po/Makefile.in
 src/Makefile
+tools/Makefile
 src/Core/Makefile
 src/Games/Makefile
 src/Clients/Classical/Makefile
diff --git a/data/games.xml b/data/games.xml
index 3e8b8f2..017210b 100644
--- a/data/games.xml
+++ b/data/games.xml
@@ -1,5 +1,4 @@
 <!---
-
 		This is a collection of games definitions for gbrainy. This work is licensed under GPL 2.0 or higher license.
 		This is the same license that gbrainy package.
 
@@ -8,7 +7,6 @@
 
 		TODO:
 			* Mouse sensitive areas
-			* Answer regular expression + patterns
 -->
 <games>
 	<game>
@@ -21,7 +19,8 @@
 		</variables>
 		<_rationale>Every hour rotates 360 degrees.</_rationale>
 		<svg file = "clock.svg" x = "0.25" y = "0.25" width = "0.5" height = "0.5"/>
-		<_question>How many degrees rotates the minute hand of a clock in 2 hours [num] minutes?</_question>
+		<question>How many degrees rotates the minute hand of a clock in 2 hours [num] minute?</question>
+		<question plural ="[rslt]">How many degrees rotates the minute hand of a clock in 2 hours [num] minutes?</question>
 		<answer>[rslt]</answer>
 	</game>
 
@@ -58,7 +57,8 @@
 				int difference = 2 + random.Next (8);
 				int son = (father / 2) - difference;
 			</variables>
-			<_question>John's is 46 years old. His son is [difference] years younger than half of John's age. How old is John's son?</_question>
+			<question>John's is 46 years old. His son is [difference] year younger than half of John's age. How old is John's son?</question>
+			<question plural ="[difference]">John's is 46 years old. His son is [difference] years younger than half of John's age. How old is John's son?</question>
 			<answer>[son]</answer>
 		</variant>
 		<variant>
@@ -70,9 +70,11 @@
 				int ago = years [idx];
 				int proportion = proportions [idx];
 			</variables>
-			<_question>John's age is nowadays 2 times his son's age. [ago] years ago, John was [proportion] times older than his son. How old is John's son nowadays?</_question>
+			<question>John's age is nowadays 2 times his son's age. [ago] year ago, John was [proportion] times older than his son. How old is John's son nowadays?</question>
+			<question plural ="[ago]">John's age is nowadays 2 times his son's age. [ago] years ago, John was [proportion] times older than his son. How old is John's son nowadays?</question>
 			<answer>40</answer>
-			<_rationale>[ago] years ago, John's age minus [ago] was equal to [proportion] times his son age minus [ago].</_rationale>
+			<rationale>[ago] year ago, John's age minus [ago] was equal to [proportion] times his son age minus [ago].</rationale>
+			<rationale plural ="[ago]">[ago] years ago, John's age minus [ago] was equal to [proportion] times his son age minus [ago].</rationale>
 		</variant>
 	</game>
 
@@ -86,7 +88,8 @@
 				int digits = 4 + random.Next (3);
 				int rslt = (int) Math.Pow (10, digits);
 			</variables>
-			<_question>A file is protected by a password formed by a [digits] digit number (ranging from 0 to 9). How many different passwords can you have?</_question>
+			<question>A file is protected by a password formed by a [digits] digit number (ranging from 0 to 9). How many different passwords can you have?</question>
+			<question plural="[digits]">A file is protected by a password formed by a [digits] digits number (ranging from 0 to 9). How many different passwords can you have?</question>
 			<answer>[rslt]</answer>
 			<_rationale>Every digit has 10 possibilities. The total number of possibilities is 10 at the power of [digits].</_rationale>
 		</variant>
@@ -95,7 +98,8 @@
 				int digits = 2 + random.Next (2);
 				int rslt = (int) Math.Pow (8, digits);
 			</variables>
-			<_question>A file is protected by a password formed by a [digits] digit octal number (ranging from 0 to 7). How many different passwords can you have?</_question>
+			<question>A file is protected by a password formed by a [digits] digit octal number (ranging from 0 to 7). How many different passwords can you have?</question>
+			<question plural="[digits]">A file is protected by a password formed by a [digits] digits octal number (ranging from 0 to 7). How many different passwords can you have?</question>
 			<answer>[rslt]</answer>
 			<_rationale>Every digit has 8 possibilities. The total number of possibilities is 8 at the power of [digits].</_rationale>
 		</variant>
@@ -111,7 +115,8 @@
 				int games = 5 + random.Next (5);
 				int rslt = (int) Math.Pow (2, games);
 			</variables>
-			<_question>There are [games] tennis games played simultaneous. How many different forecast are possible?</_question>
+			<question>There are [games] tennis game played simultaneous. How many different forecast are possible?</question>
+			<question plural="[games]">There are [games] tennis games played simultaneous. How many different forecast are possible?</question>
 			<answer>[rslt]</answer>
 			<_rationale>Every game is an independent event with 2 possible results. The total number of possibilities is 2 at the power of [games].</_rationale>
 		</variant>
@@ -120,7 +125,8 @@
 				int players = 32 + (random.Next (16) * 2);
 				int rslt = players -1;
 			</variables>
-			<_question>How many matches does it take to determine the winner of a tennis tournament that starts with [players] players?</_question>
+			<question>How many matches does it take to determine the winner of a tennis tournament that starts with [players] player?</question>
+			<question plural ="[players]">How many matches does it take to determine the winner of a tennis tournament that starts with [players] players?</question>
 			<_rationale>In every match you eliminate one player, you need the total number of games minus 1 to find out the winner.</_rationale>
 			<answer>[rslt]</answer>
 		</variant>
@@ -140,7 +146,8 @@
 
 				double rslt =  money * (Math.Pow (1 + interest, years));
 			</variables>
-			<_question>You have [money] monetary units in your bank account at 10% compound interest annually. How much money will you have at end of 2 years?</_question>
+			<question>You have [money] monetary unit in your bank account at 10% compound interest annually. How much money will you have at end of 2 years?</question>
+			<question plural = "[money]">You have [money] monetary units in your bank account at 10% compound interest annually. How much money will you have at end of 2 years?</question>
 			<answer>[rslt]</answer>
 			<_rationale>Compound interest is paid on the original amount and on the accumulated past interest.</_rationale>
 		</variant>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 1ae3ccd..97a9092 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -1,3 +1,4 @@
+tools/GameXmlGetString.cs
 data/games.xml
 data/gbrainy.desktop.in
 data/verbal_analogies.xml
diff --git a/src/Core/Main/Xml/GameXml.cs b/src/Core/Main/Xml/GameXml.cs
index 318455a..2ca4731 100644
--- a/src/Core/Main/Xml/GameXml.cs
+++ b/src/Core/Main/Xml/GameXml.cs
@@ -77,7 +77,7 @@ namespace gbrainy.Core.Main.Xml
 
 				if (String.IsNullOrEmpty (expression))
 					return base.AnswerCheckExpression;
-	
+
 				return expression;
 			}
 		}
@@ -103,7 +103,7 @@ namespace gbrainy.Core.Main.Xml
 		}
 
 		public override string Name {
-			get { return game.Name; }
+			get { return Catalog.GetString (game.Name); }
 		}
 
 		public override string Question {
@@ -130,9 +130,37 @@ namespace gbrainy.Core.Main.Xml
 		{
 			string variables;
 			bool variants;
+			LocalizableString localizable_question, localizable_rationale;
 
 			variants = game.Variants.Count > 0;
 
+			if (variants && game.Variants[current.Variant].Variables != null)
+				variables = game.Variants[current.Variant].Variables;
+			else
+				variables = game.Variables;
+
+			if (variants && game.Variants[current.Variant].Question != null)
+				localizable_question = game.Variants[current.Variant].Question;
+			else
+				localizable_question = game.Question;
+
+			if (variants && game.Variants[current.Variant].Rationale != null)
+				localizable_rationale = game.Variants[current.Variant].Rationale;
+			else
+				localizable_rationale = game.Rationale;
+
+			if (String.IsNullOrEmpty (variables) == false)
+			{
+				// Evaluate code
+				CodeEvaluation.EvaluateVariables (variables);
+
+				if (String.IsNullOrEmpty (localizable_question.Value) == false)
+					localizable_question.ValueComputed = Int32.Parse (CodeEvaluation.ReplaceVariables (localizable_question.Value));
+
+				if (localizable_rationale != null && String.IsNullOrEmpty (localizable_rationale.Value) == false)
+					localizable_rationale.ValueComputed = Int32.Parse (CodeEvaluation.ReplaceVariables (localizable_rationale.Value));
+			}
+
 			if (variants && game.Variants[current.Variant].Question != null)
 				question = CatalogGetString (game.Variants[current.Variant].Question);
 			else
@@ -153,18 +181,11 @@ namespace gbrainy.Core.Main.Xml
 			else
 				answer_value = game.AnswerShow;
 
-			if (variants && game.Variants[current.Variant].Variables != null)
-				variables = game.Variants[current.Variant].Variables;
-			else
-				variables = game.Variables;
-
 			if (String.IsNullOrEmpty (variables) == false)
 			{
-				// Evaluate code
-				CodeEvaluation.EvaluateVariables (variables);
 				question = CodeEvaluation.ReplaceVariables (question);
-				answer = CodeEvaluation.ReplaceVariables (answer);
 				rationale = CodeEvaluation.ReplaceVariables (rationale);
+				answer = CodeEvaluation.ReplaceVariables (answer);
 				answer_value = CodeEvaluation.ReplaceVariables (answer_value);
 			}
 
@@ -213,7 +234,7 @@ namespace gbrainy.Core.Main.Xml
 					{
 						string text;
 						TextDrawingObject draw_string = draw_object as TextDrawingObject;
-			
+
 						text = CatalogGetString (draw_string.Text);
 						text = CodeEvaluation.ReplaceVariables (text);
 
@@ -229,7 +250,7 @@ namespace gbrainy.Core.Main.Xml
 							gr.ShowPangoText (text);
 							gr.Stroke ();
 						}
-					} 
+					}
 					else if (draw_object is ImageDrawingObject)
 					{
 						ImageDrawingObject image = draw_object as ImageDrawingObject;
@@ -264,5 +285,17 @@ namespace gbrainy.Core.Main.Xml
 
 			return Catalog.GetString (str);
 		}
+
+		// Protect from calling with null + resolve plurals
+		string CatalogGetString (LocalizableString localizable)
+		{
+			if (localizable == null)
+				return string.Empty;
+
+			if (localizable.IsPlural () == false)
+				return CatalogGetString (localizable.String);
+
+			return Catalog.GetPluralString (localizable.String, localizable.PluralString, localizable.ValueComputed);
+		}
 	}
 }
diff --git a/src/Core/Main/Xml/GameXmlDefinition.cs b/src/Core/Main/Xml/GameXmlDefinition.cs
index 0855ac4..a280eea 100644
--- a/src/Core/Main/Xml/GameXmlDefinition.cs
+++ b/src/Core/Main/Xml/GameXmlDefinition.cs
@@ -23,6 +23,24 @@ using System.Collections.Generic;
 
 namespace gbrainy.Core.Main.Xml
 {
+	public class LocalizableString
+	{
+		public string String { get; set; }
+		public string Value { get; set; }
+		public int ValueComputed { get; set; }
+		public string PluralString { get; set; }
+
+		public bool IsPlural ()
+		{
+			if (String.IsNullOrEmpty (String) == true ||
+				String.IsNullOrEmpty (PluralString) == true ||
+				String.IsNullOrEmpty (Value) == true)
+				return false;
+			else
+				return true;
+		}
+	}
+
 	public class DrawingObject
 	{
 
@@ -48,9 +66,9 @@ namespace gbrainy.Core.Main.Xml
 
 	public class GameXmlDefinitionVariant
 	{
-		public string Question { get; set; }
+		public LocalizableString Question { get; set; }
 		public string Tip { get; set; }
-		public string Rationale { get; set; }
+		public LocalizableString Rationale { get; set; }
 		public string Answer { get; set; }
 		public string Variables { get; set; }
 		public GameAnswerCheckAttributes CheckAttributes { get; set; }
diff --git a/src/Core/Main/Xml/GameXmlFactory.cs b/src/Core/Main/Xml/GameXmlFactory.cs
index 3cbfef5..d927df2 100644
--- a/src/Core/Main/Xml/GameXmlFactory.cs
+++ b/src/Core/Main/Xml/GameXmlFactory.cs
@@ -42,7 +42,7 @@ namespace gbrainy.Core.Main.Xml
 		public void Read (string file)
 		{
 			GameXmlDefinition game;
-			string name, str;
+			string name, str, plural;
 			bool processing_variant = false;
 			int variant = 0;
 
@@ -57,7 +57,7 @@ namespace gbrainy.Core.Main.Xml
 
 				while (reader.Read ())
 				{
-					name = reader.Name.ToLower ();
+					name = reader.Name.ToLower ();					
 					switch (name) {
 					case "games":
 						break;
@@ -186,24 +186,72 @@ namespace gbrainy.Core.Main.Xml
 
 						break;
 					case "_question":
+					case "question":
 						if (reader.NodeType != XmlNodeType.Element)
 							break;
 
-						if (processing_variant)
-							game.Variants[variant].Question = reader.ReadElementString ();
-						else
-							game.Question = reader.ReadElementString ();
+						// Create object if needed
+						if (processing_variant) {
+							if (game.Variants[variant].Question == null)
+								game.Variants[variant].Question = new LocalizableString ();
+						}
+						else {
+							if (game.Question == null)
+								game.Question = new LocalizableString ();
+						}
 
+						plural = reader.GetAttribute ("plural");
+
+						if (String.IsNullOrEmpty (plural) == false) { // Plural
+							if (processing_variant) {
+								game.Variants[variant].Question.PluralString = reader.ReadElementString ();
+								game.Variants[variant].Question.Value = plural;
+							}
+							else {
+								game.Question.PluralString = reader.ReadElementString ();
+								game.Question.Value = plural;
+							}
+						}
+						else {
+							if (processing_variant)
+								game.Variants[variant].Question.String = reader.ReadElementString ();
+							else
+								game.Question.String = reader.ReadElementString ();
+						}
 						break;
+					case "rationale":
 					case "_rationale":
 						if (reader.NodeType != XmlNodeType.Element)
 							break;
 
-						if (processing_variant)
-							game.Variants[variant].Rationale = reader.ReadElementString ();
-						else
-							game.Rationale = reader.ReadElementString ();
+						// Create object if needed
+						if (processing_variant) {
+							if (game.Variants[variant].Rationale == null)
+								game.Variants[variant].Rationale = new LocalizableString ();
+						}
+						else {
+							if (game.Rationale == null)
+								game.Rationale = new LocalizableString ();
+						}
 
+						plural = reader.GetAttribute ("plural");
+
+						if (String.IsNullOrEmpty (plural) == false) { // Plural
+							if (processing_variant) {
+								game.Variants[variant].Rationale.PluralString = reader.ReadElementString ();
+								game.Variants[variant].Rationale.Value = plural;
+							}
+							else {
+								game.Rationale.PluralString = reader.ReadElementString ();
+								game.Rationale.Value = plural;
+							}
+						}
+						else {
+							if (processing_variant)
+								game.Variants[variant].Rationale.String = reader.ReadElementString ();
+							else
+								game.Rationale.String = reader.ReadElementString ();
+						}
 						break;
 					case "answer":
 					case "_answer":
diff --git a/tools/GameXmlGetStringTemplate.cs b/tools/GameXmlGetStringTemplate.cs
new file mode 100644
index 0000000..6c033e0
--- /dev/null
+++ b/tools/GameXmlGetStringTemplate.cs
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2010 Jordi Mas i Hernàndez <jmas softcatala org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+using System;
+using Mono.Unix;
+
+// This is an auto-generated file GameXmlToGetString tool. Do not edit manually
+public class GameXmlSttringFactory
+{
+	void LoadStrings ()
+	{
+		int variable = 0;
+ STRINGS@
+	}
+}
+
+
diff --git a/tools/GameXmlToGetString.cs b/tools/GameXmlToGetString.cs
new file mode 100644
index 0000000..0abead1
--- /dev/null
+++ b/tools/GameXmlToGetString.cs
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2010 Jordi Mas i Hernàndez <jmas softcatala org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+using System;
+using System.Text;
+using System.IO;
+using System.Collections.Generic;
+
+using gbrainy.Core.Main.Xml;
+
+public class GameXmlToAssembly
+{
+	static readonly string output = "GameXmlGetString.cs";
+	static readonly string games = "../data/games.xml";
+	static readonly string template = "GameXmlGetStringTemplate.cs";
+
+	static void Main (string[] args)
+	{
+		Dictionary <string, string> tokens = new Dictionary <string, string> ();
+		StringBuilder strings = new StringBuilder ();
+		GamesXmlFactory factory;
+		TextWriter tw;
+		string file, games_file, template_file, output_file, str;
+		int cnt = 0;
+
+		output_file = args.Length > 1 ?  Path.Combine (args[1], output) : output;
+
+		tw = new StreamWriter (output_file);
+		factory = new GamesXmlFactory ();
+
+		games_file = args.Length > 0 ?  Path.Combine (args[0], games) : games;
+		factory.Read (games_file);
+
+		// Build GetStrings
+		foreach (GameXmlDefinition definition in factory.Definitions)
+		{
+			if (definition.Question != null) {
+				str = GetStringFromDefinition (definition.Question);
+				if (String.IsNullOrEmpty (str) == false) {
+					strings.AppendLine (str);
+					cnt++;
+				}
+			}
+
+			if (definition.Rationale != null) {
+				str = GetStringFromDefinition (definition.Rationale);
+				if (String.IsNullOrEmpty (str) == false) {
+					strings.AppendLine (str);
+					cnt++;
+				}
+			}
+
+			foreach (GameXmlDefinitionVariant variant in definition.Variants)
+			{
+				if (variant.Question != null)
+				{
+					str = GetStringFromDefinition (variant.Question);
+					if (String.IsNullOrEmpty (str) == false) {
+						strings.AppendLine (str);
+						cnt++;
+					}
+				}
+
+				if (variant.Rationale != null)
+				{
+					str = GetStringFromDefinition (variant.Rationale);
+					if (String.IsNullOrEmpty (str) == false) {
+						strings.AppendLine (str);
+						cnt++;
+					}
+				}
+			}
+		}
+
+		tokens.Add ("@STRINGS@", strings.ToString ());
+
+		// Replace tokens
+		template_file = args.Length > 0 ?  Path.Combine (args[0], template) : template;
+
+		string line;
+		Stream read = File.OpenRead (template_file);
+		StreamReader sr = new StreamReader (read);
+		while (true)
+		{
+			line = sr.ReadLine ();
+			if (line == null)
+				break;
+
+			foreach (string token in tokens.Keys)
+			{
+				line = line.Replace (token, tokens[token]);
+			}
+			tw.WriteLine (line);
+		}
+		read.Close ();
+		tw.Close ();
+
+		Console.WriteLine ("gbrainy's GameXmlToGetString, {0} strings extracted", cnt);
+	}
+
+	static string GetStringFromDefinition (LocalizableString localizable)
+	{
+		if (localizable.IsPlural () == false)
+			return null;
+
+		return String.Format ("\t\tCatalog.GetPluralString (\"{0}\",\n\t\t\t\"{1}\",\n\t\t\t{2});\n",
+			localizable.String,
+			localizable.PluralString,
+			"variable");
+	}
+}
diff --git a/tools/Makefile.am b/tools/Makefile.am
new file mode 100644
index 0000000..8c99c83
--- /dev/null
+++ b/tools/Makefile.am
@@ -0,0 +1,23 @@
+EXTRAFLAGS = $(CSC_DEFINES)
+
+GAMESXMLSTRINGS_CSFILES = \
+	$(srcdir)/GameXmlToGetString.cs
+
+ASSEMBLIES = \
+	-r:../src/gbrainy.Core.dll	\
+	-r:Mono.Posix
+
+GameXmlToGetString.exe: $(GAMESXMLSTRINGS_CSFILES) ../data/games.xml $(srcdir)/GameXmlGetStringTemplate.cs
+		   $(CSC) -target:winexe -out:$@ $(EXTRAFLAGS) $(GAMESXMLSTRINGS_CSFILES) $(ASSEMBLIES)
+		   export MONO_PATH=../src && $(MONO) $@ $(srcdir)
+
+all: GameXmlToGetString.exe
+
+EXTRA_DIST = $(GAMESXMLSTRINGS_CSFILES) $(srcdir)/GameXmlGetStringTemplate.cs  $(srcdir)/GameXmlGetString.cs
+
+CLEANFILES = GameXmlToGetString.exe \
+	     GameXmlGetString.cs
+
+DISTCLEANFILES = 				\
+	Makefile
+



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]